diff --git a/.circleci/config.yml b/.circleci/config.yml index f0ea4449621..539e53df7bb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,11 +4,11 @@ commonSteps: &commonSteps - run: name: Install prerequisites command: | - set -x + set -ux cd .. if [ "$CI_OS" = "linux" ]; then export DEBIAN_FRONTEND=noninteractive - if [[ "$EXTRA_CMAKE_FLAGS" = *-DMULTILIB?ON* ]]; then + if [[ "${EXTRA_CMAKE_FLAGS:-}" = *-DMULTILIB?ON* ]]; then dpkg --add-architecture i386 gcc_pkg="g++-multilib" libcurl_pkg="libcurl4 libcurl4:i386" @@ -20,7 +20,13 @@ commonSteps: &commonSteps apt-get -yq install \ git-core $gcc_pkg \ zlib1g-dev $libcurl_pkg curl gdb python3 python3-pip tzdata unzip zip \ - $EXTRA_APT_PACKAGES + software-properties-common gnupg \ + ${EXTRA_APT_PACKAGES:-} + # set up apt.llvm.org repo for being able to install more recent LLVM versions than provided by the distro + curl -fsS https://apt.llvm.org/llvm-snapshot.gpg.key | tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc + add-apt-repository -y "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-$LLVM_MAJOR main" + apt-get -q update + apt-get -yq install llvm-$LLVM_MAJOR-dev libclang-common-$LLVM_MAJOR-dev # Download & extract CMake curl -fL --retry 3 --max-time 300 -o cmake.tar.gz https://github.com/Kitware/CMake/releases/download/v3.27.1/cmake-3.27.1-linux-x86_64.tar.gz mkdir cmake @@ -39,11 +45,13 @@ commonSteps: &commonSteps python3 -m pip install --user setuptools wheel python3 -m pip install --user lit python3 -c "import lit.main; lit.main.main();" --version . | head -n 1 - # Download & extract host LDC - curl -fL --retry 3 --max-time 300 -o ldc2.tar.xz https://github.com/ldc-developers/ldc/releases/download/v$HOST_LDC_VERSION/ldc2-$HOST_LDC_VERSION-$CI_OS-x86_64.tar.xz - mkdir host-ldc - tar -xf ldc2.tar.xz --strip 1 -C host-ldc - rm ldc2.tar.xz + # Download & extract host LDC if HOST_LDC_VERSION is set + if [[ -v HOST_LDC_VERSION ]]; then + curl -fL --retry 3 --max-time 300 -o ldc2.tar.xz https://github.com/ldc-developers/ldc/releases/download/v$HOST_LDC_VERSION/ldc2-$HOST_LDC_VERSION-$CI_OS-x86_64.tar.xz + mkdir host-ldc + tar -xf ldc2.tar.xz --strip 1 -C host-ldc + rm ldc2.tar.xz + fi - checkout - run: name: Checkout git submodules @@ -51,7 +59,7 @@ commonSteps: &commonSteps - run: name: Build LDC & LDC D unittests & defaultlib unittest runners command: | - set -x + set -ux cd .. cmake --version ninja --version @@ -59,9 +67,9 @@ commonSteps: &commonSteps cd build cmake -G Ninja $CIRCLE_WORKING_DIRECTORY \ -DCMAKE_BUILD_TYPE=Release \ - -DD_COMPILER=$PWD/../host-ldc/bin/ldmd2 \ + ${HOST_LDC_VERSION:+-DD_COMPILER=$PWD/../host-ldc/bin/ldmd2} \ -DLDC_LINK_MANUALLY=OFF \ - $EXTRA_CMAKE_FLAGS + ${EXTRA_CMAKE_FLAGS:-} ninja -j$PARALLELISM obj/ldc2.o all ldc2-unittest all-test-runners bin/ldc2 -version - run: @@ -97,7 +105,7 @@ jobs: environment: - PARALLELISM: 4 - CI_OS: linux - - EXTRA_APT_PACKAGES: llvm-11-dev libclang-common-11-dev + - LLVM_MAJOR: 15 - HOST_LDC_VERSION: 1.24.0 - EXTRA_CMAKE_FLAGS: "-DMULTILIB=ON -DRT_SUPPORT_SANITIZERS=ON -DBUILD_LTO_LIBS=ON" Ubuntu-20.04-sharedLibsOnly-gdmd: @@ -108,8 +116,8 @@ jobs: environment: - PARALLELISM: 4 - CI_OS: linux - - EXTRA_APT_PACKAGES: gdmd llvm-11-dev libclang-common-11-dev - - HOST_LDC_VERSION: 1.24.0 + - LLVM_MAJOR: 15 + - EXTRA_APT_PACKAGES: gdmd - EXTRA_CMAKE_FLAGS: "-DBUILD_SHARED_LIBS=ON -DBUILD_LTO_LIBS=ON -DD_COMPILER=gdmd -DLDC_LINK_MANUALLY=ON" workflows: diff --git a/.cirrus.yml b/.cirrus.yml index c2263856d93..52cb886c932 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -248,7 +248,7 @@ task: -DEXTRA_CXXFLAGS=-flto=full -DCMAKE_EXE_LINKER_FLAGS=-static-libstdc++ -DJITRT_EXTRA_LDFLAGS=-static-libstdc++ - -DLDC_INSTALL_LLVM_RUNTIME_LIBS_OS=aarch64-unknown-linux-gnu + -DCOMPILER_RT_LIBDIR_OS=aarch64-unknown-linux-gnu -DLLVM_ROOT_DIR=$CIRRUS_WORKING_DIR/../llvm -DD_COMPILER=$CIRRUS_WORKING_DIR/../bootstrap-ldc/bin/ldmd2 PARALLELISM: 4 @@ -304,9 +304,9 @@ task: << : *PACKAGING_STEPS_TEMPLATE task: - name: FreeBSD 13.2 x64 + name: FreeBSD 13.3 x64 freebsd_instance: - image_family: freebsd-13-2 + image_family: freebsd-13-3 cpu: 4 memory: 8G timeout_in: 60m diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bb2474f1fa3..166f00739f1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -188,7 +188,7 @@ jobs: arch: aarch64 android_x86_arch: x86_64 extra_cmake_flags: >- - -DLDC_INSTALL_LLVM_RUNTIME_LIBS_OS=linux + -DCOMPILER_RT_LIBDIR_OS=linux -DLDC_INSTALL_LLVM_RUNTIME_LIBS_ARCH=aarch64-android name: ${{ matrix.job_name }} diff --git a/.github/workflows/supported_llvm_versions.yml b/.github/workflows/supported_llvm_versions.yml index e6aac84e10a..5467403eb38 100644 --- a/.github/workflows/supported_llvm_versions.yml +++ b/.github/workflows/supported_llvm_versions.yml @@ -15,11 +15,12 @@ jobs: fail-fast: false matrix: include: - - job_name: Ubuntu 20.04, LDC-LLVM 18, latest LDC beta + - job_name: Ubuntu 20.04, LDC-LLVM 18, bootstrap LDC os: ubuntu-20.04 - host_dc: ldc-beta + host_dc: ldc-1.19.0 # FIXME: no usable official package available yet llvm_version: https://github.com/ldc-developers/llvm-project/releases/download/ldc-v18.1.5/llvm-18.1.5-linux-x86_64.tar.xz + cmake_flags: -DRT_SUPPORT_SANITIZERS=ON - job_name: macOS 14, LLVM 17, latest LDC beta os: macos-14 host_dc: ldc-beta @@ -30,30 +31,11 @@ jobs: host_dc: ldc-beta llvm_version: 16.0.5 cmake_flags: -DBUILD_SHARED_LIBS=OFF -DD_COMPILER_FLAGS=-gcc=/usr/bin/c++ -DCMAKE_EXE_LINKER_FLAGS=-L/opt/homebrew/opt/zstd/lib - - job_name: Ubuntu 20.04, LLVM 15, latest LDC beta - os: ubuntu-20.04 - host_dc: ldc-beta - llvm_version: 15.0.6 - - job_name: macOS 11, LLVM 14, latest DMD beta - os: macos-11 - host_dc: dmd-beta - llvm_version: 14.0.6 - cmake_flags: -DBUILD_SHARED_LIBS=ON -DRT_SUPPORT_SANITIZERS=ON -DLDC_LINK_MANUALLY=ON -DCMAKE_CXX_COMPILER=/usr/bin/c++ -DCMAKE_C_COMPILER=/usr/bin/cc - - job_name: Ubuntu 20.04, LLVM 13, latest LDC beta - os: ubuntu-20.04 - host_dc: ldc-beta - llvm_version: 13.0.1 - cmake_flags: -DRT_SUPPORT_SANITIZERS=ON - - job_name: Ubuntu 20.04, LLVM 12, bootstrap LDC - os: ubuntu-20.04 - host_dc: ldc-1.19.0 - llvm_version: 12.0.1 - cmake_flags: -DBUILD_SHARED_LIBS=ON -DLIB_SUFFIX=64 - - job_name: Ubuntu 20.04, LLVM 11, latest DMD beta + - job_name: Ubuntu 20.04, LLVM 15, latest DMD beta os: ubuntu-20.04 host_dc: dmd-beta - llvm_version: 11.1.0 - cmake_flags: -DBUILD_SHARED_LIBS=OFF -DRT_SUPPORT_SANITIZERS=ON -DLDC_LINK_MANUALLY=ON + llvm_version: 15.0.6 + cmake_flags: -DBUILD_SHARED_LIBS=ON -DRT_SUPPORT_SANITIZERS=ON -DLIB_SUFFIX=64 -DLDC_LINK_MANUALLY=ON name: ${{ matrix.job_name }} runs-on: ${{ matrix.os }} env: @@ -116,10 +98,8 @@ jobs: fi elif [[ "$version" =~ ^1[7-9]\. ]]; then suffix='x86_64-linux-gnu-ubuntu-22.04' # LLVM 17+ - elif [[ "$version" =~ ^1[3-6]\. ]]; then - suffix='x86_64-linux-gnu-ubuntu-18.04' # LLVM 13.0.1+ else - suffix='x86_64-linux-gnu-ubuntu-16.04' + suffix='x86_64-linux-gnu-ubuntu-18.04' # LLVM 14+ fi url="https://github.com/llvm/llvm-project/releases/download/llvmorg-$version/clang+llvm-$version-$suffix.tar.xz" @@ -136,7 +116,7 @@ jobs: fi - name: 'Linux: Make lld the default linker' - if: runner.os == 'Linux' && matrix.host_dc != 'ldc-1.9.0' + if: runner.os == 'Linux' run: | set -eux echo "Using lld to work around sporadic failures" @@ -161,8 +141,8 @@ jobs: if: success() || failure() run: | set -eux - # LLVM 14+ on Linux: don't use vanilla llvm-symbolizer (no support for zlib-compressed debug sections => failing ASan tests) - if [[ '${{ runner.os }}' == 'Linux' && ! '${{ matrix.llvm_version }}' =~ ^1[1-3]\. ]]; then + # Linux: don't use vanilla llvm-symbolizer (no support for zlib-compressed debug sections => failing ASan tests) + if [[ '${{ runner.os }}' == 'Linux' ]]; then mv llvm/bin/llvm-symbolizer llvm/bin/llvm-symbolizer.bak fi ctest -V -R "lit-tests" diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bb6ae5b4d1..fc8d6ece7af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ #### Big news #### Platform support +- Supports LLVM 15 - 18. Support for LLVM 11 - 14 was dropped. The CLI options `-passmanager` and `-opaque-pointers` were removed. #### Bug fixes diff --git a/CMakeLists.txt b/CMakeLists.txt index 226a802d974..1b5f8104ff9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,7 +34,7 @@ endfunction() # Locate LLVM. # -find_package(LLVM 11.0 REQUIRED +find_package(LLVM 15.0 REQUIRED all-targets analysis asmparser asmprinter bitreader bitwriter codegen core debuginfodwarf debuginfomsf debuginfopdb demangle instcombine ipo instrumentation irreader libdriver linker lto mc @@ -604,28 +604,15 @@ if(LDC_WITH_LLD) else() set(LDC_LINKERFLAG_LIST -lLLVMSymbolize ${LDC_LINKERFLAG_LIST}) endif() - if (LDC_LLVM_VER LESS 1200 OR NOT LDC_LLVM_VER LESS 1400) - set(LLD_MACHO lldMachO) - else() - set(LLD_MACHO lldMachO2) - endif() + set(LLD_MACHO lldMachO) if(MSVC) - if(NOT (LDC_LLVM_VER LESS 1400)) - list(APPEND LDC_LINKERFLAG_LIST lldMinGW.lib lldCOFF.lib lldELF.lib ${LLD_MACHO}.lib lldWasm.lib lldCommon.lib) - else() - list(APPEND LDC_LINKERFLAG_LIST lldDriver.lib lldMinGW.lib lldCOFF.lib lldELF.lib ${LLD_MACHO}.lib lldWasm.lib lldYAML.lib lldReaderWriter.lib lldCommon.lib lldCore.lib) - endif() + list(APPEND LDC_LINKERFLAG_LIST lldMinGW.lib lldCOFF.lib lldELF.lib ${LLD_MACHO}.lib lldWasm.lib lldCommon.lib) else() - if(NOT (LDC_LLVM_VER LESS 1400)) - set(LDC_LINKERFLAG_LIST -llldMinGW -llldCOFF -llldELF -l${LLD_MACHO} -llldWasm -llldCommon ${LDC_LINKERFLAG_LIST}) - else() - set(LDC_LINKERFLAG_LIST -llldDriver -llldMinGW -llldCOFF -llldELF -l${LLD_MACHO} -llldWasm -llldYAML -llldReaderWriter -llldCommon -llldCore ${LDC_LINKERFLAG_LIST}) - endif() + set(LDC_LINKERFLAG_LIST -llldMinGW -llldCOFF -llldELF -l${LLD_MACHO} -llldWasm -llldCommon ${LDC_LINKERFLAG_LIST}) endif() if(APPLE) - if(NOT (LDC_LLVM_VER LESS 1300)) # LLD 13.0.0 on Mac needs libxar - list(APPEND LDC_LINKERFLAG_LIST -lxar) - endif() + # LLD 13.0.0 on Mac needs libxar + list(APPEND LDC_LINKERFLAG_LIST -lxar) endif() endif() @@ -798,9 +785,34 @@ else() endif() # -# Locate ASan and other LLVM compiler-rt libraries, and copy them to our lib folder -# Location is LLVM_LIBRARY_DIRS/clang//lib// , for example LLVM_LIBRARY_DIRS/clang/4.0.0/lib/darwin/ -# +# Locate ASan and other LLVM compiler-rt libraries, and copy them to our lib +# folder or save that folder in the config files. Location is typically +# LLVM_LIBRARY_DIRS/clang//lib// , for example +# LLVM_LIBRARY_DIRS/clang/4.0.0/lib/darwin/ , but we allow the user to specify +# another directory. +set(COMPILER_RT_BASE_DIR "${LLVM_LIBRARY_DIRS}" CACHE PATH "Base path of compiler-rt libraries. If they in are /usr/lib/clang/17/lib/linux/libclang_rt* you should set this value to /usr/lib") +# If it's different than the default it will need to be added to the config files +if(COMPILER_RT_BASE_DIR STREQUAL LLVM_LIBRARY_DIRS) + set(WANT_COMPILER_RT_LIBDIR_CONFIG FALSE) +else() + set(WANT_COMPILER_RT_LIBDIR_CONFIG TRUE) +endif() +set(COMPILER_RT_LIBDIR "${COMPILER_RT_BASE_DIR}/clang") +if(LDC_LLVM_VER LESS 1600) + set(COMPILER_RT_LIBDIR "${COMPILER_RT_LIBDIR}/${LLVM_VERSION_BASE_STRING}") +else() + set(COMPILER_RT_LIBDIR "${COMPILER_RT_LIBDIR}/${LLVM_VERSION_MAJOR}") +endif() +set(COMPILER_RT_LIBDIR "${COMPILER_RT_LIBDIR}/lib") +if(APPLE) + set(COMPILER_RT_LIBDIR "${COMPILER_RT_LIBDIR}/darwin") +elseif(UNIX) + set(COMPILER_RT_LIBDIR_OS_DEFAULT "x86_64-unknown-linux-gnu") + set(COMPILER_RT_LIBDIR_OS "${COMPILER_RT_LIBDIR_OS_DEFAULT}" CACHE STRING "Non-Mac Posix: OS used as directory name for the compiler-rt source libraries, e.g., 'freebsd'.") + set(COMPILER_RT_LIBDIR "${COMPILER_RT_LIBDIR}/${COMPILER_RT_LIBDIR_OS}") +elseif(WIN32) + set(COMPILER_RT_LIBDIR "${COMPILER_RT_LIBDIR}/windows") +endif() if(LLVM_IS_SHARED) set(LDC_INSTALL_LLVM_RUNTIME_LIBS_DEFAULT OFF) else() @@ -808,11 +820,7 @@ else() endif() set(LDC_INSTALL_LLVM_RUNTIME_LIBS ${LDC_INSTALL_LLVM_RUNTIME_LIBS_DEFAULT} CACHE BOOL "Copy/install LLVM compiler-rt libraries (ASan, libFuzzer, ...) from LLVM/Clang into LDC lib dir when available.") function(copy_compilerrt_lib llvm_lib_name ldc_lib_name fixup_dylib) - if(LDC_LLVM_VER LESS 1600) - set(llvm_lib_path ${LLVM_LIBRARY_DIRS}/clang/${LLVM_VERSION_BASE_STRING}/lib/${llvm_lib_name}) - else() - set(llvm_lib_path ${LLVM_LIBRARY_DIRS}/clang/${LLVM_VERSION_MAJOR}/lib/${llvm_lib_name}) - endif() + set(llvm_lib_path ${COMPILER_RT_LIBDIR}/${llvm_lib_name}) if(EXISTS ${llvm_lib_path}) message(STATUS "-- - ${llvm_lib_path} --> ${ldc_lib_name}") copy_and_install_llvm_library(${llvm_lib_path} ${ldc_lib_name} ${fixup_dylib}) @@ -824,57 +832,58 @@ message(STATUS "-- Including LLVM compiler-rt libraries (LDC_INSTALL_LLVM_RUNTIM if (LDC_INSTALL_LLVM_RUNTIME_LIBS) # Locate LLVM sanitizer runtime libraries, and copy them to our lib folder + # No need to add another libdir, the default ldc one will have the libraries + set(WANT_COMPILER_RT_LIBDIR_CONFIG FALSE) + if(APPLE) - copy_compilerrt_lib("darwin/libclang_rt.asan_osx_dynamic.dylib" "libldc_rt.asan.dylib" TRUE) - copy_compilerrt_lib("darwin/libclang_rt.lsan_osx_dynamic.dylib" "libldc_rt.lsan.dylib" TRUE) - copy_compilerrt_lib("darwin/libclang_rt.tsan_osx_dynamic.dylib" "libldc_rt.tsan.dylib" TRUE) - copy_compilerrt_lib("darwin/libclang_rt.osx.a" "libldc_rt.builtins.a" FALSE) - copy_compilerrt_lib("darwin/libclang_rt.profile_osx.a" "libldc_rt.profile.a" FALSE) - copy_compilerrt_lib("darwin/libclang_rt.fuzzer_osx.a" "libldc_rt.fuzzer.a" FALSE) - copy_compilerrt_lib("darwin/libclang_rt.xray_osx.a" "libldc_rt.xray.a" FALSE) - copy_compilerrt_lib("darwin/libclang_rt.xray-basic_osx.a" "libldc_rt.xray-basic.a" FALSE) - copy_compilerrt_lib("darwin/libclang_rt.xray-fdr_osx.a" "libldc_rt.xray-fdr.a" FALSE) - copy_compilerrt_lib("darwin/libclang_rt.xray-profiling_osx.a" "libldc_rt.xray-profiling.a" FALSE) + copy_compilerrt_lib("libclang_rt.asan_osx_dynamic.dylib" "libldc_rt.asan.dylib" TRUE) + copy_compilerrt_lib("libclang_rt.lsan_osx_dynamic.dylib" "libldc_rt.lsan.dylib" TRUE) + copy_compilerrt_lib("libclang_rt.tsan_osx_dynamic.dylib" "libldc_rt.tsan.dylib" TRUE) + copy_compilerrt_lib("libclang_rt.osx.a" "libldc_rt.builtins.a" FALSE) + copy_compilerrt_lib("libclang_rt.profile_osx.a" "libldc_rt.profile.a" FALSE) + copy_compilerrt_lib("libclang_rt.fuzzer_osx.a" "libldc_rt.fuzzer.a" FALSE) + copy_compilerrt_lib("libclang_rt.xray_osx.a" "libldc_rt.xray.a" FALSE) + copy_compilerrt_lib("libclang_rt.xray-basic_osx.a" "libldc_rt.xray-basic.a" FALSE) + copy_compilerrt_lib("libclang_rt.xray-fdr_osx.a" "libldc_rt.xray-fdr.a" FALSE) + copy_compilerrt_lib("libclang_rt.xray-profiling_osx.a" "libldc_rt.xray-profiling.a" FALSE) elseif(UNIX) - if(LDC_LLVM_VER LESS 1500) - set(LDC_INSTALL_LLVM_RUNTIME_LIBS_OS_DEFAULT "linux") - set(LDC_INSTALL_LLVM_RUNTIME_LIBS_ARCH_DEFAULT "x86_64") - else() - set(LDC_INSTALL_LLVM_RUNTIME_LIBS_OS_DEFAULT "x86_64-unknown-linux-gnu") - set(LDC_INSTALL_LLVM_RUNTIME_LIBS_ARCH_DEFAULT "") - endif() - set(LDC_INSTALL_LLVM_RUNTIME_LIBS_OS "${LDC_INSTALL_LLVM_RUNTIME_LIBS_OS_DEFAULT}" CACHE STRING "Non-Mac Posix: OS used as directory name for the compiler-rt source libraries, e.g., 'freebsd'.") - set(LDC_INSTALL_LLVM_RUNTIME_LIBS_ARCH "${LDC_INSTALL_LLVM_RUNTIME_LIBS_ARCH_DEFAULT}" CACHE STRING "Non-Mac Posix: architecture used as libname suffix for the compiler-rt source libraries, e.g., 'aarch64'.") + set(LDC_INSTALL_LLVM_RUNTIME_LIBS_ARCH "" CACHE STRING + "Non-Mac Posix: architecture used as libname suffix for the compiler-rt source libraries, e.g., 'aarch64'.") if(LDC_INSTALL_LLVM_RUNTIME_LIBS_ARCH STREQUAL "") set(compilerrt_suffix "") else() set(compilerrt_suffix "-${LDC_INSTALL_LLVM_RUNTIME_LIBS_ARCH}") endif() - copy_compilerrt_lib("${LDC_INSTALL_LLVM_RUNTIME_LIBS_OS}/libclang_rt.asan${compilerrt_suffix}.a" "libldc_rt.asan.a" FALSE) - copy_compilerrt_lib("${LDC_INSTALL_LLVM_RUNTIME_LIBS_OS}/libclang_rt.lsan${compilerrt_suffix}.a" "libldc_rt.lsan.a" FALSE) - copy_compilerrt_lib("${LDC_INSTALL_LLVM_RUNTIME_LIBS_OS}/libclang_rt.msan${compilerrt_suffix}.a" "libldc_rt.msan.a" FALSE) - copy_compilerrt_lib("${LDC_INSTALL_LLVM_RUNTIME_LIBS_OS}/libclang_rt.tsan${compilerrt_suffix}.a" "libldc_rt.tsan.a" FALSE) - copy_compilerrt_lib("${LDC_INSTALL_LLVM_RUNTIME_LIBS_OS}/libclang_rt.builtins${compilerrt_suffix}.a" "libldc_rt.builtins.a" FALSE) - copy_compilerrt_lib("${LDC_INSTALL_LLVM_RUNTIME_LIBS_OS}/libclang_rt.profile${compilerrt_suffix}.a" "libldc_rt.profile.a" FALSE) - copy_compilerrt_lib("${LDC_INSTALL_LLVM_RUNTIME_LIBS_OS}/libclang_rt.xray${compilerrt_suffix}.a" "libldc_rt.xray.a" FALSE) - copy_compilerrt_lib("${LDC_INSTALL_LLVM_RUNTIME_LIBS_OS}/libclang_rt.fuzzer${compilerrt_suffix}.a" "libldc_rt.fuzzer.a" FALSE) - copy_compilerrt_lib("${LDC_INSTALL_LLVM_RUNTIME_LIBS_OS}/libclang_rt.xray-basic${compilerrt_suffix}.a" "libldc_rt.xray-basic.a" FALSE) - copy_compilerrt_lib("${LDC_INSTALL_LLVM_RUNTIME_LIBS_OS}/libclang_rt.xray-fdr${compilerrt_suffix}.a" "libldc_rt.xray-fdr.a" FALSE) - copy_compilerrt_lib("${LDC_INSTALL_LLVM_RUNTIME_LIBS_OS}/libclang_rt.xray-profiling${compilerrt_suffix}.a" "libldc_rt.xray-profiling.a" FALSE) + copy_compilerrt_lib("libclang_rt.asan${compilerrt_suffix}.a" "libldc_rt.asan.a" FALSE) + copy_compilerrt_lib("libclang_rt.lsan${compilerrt_suffix}.a" "libldc_rt.lsan.a" FALSE) + copy_compilerrt_lib("libclang_rt.msan${compilerrt_suffix}.a" "libldc_rt.msan.a" FALSE) + copy_compilerrt_lib("libclang_rt.tsan${compilerrt_suffix}.a" "libldc_rt.tsan.a" FALSE) + copy_compilerrt_lib("libclang_rt.builtins${compilerrt_suffix}.a" "libldc_rt.builtins.a" FALSE) + copy_compilerrt_lib("libclang_rt.profile${compilerrt_suffix}.a" "libldc_rt.profile.a" FALSE) + copy_compilerrt_lib("libclang_rt.xray${compilerrt_suffix}.a" "libldc_rt.xray.a" FALSE) + copy_compilerrt_lib("libclang_rt.fuzzer${compilerrt_suffix}.a" "libldc_rt.fuzzer.a" FALSE) + copy_compilerrt_lib("libclang_rt.xray-basic${compilerrt_suffix}.a" "libldc_rt.xray-basic.a" FALSE) + copy_compilerrt_lib("libclang_rt.xray-fdr${compilerrt_suffix}.a" "libldc_rt.xray-fdr.a" FALSE) + copy_compilerrt_lib("libclang_rt.xray-profiling${compilerrt_suffix}.a" "libldc_rt.xray-profiling.a" FALSE) elseif(WIN32) set(compilerrt_arch_suffix "x86_64") if(CMAKE_SIZEOF_VOID_P EQUAL 4) set(compilerrt_arch_suffix "i386") endif() - copy_compilerrt_lib("windows/clang_rt.asan-${compilerrt_arch_suffix}.lib" "ldc_rt.asan.lib" FALSE) - copy_compilerrt_lib("windows/clang_rt.lsan-${compilerrt_arch_suffix}.lib" "ldc_rt.lsan.lib" FALSE) - copy_compilerrt_lib("windows/clang_rt.builtins-${compilerrt_arch_suffix}.lib" "ldc_rt.builtins.lib" FALSE) - copy_compilerrt_lib("windows/clang_rt.profile-${compilerrt_arch_suffix}.lib" "ldc_rt.profile.lib" FALSE) - copy_compilerrt_lib("windows/clang_rt.fuzzer-${compilerrt_arch_suffix}.lib" "ldc_rt.fuzzer.lib" FALSE) + copy_compilerrt_lib("clang_rt.asan-${compilerrt_arch_suffix}.lib" "ldc_rt.asan.lib" FALSE) + copy_compilerrt_lib("clang_rt.lsan-${compilerrt_arch_suffix}.lib" "ldc_rt.lsan.lib" FALSE) + copy_compilerrt_lib("clang_rt.builtins-${compilerrt_arch_suffix}.lib" "ldc_rt.builtins.lib" FALSE) + copy_compilerrt_lib("clang_rt.profile-${compilerrt_arch_suffix}.lib" "ldc_rt.profile.lib" FALSE) + copy_compilerrt_lib("clang_rt.fuzzer-${compilerrt_arch_suffix}.lib" "ldc_rt.fuzzer.lib" FALSE) endif() endif() +if(WANT_COMPILER_RT_LIBDIR_CONFIG) + message(STATUS "Adding ${COMPILER_RT_LIBDIR} to libdir in configuration files") + set(OPTIONAL_COMPILER_RT_DIR "\n \"${COMPILER_RT_LIBDIR}\",") +endif() + # # Auxiliary build and test utils. # diff --git a/cmake/Modules/FindLLVM.cmake b/cmake/Modules/FindLLVM.cmake index c002e9d4034..9dfc883a31c 100644 --- a/cmake/Modules/FindLLVM.cmake +++ b/cmake/Modules/FindLLVM.cmake @@ -36,10 +36,6 @@ set(llvm_config_names llvm-config-18.1 llvm-config181 llvm-config-18 llvm-config-17.0 llvm-config170 llvm-config-17 llvm-config-16.0 llvm-config160 llvm-config-16 llvm-config-15.0 llvm-config150 llvm-config-15 - llvm-config-14.0 llvm-config140 llvm-config-14 - llvm-config-13.0 llvm-config130 llvm-config-13 - llvm-config-12.0 llvm-config120 llvm-config-12 - llvm-config-11.0 llvm-config110 llvm-config-11 llvm-config) find_program(LLVM_CONFIG NAMES ${llvm_config_names} @@ -52,13 +48,9 @@ if(APPLE) NAMES ${llvm_config_names} PATHS /opt/local/libexec/llvm-18/bin /opt/local/libexec/llvm-17/bin /opt/local/libexec/llvm-16/bin /opt/local/libexec/llvm-15/bin - /opt/local/libexec/llvm-14/bin /opt/local/libexec/llvm-13/bin - /opt/local/libexec/llvm-12/bin /opt/local/libexec/llvm-11/bin /opt/local/libexec/llvm/bin /usr/local/opt/llvm@18/bin /usr/local/opt/llvm@17/bin /usr/local/opt/llvm@16/bin /usr/local/opt/llvm@15/bin - /usr/local/opt/llvm@14/bin /usr/local/opt/llvm@13/bin - /usr/local/opt/llvm@12/bin /usr/local/opt/llvm@11/bin /usr/local/opt/llvm/bin NO_DEFAULT_PATH) endif() diff --git a/driver/args.cpp b/driver/args.cpp index bad8e45dee5..2485dab7ddb 100644 --- a/driver/args.cpp +++ b/driver/args.cpp @@ -116,17 +116,12 @@ struct ResponseFile { return true; // nothing to do #if defined(_WIN32) -#if LDC_LLVM_VER >= 1200 const llvm::ErrorOr wcontent = llvm::sys::flattenWindowsCommandLine(toRefsVector(args)); std::string content; if (!wcontent || !llvm::convertWideToUTF8(*wcontent, content)) return false; -#else - const std::string content = - llvm::sys::flattenWindowsCommandLine(toRefsVector(args)); -#endif #else std::string content; content.reserve(65536); diff --git a/driver/cl_options-llvm.cpp b/driver/cl_options-llvm.cpp index 50d3026fa34..036e0bfa4f6 100644 --- a/driver/cl_options-llvm.cpp +++ b/driver/cl_options-llvm.cpp @@ -43,11 +43,7 @@ Optional getCodeModel() { return codegen::getExplicitCodeModel(); } -#if LDC_LLVM_VER >= 1300 using FPK = llvm::FramePointerKind; -#else -using FPK = llvm::FramePointer::FP; -#endif llvm::Optional framePointerUsage() { // Defaults to `FP::None`; no way to check if set explicitly by user except @@ -67,11 +63,7 @@ bool printTargetFeaturesHelp() { } TargetOptions InitTargetOptionsFromCodeGenFlags(const llvm::Triple &triple) { -#if LDC_LLVM_VER >= 1200 return codegen::InitTargetOptionsFromCodeGenFlags(triple); -#else - return codegen::InitTargetOptionsFromCodeGenFlags(); -#endif } std::string getCPUStr() { diff --git a/driver/cl_options-llvm.h b/driver/cl_options-llvm.h index 8303af11b3a..2f39b2401a4 100644 --- a/driver/cl_options-llvm.h +++ b/driver/cl_options-llvm.h @@ -31,11 +31,7 @@ namespace opts { std::string getArchStr(); llvm::Optional getRelocModel(); llvm::Optional getCodeModel(); -#if LDC_LLVM_VER >= 1300 llvm::Optional framePointerUsage(); -#else -llvm::Optional framePointerUsage(); -#endif bool disableRedZone(); bool printTargetFeaturesHelp(); diff --git a/driver/cl_options.cpp b/driver/cl_options.cpp index 044c9dcab31..6e6b498bf13 100644 --- a/driver/cl_options.cpp +++ b/driver/cl_options.cpp @@ -536,18 +536,6 @@ cl::opt noPLT( "fno-plt", cl::ZeroOrMore, cl::desc("Do not use the PLT to make function calls")); -static cl::opt passmanager("passmanager", - cl::desc("Setting the passmanager (new,legacy):"), cl::ZeroOrMore, - #if LDC_LLVM_VER < 1500 - cl::init(0), - #else - cl::init(1), - #endif - cl::values( - clEnumValN(0, "legacy", "Use the legacy passmanager (available for LLVM14 and below) "), - clEnumValN(1, "new", "Use the new passmanager (available for LLVM14 and above)"))); -bool isUsingLegacyPassManager() { return passmanager == 0; } - // Math options bool fFastMath; // Storage for the dynamically created ffast-math option. llvm::FastMathFlags defaultFMF; @@ -714,14 +702,10 @@ cl::opt "of optimizations performed by LLVM"), cl::ValueOptional); -#if LDC_LLVM_VER >= 1300 -// LLVM < 13 has "--warn-stack-size", but let's not do the effort of forwarding -// the string to that option, and instead let the user do it himself. cl::opt fWarnStackSize("fwarn-stack-size", cl::ZeroOrMore, cl::init(UINT_MAX), cl::desc("Warn for stack size bigger than the given number"), cl::value_desc("threshold")); -#endif #if LDC_LLVM_SUPPORTED_TARGET_SPIRV || LDC_LLVM_SUPPORTED_TARGET_NVPTX cl::list @@ -750,12 +734,6 @@ cl::opt dynamicCompileTlsWorkaround( cl::Hidden); #endif -#if LDC_LLVM_VER >= 1700 -bool enableOpaqueIRPointers = true; // typed pointers are no longer supported from LLVM 17 -#elif LDC_LLVM_VER >= 1400 -bool enableOpaqueIRPointers = false; -#endif - static cl::extrahelp footer("\n" "-d-debug can also be specified without options, in which case it " @@ -814,12 +792,7 @@ void createClashingOptions() { clEnumValN(FloatABI::Hard, "hard", "Hardware floating-point ABI and instructions"))); -#if LDC_LLVM_VER >= 1400 renameAndHide("opaque-pointers", nullptr); // remove - new cl::opt( - "opaque-pointers", cl::ZeroOrMore, cl::location(enableOpaqueIRPointers), - cl::desc("Use opaque IR pointers (experimental!)"), cl::Hidden); -#endif } /// Hides command line options exposed from within LLVM that are unlikely @@ -907,7 +880,7 @@ void hideLLVMOptions() { "no-discriminators", "no-integrated-as", "no-type-check", "no-xray-index", "nozero-initialized-in-bss", "nvptx-sched4reg", "objc-arc-annotation-target-identifier", - "object-size-offset-visitor-max-visit-instructions", "opaque-pointers", + "object-size-offset-visitor-max-visit-instructions", "pgo-block-coverage", "pgo-temporal-instrumentation", "pgo-view-block-coverage-graph", "pie-copy-relocations", "poison-checking-function-local", diff --git a/driver/cl_options.h b/driver/cl_options.h index 0dba12967c9..da5e8d9906b 100644 --- a/driver/cl_options.h +++ b/driver/cl_options.h @@ -91,8 +91,6 @@ extern cl::opt noPLT; extern cl::opt useDIP25; extern cl::opt useDIP1000; -bool isUsingLegacyPassManager(); - // Math options extern bool fFastMath; extern llvm::FastMathFlags defaultFMF; @@ -139,9 +137,7 @@ extern cl::opt ltoFatObjects; extern cl::opt saveOptimizationRecord; -#if LDC_LLVM_VER >= 1300 extern cl::opt fWarnStackSize; -#endif #if LDC_LLVM_SUPPORTED_TARGET_SPIRV || LDC_LLVM_SUPPORTED_TARGET_NVPTX extern cl::list dcomputeTargets; @@ -154,8 +150,4 @@ extern cl::opt dynamicCompileTlsWorkaround; #else constexpr bool enableDynamicCompile = false; #endif - -#if LDC_LLVM_VER >= 1400 -extern bool enableOpaqueIRPointers; -#endif } diff --git a/driver/cl_options_sanitizers.cpp b/driver/cl_options_sanitizers.cpp index 2ee8e37738a..67ebe34f949 100644 --- a/driver/cl_options_sanitizers.cpp +++ b/driver/cl_options_sanitizers.cpp @@ -21,19 +21,8 @@ #include "llvm/ADT/StringSwitch.h" #include "llvm/Support/raw_ostream.h" #include "llvm/Support/SpecialCaseList.h" -#if LDC_LLVM_VER >= 1300 #include "llvm/Support/VirtualFileSystem.h" -#endif - -#if LDC_LLVM_VER >= 1400 #include "llvm/Transforms/Instrumentation/AddressSanitizerOptions.h" -#else -namespace llvm { -// Declaring this simplifies code later, but the option is never used with LLVM -// <= 13. -enum class AsanDetectStackUseAfterReturnMode { Never, Runtime, Always }; -} -#endif using namespace dmd; @@ -97,14 +86,12 @@ void parseFSanitizeCoverageParameter(llvm::StringRef name, else if (name == "trace-gep") { opts.TraceGep = true; } -#if LDC_LLVM_VER >= 1400 else if (name == "trace-loads") { opts.TraceLoads = true; } else if (name == "trace-stores") { opts.TraceStores = true; } -#endif else if (name == "8bit-counters") { opts.Use8bitCounters = true; } diff --git a/driver/codegenerator.cpp b/driver/codegenerator.cpp index 4f8e4042623..2881238960e 100644 --- a/driver/codegenerator.cpp +++ b/driver/codegenerator.cpp @@ -26,20 +26,14 @@ #include "gen/runtime.h" #include "gen/tollvm.h" #include "ir/irdsymbol.h" -#if LDC_LLVM_VER >= 1400 #include "llvm/IR/DiagnosticInfo.h" -#endif #include "llvm/IR/LLVMRemarkStreamer.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" #include "llvm/Support/ToolOutputFile.h" #include "llvm/Support/YAMLTraits.h" #if LDC_MLIR_ENABLED -#if LDC_LLVM_VER >= 1200 #include "mlir/IR/BuiltinOps.h" -#else -#include "mlir/IR/Module.h" -#endif #include "mlir/IR/MLIRContext.h" #endif @@ -149,19 +143,6 @@ bool inlineAsmDiagnostic(IRState *irs, const llvm::SMDiagnostic &d, return true; } -#if LDC_LLVM_VER < 1300 -void inlineAsmDiagnosticHandler(const llvm::SMDiagnostic &d, void *context, - unsigned locCookie) { - if (d.getKind() == llvm::SourceMgr::DK_Error) { - ++global.errors; - } else if (global.params.warnings == DIAGNOSTICerror && - d.getKind() == llvm::SourceMgr::DK_Warning) { - ++global.warnings; - } - - inlineAsmDiagnostic(static_cast(context), d, locCookie); -} -#else struct InlineAsmDiagnosticHandler : public llvm::DiagnosticHandler { IRState *irs; InlineAsmDiagnosticHandler(IRState *irs) : irs(irs) {} @@ -185,7 +166,6 @@ struct InlineAsmDiagnosticHandler : public llvm::DiagnosticHandler { return inlineAsmDiagnostic(irs, DISM.getSMDiag(), DISM.getLocCookie()); } }; -#endif } // anonymous namespace @@ -280,12 +260,8 @@ void CodeGenerator::writeAndFreeLLModule(const char *filename) { llvm::Metadata *IdentNode[] = {llvm::MDString::get(ir_->context(), Version)}; IdentMetadata->addOperand(llvm::MDNode::get(ir_->context(), IdentNode)); -#if LDC_LLVM_VER < 1300 - context_.setInlineAsmDiagnosticHandler(inlineAsmDiagnosticHandler, ir_); -#else context_.setDiagnosticHandler( std::make_unique(ir_)); -#endif std::unique_ptr diagnosticsOutputFile = createAndSetDiagnosticsOutputFile(*ir_, context_, filename); diff --git a/driver/cpreprocessor.cpp b/driver/cpreprocessor.cpp index a9f7b82f6ce..a3c5c41293e 100644 --- a/driver/cpreprocessor.cpp +++ b/driver/cpreprocessor.cpp @@ -111,12 +111,7 @@ FileName runCPreprocessor(FileName csrcfile, const Loc &loc, args.push_back("/nologo"); args.push_back("/P"); // preprocess only - const bool isClangCl = llvm::StringRef(cc) -#if LDC_LLVM_VER >= 1300 - .contains_insensitive("clang-cl"); -#else - .contains_lower("clang-cl"); -#endif + const bool isClangCl = llvm::StringRef(cc).contains_insensitive("clang-cl"); if (!isClangCl) { args.push_back("/PD"); // print all macro definitions diff --git a/driver/ldmd.cpp b/driver/ldmd.cpp index 1f9d0747c0e..919add2ea26 100644 --- a/driver/ldmd.cpp +++ b/driver/ldmd.cpp @@ -758,13 +758,7 @@ void translateArgs(const llvm::SmallVectorImpl &ldmdArgs, } } else { const auto ext = ls::path::extension(p); - if ( -#if LDC_LLVM_VER >= 1300 - ext.equals_insensitive(".exe") -#else - ext.equals_lower(".exe") -#endif - ) { + if (ext.equals_insensitive(".exe")) { // should be for Windows targets only ldcArgs.push_back(concat("-of=", p)); continue; diff --git a/driver/linker-gcc.cpp b/driver/linker-gcc.cpp index 42ecf0d94f9..6d10d4b7bbc 100644 --- a/driver/linker-gcc.cpp +++ b/driver/linker-gcc.cpp @@ -776,66 +776,22 @@ int linkObjToBinaryGcc(llvm::StringRef outputPath, bool success = false; if (global.params.targetTriple->isOSBinFormatELF()) { - success = lld::elf::link(fullArgs -#if LDC_LLVM_VER < 1400 - , - CanExitEarly -#endif - , - llvm::outs(), llvm::errs() -#if LDC_LLVM_VER >= 1400 - , - CanExitEarly, false -#endif - ); + success = lld::elf::link(fullArgs, llvm::outs(), llvm::errs(), + CanExitEarly, false); } else if (global.params.targetTriple->isOSBinFormatMachO()) { -#if LDC_LLVM_VER >= 1200 - success = lld::macho::link(fullArgs -#else - success = lld::mach_o::link(fullArgs -#endif -#if LDC_LLVM_VER < 1400 - , - CanExitEarly -#endif - , - llvm::outs(), llvm::errs() -#if LDC_LLVM_VER >= 1400 - , - CanExitEarly, false -#endif - ); + success = lld::macho::link(fullArgs, llvm::outs(), llvm::errs(), + CanExitEarly, false); } else if (global.params.targetTriple->isOSBinFormatCOFF()) { - success = lld::mingw::link(fullArgs -#if LDC_LLVM_VER < 1400 - , - CanExitEarly -#endif - , - llvm::outs(), llvm::errs() -#if LDC_LLVM_VER >= 1400 - , - CanExitEarly, false -#endif - ); + success = lld::mingw::link(fullArgs, llvm::outs(), llvm::errs(), + CanExitEarly, false); } else if (global.params.targetTriple->isOSBinFormatWasm()) { #if __linux__ // FIXME: segfault in cleanup (`freeArena()`) after successful linking, // but only on Linux? CanExitEarly = true; #endif - success = lld::wasm::link(fullArgs -#if LDC_LLVM_VER < 1400 - , - CanExitEarly -#endif - , - llvm::outs(), llvm::errs() -#if LDC_LLVM_VER >= 1400 - , - CanExitEarly, false -#endif - ); + success = lld::wasm::link(fullArgs, llvm::outs(), llvm::errs(), + CanExitEarly, false); } else { error(Loc(), "unknown target binary format for internal linking"); } diff --git a/driver/linker-msvc.cpp b/driver/linker-msvc.cpp index ba93138cdfa..5003ecf74de 100644 --- a/driver/linker-msvc.cpp +++ b/driver/linker-msvc.cpp @@ -42,7 +42,7 @@ void addMscrtLibs(bool useInternalToolchain, std::vector &args) { #if LDC_LLVM_VER >= 1700 #define contains_lower contains_insensitive #define endswith_lower ends_with_insensitive -#elif LDC_LLVM_VER >= 1300 +#else #define contains_lower contains_insensitive #define endswith_lower endswith_insensitive #endif @@ -278,18 +278,8 @@ int linkObjToBinaryMSVC(llvm::StringRef outputPath, getFullArgs("lld-link", args, global.params.v.verbose); const bool canExitEarly = false; - const bool success = lld::coff::link(fullArgs -#if LDC_LLVM_VER < 1400 - , - canExitEarly -#endif - , - llvm::outs(), llvm::errs() -#if LDC_LLVM_VER >= 1400 - , - canExitEarly, false -#endif - ); + const bool success = lld::coff::link(fullArgs, llvm::outs(), llvm::errs(), + canExitEarly, false); if (!success) error(Loc(), "linking with LLD failed"); diff --git a/driver/main.cpp b/driver/main.cpp index bf732a5e72a..ea89e8cba4d 100644 --- a/driver/main.cpp +++ b/driver/main.cpp @@ -65,11 +65,7 @@ #include "llvm/Support/ManagedStatic.h" #include "llvm/Support/Path.h" #include "llvm/Support/StringSaver.h" -#if LDC_LLVM_VER >= 1400 #include "llvm/MC/TargetRegistry.h" -#else -#include "llvm/Support/TargetRegistry.h" -#endif #include "llvm/Support/TargetSelect.h" #include "llvm/Target/TargetMachine.h" #if LDC_MLIR_ENABLED @@ -562,29 +558,11 @@ void parseCommandLine(Strings &sourceFiles) { global.params.dihdr.fullOutput = opts::hdrKeepAllBodies; global.params.disableRedZone = opts::disableRedZone(); - // Passmanager selection options depend on LLVM version -#if LDC_LLVM_VER < 1400 - // LLVM < 14 only supports the legacy passmanager - if (!opts::isUsingLegacyPassManager()) { - error(Loc(), "LLVM version 13 or below only supports --passmanager=legacy"); - } -#endif -#if LDC_LLVM_VER >= 1500 - // LLVM >= 15 only supports the new passmanager - if (opts::isUsingLegacyPassManager()) { - error(Loc(), "LLVM version 15 or above only supports --passmanager=new"); - } -#endif - + // enforce opaque IR pointers #if LDC_LLVM_VER >= 1700 - if (!opts::enableOpaqueIRPointers) - error(Loc(), - "LLVM version 17 or above only supports --opaque-pointers=true"); -#elif LDC_LLVM_VER >= 1500 - getGlobalContext().setOpaquePointers(opts::enableOpaqueIRPointers); -#elif LDC_LLVM_VER >= 1400 - if (opts::enableOpaqueIRPointers) - getGlobalContext().enableOpaquePointers(); + // supports opaque IR pointers only +#else + getGlobalContext().setOpaquePointers(true); #endif } @@ -930,6 +908,9 @@ void registerPredefinedTargetVersions() { VersionCondition::addPredefinedGlobalIdent("WASI"); VersionCondition::addPredefinedGlobalIdent("CRuntime_WASI"); break; + case llvm::Triple::Emscripten: + VersionCondition::addPredefinedGlobalIdent("Emscripten"); + break; default: if (triple.getEnvironment() == llvm::Triple::Android) { VersionCondition::addPredefinedGlobalIdent("Android"); @@ -1041,16 +1022,6 @@ void registerPredefinedVersions() { VersionCondition::addPredefinedGlobalIdent("LDC_ThreadSanitizer"); } - // Set a version identifier for whether opaque pointers are enabled or not. (needed e.g. for intrinsic mangling) -#if LDC_LLVM_VER >= 1700 - // Since LLVM 17, IR pointers are always opaque. - VersionCondition::addPredefinedGlobalIdent("LDC_LLVM_OpaquePointers"); -#elif LDC_LLVM_VER >= 1400 - if (!getGlobalContext().supportsTypedPointers()) { - VersionCondition::addPredefinedGlobalIdent("LDC_LLVM_OpaquePointers"); - } -#endif - // Expose LLVM version to runtime #define STR(x) #x #define XSTR(x) STR(x) diff --git a/driver/plugins.cpp b/driver/plugins.cpp index 713d8bee806..1356b4c5fde 100644 --- a/driver/plugins.cpp +++ b/driver/plugins.cpp @@ -24,14 +24,11 @@ #include "llvm/Passes/PassBuilder.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/DynamicLibrary.h" - -#if LDC_LLVM_VER >= 1400 #include "llvm/ADT/SmallVector.h" #include "llvm/Passes/PassPlugin.h" #include "llvm/Support/Error.h" #include "driver/cl_options.h" -#endif namespace { namespace cl = llvm::cl; @@ -78,20 +75,6 @@ bool loadSemanticAnalysisPlugin(const std::string &filename) { return true; } -/// Loads plugin for the legacy pass manager. The static constructor of -/// the plugin should take care of the plugins registering themself with the -/// rest of LDC/LLVM. -void loadLLVMPluginLegacyPM(const std::string &filename) { - std::string errorString; - if (llvm::sys::DynamicLibrary::LoadLibraryPermanently(filename.c_str(), - &errorString)) { - error(Loc(), "Error loading plugin '%s': %s", filename.c_str(), - errorString.c_str()); - } -} - -#if LDC_LLVM_VER >= 1400 - namespace { llvm::SmallVector llvm_plugins; @@ -110,17 +93,8 @@ void loadLLVMPluginNewPM(const std::string &filename) { } // anonymous namespace -#endif // LDC_LLVM_VER >= 1400 - void loadLLVMPlugin(const std::string &filename) { -#if LDC_LLVM_VER >= 1400 - if (opts::isUsingLegacyPassManager()) - loadLLVMPluginLegacyPM(filename); - else - loadLLVMPluginNewPM(filename); -#else - loadLLVMPluginLegacyPM(filename); -#endif + loadLLVMPluginNewPM(filename); } void loadAllPlugins() { @@ -134,11 +108,9 @@ void loadAllPlugins() { } void registerAllPluginsWithPassBuilder(llvm::PassBuilder &PB) { -#if LDC_LLVM_VER >= 1400 for (auto &plugin : llvm_plugins) { plugin.registerPassBuilderCallbacks(PB); } -#endif } void runAllSemanticAnalysisPlugins(Module *m) { diff --git a/driver/targetmachine.cpp b/driver/targetmachine.cpp index a8f1704291f..1408d632377 100644 --- a/driver/targetmachine.cpp +++ b/driver/targetmachine.cpp @@ -26,10 +26,8 @@ #include "llvm/MC/SubtargetFeature.h" #include "llvm/Support/Host.h" #include "llvm/Support/TargetParser.h" -#if LDC_LLVM_VER >= 1400 #include "llvm/Support/AArch64TargetParser.h" #include "llvm/Support/ARMTargetParser.h" -#endif #else #include "llvm/TargetParser/AArch64TargetParser.h" #include "llvm/TargetParser/ARMTargetParser.h" @@ -41,11 +39,7 @@ #include "llvm/IR/Module.h" #include "llvm/MC/MCObjectFileInfo.h" #include "llvm/Support/CommandLine.h" -#if LDC_LLVM_VER >= 1400 #include "llvm/MC/TargetRegistry.h" -#else -#include "llvm/Support/TargetRegistry.h" -#endif #include "llvm/Support/TargetSelect.h" #include "llvm/Target/TargetMachine.h" #include "llvm/Target/TargetOptions.h" @@ -467,14 +461,9 @@ createTargetMachine(const std::string targetTriple, const std::string arch, if (!envVersion.empty()) { osname += envVersion; } else { -#if LDC_LLVM_VER >= 1400 llvm::VersionTuple OSVersion; triple.getMacOSXVersion(OSVersion); osname += OSVersion.getAsString(); -#else - // Hardcode the version, because `getMacOSXVersion` is not available. - osname += "10.7"; -#endif } triple.setOSName(osname); diff --git a/gen/aa.cpp b/gen/aa.cpp index cce1228148e..b8165d56741 100644 --- a/gen/aa.cpp +++ b/gen/aa.cpp @@ -27,12 +27,11 @@ using namespace dmd; // returns the keytype typeinfo -static LLConstant *to_keyti(const Loc &loc, DValue *aa, LLType *targetType) { +static LLConstant *to_keyti(const Loc &loc, DValue *aa) { // keyti param assert(aa->type->toBasetype()->ty == TY::Taarray); TypeAArray *aatype = static_cast(aa->type->toBasetype()); - LLConstant *ti = DtoTypeInfoOf(loc, aatype->index, /*base=*/false); - return DtoBitCast(ti, targetType); + return DtoTypeInfoOf(loc, aatype->index); } //////////////////////////////////////////////////////////////////////////////// @@ -49,35 +48,28 @@ DLValue *DtoAAIndex(const Loc &loc, Type *type, DValue *aa, DValue *key, // first get the runtime function llvm::Function *func = getRuntimeFunction(loc, gIR->module, lvalue ? "_aaGetY" : "_aaInX"); - LLFunctionType *funcTy = func->getFunctionType(); // aa param LLValue *aaval = lvalue ? DtoLVal(aa) : DtoRVal(aa); - aaval = DtoBitCast(aaval, funcTy->getParamType(0)); + assert(aaval->getType()->isPointerTy()); // pkey param LLValue *pkey = makeLValue(loc, key); - pkey = DtoBitCast(pkey, funcTy->getParamType(lvalue ? 3 : 2)); // call runtime LLValue *ret; if (lvalue) { auto t = mutableOf(unSharedOf(aa->type)); - LLValue *rawAATI = DtoTypeInfoOf(loc, t, /*base=*/false); - LLValue *castedAATI = DtoBitCast(rawAATI, funcTy->getParamType(1)); + LLValue *aati = DtoTypeInfoOf(loc, t); LLValue *valsize = DtoConstSize_t(getTypeAllocSize(DtoType(type))); - ret = gIR->CreateCallOrInvoke(func, aaval, castedAATI, valsize, pkey, + ret = gIR->CreateCallOrInvoke(func, aaval, aati, valsize, pkey, "aa.index"); } else { - LLValue *keyti = to_keyti(loc, aa, funcTy->getParamType(1)); + LLValue *keyti = to_keyti(loc, aa); ret = gIR->CreateCallOrInvoke(func, aaval, keyti, pkey, "aa.index"); } - // cast return value - LLType *targettype = DtoPtrToType(type); - if (ret->getType() != targettype) { - ret = DtoBitCast(ret, targettype); - } + assert(ret->getType()->isPointerTy()); // Only check bounds for rvalues ('aa[key]'). // Lvalue use ('aa[key] = value') auto-adds an element. @@ -112,34 +104,25 @@ DValue *DtoAAIn(const Loc &loc, Type *type, DValue *aa, DValue *key) { // first get the runtime function llvm::Function *func = getRuntimeFunction(loc, gIR->module, "_aaInX"); - LLFunctionType *funcTy = func->getFunctionType(); IF_LOG Logger::cout() << "_aaIn = " << *func << '\n'; // aa param LLValue *aaval = DtoRVal(aa); + assert(aaval->getType()->isPointerTy()); IF_LOG { Logger::cout() << "aaval: " << *aaval << '\n'; - Logger::cout() << "totype: " << *funcTy->getParamType(0) << '\n'; } - aaval = DtoBitCast(aaval, funcTy->getParamType(0)); // keyti param - LLValue *keyti = to_keyti(loc, aa, funcTy->getParamType(1)); + LLValue *keyti = to_keyti(loc, aa); // pkey param LLValue *pkey = makeLValue(loc, key); - pkey = DtoBitCast(pkey, getVoidPtrType()); // call runtime LLValue *ret = gIR->CreateCallOrInvoke(func, aaval, keyti, pkey, "aa.in"); - // cast return value - LLType *targettype = DtoType(type); - if (ret->getType() != targettype) { - ret = DtoBitCast(ret, targettype); - } - return new DImValue(type, ret); } @@ -156,24 +139,21 @@ DValue *DtoAARemove(const Loc &loc, DValue *aa, DValue *key) { // first get the runtime function llvm::Function *func = getRuntimeFunction(loc, gIR->module, "_aaDelX"); - LLFunctionType *funcTy = func->getFunctionType(); IF_LOG Logger::cout() << "_aaDel = " << *func << '\n'; // aa param LLValue *aaval = DtoRVal(aa); + assert(aaval->getType()->isPointerTy()); IF_LOG { Logger::cout() << "aaval: " << *aaval << '\n'; - Logger::cout() << "totype: " << *funcTy->getParamType(0) << '\n'; } - aaval = DtoBitCast(aaval, funcTy->getParamType(0)); // keyti param - LLValue *keyti = to_keyti(loc, aa, funcTy->getParamType(1)); + LLValue *keyti = to_keyti(loc, aa); // pkey param LLValue *pkey = makeLValue(loc, key); - pkey = DtoBitCast(pkey, funcTy->getParamType(2)); // call runtime LLValue *res = gIR->CreateCallOrInvoke(func, aaval, keyti, pkey); @@ -188,10 +168,11 @@ LLValue *DtoAAEquals(const Loc &loc, EXP op, DValue *l, DValue *r) { assert(t == r->type->toBasetype() && "aa equality is only defined for aas of same type"); llvm::Function *func = getRuntimeFunction(loc, gIR->module, "_aaEqual"); - LLFunctionType *funcTy = func->getFunctionType(); - LLValue *aaval = DtoBitCast(DtoRVal(l), funcTy->getParamType(1)); - LLValue *abval = DtoBitCast(DtoRVal(r), funcTy->getParamType(2)); + LLValue *aaval = DtoRVal(l); + assert(aaval->getType()->isPointerTy()); + LLValue *abval = DtoRVal(r); + assert(abval->getType()->isPointerTy()); LLValue *aaTypeInfo = DtoTypeInfoOf(loc, t); LLValue *res = gIR->CreateCallOrInvoke(func, aaTypeInfo, aaval, abval, "aaEqRes"); diff --git a/gen/abi/abi.cpp b/gen/abi/abi.cpp index d015ee65c07..114117c69a7 100644 --- a/gen/abi/abi.cpp +++ b/gen/abi/abi.cpp @@ -32,7 +32,7 @@ using namespace dmd; llvm::Value *ABIRewrite::getRVal(Type *dty, LLValue *v) { llvm::Type *t = DtoType(dty); - return DtoLoad(t, DtoBitCast(getLVal(dty, v), t->getPointerTo())); + return DtoLoad(t, getLVal(dty, v)); } ////////////////////////////////////////////////////////////////////////////// @@ -191,8 +191,8 @@ void TargetABI::rewriteVarargs(IrFuncTy &fty, ////////////////////////////////////////////////////////////////////////////// LLValue *TargetABI::prepareVaStart(DLValue *ap) { - // pass a i8* pointer to ap to LLVM's va_start intrinsic - return DtoBitCast(DtoLVal(ap), getVoidPtrType()); + // pass an opaque pointer to ap to LLVM's va_start intrinsic + return DtoLVal(ap); } ////////////////////////////////////////////////////////////////////////////// @@ -209,8 +209,8 @@ void TargetABI::vaCopy(DLValue *dest, DValue *src) { ////////////////////////////////////////////////////////////////////////////// LLValue *TargetABI::prepareVaArg(DLValue *ap) { - // pass a i8* pointer to ap to LLVM's va_arg intrinsic - return DtoBitCast(DtoLVal(ap), getVoidPtrType()); + // pass an opaque pointer to ap to LLVM's va_arg intrinsic + return DtoLVal(ap); } ////////////////////////////////////////////////////////////////////////////// diff --git a/gen/abi/generic.h b/gen/abi/generic.h index 19b7aee0362..3647d8be79b 100644 --- a/gen/abi/generic.h +++ b/gen/abi/generic.h @@ -148,7 +148,6 @@ struct BaseBitcastABIRewrite : ABIRewrite { return DtoLoad(asType, paddedDump, name); } - address = DtoBitCast(address, getPtrToType(asType)); return DtoLoad(asType, address, name); } @@ -248,7 +247,7 @@ struct IndirectByvalRewrite : ABIRewrite { } LLValue *getLVal(Type *dty, LLValue *v) override { - return DtoBitCast(v, DtoPtrToType(dty)); + return v; } LLType *type(Type *t) override { return DtoPtrToType(t); } diff --git a/gen/abi/loongarch64.cpp b/gen/abi/loongarch64.cpp index f0f2d853d59..4141bd124f2 100644 --- a/gen/abi/loongarch64.cpp +++ b/gen/abi/loongarch64.cpp @@ -9,7 +9,7 @@ //===----------------------------------------------------------------------===// // // ABI spec: -// https://loongson.github.io/LoongArch-Documentation/LoongArch-ELF-ABI-EN.html +// https://github.com/loongson/la-abi-specs/blob/release/lapcs.adoc // //===----------------------------------------------------------------------===// @@ -20,9 +20,101 @@ #include "gen/llvmhelpers.h" #include "gen/tollvm.h" +using namespace dmd; + +namespace { +struct Integer2Rewrite : BaseBitcastABIRewrite { + LLType *type(Type *t) override { + return LLStructType::get(gIR->context(), + {DtoType(Type::tint64), DtoType(Type::tint64)}); + } +}; + +struct FlattenedFields { + Type *fields[2]; + int length = 0; // use -1 to represent "no need to rewrite" condition +}; + +FlattenedFields visitStructFields(Type *ty, unsigned baseOffset) { + // recursively visit a POD struct to flatten it + FlattenedFields result; + if (auto ts = ty->isTypeStruct()) { + for (auto fi : ts->sym->fields) { + auto sub = + visitStructFields(fi->type->toBasetype(), baseOffset + fi->offset); + if (sub.length == -1 || result.length + sub.length > 2) { + result.length = -1; + return result; + } + for (unsigned i = 0; i < (unsigned)sub.length; ++i) { + result.fields[result.length++] = sub.fields[i]; + } + } + return result; + } + switch (ty->ty) { + case TY::Tcomplex32: // treat it as {float32, float32} + result.fields[0] = pointerTo(Type::tfloat32); + result.fields[1] = pointerTo(Type::tfloat32); + result.length = 2; + break; + case TY::Tcomplex64: // treat it as {float64, float64} + result.fields[0] = pointerTo(Type::tfloat64); + result.fields[1] = pointerTo(Type::tfloat64); + result.length = 2; + break; + default: + if (ty->size() > 8) { + // field larger than GRLEN and FRLEN + result.length = -1; + break; + } + result.fields[0] = ty; + result.length = 1; + break; + } + return result; +} + +struct HardfloatRewrite : BaseBitcastABIRewrite { + LLType *type(Type *ty, const FlattenedFields &flat) { + if (flat.length == 1) { + return LLStructType::get(gIR->context(), {DtoType(flat.fields[0])}, + false); + } + assert(flat.length == 2); + LLType *t[2]; + for (unsigned i = 0; i < 2; ++i) { + t[i] = + flat.fields[i]->isfloating() + ? DtoType(flat.fields[i]) + : LLIntegerType::get(gIR->context(), flat.fields[i]->size() * 8); + } + return LLStructType::get(gIR->context(), {t[0], t[1]}, false); + } + LLType *type(Type *ty) override { + return type(ty, visitStructFields(ty->toBasetype(), 0)); + } +}; +} // anonymous namespace + struct LoongArch64TargetABI : TargetABI { private: + HardfloatRewrite hardfloatRewrite; IndirectByvalRewrite indirectByvalRewrite{}; + Integer2Rewrite integer2Rewrite; + IntegerRewrite integerRewrite; + + bool requireHardfloatRewrite(Type *ty) { + if (!isPOD(ty) || !ty->toBasetype()->isTypeStruct()) + return false; + auto result = visitStructFields(ty->toBasetype(), 0); + if (result.length <= 0) + return false; + if (result.length == 1) + return result.fields[0]->isfloating(); + return result.fields[0]->isfloating() || result.fields[1]->isfloating(); + } public: auto returnInArg(TypeFunction *tf, bool) -> bool override { @@ -45,27 +137,51 @@ struct LoongArch64TargetABI : TargetABI { } void rewriteFunctionType(IrFuncTy &fty) override { - if (!fty.ret->byref) { - rewriteArgument(fty, *fty.ret); + if (!skipReturnValueRewrite(fty)) { + if (requireHardfloatRewrite(fty.ret->type)) { + // rewrite here because we should not apply this to variadic arguments + hardfloatRewrite.applyTo(*fty.ret); + } else { + rewriteArgument(fty, *fty.ret); + } } for (auto arg : fty.args) { if (!arg->byref) { - rewriteArgument(fty, *arg); + if (requireHardfloatRewrite(arg->type)) { + // rewrite here because we should not apply this to variadic arguments + hardfloatRewrite.applyTo(*arg); + } else { + rewriteArgument(fty, *arg); + } } } } void rewriteArgument(IrFuncTy &fty, IrFuncTyArg &arg) override { - if (arg.byref) { - return; - } - if (!isPOD(arg.type)) { // non-PODs should be passed in memory indirectByvalRewrite.applyTo(arg); return; } + + Type *ty = arg.type->toBasetype(); + if (ty->isintegral() && (ty->ty == TY::Tint32 || ty->ty == TY::Tuns32 || + ty->ty == TY::Tdchar)) { + // In the LP64D ABI, both int32 and unsigned int32 are stored in + // general-purpose registers as proper sign extensions of their + // 32-bit values. So, the native ABI function's int32 arguments and + // return values should have the `signext` attribute. + // C example: https://godbolt.org/z/vcjErxj76 + arg.attrs.addAttribute(LLAttribute::SExt); + } else if (isAggregate(ty) && ty->size() && ty->size() <= 16) { + if (ty->size() > 8 && DtoAlignment(ty) < 16) { + // pass the aggregate as {int64, int64} to avoid wrong alignment + integer2Rewrite.applyToIfNotObsolete(arg); + } else { + integerRewrite.applyToIfNotObsolete(arg); + } + } } }; diff --git a/gen/abi/riscv64.cpp b/gen/abi/riscv64.cpp index 10e3e5db515..7c36eb100cb 100644 --- a/gen/abi/riscv64.cpp +++ b/gen/abi/riscv64.cpp @@ -110,8 +110,7 @@ struct HardfloatRewrite : ABIRewrite { DtoRawAlloca(asType, alignment, ".HardfloatRewrite_arg_storage"); for (unsigned i = 0; i < (unsigned)flat.length; ++i) { DtoMemCpy(DtoGEP(asType, buffer, 0, i), - DtoGEP1(getI8Type(), DtoBitCast(address, getVoidPtrType()), - flat.fields[i].offset), + DtoGEP1(getI8Type(), address, flat.fields[i].offset), DtoConstSize_t(flat.fields[i].ty->size())); } return DtoLoad(asType, buffer, ".HardfloatRewrite_arg"); @@ -126,8 +125,7 @@ struct HardfloatRewrite : ABIRewrite { LLValue *ret = DtoRawAlloca(DtoType(dty), alignment, ".HardfloatRewrite_param_storage"); for (unsigned i = 0; i < (unsigned)flat.length; ++i) { - DtoMemCpy(DtoGEP1(getI8Type(), DtoBitCast(ret, getVoidPtrType()), - flat.fields[i].offset), + DtoMemCpy(DtoGEP1(getI8Type(), ret, flat.fields[i].offset), DtoGEP(asType, buffer, 0, i), DtoConstSize_t(flat.fields[i].ty->size())); } diff --git a/gen/abi/x86-64.cpp b/gen/abi/x86-64.cpp index c8c59f1a244..6018b380469 100644 --- a/gen/abi/x86-64.cpp +++ b/gen/abi/x86-64.cpp @@ -134,11 +134,7 @@ struct ImplicitByvalRewrite : ABIRewrite { void applyTo(IrFuncTyArg &arg, LLType *finalLType = nullptr) override { ABIRewrite::applyTo(arg, finalLType); -#if LDC_LLVM_VER >= 1200 arg.attrs.addByValAttr(DtoType(arg.type)); -#else - arg.attrs.addAttribute(LLAttribute::ByVal); -#endif if (auto alignment = DtoAlignment(arg.type)) arg.attrs.addAlignmentAttr(alignment); } @@ -356,27 +352,25 @@ LLValue *X86_64TargetABI::prepareVaStart(DLValue *ap) { // invoking va_start, we first need to allocate the actual __va_list_tag struct // and set `ap` to its address. LLValue *valistmem = DtoRawAlloca(getValistType(), 0, "__va_list_mem"); - DtoStore(valistmem, - DtoBitCast(DtoLVal(ap), getPtrToType(valistmem->getType()))); - // Pass a i8* pointer to the actual struct to LLVM's va_start intrinsic. - return DtoBitCast(valistmem, getVoidPtrType()); + DtoStore(valistmem, DtoLVal(ap)); + // Pass an opaque pointer to the actual struct to LLVM's va_start intrinsic. + return valistmem; } void X86_64TargetABI::vaCopy(DLValue *dest, DValue *src) { // Analog to va_start, we first need to allocate a new __va_list_tag struct on // the stack and set `dest` to its address. LLValue *valistmem = DtoRawAlloca(getValistType(), 0, "__va_list_mem"); - DtoStore(valistmem, - DtoBitCast(DtoLVal(dest), getPtrToType(valistmem->getType()))); + DtoStore(valistmem, DtoLVal(dest)); // Then fill the new struct with a bitcopy of the source struct. // `src` is a __va_list_tag* pointer to the source struct. DtoMemCpy(getValistType(), valistmem, DtoRVal(src)); } LLValue *X86_64TargetABI::prepareVaArg(DLValue *ap) { - // Pass a i8* pointer to the actual __va_list_tag struct to LLVM's va_arg + // Pass an opaque pointer to the actual __va_list_tag struct to LLVM's va_arg // intrinsic. - return DtoBitCast(DtoRVal(ap), getVoidPtrType()); + return DtoRVal(ap); } Type *X86_64TargetABI::vaListType() { diff --git a/gen/abi/x86.cpp b/gen/abi/x86.cpp index 366bfad1423..5965a7f38d2 100644 --- a/gen/abi/x86.cpp +++ b/gen/abi/x86.cpp @@ -261,21 +261,11 @@ struct X86TargetABI : TargetABI { if (isMSVC) { for (auto arg : args) { if (arg->isByVal()) { -#if LDC_LLVM_VER < 1300 - arg->attrs.removeAttribute(LLAttribute::Alignment); -#else // Keep alignment for LLVM 13+, to prevent invalid `movaps` etc., // but limit to 4 (required according to runnable/ldc_cabi1.d). auto align4 = llvm::Align(4); - if (arg->attrs.getAlignment(). -#if LDC_LLVM_VER >= 1500 - value_or -#else - getValueOr -#endif - (align4) > align4) + if (arg->attrs.getAlignment().value_or(align4) > align4) arg->attrs.addAlignmentAttr(align4); -#endif } } } diff --git a/gen/arrays.cpp b/gen/arrays.cpp index 8d5092cbc10..716026331a0 100644 --- a/gen/arrays.cpp +++ b/gen/arrays.cpp @@ -36,15 +36,6 @@ static void DtoSetArray(DValue *array, DValue *rhs); //////////////////////////////////////////////////////////////////////////////// -namespace { -LLValue *DtoSlice(LLValue *ptr, LLValue *length, LLType *elemType) { - elemType = i1ToI8(voidToI8(elemType)); - return DtoAggrPair(length, DtoBitCast(ptr, elemType->getPointerTo())); -} -} - -//////////////////////////////////////////////////////////////////////////////// - LLStructType *DtoArrayType(Type *arrayTy) { assert(arrayTy->nextOf()); llvm::Type *elems[] = {DtoSize_t(), DtoPtrToType(arrayTy->nextOf())}; @@ -223,20 +214,19 @@ void DtoArrayAssign(const Loc &loc, DValue *lhs, DValue *rhs, EXP op, Type *const elemType = t->nextOf()->toBasetype(); const bool needsDestruction = (!isConstructing && elemType->needsDestruction()); - LLValue *realLhsPtr = DtoArrayPtr(lhs); - LLValue *lhsPtr = DtoBitCast(realLhsPtr, getVoidPtrType()); + LLValue *lhsPtr = DtoArrayPtr(lhs); LLValue *lhsLength = DtoArrayLen(lhs); // Be careful to handle void arrays correctly when modifying this (see tests // for DMD issue 7493). // TODO: This should use AssignExp::memset. - LLValue *realRhsArrayPtr = (t2->ty == TY::Tarray || t2->ty == TY::Tsarray) - ? DtoArrayPtr(rhs) - : nullptr; - if (realRhsArrayPtr && DtoMemType(t2->nextOf()) == DtoMemType(t->nextOf())) { + LLValue *rhsArrayPtr = (t2->ty == TY::Tarray || t2->ty == TY::Tsarray) + ? DtoArrayPtr(rhs) + : nullptr; + if (rhsArrayPtr && DtoMemType(t2->nextOf()) == DtoMemType(t->nextOf())) { // T[] = T[] T[] = T[n] // T[n] = T[n] T[n] = T[] - LLValue *rhsPtr = DtoBitCast(realRhsArrayPtr, getVoidPtrType()); + LLValue *rhsPtr = rhsArrayPtr; LLValue *rhsLength = DtoArrayLen(rhs); const bool needsPostblit = (op != EXP::blit && arrayNeedsPostblit(t) && @@ -274,9 +264,9 @@ void DtoArrayAssign(const Loc &loc, DValue *lhs, DValue *rhs, EXP op, LLFunction *fn = getRuntimeFunction( loc, gIR->module, !canSkipPostblit ? "_d_arrayassign_l" : "_d_arrayassign_r"); - gIR->CreateCallOrInvoke( - fn, DtoTypeInfoOf(loc, elemType), DtoSlice(rhsPtr, rhsLength, getI8Type()), - DtoSlice(lhsPtr, lhsLength, getI8Type()), DtoBitCast(tmpSwap, getVoidPtrType())); + gIR->CreateCallOrInvoke(fn, DtoTypeInfoOf(loc, elemType), + DtoAggrPair(rhsLength, rhsPtr), + DtoAggrPair(lhsLength, lhsPtr), tmpSwap); } } else { // scalar rhs: @@ -291,7 +281,6 @@ void DtoArrayAssign(const Loc &loc, DValue *lhs, DValue *rhs, EXP op, getTypeAllocSize(DtoMemType(lhs->type->nextOf())); LLType *rhsType = DtoMemType(t2); const size_t rhsSize = getTypeAllocSize(rhsType); - LLValue *actualPtr = DtoBitCast(realLhsPtr, rhsType->getPointerTo()); LLValue *actualLength = lhsLength; if (rhsSize != lhsElementSize) { LLValue *lhsSize = computeSize(lhsLength, lhsElementSize); @@ -300,7 +289,7 @@ void DtoArrayAssign(const Loc &loc, DValue *lhs, DValue *rhs, EXP op, ? lhsSize : gIR->ir->CreateExactUDiv(lhsSize, DtoConstSize_t(rhsSize)); } - DtoArrayInit(loc, actualPtr, actualLength, rhs); + DtoArrayInit(loc, lhsPtr, actualLength, rhs); } else if (isConstructing) { error(loc, "ICE: array construction should have been lowered to " "`_d_arraysetctor`"); @@ -309,7 +298,7 @@ void DtoArrayAssign(const Loc &loc, DValue *lhs, DValue *rhs, EXP op, LLFunction *fn = getRuntimeFunction(loc, gIR->module, "_d_arraysetassign"); gIR->CreateCallOrInvoke( - fn, lhsPtr, DtoBitCast(makeLValue(loc, rhs), getVoidPtrType()), + fn, lhsPtr, makeLValue(loc, rhs), gIR->ir->CreateTruncOrBitCast(lhsLength, LLType::getInt32Ty(gIR->context())), DtoTypeInfoOf(loc, stripModifiers(t2))); @@ -461,13 +450,10 @@ LLConstant *DtoConstArrayInitializer(ArrayInitializer *arrinit, if (arrty->ty == TY::Tpointer) { // we need to return pointer to the static array. - return DtoBitCast(gvar, DtoType(arrty)); + return gvar; } - LLConstant *gep = DtoGEP(gvar->getValueType(), gvar, 0u, 0u); - gep = llvm::ConstantExpr::getBitCast(gvar, getPtrToType(llelemty)); - - return DtoConstSlice(DtoConstSize_t(arrlen), gep, arrty); + return DtoConstSlice(DtoConstSize_t(arrlen), gvar); } //////////////////////////////////////////////////////////////////////////////// @@ -607,7 +593,7 @@ void initializeArrayLiteral(IRState *p, ArrayLiteralExp *ale, // optimizer can still decide to promote the memcpy intrinsic, so // the cutoff merely affects compilation speed. if (elemCount <= 4) { - DtoStore(constarr, DtoBitCast(dstMem, getPtrToType(constarr->getType()))); + DtoStore(constarr, dstMem); } else { auto gvar = new llvm::GlobalVariable(gIR->module, constarr->getType(), true, LLGlobalValue::InternalLinkage, @@ -622,7 +608,7 @@ void initializeArrayLiteral(IRState *p, ArrayLiteralExp *ale, Expression *rhsExp = indexArrayLiteral(ale, i); LLValue *lhsPtr = DtoGEP(dstType, dstMem, 0, i, "", p->scopebb()); - DLValue lhs(rhsExp->type, DtoBitCast(lhsPtr, DtoPtrToType(rhsExp->type))); + DLValue lhs(rhsExp->type, lhsPtr); // try to construct it in-place if (!toInPlaceConstruction(&lhs, rhsExp)) @@ -632,28 +618,13 @@ void initializeArrayLiteral(IRState *p, ArrayLiteralExp *ale, } //////////////////////////////////////////////////////////////////////////////// -LLConstant *DtoConstSlice(LLConstant *dim, LLConstant *ptr, Type *type) { +LLConstant *DtoConstSlice(LLConstant *dim, LLConstant *ptr) { LLConstant *values[2] = {dim, ptr}; LLStructType *lltype = - type ? isaStruct(DtoType(type)) - : LLConstantStruct::getTypeForElements(gIR->context(), values); + LLConstantStruct::getTypeForElements(gIR->context(), values); return LLConstantStruct::get(lltype, values); } -//////////////////////////////////////////////////////////////////////////////// - -static DSliceValue *getSlice(Type *arrayType, LLValue *array) { - LLType *llArrayType = DtoType(arrayType); - if (array->getType() == llArrayType) - return new DSliceValue(arrayType, array); - - LLValue *len = DtoExtractValue(array, 0, ".len"); - LLValue *ptr = DtoExtractValue(array, 1, ".ptr"); - ptr = DtoBitCast(ptr, llArrayType->getContainedType(1)); - - return new DSliceValue(arrayType, len, ptr); -} - //////////////////////////////////////////////////////////////////////////////// DSliceValue *DtoNewDynArray(const Loc &loc, Type *arrayType, DValue *dim, bool defaultInit) { @@ -684,8 +655,7 @@ DSliceValue *DtoNewDynArray(const Loc &loc, Type *arrayType, DValue *dim, gIR->CreateCallOrInvoke(fn, arrayTypeInfo, arrayLen, ".gc_mem"); // return a DSliceValue with the well-known length for better optimizability - auto ptr = - DtoBitCast(DtoExtractValue(newArray, 1, ".ptr"), DtoPtrToType(eltType)); + auto ptr = DtoExtractValue(newArray, 1, ".ptr"); return new DSliceValue(arrayType, arrayLen, ptr); } @@ -700,11 +670,11 @@ DSliceValue *DtoAppendDChar(const Loc &loc, DValue *arr, Expression *exp, // Call function (ref string x, dchar c) LLValue *newArray = gIR->CreateCallOrInvoke( - fn, DtoBitCast(DtoLVal(arr), fn->getFunctionType()->getParamType(0)), + fn, DtoLVal(arr), DtoBitCast(valueToAppend, fn->getFunctionType()->getParamType(1)), ".appendedArray"); - return getSlice(arr->type, newArray); + return new DSliceValue(arr->type, newArray); } //////////////////////////////////////////////////////////////////////////////// @@ -753,7 +723,7 @@ LLValue *DtoArrayEqCmp_impl(const Loc &loc, const char *func, DValue *l, // pass array typeinfo ? if (useti) { LLValue *tival = DtoTypeInfoOf(loc, l->type); - args.push_back(DtoBitCast(tival, fn->getFunctionType()->getParamType(2))); + args.push_back(tival); } return gIR->CreateCallOrInvoke(fn, args); @@ -833,8 +803,7 @@ llvm::CallInst *callMemcmp(const Loc &loc, IRState &irs, LLValue *l_ptr, sizeInBytes = irs.ir->CreateMul(sizeInBytes, DtoConstSize_t(elementSize)); } // Call memcmp. - LLValue *args[] = {DtoBitCast(l_ptr, getVoidPtrType()), - DtoBitCast(r_ptr, getVoidPtrType()), sizeInBytes}; + LLValue *args[] = {l_ptr, r_ptr, sizeInBytes}; return irs.ir->CreateCall(fn, args); } @@ -983,7 +952,7 @@ LLValue *DtoArrayPtr(DValue *v) { llvm_unreachable("Unexpected array type."); } - return DtoBitCast(ptr, wantedLLPtrType); + return ptr; } //////////////////////////////////////////////////////////////////////////////// @@ -991,8 +960,6 @@ DValue *DtoCastArray(const Loc &loc, DValue *u, Type *to) { IF_LOG Logger::println("DtoCastArray"); LOG_SCOPE; - LLType *tolltype = DtoType(to); - Type *totype = to->toBasetype(); Type *fromtype = u->type->toBasetype(); if (fromtype->ty != TY::Tarray && fromtype->ty != TY::Tsarray) { @@ -1005,9 +972,6 @@ DValue *DtoCastArray(const Loc &loc, DValue *u, Type *to) { if (totype->ty == TY::Tpointer) { IF_LOG Logger::cout() << "to pointer" << '\n'; LLValue *ptr = DtoArrayPtr(u); - if (ptr->getType() != tolltype) { - ptr = gIR->ir->CreateBitCast(ptr, tolltype); - } return new DImValue(to, ptr); } @@ -1047,8 +1011,7 @@ DValue *DtoCastArray(const Loc &loc, DValue *u, Type *to) { } } - LLType *ptrty = tolltype->getStructElementType(1); - return new DSliceValue(to, length, DtoBitCast(ptr, ptrty)); + return new DSliceValue(to, length, ptr); } if (totype->ty == TY::Tsarray) { @@ -1066,7 +1029,7 @@ DValue *DtoCastArray(const Loc &loc, DValue *u, Type *to) { ptr = DtoArrayPtr(u); } - return new DLValue(to, DtoBitCast(ptr, getPtrToType(tolltype))); + return new DLValue(to, ptr); } if (totype->ty == TY::Tbool) { @@ -1076,7 +1039,7 @@ DValue *DtoCastArray(const Loc &loc, DValue *u, Type *to) { return new DImValue(to, gIR->ir->CreateICmpNE(ptr, nul)); } - const auto castedPtr = DtoBitCast(DtoArrayPtr(u), getPtrToType(tolltype)); + const auto castedPtr = DtoArrayPtr(u); return new DLValue(to, castedPtr); } diff --git a/gen/arrays.h b/gen/arrays.h index 866b6203f99..9e38cb7fa40 100644 --- a/gen/arrays.h +++ b/gen/arrays.h @@ -35,8 +35,7 @@ llvm::ArrayType *DtoStaticArrayType(Type *sarrayTy); LLConstant *DtoConstArrayInitializer(ArrayInitializer *si, Type *targetType, const bool isCfile); -LLConstant *DtoConstSlice(LLConstant *dim, LLConstant *ptr, - Type *type = nullptr); +LLConstant *DtoConstSlice(LLConstant *dim, LLConstant *ptr); /// Returns the element at position idx of the literal (assumed to be in range). Expression *indexArrayLiteral(ArrayLiteralExp *ale, unsigned idx); diff --git a/gen/asm-x86.h b/gen/asm-x86.h index 36b9aac5f16..2eb805a6d2e 100644 --- a/gen/asm-x86.h +++ b/gen/asm-x86.h @@ -3742,13 +3742,11 @@ struct AsmProcessor { if (e->op == EXP::identifier) { for (int i = 0; i < N_Regs; i++) { const auto reg = regInfo[i].ident; - const auto matchesRegister = stmt->caseSensitive ? - ident == reg : -#if LDC_LLVM_VER >= 1300 - reg && llvm::StringRef(ident->toChars()).equals_insensitive(reg->toChars()); -#else - reg && llvm::StringRef(ident->toChars()).equals_lower(reg->toChars()); -#endif + const auto matchesRegister = + stmt->caseSensitive + ? ident == reg + : reg && llvm::StringRef(ident->toChars()) + .equals_insensitive(reg->toChars()); if (matchesRegister) { if (static_cast(i) == Reg_ST && token->value == TOK::leftParenthesis) { diff --git a/gen/attributes.cpp b/gen/attributes.cpp index fd95cbe2fc1..f44f0b6fdb6 100644 --- a/gen/attributes.cpp +++ b/gen/attributes.cpp @@ -11,33 +11,18 @@ #include "gen/irstate.h" AttrSet::AttrSet(const AttrSet &base, unsigned index, LLAttribute attribute) -#if LDC_LLVM_VER >= 1400 : set(base.set.addAttributeAtIndex(gIR->context(), index, attribute)) {} -#else - : set(base.set.addAttribute(gIR->context(), index, attribute)) {} -#endif AttrSet AttrSet::extractFunctionAndReturnAttributes(const llvm::Function *function) { auto old = function->getAttributes(); - return {LLAttributeList::get(gIR->context(), -#if LDC_LLVM_VER >= 1400 - old.getFnAttrs(), - old.getRetAttrs(), -#else - old.getFnAttributes(), - old.getRetAttributes(), -#endif - {})}; + return {LLAttributeList::get(gIR->context(), old.getFnAttrs(), + old.getRetAttrs(), {})}; } AttrSet &AttrSet::add(unsigned index, const llvm::AttrBuilder &builder) { if (builder.hasAttributes()) { -#if LDC_LLVM_VER >= 1400 set = set.addAttributesAtIndex(gIR->context(), index, builder); -#else - set = set.addAttributes(gIR->context(), index, builder); -#endif } return *this; } diff --git a/gen/binops.cpp b/gen/binops.cpp index 723e76f945e..f4e4f344712 100644 --- a/gen/binops.cpp +++ b/gen/binops.cpp @@ -96,7 +96,6 @@ DValue *emitPointerOffset(Loc loc, DValue *base, Expression *offset, llOffset = DtoConstSize_t(byteOffset / pointeeSize); } else { // need to cast base to i8* llBaseTy = getI8Type(); - llBase = DtoBitCast(llBase, getVoidPtrType()); llOffset = DtoConstSize_t(byteOffset); } } @@ -109,7 +108,6 @@ DValue *emitPointerOffset(Loc loc, DValue *base, Expression *offset, llOffset = DtoRVal(rvals.rhs); if (!noStrideInc) { // byte offset => cast base to i8* llBaseTy = getI8Type(); - llBase = DtoBitCast(llBase, getVoidPtrType()); } } @@ -119,7 +117,7 @@ DValue *emitPointerOffset(Loc loc, DValue *base, Expression *offset, llResult = DtoGEP1(llBaseTy, llBase, llOffset); } - return new DImValue(resultType, DtoBitCast(llResult, DtoType(resultType))); + return new DImValue(resultType, llResult); } // LDC issue #2537 / DMD issue #18317: associative arrays can be diff --git a/gen/classes.cpp b/gen/classes.cpp index a5cccb64368..3ac7f2ef664 100644 --- a/gen/classes.cpp +++ b/gen/classes.cpp @@ -87,12 +87,9 @@ DValue *DtoNewClass(const Loc &loc, TypeClass *tc, NewExp *newexp) { const bool useEHAlloc = global.params.ehnogc && newexp->thrownew; llvm::Function *fn = getRuntimeFunction( loc, gIR->module, useEHAlloc ? "_d_newThrowable" : "_d_allocclass"); - LLConstant *ci = - DtoBitCast(irClass->getClassInfoSymbol(), DtoType(getClassInfoType())); + LLConstant *ci = irClass->getClassInfoSymbol(); mem = gIR->CreateCallOrInvoke( - fn, ci, useEHAlloc ? ".newthrowable_alloc" : ".newclass_gc_alloc"); - mem = DtoBitCast(mem, DtoType(tc), - useEHAlloc ? ".newthrowable" : ".newclass_gc"); + fn, ci, useEHAlloc ? ".newthrowable" : ".newclass_gc"); doInit = !useEHAlloc; } @@ -108,7 +105,7 @@ DValue *DtoNewClass(const Loc &loc, TypeClass *tc, NewExp *newexp) { LLValue *src = DtoRVal(newexp->thisexp); LLValue *dst = DtoGEP(irClass->getLLStructType(), mem, 0, idx); IF_LOG Logger::cout() << "dst: " << *dst << "\nsrc: " << *src << '\n'; - DtoStore(src, DtoBitCast(dst, getPtrToType(src->getType()))); + DtoStore(src, dst); } // set the context for nested classes else if (tc->sym->isNested() && tc->sym->vthis) { @@ -169,7 +166,6 @@ void DtoInitClass(TypeClass *tc, LLValue *dst) { // init symbols might not have valid types LLValue *initsym = irClass->getInitSymbol(); - initsym = DtoBitCast(initsym, DtoType(tc)); LLValue *srcarr = DtoGEP(st, initsym, 0, firstDataIdx); DtoMemCpy(dstarr, srcarr, DtoConstSize_t(dataBytes)); @@ -182,8 +178,7 @@ void DtoFinalizeClass(const Loc &loc, LLValue *inst) { llvm::Function *fn = getRuntimeFunction(loc, gIR->module, "_d_callfinalizer"); - gIR->CreateCallOrInvoke( - fn, DtoBitCast(inst, fn->getFunctionType()->getParamType(0)), ""); + gIR->CreateCallOrInvoke(fn, inst, ""); } //////////////////////////////////////////////////////////////////////////////// @@ -244,9 +239,7 @@ DValue *DtoCastClass(const Loc &loc, DValue *val, Type *_to) { // class -> pointer if (to->ty == TY::Tpointer) { IF_LOG Logger::println("to pointer"); - LLType *tolltype = DtoType(_to); - LLValue *rval = DtoBitCast(DtoRVal(val), tolltype); - return new DImValue(_to, rval); + return new DImValue(_to, DtoRVal(val)); } // class -> bool if (to->ty == TY::Tbool) { @@ -288,7 +281,6 @@ DValue *DtoCastClass(const Loc &loc, DValue *val, Type *_to) { // else if from is interface: _d_interface_cast(to) // else if from is class: _d_dynamic_cast(to) - LLType *toType = DtoType(_to); int offset = 0; if (tc->sym->isBaseOf(fc->sym, &offset)) { Logger::println("static down cast"); @@ -300,14 +292,11 @@ DValue *DtoCastClass(const Loc &loc, DValue *val, Type *_to) { LLValue *v = orig; if (offset != 0) { assert(offset > 0); - v = DtoBitCast(v, getVoidPtrType()); - v = DtoGEP1(LLType::getInt8Ty(gIR->context()), v, DtoConstUint(offset)); + v = DtoGEP1(getI8Type(), v, DtoConstUint(offset)); } IF_LOG { Logger::cout() << "V = " << *v << std::endl; - Logger::cout() << "T = " << *toType << std::endl; } - v = DtoBitCast(v, toType); // Check whether the original value was null, and return null if so. // Sure we could have jumped over the code above in this case, but @@ -316,8 +305,8 @@ DValue *DtoCastClass(const Loc &loc, DValue *val, Type *_to) { // null. LLValue *isNull = gIR->ir->CreateICmpEQ( orig, LLConstant::getNullValue(orig->getType()), ".nullcheck"); - v = gIR->ir->CreateSelect(isNull, LLConstant::getNullValue(toType), v, - ".interface"); + v = gIR->ir->CreateSelect( + isNull, LLConstant::getNullValue(getVoidPtrType()), v, ".interface"); // return r-value return new DImValue(_to, v); } @@ -325,8 +314,8 @@ DValue *DtoCastClass(const Loc &loc, DValue *val, Type *_to) { if (fc->sym->classKind == ClassKind::cpp) { Logger::println("C++ class/interface cast"); LLValue *v = tc->sym->classKind == ClassKind::cpp - ? DtoBitCast(DtoRVal(val), toType) - : LLConstant::getNullValue(toType); + ? DtoRVal(val) + : LLConstant::getNullValue(getVoidPtrType()); return new DImValue(_to, v); } @@ -363,7 +352,6 @@ DValue *DtoDynamicCastObject(const Loc &loc, DValue *val, Type *_to) { // Object o LLValue *obj = DtoRVal(val); - obj = DtoBitCast(obj, funcTy->getParamType(0)); assert(funcTy->getParamType(0) == obj->getType()); // ClassInfo c @@ -371,18 +359,11 @@ DValue *DtoDynamicCastObject(const Loc &loc, DValue *val, Type *_to) { DtoResolveClass(to->sym); LLValue *cinfo = getIrAggr(to->sym)->getClassInfoSymbol(); - // unfortunately this is needed as the implementation of object differs - // somehow from the declaration - // this could happen in user code as well :/ - cinfo = DtoBitCast(cinfo, funcTy->getParamType(1)); assert(funcTy->getParamType(1) == cinfo->getType()); // call it LLValue *ret = gIR->CreateCallOrInvoke(func, obj, cinfo); - // cast return value - ret = DtoBitCast(ret, DtoType(_to)); - return new DImValue(_to, ret); } @@ -394,29 +375,20 @@ DValue *DtoDynamicCastInterface(const Loc &loc, DValue *val, Type *_to) { llvm::Function *func = getRuntimeFunction(loc, gIR->module, "_d_interface_cast"); - LLFunctionType *funcTy = func->getFunctionType(); resolveObjectAndClassInfoClasses(); // void* p LLValue *ptr = DtoRVal(val); - ptr = DtoBitCast(ptr, funcTy->getParamType(0)); // ClassInfo c TypeClass *to = static_cast(_to->toBasetype()); DtoResolveClass(to->sym); LLValue *cinfo = getIrAggr(to->sym)->getClassInfoSymbol(); - // unfortunately this is needed as the implementation of object differs - // somehow from the declaration - // this could happen in user code as well :/ - cinfo = DtoBitCast(cinfo, funcTy->getParamType(1)); // call it LLValue *ret = gIR->CreateCallOrInvoke(func, ptr, cinfo); - // cast return value - ret = DtoBitCast(ret, DtoType(_to)); - return new DImValue(_to, ret); } @@ -460,9 +432,6 @@ DtoVirtualFunctionPointer(DValue *inst, FuncDeclaration *fdecl) { IF_LOG Logger::cout() << "funcval: " << *funcval << '\n'; - // cast to funcptr type - funcval = DtoBitCast(funcval, getPtrToType(DtoFunctionType(fdecl))); - // postpone naming until after casting to get the name in call instructions funcval->setName(name); diff --git a/gen/coverage.cpp b/gen/coverage.cpp index d6b807b2c75..f7abd9b5c84 100644 --- a/gen/coverage.cpp +++ b/gen/coverage.cpp @@ -43,10 +43,7 @@ void emitCoverageLinecountInc(const Loc &loc) { case opts::CoverageIncrement::atomic: // Do an atomic increment, so this works when multiple threads are executed. gIR->ir->CreateAtomicRMW(llvm::AtomicRMWInst::Add, ptr, DtoConstUint(1), -#if LDC_LLVM_VER >= 1300 - llvm::Align(4), -#endif - llvm::AtomicOrdering::Monotonic); + llvm::Align(4), llvm::AtomicOrdering::Monotonic); break; case opts::CoverageIncrement::nonatomic: { // Do a non-atomic increment, user is responsible for correct results with diff --git a/gen/dcompute/abi-rewrites.h b/gen/dcompute/abi-rewrites.h index 82c48938fe8..1821df81b0c 100644 --- a/gen/dcompute/abi-rewrites.h +++ b/gen/dcompute/abi-rewrites.h @@ -18,13 +18,12 @@ struct DComputePointerRewrite : ABIRewrite { LLValue *put(DValue *v, bool isLValueExp, bool) override { LLValue *address = DtoLVal(v); - address = DtoGEP(DtoType(v->type), address, 0u, 0u); return DtoLoad(type(v->type), address, ".DComputePointerRewrite_arg"); } LLValue *getLVal(Type *dty, LLValue *v) override { LLValue *mem = DtoAlloca(dty, ".DComputePointerRewrite_param_storage"); - DtoStore(v, DtoGEP(DtoType(dty), mem, 0u, 0u)); + DtoStore(v, mem); return mem; } diff --git a/gen/dibuilder.cpp b/gen/dibuilder.cpp index 4de8e16dd9e..106291ae072 100644 --- a/gen/dibuilder.cpp +++ b/gen/dibuilder.cpp @@ -1146,12 +1146,7 @@ void DIBuilder::EmitValue(llvm::Value *val, VarDeclaration *vd) { void DIBuilder::EmitLocalVariable(llvm::Value *ll, VarDeclaration *vd, Type *type, bool isThisPtr, bool forceAsLocal, bool isRefRVal, -#if LDC_LLVM_VER >= 1400 - llvm::ArrayRef addr -#else - llvm::ArrayRef addr -#endif - ) { + llvm::ArrayRef addr) { if (!mustEmitFullDebugInfo()) return; diff --git a/gen/dibuilder.h b/gen/dibuilder.h index 64a50d32e70..e6c31da43e4 100644 --- a/gen/dibuilder.h +++ b/gen/dibuilder.h @@ -140,12 +140,7 @@ class DIBuilder { EmitLocalVariable(llvm::Value *ll, VarDeclaration *vd, Type *type = nullptr, bool isThisPtr = false, bool forceAsLocal = false, bool isRefRVal = false, -#if LDC_LLVM_VER >= 1400 - llvm::ArrayRef addr = llvm::ArrayRef() -#else - llvm::ArrayRef addr = llvm::ArrayRef() -#endif - ); + llvm::ArrayRef addr = llvm::ArrayRef()); /// \brief Emits all things necessary for making debug info for a global /// variable vd. diff --git a/gen/dvalue.cpp b/gen/dvalue.cpp index d5aad1ffac3..2673f891d86 100644 --- a/gen/dvalue.cpp +++ b/gen/dvalue.cpp @@ -159,12 +159,7 @@ DRValue *DLValue::getRVal() { //////////////////////////////////////////////////////////////////////////////// DSpecialRefValue::DSpecialRefValue(Type *t, LLValue *v) : DLValue(v, t) { -#if LDC_LLVM_VER >= 1700 // LLVM >= 17 uses opaque pointers, type check boils - // down to pointer check only. assert(v->getType()->isPointerTy()); -#else - assert(v->getType() == DtoPtrToType(t)->getPointerTo()); -#endif } DRValue *DSpecialRefValue::getRVal() { @@ -186,7 +181,7 @@ DBitFieldLValue::DBitFieldLValue(Type *t, LLValue *ptr, BitFieldDeclaration *bf) DRValue *DBitFieldLValue::getRVal() { const auto sizeInBits = intType->getBitWidth(); - const auto ptr = DtoBitCast(val, getPtrToType(intType)); + const auto ptr = val; LLValue *v = gIR->ir->CreateAlignedLoad(intType, ptr, llvm::MaybeAlign(1)); if (bf->type->isunsigned()) { @@ -211,7 +206,7 @@ DRValue *DBitFieldLValue::getRVal() { void DBitFieldLValue::store(LLValue *value) { assert(value->getType()->isIntegerTy()); - const auto ptr = DtoBitCast(val, getPtrToType(intType)); + const auto ptr = val; const auto mask = llvm::APInt::getLowBitsSet(intType->getBitWidth(), bf->fieldWidth); diff --git a/gen/functions.cpp b/gen/functions.cpp index a09755a4780..ada3c8141d8 100644 --- a/gen/functions.cpp +++ b/gen/functions.cpp @@ -99,24 +99,12 @@ llvm::FunctionType *DtoFunctionType(Type *type, IrFuncTy &irFty, Type *thistype, } else { Type *rt = f->next; const bool byref = f->isref() && rt->toBasetype()->ty != TY::Tvoid; -#if LDC_LLVM_VER >= 1400 - llvm::AttrBuilder attrs(getGlobalContext()); -#else - llvm::AttrBuilder attrs; -#endif + llvm::AttrBuilder attrs(getGlobalContext()); if (abi->returnInArg(f, fd && fd->needThis())) { // sret return -#if LDC_LLVM_VER >= 1400 llvm::AttrBuilder sretAttrs(getGlobalContext()); -#else - llvm::AttrBuilder sretAttrs; -#endif -#if LDC_LLVM_VER >= 1200 sretAttrs.addStructRetAttr(DtoType(rt)); -#else - sretAttrs.addAttribute(LLAttribute::StructRet); -#endif sretAttrs.addAttribute(LLAttribute::NoAlias); if (unsigned alignment = DtoAlignment(rt)) sretAttrs.addAlignmentAttr(alignment); @@ -133,11 +121,7 @@ llvm::FunctionType *DtoFunctionType(Type *type, IrFuncTy &irFty, Type *thistype, if (thistype) { // Add the this pointer for member functions -#if LDC_LLVM_VER >= 1400 llvm::AttrBuilder attrs(getGlobalContext()); -#else - llvm::AttrBuilder attrs; -#endif if (!opts::fNullPointerIsValid) attrs.addAttribute(LLAttribute::NonNull); if (fd && fd->isCtorDeclaration()) { @@ -148,11 +132,7 @@ llvm::FunctionType *DtoFunctionType(Type *type, IrFuncTy &irFty, Type *thistype, ++nextLLArgIdx; } else if (nesttype) { // Add the context pointer for nested functions -#if LDC_LLVM_VER >= 1400 llvm::AttrBuilder attrs(getGlobalContext()); -#else - llvm::AttrBuilder attrs; -#endif if (!opts::fNullPointerIsValid) attrs.addAttribute(LLAttribute::NonNull); newIrFty.arg_nest = new IrFuncTyArg(nesttype, false, std::move(attrs)); @@ -202,11 +182,7 @@ llvm::FunctionType *DtoFunctionType(Type *type, IrFuncTy &irFty, Type *thistype, bool passPointer = arg->storageClass & (STCref | STCout); Type *loweredDType = arg->type; -#if LDC_LLVM_VER >= 1400 llvm::AttrBuilder attrs(getGlobalContext()); -#else - llvm::AttrBuilder attrs; -#endif if (arg->storageClass & STClazy) { // Lazy arguments are lowered to delegates. Logger::println("lazy param"); @@ -229,11 +205,7 @@ llvm::FunctionType *DtoFunctionType(Type *type, IrFuncTy &irFty, Type *thistype, // LLVM ByVal parameters are pointers to a copy in the function // parameters stack. The caller needs to provide a pointer to the // original argument. -#if LDC_LLVM_VER >= 1200 attrs.addByValAttr(DtoType(loweredDType)); -#else - attrs.addAttribute(LLAttribute::ByVal); -#endif if (auto alignment = DtoAlignment(loweredDType)) attrs.addAlignmentAttr(alignment); passPointer = true; @@ -617,7 +589,6 @@ void DtoDeclareFunction(FuncDeclaration *fdecl, const bool willDefine) { if (f->next->toBasetype()->ty == TY::Tnoreturn) { func->addFnAttr(LLAttribute::NoReturn); } -#if LDC_LLVM_VER >= 1300 if (opts::fWarnStackSize.getNumOccurrences() > 0 && opts::fWarnStackSize < UINT_MAX) { // Cache the int->string conversion result. @@ -625,7 +596,6 @@ void DtoDeclareFunction(FuncDeclaration *fdecl, const bool willDefine) { func->addFnAttr("warn-stack-size", thresholdString); } -#endif applyFuncDeclUDAs(fdecl, irFunc); @@ -838,12 +808,7 @@ void defineParameters(IrFuncTy &irFty, VarDeclarations ¶meters) { if (irparam->arg->byref) { // The argument is an appropriate lvalue passed by reference. // Use the passed pointer as parameter storage. -#if LDC_LLVM_VER >= 1700 // LLVM >= 17 uses opaque pointers, type check boils - // down to pointer check only. assert(irparam->value->getType()->isPointerTy()); -#else - assert(irparam->value->getType() == DtoPtrToType(paramType)); -#endif } else { // Let the ABI transform the parameter back to an lvalue. irparam->value = @@ -1174,11 +1139,7 @@ void DtoDefineFunction(FuncDeclaration *fd, bool linkageAvailableExternally) { // function attributes if (gABI->needsUnwindTables()) { -#if LDC_LLVM_VER >= 1500 func->setUWTableKind(llvm::UWTableKind::Default); -#else - func->addFnAttr(LLAttribute::UWTable); -#endif } if (opts::isAnySanitizerEnabled() && !opts::functionIsInSanitizerBlacklist(fd)) { @@ -1260,11 +1221,8 @@ void DtoDefineFunction(FuncDeclaration *fd, bool linkageAvailableExternally) { if (!irFty.arg_this->byref) { if (fd->interfaceVirtual) { // Adjust the 'this' pointer instead of using a thunk - LLType *targetThisType = thismem->getType(); - thismem = DtoBitCast(thismem, getVoidPtrType()); auto off = DtoConstInt(-fd->interfaceVirtual->offset); - thismem = DtoGEP1(llvm::Type::getInt8Ty(gIR->context()), thismem, off); - thismem = DtoBitCast(thismem, targetThisType); + thismem = DtoGEP1(getI8Type(), thismem, off); } thismem = DtoAllocaDump(thismem, 0, "this"); irFunc->thisArg = thismem; diff --git a/gen/inlineir.cpp b/gen/inlineir.cpp index 092ee817ed8..2481d763c7e 100644 --- a/gen/inlineir.cpp +++ b/gen/inlineir.cpp @@ -39,13 +39,8 @@ struct TempDisableDiscardValueNames { /// Note: don't add function _parameter_ attributes void copyFnAttributes(llvm::Function *wannabe, llvm::Function *idol) { auto attrSet = idol->getAttributes(); -#if LDC_LLVM_VER >= 1400 auto fnAttrSet = attrSet.getFnAttrs(); wannabe->addFnAttrs(llvm::AttrBuilder(getGlobalContext(), fnAttrSet)); -#else - auto fnAttrSet = attrSet.getFnAttributes(); - wannabe->addAttributes(LLAttributeList::FunctionIndex, fnAttrSet); -#endif } llvm::StringRef exprToString(StringExp *strexp) { @@ -243,7 +238,7 @@ DValue *DtoInlineIRExpr(const Loc &loc, FuncDeclaration *fdecl, Type *type = fdecl->type->nextOf(); if (sretPointer) { - DtoStore(rv, DtoBitCast(sretPointer, getPtrToType(rv->getType()))); + DtoStore(rv, sretPointer); return new DLValue(type, sretPointer); } diff --git a/gen/irstate.cpp b/gen/irstate.cpp index dd96a357d1c..60beaca14b8 100644 --- a/gen/irstate.cpp +++ b/gen/irstate.cpp @@ -132,8 +132,8 @@ bool IRState::emitArrayBoundsChecks() { return t->ty == TY::Tfunction && ((TypeFunction *)t)->trust == TRUST::safe; } -LLConstant * -IRState::setGlobalVarInitializer(LLGlobalVariable *&globalVar, +LLGlobalVariable * +IRState::setGlobalVarInitializer(LLGlobalVariable *globalVar, LLConstant *initializer, Dsymbol *symbolForLinkageAndVisibility) { if (initializer->getType() == globalVar->getValueType()) { @@ -155,17 +155,13 @@ IRState::setGlobalVarInitializer(LLGlobalVariable *&globalVar, defineGlobal(globalHelperVar, initializer, symbolForLinkageAndVisibility); - // Replace all existing uses of globalVar by the bitcast pointer. - auto castHelperVar = DtoBitCast(globalHelperVar, globalVar->getType()); - globalVar->replaceAllUsesWith(castHelperVar); + // Replace all existing uses of globalVar by the helper variable. + globalVar->replaceAllUsesWith(globalHelperVar); // Register replacement for later occurrences of the original globalVar. - globalsToReplace.emplace_back(globalVar, castHelperVar); - - // Reset globalVar to the helper variable. - globalVar = globalHelperVar; + globalsToReplace.emplace_back(globalVar, globalHelperVar); - return castHelperVar; + return globalHelperVar; } void IRState::replaceGlobals() { @@ -179,13 +175,13 @@ void IRState::replaceGlobals() { //////////////////////////////////////////////////////////////////////////////// -LLConstant *IRState::getStructLiteralConstant(StructLiteralExp *sle) const { - return static_cast(structLiteralConstants.lookup(sle->origin)); +LLGlobalVariable *IRState::getStructLiteralGlobal(StructLiteralExp *sle) const { + return structLiteralGlobals.lookup(sle->origin); } -void IRState::setStructLiteralConstant(StructLiteralExp *sle, - LLConstant *constant) { - structLiteralConstants[sle->origin] = constant; +void IRState::setStructLiteralGlobal(StructLiteralExp *sle, + LLGlobalVariable *global) { + structLiteralGlobals[sle->origin] = global; } //////////////////////////////////////////////////////////////////////////////// @@ -281,12 +277,11 @@ IRState::createInlineAsmCall(const Loc &loc, llvm::InlineAsm *ia, llvm::CallInst *call = ir->CreateCall(ia, args); addInlineAsmSrcLoc(loc, call); -#if LDC_LLVM_VER >= 1400 // a non-indirect output constraint (=> return value of call) shifts the // constraint/argument index mapping ptrdiff_t i = call->getType()->isVoidTy() ? 0 : -1; size_t indirectIdx = 0; - + for (const auto &constraintInfo : ia->ParseConstraints()) { if (constraintInfo.isIndirect) { call->addParamAttr(i, llvm::Attribute::get( @@ -297,7 +292,6 @@ IRState::createInlineAsmCall(const Loc &loc, llvm::InlineAsm *ia, } ++i; } -#endif return call; } diff --git a/gen/irstate.h b/gen/irstate.h index c1b921828f7..8720e55f3db 100644 --- a/gen/irstate.h +++ b/gen/irstate.h @@ -117,12 +117,12 @@ struct IRState { globalsToReplace; Array inlineAsmLocs; // tracked by GC - // Cache of (possibly bitcast) global variables for taking the address of - // struct literal constants. (Also) used to resolve self-references. Must be - // cached per IR module: https://github.com/ldc-developers/ldc/issues/2990 + // Cache of global variables for taking the address of struct literal + // constants. (Also) used to resolve self-references. Must be cached per IR + // module: https://github.com/ldc-developers/ldc/issues/2990 // [The real key type is `StructLiteralExp *`; a fwd class declaration isn't // enough to use it directly.] - llvm::DenseMap structLiteralConstants; + llvm::DenseMap structLiteralGlobals; // Global variables bound to string literals. Once created such a variable // is reused whenever an equivalent string literal is referenced in the @@ -226,12 +226,11 @@ struct IRState { // Sets the initializer for a global LL variable. // If the types don't match, this entails creating a new helper global // matching the initializer type and replacing all existing uses of globalVar - // by a bitcast pointer to the helper global's payload. - // Returns either the specified globalVar if the types match, or the bitcast - // pointer replacing globalVar (and resets globalVar to the new helper - // global). - llvm::Constant * - setGlobalVarInitializer(llvm::GlobalVariable *&globalVar, + // by the new helper global. + // Returns either the specified globalVar if the types match, or the new + // helper global replacing globalVar. + llvm::GlobalVariable * + setGlobalVarInitializer(llvm::GlobalVariable *globalVar, llvm::Constant *initializer, Dsymbol *symbolForLinkageAndVisibility); @@ -240,9 +239,9 @@ struct IRState { // setGlobalVarInitializer(). void replaceGlobals(); - llvm::Constant *getStructLiteralConstant(StructLiteralExp *sle) const; - void setStructLiteralConstant(StructLiteralExp *sle, - llvm::Constant *constant); + llvm::GlobalVariable *getStructLiteralGlobal(StructLiteralExp *sle) const; + void setStructLiteralGlobal(StructLiteralExp *sle, + llvm::GlobalVariable *global); // Constructs a global variable for a StringExp. // Caches the result based on StringExp::peekData() such that any subsequent diff --git a/gen/llvmhelpers.cpp b/gen/llvmhelpers.cpp index 63222991feb..0850aadbb84 100644 --- a/gen/llvmhelpers.cpp +++ b/gen/llvmhelpers.cpp @@ -90,39 +90,31 @@ LLValue *DtoNew(const Loc &loc, Type *newtype) { LLConstant *ti = DtoTypeInfoOf(loc, newtype); assert(isaPointer(ti)); // call runtime allocator - LLValue *mem = gIR->CreateCallOrInvoke(fn, ti, ".gc_mem"); - // cast - return DtoBitCast(mem, DtoPtrToType(newtype), ".gc_mem"); + return gIR->CreateCallOrInvoke(fn, ti, ".gc_mem"); } void DtoDeleteMemory(const Loc &loc, DValue *ptr) { llvm::Function *fn = getRuntimeFunction(loc, gIR->module, "_d_delmemory"); LLValue *lval = (ptr->isLVal() ? DtoLVal(ptr) : makeLValue(loc, ptr)); - gIR->CreateCallOrInvoke( - fn, DtoBitCast(lval, fn->getFunctionType()->getParamType(0))); + gIR->CreateCallOrInvoke(fn, lval); } void DtoDeleteStruct(const Loc &loc, DValue *ptr) { llvm::Function *fn = getRuntimeFunction(loc, gIR->module, "_d_delstruct"); LLValue *lval = (ptr->isLVal() ? DtoLVal(ptr) : makeLValue(loc, ptr)); - gIR->CreateCallOrInvoke( - fn, DtoBitCast(lval, fn->getFunctionType()->getParamType(0)), - DtoBitCast(DtoTypeInfoOf(loc, ptr->type->nextOf()), - fn->getFunctionType()->getParamType(1))); + gIR->CreateCallOrInvoke(fn, lval, DtoTypeInfoOf(loc, ptr->type->nextOf())); } void DtoDeleteClass(const Loc &loc, DValue *inst) { llvm::Function *fn = getRuntimeFunction(loc, gIR->module, "_d_delclass"); LLValue *lval = (inst->isLVal() ? DtoLVal(inst) : makeLValue(loc, inst)); - gIR->CreateCallOrInvoke( - fn, DtoBitCast(lval, fn->getFunctionType()->getParamType(0))); + gIR->CreateCallOrInvoke(fn, lval); } void DtoDeleteInterface(const Loc &loc, DValue *inst) { llvm::Function *fn = getRuntimeFunction(loc, gIR->module, "_d_delinterface"); LLValue *lval = (inst->isLVal() ? DtoLVal(inst) : makeLValue(loc, inst)); - gIR->CreateCallOrInvoke( - fn, DtoBitCast(lval, fn->getFunctionType()->getParamType(0))); + gIR->CreateCallOrInvoke(fn, lval); } void DtoDeleteArray(const Loc &loc, DValue *arr) { @@ -137,8 +129,7 @@ void DtoDeleteArray(const Loc &loc, DValue *arr) { : DtoTypeInfoOf(loc, elementType); LLValue *lval = (arr->isLVal() ? DtoLVal(arr) : makeLValue(loc, arr)); - gIR->CreateCallOrInvoke(fn, DtoBitCast(lval, fty->getParamType(0)), - DtoBitCast(typeInfo, fty->getParamType(1))); + gIR->CreateCallOrInvoke(fn, lval, typeInfo); } /****************************************************************************** @@ -249,8 +240,8 @@ LLValue *DtoAllocaDump(LLValue *val, LLType *asType, int alignment, (getTypeStoreSize(memType) <= getTypeAllocSize(asMemType) ? asMemType : memType); LLValue *mem = DtoRawAlloca(allocaType, alignment, name); - DtoStoreZextI8(val, DtoBitCast(mem, memType->getPointerTo())); - return DtoBitCast(mem, asMemType->getPointerTo()); + DtoStoreZextI8(val, mem); + return mem; } /****************************************************************************** @@ -300,7 +291,8 @@ void DtoCAssert(Module *M, const Loc &loc, LLValue *msg) { args.push_back(line); args.push_back(msg); } else if (triple.isOSSolaris() || triple.isMusl() || - global.params.isUClibcEnvironment) { + global.params.isUClibcEnvironment || + triple.isGNUEnvironment()) { const auto irFunc = gIR->func(); const auto funcName = (irFunc && irFunc->decl) ? irFunc->decl->toPrettyChars() : ""; @@ -337,7 +329,7 @@ void DtoCAssert(Module *M, const Loc &loc, LLValue *msg) { void DtoThrow(const Loc &loc, DValue *e) { LLFunction *fn = getRuntimeFunction(loc, gIR->module, "_d_throw_exception"); - LLValue *arg = DtoBitCast(DtoRVal(e), fn->getFunctionType()->getParamType(0)); + LLValue *arg = DtoRVal(e); gIR->CreateCallOrInvoke(fn, arg); gIR->ir->CreateUnreachable(); @@ -426,7 +418,6 @@ void DtoAssign(const Loc &loc, DValue *lhs, DValue *rhs, EXP op, Logger::cout() << "l : " << *l << '\n'; Logger::cout() << "r : " << *r << '\n'; } - r = DtoBitCast(r, DtoType(lhs->type)); DtoStore(r, l); } else if (t->iscomplex()) { LLValue *dst = DtoLVal(lhs); @@ -680,9 +671,7 @@ DValue *DtoCastStruct(const Loc &loc, DValue *val, Type *to) { // allows for identical layouts (opCast() and so on have been lowered // earlier by the frontend). llvm::Value *lval = DtoLVal(val); - llvm::Value *result = DtoBitCast(lval, DtoType(to)->getPointerTo(), - lval->getName() + ".repaint"); - return new DLValue(to, result); + return new DLValue(to, lval); } error(loc, "Internal Compiler Error: Invalid struct cast from `%s` to `%s`", @@ -705,8 +694,7 @@ DValue *DtoCast(const Loc &loc, DValue *val, Type *to) { // implemented as structs. if (totype->ty == TY::Tpointer) { IF_LOG Logger::println("Casting AA to pointer."); - LLValue *rval = DtoBitCast(DtoRVal(val), DtoType(to)); - return new DImValue(to, rval); + return new DImValue(to, DtoRVal(val)); } if (totype->ty == TY::Tbool) { IF_LOG Logger::println("Casting AA to bool."); @@ -771,20 +759,16 @@ DValue *DtoPaintType(const Loc &loc, DValue *val, Type *to) { Type *tb = to->toBasetype(); if (val->isLVal()) { - auto ptr = DtoBitCast(DtoLVal(val), DtoPtrToType(tb)); - return new DLValue(to, ptr); + return new DLValue(to, DtoLVal(val)); } if (auto slice = val->isSlice()) { if (tb->ty == TY::Tarray) { - auto ptr = DtoBitCast(slice->getPtr(), DtoPtrToType(tb->nextOf())); - return new DSliceValue(to, slice->getLength(), ptr); + return new DSliceValue(to, slice->getLength(), slice->getPtr()); } } else if (auto func = val->isFunc()) { if (tb->ty == TY::Tdelegate) { - auto funcptr = - DtoBitCast(DtoRVal(func), DtoType(tb)->getContainedType(1)); - return new DFuncValue(to, func->func, funcptr, func->vthis); + return new DFuncValue(to, func->func, DtoRVal(func), func->vthis); } } else { // generic rvalue LLValue *rval = DtoRVal(val); @@ -794,12 +778,11 @@ DValue *DtoPaintType(const Loc &loc, DValue *val, Type *to) { return new DImValue(to, rval); } if (rval->getType()->isPointerTy() && tll->isPointerTy()) { - return new DImValue(to, DtoBitCast(rval, tll)); + return new DImValue(to, rval); } if (from->ty == TY::Tdelegate && tb->ty == TY::Tdelegate) { LLValue *context = gIR->ir->CreateExtractValue(rval, 0, ".context"); LLValue *funcptr = gIR->ir->CreateExtractValue(rval, 1, ".funcptr"); - funcptr = DtoBitCast(funcptr, tll->getContainedType(1)); return new DImValue(to, DtoAggrPair(context, funcptr)); } } @@ -1198,11 +1181,7 @@ LLConstant *DtoConstExpInit(const Loc &loc, Type *targetType, Expression *exp) { assert(tv->basetype->ty == TY::Tsarray); dinteger_t elemCount = static_cast(tv->basetype)->dim->toInteger(); -#if LDC_LLVM_VER >= 1200 const auto elementCount = llvm::ElementCount::getFixed(elemCount); -#else - const auto elementCount = llvm::ElementCount(elemCount, false); -#endif return llvm::ConstantVector::getSplat(elementCount, val); } @@ -1231,16 +1210,12 @@ LLConstant *DtoConstExpInit(const Loc &loc, Type *targetType, Expression *exp) { //////////////////////////////////////////////////////////////////////////////// -LLConstant *DtoTypeInfoOf(const Loc &loc, Type *type, bool base) { - IF_LOG Logger::println("DtoTypeInfoOf(type = '%s', base='%d')", - type->toChars(), base); +LLConstant *DtoTypeInfoOf(const Loc &loc, Type *type) { + IF_LOG Logger::println("DtoTypeInfoOf(type = '%s')", type->toChars()); LOG_SCOPE auto tidecl = getOrCreateTypeInfoDeclaration(loc, type); auto tiglobal = DtoResolveTypeInfo(tidecl); - if (base) { - return llvm::ConstantExpr::getBitCast(tiglobal, DtoType(getTypeInfoType())); - } return tiglobal; } @@ -1512,11 +1487,7 @@ DValue *DtoSymbolAddress(const Loc &loc, Type *type, Declaration *decl) { // typeinfo if (TypeInfoDeclaration *tid = vd->isTypeInfoDeclaration()) { Logger::println("TypeInfoDeclaration"); - LLType *vartype = DtoType(type); LLValue *m = DtoResolveTypeInfo(tid); - if (m->getType() != getPtrToType(vartype)) { - m = gIR->ir->CreateBitCast(m, vartype); - } return new DImValue(type, m); } // special vtbl symbol, used by LDC as alias to the actual vtbl (with @@ -1524,8 +1495,7 @@ DValue *DtoSymbolAddress(const Loc &loc, Type *type, Declaration *decl) { if (vd->isClassMember() && vd == vd->isClassMember()->vtblsym) { Logger::println("vtbl symbol"); auto cd = vd->isClassMember(); - return new DLValue( - type, DtoBitCast(getIrAggr(cd)->getVtblSymbol(), DtoPtrToType(type))); + return new DLValue(type, getIrAggr(cd)->getVtblSymbol()); } // nested variable if (vd->nestedrefs.length) { @@ -1550,7 +1520,7 @@ DValue *DtoSymbolAddress(const Loc &loc, Type *type, Declaration *decl) { assert(!isSpecialRefVar(vd) && "Code not expected to handle special " "ref vars, although it can easily be " "made to."); - return new DLValue(type, DtoBitCast(getIrValue(vd), DtoPtrToType(type))); + return new DLValue(type, getIrValue(vd)); } Logger::println("a normal variable"); @@ -1601,10 +1571,9 @@ DValue *DtoSymbolAddress(const Loc &loc, Type *type, Declaration *decl) { if (tb->ty != TY::Tstruct) { assert(tb->ty == TY::Tarray && tb->nextOf()->ty == TY::Tvoid); const auto size = DtoConstSize_t(ad->structsize); - llvm::Constant *ptr = - sd && sd->zeroInit() - ? getNullValue(getVoidPtrType()) - : DtoBitCast(getIrAggr(ad)->getInitSymbol(), getVoidPtrType()); + llvm::Constant *ptr = sd && sd->zeroInit() + ? getNullValue(getVoidPtrType()) + : getIrAggr(ad)->getInitSymbol(); return new DSliceValue(type, size, ptr); } @@ -1615,7 +1584,7 @@ DValue *DtoSymbolAddress(const Loc &loc, Type *type, Declaration *decl) { } LLValue *initsym = getIrAggr(sd)->getInitSymbol(); - return new DLValue(type, DtoBitCast(initsym, DtoPtrToType(sd->type))); + return new DLValue(type, initsym); } llvm_unreachable("Unimplemented VarExp type"); @@ -1882,9 +1851,8 @@ DLValue *DtoIndexAggregate(LLValue *src, AggregateDeclaration *ad, LLValue *ptr = src; LLType * ty = nullptr; if (!isFieldIdx) { - // Cast to void* to apply byte-wise offset from object start. - ptr = DtoBitCast(ptr, getVoidPtrType()); - ptr = DtoGEP1(llvm::Type::getInt8Ty(gIR->context()), ptr, off); + // apply byte-wise offset from object start + ptr = DtoGEP1(getI8Type(), ptr, off); ty = DtoType(vd->type); } else { if (ad->structsize == 0) { // can happen for extern(C) structs @@ -1898,15 +1866,11 @@ DLValue *DtoIndexAggregate(LLValue *src, AggregateDeclaration *ad, } else { st = irTypeAggr->getLLType(); } - ptr = DtoBitCast(ptr, st->getPointerTo()); ptr = DtoGEP(st, ptr, 0, off); ty = isaStruct(st)->getElementType(off); } } - // Cast the (possibly void*) pointer to the canonical variable type. - ptr = DtoBitCast(ptr, DtoPtrToType(vd->type)); - IF_LOG Logger::cout() << "Pointer: " << *ptr << '\n'; if (auto p = isaPointer(ty)) { if (p->getAddressSpace()) @@ -1931,31 +1895,9 @@ DValue *makeVarDValue(Type *type, VarDeclaration *vd, llvm::Value *storage) { val = getIrValue(vd); } - // We might need to cast. - llvm::Type *expectedType = DtoPtrToType(type); - const bool isSpecialRef = isSpecialRefVar(vd); - if (isSpecialRef) - expectedType = expectedType->getPointerTo(); - - if (val->getType() != expectedType) { -#if LDC_LLVM_VER < 1500 - // The type of globals is determined by their initializer, and the front-end - // may inject implicit casts for class references and static arrays. - assert(vd->isDataseg() || (vd->storage_class & STCextern) || - type->toBasetype()->ty == TY::Tclass || - type->toBasetype()->ty == TY::Tsarray); - llvm::Type *pointeeType = val->getType()->getPointerElementType(); - if (isSpecialRef) - pointeeType = pointeeType->getPointerElementType(); - // Make sure that the type sizes fit - '==' instead of '<=' should probably - // work as well. - assert(getTypeAllocSize(DtoType(type)) <= getTypeAllocSize(pointeeType) && - "LValue type mismatch, encountered type too small."); -#endif - val = DtoBitCast(val, expectedType); - } + assert(val->getType()->isPointerTy()); - if (isSpecialRef) + if (isSpecialRefVar(vd)) return new DSpecialRefValue(type, val); return new DLValue(type, val); diff --git a/gen/llvmhelpers.h b/gen/llvmhelpers.h index 6720309942d..fdf1f5373c9 100644 --- a/gen/llvmhelpers.h +++ b/gen/llvmhelpers.h @@ -121,8 +121,8 @@ LLConstant *DtoConstInitializer(const Loc &loc, Type *type, Initializer *init, bool isCfile); LLConstant *DtoConstExpInit(const Loc &loc, Type *targetType, Expression *exp); -// getting typeinfo of type, base=true casts to object.TypeInfo -LLConstant *DtoTypeInfoOf(const Loc &loc, Type *type, bool base = true); +// getting typeinfo of type +LLConstant *DtoTypeInfoOf(const Loc &loc, Type *type); // target stuff void findDefaultTarget(); diff --git a/gen/moduleinfo.cpp b/gen/moduleinfo.cpp index bf6cd50b41a..3763a4554e4 100644 --- a/gen/moduleinfo.cpp +++ b/gen/moduleinfo.cpp @@ -160,30 +160,25 @@ llvm::Function *buildOrderIndependentModuleCtor(Module *m) { /// Builds the (constant) data content for the importedModules[] array. llvm::Constant *buildImportedModules(Module *m, size_t &count) { - const auto moduleInfoPtrTy = DtoPtrToType(getModuleInfoType()); - std::vector importInits; for (auto mod : m->aimports) { if (!mod->needModuleInfo() || mod == m) { continue; } - importInits.push_back( - DtoBitCast(getIrModule(mod)->moduleInfoSymbol(), moduleInfoPtrTy)); + importInits.push_back(getIrModule(mod)->moduleInfoSymbol()); } count = importInits.size(); if (importInits.empty()) return nullptr; - const auto type = llvm::ArrayType::get(moduleInfoPtrTy, importInits.size()); + const auto type = llvm::ArrayType::get(getVoidPtrType(), importInits.size()); return LLConstantArray::get(type, importInits); } /// Builds the (constant) data content for the localClasses[] array. llvm::Constant *buildLocalClasses(Module *m, size_t &count) { - const auto classinfoTy = DtoType(getClassInfoType()); - ClassDeclarations aclasses; getLocalClasses(m, aclasses); @@ -205,15 +200,14 @@ llvm::Constant *buildLocalClasses(Module *m, size_t &count) { } IF_LOG Logger::println("class: %s", cd->toPrettyChars()); - classInfoRefs.push_back( - DtoBitCast(getIrAggr(cd)->getClassInfoSymbol(), classinfoTy)); + classInfoRefs.push_back(getIrAggr(cd)->getClassInfoSymbol()); } count = classInfoRefs.size(); if (classInfoRefs.empty()) return nullptr; - const auto type = llvm::ArrayType::get(classinfoTy, classInfoRefs.size()); + const auto type = llvm::ArrayType::get(getVoidPtrType(), classInfoRefs.size()); return LLConstantArray::get(type, classInfoRefs); } } diff --git a/gen/modules.cpp b/gen/modules.cpp index cab2c97bdd1..6d0c3c4a68a 100644 --- a/gen/modules.cpp +++ b/gen/modules.cpp @@ -136,10 +136,8 @@ LLFunction *build_module_reference_and_ctor(const char *moduleMangle, // provide the default initializer LLStructType *modulerefTy = DtoModuleReferenceType(); - LLConstant *mrefvalues[] = { - LLConstant::getNullValue(modulerefTy->getContainedType(0)), - llvm::ConstantExpr::getBitCast(moduleinfo, - modulerefTy->getContainedType(1))}; + LLConstant *mrefvalues[] = {LLConstant::getNullValue(getVoidPtrType()), + moduleinfo}; LLConstant *thismrefinit = LLConstantStruct::get( modulerefTy, llvm::ArrayRef(mrefvalues)); @@ -151,13 +149,12 @@ LLFunction *build_module_reference_and_ctor(const char *moduleMangle, // make sure _Dmodule_ref is declared const auto mrefIRMangle = getIRMangledVarName("_Dmodule_ref", LINK::c); LLConstant *mref = gIR->module.getNamedGlobal(mrefIRMangle); - LLType *modulerefPtrTy = getPtrToType(modulerefTy); + LLType *modulerefPtrTy = getVoidPtrType(); if (!mref) { mref = declareGlobal(Loc(), gIR->module, modulerefPtrTy, mrefIRMangle, false, false, global.params.dllimport != DLLImport::none); } - mref = DtoBitCast(mref, getPtrToType(modulerefPtrTy)); // make the function insert this moduleinfo as the beginning of the // _Dmodule_ref linked list @@ -189,8 +186,6 @@ LLFunction *build_module_reference_and_ctor(const char *moduleMangle, // .minfo (COFF & MachO) / __minfo section. void emitModuleRefToSection(std::string moduleMangle, llvm::Constant *thisModuleInfo) { - const auto moduleInfoPtrTy = DtoPtrToType(getModuleInfoType()); - const auto &triple = *global.params.targetTriple; const auto sectionName = triple.isOSBinFormatCOFF() @@ -199,9 +194,9 @@ void emitModuleRefToSection(std::string moduleMangle, const auto thismrefIRMangle = getIRMangledModuleRefSymbolName(moduleMangle.c_str()); - auto thismref = defineGlobal(Loc(), gIR->module, thismrefIRMangle, - DtoBitCast(thisModuleInfo, moduleInfoPtrTy), - LLGlobalValue::LinkOnceODRLinkage, false, false); + auto thismref = + defineGlobal(Loc(), gIR->module, thismrefIRMangle, thisModuleInfo, + LLGlobalValue::LinkOnceODRLinkage, false, false); thismref->setVisibility(LLGlobalValue::HiddenVisibility); thismref->setSection(sectionName); gIR->usedArray.push_back(thismref); @@ -237,11 +232,8 @@ void addCoverageAnalysis(Module *m) { m->d_cover_valid = new llvm::GlobalVariable( gIR->module, type, /*isConstant=*/true, LLGlobalValue::InternalLinkage, zeroinitializer, "_d_cover_valid"); - LLConstant *idxs[] = {DtoConstUint(0), DtoConstUint(0)}; - d_cover_valid_slice = - DtoConstSlice(DtoConstSize_t(type->getArrayNumElements()), - llvm::ConstantExpr::getGetElementPtr( - type, m->d_cover_valid, idxs, true)); + d_cover_valid_slice = DtoConstSlice( + DtoConstSize_t(type->getArrayNumElements()), m->d_cover_valid); // Assert that initializer array elements have enough bits assert(sizeof(m->d_cover_valid_init[0]) * 8 >= @@ -272,9 +264,8 @@ void addCoverageAnalysis(Module *m) { LLGlobalValue::InternalLinkage, init, "_d_cover_data"); - d_cover_data_slice = DtoConstSlice(DtoConstSize_t(m->numlines), - DtoGEP(m->d_cover_data->getValueType(), - m->d_cover_data, 0, 0)); + d_cover_data_slice = + DtoConstSlice(DtoConstSize_t(m->numlines), m->d_cover_data); } // Create "static constructor" that calls _d_cover_register2(string filename, @@ -299,11 +290,7 @@ void addCoverageAnalysis(Module *m) { ctor->setCallingConv(gABI->callingConv(LINK::d)); // Set function attributes. See functions.cpp:DtoDefineFunction() if (global.params.targetTriple->getArch() == llvm::Triple::x86_64) { -#if LDC_LLVM_VER >= 1500 ctor->setUWTableKind(llvm::UWTableKind::Default); -#else - ctor->addFnAttr(LLAttribute::UWTable); -#endif } llvm::BasicBlock *bb = llvm::BasicBlock::Create(gIR->context(), "", ctor); @@ -404,11 +391,7 @@ void registerModuleInfo(Module *m) { } void addModuleFlags(llvm::Module &m) { -#if LDC_LLVM_VER >= 1500 const auto ModuleMinFlag = llvm::Module::Min; -#else - const auto ModuleMinFlag = llvm::Module::Warning; // Fallback value -#endif if (opts::fCFProtection == opts::CFProtectionType::Return || opts::fCFProtection == opts::CFProtectionType::Full) { diff --git a/gen/naked.cpp b/gen/naked.cpp index ff179192da2..2eb338b94f0 100644 --- a/gen/naked.cpp +++ b/gen/naked.cpp @@ -482,7 +482,7 @@ DValue *DtoInlineAsmExpr(const Loc &loc, FuncDeclaration *fd, auto lvalue = sretPointer; if (!lvalue) lvalue = DtoAlloca(returnType, ".__asm_tuple_ret"); - DtoStore(rv, DtoBitCast(lvalue, getPtrToType(irReturnType))); + DtoStore(rv, lvalue); return new DLValue(returnType, lvalue); } @@ -507,20 +507,11 @@ llvm::CallInst *DtoInlineAsmExpr(const Loc &loc, llvm::StringRef code, llvm::FunctionType *FT = llvm::FunctionType::get(returnType, operandTypes, false); -#if LDC_LLVM_VER < 1500 - // make sure the constraints are valid - if (!llvm::InlineAsm::Verify(FT, constraints)) { - error(loc, "inline asm constraints are invalid"); - fatal(); - } -#else if (auto err = llvm::InlineAsm::verify(FT, constraints)) { error(loc, "inline asm constraints are invalid"); llvm::errs() << err; fatal(); } -#endif - // build asm call bool sideeffect = true; diff --git a/gen/nested.cpp b/gen/nested.cpp index 25fe3a8e7c9..36122e3f4b2 100644 --- a/gen/nested.cpp +++ b/gen/nested.cpp @@ -138,18 +138,13 @@ DValue *DtoNestedVariable(const Loc &loc, Type *astype, VarDeclaration *vd, // Extract variable from nested context assert(irfunc->frameType); - const auto pframeType = LLPointerType::getUnqual(irfunc->frameType); IF_LOG { Logger::cout() << "casting to: " << *irfunc->frameType << '\n'; } - LLValue *val = DtoBitCast(ctx, pframeType); + LLValue *val = ctx; llvm::StructType *currFrame = irfunc->frameType; // Make the DWARF variable address relative to the context pointer (ctx); // register all ops (offsetting, dereferencing) required to get there in the // following list. -#if LDC_LLVM_VER >= 1400 LLSmallVector dwarfAddrOps; -#else - LLSmallVector dwarfAddrOps; -#endif const auto offsetToNthField = [&val, &dwarfAddrOps, &currFrame](unsigned fieldIndex, const char *name = "") { @@ -232,7 +227,7 @@ void DtoResolveNestedContext(const Loc &loc, AggregateDeclaration *decl, unsigned idx = getVthisIdx(decl); llvm::StructType *st = getIrAggr(decl)->getLLStructType(); LLValue *gep = DtoGEP(st, value, 0, idx, ".vthis"); - DtoStore(DtoBitCast(nest, st->getElementType(idx)), gep); + DtoStore(nest, gep); } } @@ -347,7 +342,6 @@ LLValue *DtoNestedContext(const Loc &loc, Dsymbol *sym) { "Calling sibling function or directly nested function"); } else { llvm::StructType *type = getIrFunc(ctxfd)->frameType; - val = DtoBitCast(val, LLPointerType::getUnqual(type)); val = DtoGEP(type, val, 0, neededDepth); val = DtoAlignedLoad(type->getElementType(neededDepth), val, (std::string(".frame.") + frameToPass->toChars()).c_str()); @@ -507,7 +501,7 @@ void DtoCreateNestedContext(FuncGenState &funcGen) { LLValue *mem = gIR->CreateCallOrInvoke(fn, DtoConstSize_t(size), ".gc_frame"); if (frameAlignment <= 16) { - frame = DtoBitCast(mem, frameType->getPointerTo(), ".frame"); + frame = mem; } else { const uint64_t mask = frameAlignment - 1; mem = gIR->ir->CreatePtrToInt(mem, DtoSize_t()); @@ -533,14 +527,11 @@ void DtoCreateNestedContext(FuncGenState &funcGen) { src = indexVThis(ad, thisptr); } if (depth > 1) { - src = DtoBitCast(src, getVoidPtrType()); - LLValue *dst = DtoBitCast(frame, getVoidPtrType()); - DtoMemCpy(dst, src, DtoConstSize_t((depth - 1) * target.ptrsize), + DtoMemCpy(frame, src, DtoConstSize_t((depth - 1) * target.ptrsize), getABITypeAlign(getVoidPtrType())); } // Copy nestArg into framelist; the outer frame is not in the list of // pointers - src = DtoBitCast(src, frameType->getContainedType(depth - 1)); LLValue *gep = DtoGEP(frameType, frame, 0, depth - 1); DtoAlignedStore(src, gep); } diff --git a/gen/objcgen.cpp b/gen/objcgen.cpp index 04b216d686c..a85ee68dd80 100644 --- a/gen/objcgen.cpp +++ b/gen/objcgen.cpp @@ -92,7 +92,7 @@ LLGlobalVariable *ObjCState::getMethVarRef(const ObjcSelector &sel) { } void ObjCState::retain(LLConstant *sym) { - retainedSymbols.push_back(DtoBitCast(sym, getVoidPtrType())); + retainedSymbols.push_back(sym); } void ObjCState::finalize() { diff --git a/gen/optimizer.cpp b/gen/optimizer.cpp index b8c8b8dd07f..76b37eb1186 100644 --- a/gen/optimizer.cpp +++ b/gen/optimizer.cpp @@ -44,7 +44,6 @@ #include "llvm/Transforms/Instrumentation/MemorySanitizer.h" #include "llvm/Transforms/Instrumentation/ThreadSanitizer.h" #include "llvm/Transforms/Instrumentation/AddressSanitizer.h" -#if LDC_LLVM_VER >= 1400 #include "llvm/Passes/PassBuilder.h" #include "llvm/Passes/StandardInstrumentations.h" #include "llvm/Transforms/Instrumentation/AddressSanitizerOptions.h" @@ -54,7 +53,6 @@ #include "llvm/Transforms/Scalar/EarlyCSE.h" #include "llvm/Transforms/Scalar/LICM.h" #include "llvm/Transforms/Scalar/Reassociate.h" -#endif #include "llvm/Transforms/Instrumentation/SanitizerCoverage.h" extern llvm::TargetMachine *gTargetMachine; @@ -166,262 +164,6 @@ std::unique_ptr createTLII(llvm::Module &M) { return std::unique_ptr(tlii); } -#if LDC_LLVM_VER < 1500 -static inline void legacyAddPass(PassManagerBase &pm, Pass *pass) { - pm.add(pass); - - if (verifyEach) { - pm.add(createVerifierPass()); - } -} - -static void legacyAddStripExternalsPass(const PassManagerBuilder &builder, - PassManagerBase &pm) { - if (builder.OptLevel >= 1) { - legacyAddPass(pm, createStripExternalsPass()); - pm.add(createGlobalDCEPass()); - } -} - -static void legacyAddSimplifyDRuntimeCallsPass(const PassManagerBuilder &builder, - PassManagerBase &pm) { - if (builder.OptLevel >= 2 && builder.SizeLevel == 0) { - legacyAddPass(pm, createSimplifyDRuntimeCalls()); - } -} - -static void legacyAddGarbageCollect2StackPass(const PassManagerBuilder &builder, - PassManagerBase &pm) { - if (builder.OptLevel >= 2 && builder.SizeLevel == 0) { - legacyAddPass(pm, createGarbageCollect2Stack()); - } -} - -static void legacyAddAddressSanitizerPasses(const PassManagerBuilder &Builder, - PassManagerBase &PM) { - PM.add(createAddressSanitizerFunctionPass(/*CompileKernel = */ false, - /*Recover = */ false, - /*UseAfterScope = */ true)); - PM.add(createModuleAddressSanitizerLegacyPassPass()); -} - -static void legacyAddMemorySanitizerPass(const PassManagerBuilder &Builder, - PassManagerBase &PM) { - int trackOrigins = fSanitizeMemoryTrackOrigins; - bool recover = false; - bool kernel = false; - PM.add(createMemorySanitizerLegacyPassPass( - MemorySanitizerOptions{trackOrigins, recover, kernel})); - - // MemorySanitizer inserts complex instrumentation that mostly follows - // the logic of the original code, but operates on "shadow" values. - // It can benefit from re-running some general purpose optimization passes. - if (Builder.OptLevel > 0) { - PM.add(createEarlyCSEPass()); - PM.add(createReassociatePass()); - PM.add(createLICMPass()); - PM.add(createGVNPass()); - PM.add(createInstructionCombiningPass()); - PM.add(createDeadStoreEliminationPass()); - } -} - -static void legacyAddThreadSanitizerPass(const PassManagerBuilder &Builder, - PassManagerBase &PM) { - PM.add(createThreadSanitizerLegacyPassPass()); -} - -static void legacyAddSanitizerCoveragePass(const PassManagerBuilder &Builder, - legacy::PassManagerBase &PM) { - PM.add(createModuleSanitizerCoverageLegacyPassPass( - opts::getSanitizerCoverageOptions())); -} - -// Adds PGO instrumentation generation and use passes. -static void legacyAddPGOPasses(PassManagerBuilder &builder, - legacy::PassManagerBase &mpm, unsigned optLevel) { - if (opts::isInstrumentingForASTBasedPGO()) { - InstrProfOptions options; - options.NoRedZone = global.params.disableRedZone; - if (global.params.datafileInstrProf) - options.InstrProfileOutput = global.params.datafileInstrProf; - mpm.add(createInstrProfilingLegacyPass(options)); - } else if (opts::isUsingASTBasedPGOProfile()) { - // We are generating code with PGO profile information available. - // Do indirect call promotion from -O1 - if (optLevel > 0) { - mpm.add(createPGOIndirectCallPromotionLegacyPass()); - } - } else if (opts::isInstrumentingForIRBasedPGO()) { - builder.EnablePGOInstrGen = true; - builder.PGOInstrGen = global.params.datafileInstrProf; - } else if (opts::isUsingIRBasedPGOProfile()) { - builder.PGOInstrUse = global.params.datafileInstrProf; - } -} - -/** - * Adds a set of optimization passes to the given module/function pass - * managers based on the given optimization and size reduction levels. - * - * The selection mirrors Clang behavior and is based on LLVM's - * PassManagerBuilder. - */ -static void legacyAddOptimizationPasses(legacy::PassManagerBase &mpm, - legacy::FunctionPassManager &fpm, - unsigned optLevel, unsigned sizeLevel) { - if (!noVerify) { - fpm.add(createVerifierPass()); - } - - PassManagerBuilder builder; - builder.OptLevel = optLevel; - builder.SizeLevel = sizeLevel; - builder.PrepareForLTO = opts::isUsingLTO(); - builder.PrepareForThinLTO = opts::isUsingThinLTO(); - - if (willInline()) { - auto params = llvm::getInlineParams(optLevel, sizeLevel); - builder.Inliner = createFunctionInliningPass(params); - } else { - builder.Inliner = createAlwaysInlinerLegacyPass(); - } - builder.DisableUnrollLoops = optLevel == 0; - - builder.DisableUnrollLoops = (disableLoopUnrolling.getNumOccurrences() > 0) - ? disableLoopUnrolling - : optLevel == 0; - - // This is final, unless there is a #pragma vectorize enable - if (disableLoopVectorization) { - builder.LoopVectorize = false; - // If option wasn't forced via cmd line (-vectorize-loops, -loop-vectorize) - } else if (!builder.LoopVectorize) { - builder.LoopVectorize = optLevel > 1 && sizeLevel < 2; - } - - // When #pragma vectorize is on for SLP, do the same as above - builder.SLPVectorize = - disableSLPVectorization ? false : optLevel > 1 && sizeLevel < 2; - - if (opts::isSanitizerEnabled(opts::AddressSanitizer)) { - builder.addExtension(PassManagerBuilder::EP_OptimizerLast, - legacyAddAddressSanitizerPasses); - builder.addExtension(PassManagerBuilder::EP_EnabledOnOptLevel0, - legacyAddAddressSanitizerPasses); - } - - if (opts::isSanitizerEnabled(opts::MemorySanitizer)) { - builder.addExtension(PassManagerBuilder::EP_OptimizerLast, - legacyAddMemorySanitizerPass); - builder.addExtension(PassManagerBuilder::EP_EnabledOnOptLevel0, - legacyAddMemorySanitizerPass); - } - - if (opts::isSanitizerEnabled(opts::ThreadSanitizer)) { - builder.addExtension(PassManagerBuilder::EP_OptimizerLast, - legacyAddThreadSanitizerPass); - builder.addExtension(PassManagerBuilder::EP_EnabledOnOptLevel0, - legacyAddThreadSanitizerPass); - } - - if (opts::isSanitizerEnabled(opts::CoverageSanitizer)) { - builder.addExtension(PassManagerBuilder::EP_OptimizerLast, - legacyAddSanitizerCoveragePass); - builder.addExtension(PassManagerBuilder::EP_EnabledOnOptLevel0, - legacyAddSanitizerCoveragePass); - } - - if (!disableLangSpecificPasses) { - if (!disableSimplifyDruntimeCalls) { - builder.addExtension(PassManagerBuilder::EP_LoopOptimizerEnd, - legacyAddSimplifyDRuntimeCallsPass); - } - - if (!disableGCToStack) { - builder.addExtension(PassManagerBuilder::EP_LoopOptimizerEnd, - legacyAddGarbageCollect2StackPass); - } - } - - // EP_OptimizerLast does not exist in LLVM 3.0, add it manually below. - builder.addExtension(PassManagerBuilder::EP_OptimizerLast, - legacyAddStripExternalsPass); - - legacyAddPGOPasses(builder, mpm, optLevel); - - builder.populateFunctionPassManager(fpm); - builder.populateModulePassManager(mpm); -} - -//////////////////////////////////////////////////////////////////////////////// -// This function runs optimization passes based on command line arguments. -// Returns true if any optimization passes were invoked. -bool legacy_ldc_optimize_module(llvm::Module *M) { - // Create a PassManager to hold and optimize the collection of - // per-module passes we are about to build. - legacy::PassManager mpm; - - // Dont optimise spirv modules because turning GEPs into extracts triggers - // asserts in the IR -> SPIR-V translation pass. SPIRV doesn't have a target - // machine, so any optimisation passes that rely on it to provide analysis, - // like DCE can't be run. - // The optimisation is supposed to happen between the SPIRV -> native machine - // code pass of the consumer of the binary. - // TODO: run rudimentary optimisations to improve IR debuggability. - if (getComputeTargetType(M) == ComputeBackend::SPIRV) - return false; - - // Add an appropriate TargetLibraryInfo pass for the module's triple. - auto tlii = createTLII(*M); - mpm.add(new TargetLibraryInfoWrapperPass(*tlii)); - - // The DataLayout is already set at the module (in module.cpp, - // method Module::genLLVMModule()) - // FIXME: Introduce new command line switch default-data-layout to - // override the module data layout - - // Add internal analysis passes from the target machine. - mpm.add(createTargetTransformInfoWrapperPass( - gTargetMachine->getTargetIRAnalysis())); - - // Also set up a manager for the per-function passes. - legacy::FunctionPassManager fpm(M); - - // Add internal analysis passes from the target machine. - fpm.add(createTargetTransformInfoWrapperPass( - gTargetMachine->getTargetIRAnalysis())); - - // If the -strip-debug command line option was specified, add it before - // anything else. - if (stripDebug) { - mpm.add(createStripSymbolsPass(true)); - } - - legacyAddOptimizationPasses(mpm, fpm, optLevel(), sizeLevel()); - - // Run per-function passes. - fpm.doInitialization(); - for (auto &F : *M) { - fpm.run(F); - } - fpm.doFinalization(); - - // Run per-module passes. - mpm.run(*M); - - // Verify the resulting module. - if (!noVerify) { - verifyModule(M); - } - - // Report that we run some passes. - return true; -} -#endif - -#if LDC_LLVM_VER >= 1400 - static OptimizationLevel getOptimizationLevel(){ switch(optimizeLevel) { case 0: return OptimizationLevel::O0; @@ -757,7 +499,7 @@ void runOptimizationPasses(llvm::Module *M) { //////////////////////////////////////////////////////////////////////////////// // This function runs optimization passes based on command line arguments. // Returns true if any optimization passes were invoked. -bool new_ldc_optimize_module(llvm::Module *M) { +bool ldc_optimize_module(llvm::Module *M) { // Dont optimise spirv modules because turning GEPs into extracts triggers // asserts in the IR -> SPIR-V translation pass. SPIRV doesn't have a target // machine, so any optimisation passes that rely on it to provide analysis, @@ -779,23 +521,6 @@ bool new_ldc_optimize_module(llvm::Module *M) { return true; } -#endif -//////////////////////////////////////////////////////////////////////////////// -// This function calls the fuction which runs optimization passes based on command -// line arguments. Calls either legacy version using legacy pass manager -// or new version using the new pass managr -// Returns true if any optimization passes were invoked. -bool ldc_optimize_module(llvm::Module *M) { -#if LDC_LLVM_VER < 1400 - return legacy_ldc_optimize_module(M); -#elif LDC_LLVM_VER < 1500 - return opts::isUsingLegacyPassManager() ? legacy_ldc_optimize_module(M) - : new_ldc_optimize_module(M); -#else - return new_ldc_optimize_module(M); -#endif -} - // Verifies the module. void verifyModule(llvm::Module *m) { diff --git a/gen/passes/DLLImportRelocation.cpp b/gen/passes/DLLImportRelocation.cpp index e1c2bf490a9..d2dc6c8369d 100644 --- a/gen/passes/DLLImportRelocation.cpp +++ b/gen/passes/DLLImportRelocation.cpp @@ -208,15 +208,11 @@ struct Impl { if (auto gep = isGEP(skipOverCast(originalInitializer))) { Constant *newOperand = createConstPointerCast(importedVar, gep->getOperand(0)->getType()); -#if LDC_LLVM_VER < 1400 - value = gep->getWithOperandReplaced(0, newOperand); -#else SmallVector newOperands; newOperands.push_back(newOperand); for (unsigned i = 1, e = gep->getNumOperands(); i != e; ++i) newOperands.push_back(gep->getOperand(i)); value = gep->getWithOperands(newOperands); -#endif } value = createConstPointerCast(value, t); diff --git a/gen/passes/GarbageCollect2Stack.cpp b/gen/passes/GarbageCollect2Stack.cpp index dc12db63b0b..8910d2d9840 100644 --- a/gen/passes/GarbageCollect2Stack.cpp +++ b/gen/passes/GarbageCollect2Stack.cpp @@ -69,8 +69,6 @@ struct G2StackAnalysis { void EmitMemSet(IRBuilder<> &B, Value *Dst, Value *Val, Value *Len, const G2StackAnalysis &A) { - Dst = B.CreateBitCast(Dst, PointerType::getUnqual(B.getInt8Ty())); - MaybeAlign Align(1); auto CS = B.CreateMemSet(Dst, Val, Len, Align, false /*isVolatile*/); @@ -197,9 +195,7 @@ Value* ArrayFI::promote(CallBase *CB, IRBuilder<> &B, const G2StackAnalysis &A) if (ReturnType == ReturnType::Array) { Value *arrStruct = llvm::UndefValue::get(CB->getType()); arrStruct = Builder.CreateInsertValue(arrStruct, arrSize, 0); - Value *memPtr = - Builder.CreateBitCast(alloca, PointerType::getUnqual(B.getInt8Ty())); - arrStruct = Builder.CreateInsertValue(arrStruct, memPtr, 1); + arrStruct = Builder.CreateInsertValue(arrStruct, alloca, 1); return arrStruct; } @@ -284,7 +280,7 @@ Value* UntypedMemoryFI::promote(CallBase *CB, IRBuilder<> &B, const G2StackAnaly AllocaInst *alloca = B.CreateAlloca(Ty, count, ".nongc_mem"); // FIXME: align? - return B.CreateBitCast(alloca, CB->getType()); + return alloca; } //} @@ -454,9 +450,7 @@ bool GarbageCollect2Stack::run(Function &F, std::function get // Make sure the type is the same as it was before, and replace all // uses of the runtime call with the alloca. - if (newVal->getType() != CB->getType()) { - newVal = Builder.CreateBitCast(newVal, CB->getType()); - } + assert(newVal->getType() == CB->getType()); static_cast(CB)->replaceAllUsesWith(newVal); RemoveCall(CB, A); diff --git a/gen/passes/SimplifyDRuntimeCalls.cpp b/gen/passes/SimplifyDRuntimeCalls.cpp index 998df4ca2f3..82af7c6dd1e 100644 --- a/gen/passes/SimplifyDRuntimeCalls.cpp +++ b/gen/passes/SimplifyDRuntimeCalls.cpp @@ -51,18 +51,12 @@ Value *LibCallOptimization::OptimizeCall(CallInst *CI, bool &Changed, const Data return CallOptimizer(CI->getCalledFunction(), CI, B); } -/// CastToCStr - Return V if it is an i8*, otherwise cast it to i8*. -Value *LibCallOptimization::CastToCStr(Value *V, IRBuilder<> &B) { - return B.CreateBitCast(V, PointerType::getUnqual(B.getInt8Ty()), "cstr"); -} - /// EmitMemCpy - Emit a call to the memcpy function to the builder. This always /// expects that the size has type 'intptr_t' and Dst/Src are pointers. Value *LibCallOptimization::EmitMemCpy(Value *Dst, Value *Src, Value *Len, unsigned Align, IRBuilder<> &B) { auto A = llvm::MaybeAlign(Align); - return B.CreateMemCpy(CastToCStr(Dst, B), A, CastToCStr(Src, B), A, Len, - false); + return B.CreateMemCpy(Dst, A, Src, A, Len, false); } //===----------------------------------------------------------------------===// diff --git a/gen/passes/SimplifyDRuntimeCalls.h b/gen/passes/SimplifyDRuntimeCalls.h index 21a74f19846..4311e6689ca 100644 --- a/gen/passes/SimplifyDRuntimeCalls.h +++ b/gen/passes/SimplifyDRuntimeCalls.h @@ -18,9 +18,6 @@ class LLVM_LIBRARY_VISIBILITY LibCallOptimization { llvm::AliasAnalysis *AA; llvm::LLVMContext *Context; - /// CastToCStr - Return V if it is an i8*, otherwise cast it to i8*. - llvm::Value *CastToCStr(llvm::Value *V, llvm::IRBuilder<> &B); - /// EmitMemCpy - Emit a call to the memcpy function to the builder. This /// always expects that the size has type 'intptr_t' and Dst/Src are pointers. llvm::Value *EmitMemCpy(llvm::Value *Dst, llvm::Value *Src, llvm::Value *Len, unsigned Align, diff --git a/gen/pgo_ASTbased.cpp b/gen/pgo_ASTbased.cpp index a14465f3675..031bdf36bfa 100644 --- a/gen/pgo_ASTbased.cpp +++ b/gen/pgo_ASTbased.cpp @@ -909,10 +909,8 @@ void CodeGenPGO::emitCounterIncrement(const RootObject *S) const { assert(counter_it != (*RegionCounterMap).end() && "Statement not found in PGO counter map!"); unsigned counter = counter_it->second; - auto *I8PtrTy = getVoidPtrType(); gIR->ir->CreateCall(GET_INTRINSIC_DECL(instrprof_increment), - {llvm::ConstantExpr::getBitCast(FuncNameVar, I8PtrTy), - gIR->ir->getInt64(FunctionHash), + {FuncNameVar, gIR->ir->getInt64(FunctionHash), gIR->ir->getInt32(NumRegionCounters), gIR->ir->getInt32(counter)}); } @@ -1117,11 +1115,9 @@ void CodeGenPGO::valueProfile(uint32_t valueKind, llvm::Instruction *valueSite, if (ptrCastNeeded) value = gIR->ir->CreatePtrToInt(value, gIR->ir->getInt64Ty()); - auto *i8PtrTy = getVoidPtrType(); - llvm::Value *Args[5] = { - llvm::ConstantExpr::getBitCast(FuncNameVar, i8PtrTy), - gIR->ir->getInt64(FunctionHash), value, gIR->ir->getInt32(valueKind), - gIR->ir->getInt32(NumValueSites[valueKind])}; + llvm::Value *Args[5] = {FuncNameVar, gIR->ir->getInt64(FunctionHash), value, + gIR->ir->getInt32(valueKind), + gIR->ir->getInt32(NumValueSites[valueKind])}; gIR->ir->CreateCall(GET_INTRINSIC_DECL(instrprof_value_profile), Args); gIR->ir->restoreIP(savedInsertPoint); diff --git a/gen/rttibuilder.cpp b/gen/rttibuilder.cpp index 0d998b4de6f..a08303aedcb 100644 --- a/gen/rttibuilder.cpp +++ b/gen/rttibuilder.cpp @@ -71,7 +71,7 @@ void RTTIBuilder::push_null_void_array() { } void RTTIBuilder::push_void_array(uint64_t dim, llvm::Constant *ptr) { - push(DtoConstSlice(DtoConstSize_t(dim), DtoBitCast(ptr, getVoidPtrType()))); + push(DtoConstSlice(DtoConstSize_t(dim), ptr)); } void RTTIBuilder::push_void_array(llvm::Constant *CI, Type *valtype, @@ -113,7 +113,7 @@ void RTTIBuilder::push_array(llvm::Constant *CI, uint64_t dim, Type *valtype, setLinkage(lwc, G); G->setAlignment(llvm::MaybeAlign(DtoAlignment(valtype))); - push_array(dim, DtoBitCast(G, DtoType(pointerTo(valtype)))); + push_array(dim, G); } void RTTIBuilder::push_array(uint64_t dim, llvm::Constant *ptr) { @@ -128,15 +128,10 @@ void RTTIBuilder::push_size_as_vp(uint64_t s) { push(llvm::ConstantExpr::getIntToPtr(DtoConstSize_t(s), getVoidPtrType())); } -void RTTIBuilder::push_funcptr(FuncDeclaration *fd, Type *castto) { +void RTTIBuilder::push_funcptr(FuncDeclaration *fd) { if (fd) { LLConstant *F = DtoCallee(fd); - if (castto) { - F = DtoBitCast(F, DtoType(castto)); - } push(F); - } else if (castto) { - push_null(castto); } else { push_null_vp(); } diff --git a/gen/rttibuilder.h b/gen/rttibuilder.h index ab3866c1caf..d6cddee740a 100644 --- a/gen/rttibuilder.h +++ b/gen/rttibuilder.h @@ -48,7 +48,7 @@ class RTTIBuilder { void push_typeinfo(Type *t); /// pushes the function pointer or a null void* if it cannot. - void push_funcptr(FuncDeclaration *fd, Type *castto = nullptr); + void push_funcptr(FuncDeclaration *fd); /// pushes the array slice given. void push_array(uint64_t dim, llvm::Constant *ptr); diff --git a/gen/runtime.cpp b/gen/runtime.cpp index 3f13351fd4a..14faefe80dc 100644 --- a/gen/runtime.cpp +++ b/gen/runtime.cpp @@ -273,11 +273,7 @@ struct LazyFunctionDeclarer { // FIXME: Move to better place (abi-x86-64.cpp?) // NOTE: There are several occurances if this line. if (global.params.targetTriple->getArch() == llvm::Triple::x86_64) { -#if LDC_LLVM_VER >= 1500 fn->setUWTableKind(llvm::UWTableKind::Default); -#else - fn->addFnAttr(LLAttribute::UWTable); -#endif } fn->setCallingConv(gABI->callingConv(dty, false)); @@ -359,6 +355,8 @@ llvm::Function *getRuntimeFunction(const Loc &loc, llvm::Module &target, // const char *funcname); // Musl: void __assert_fail(const char *assertion, const char *filename, int line_num, // const char *funcname); +// Glibc: void __assert_fail(const char *assertion, const char *filename, int line_num, +// const char *funcname); // uClibc: void __assert(const char *assertion, const char *filename, int linenumber, // const char *function); // newlib: void __assert_func(const char *file, int line, const char *func, @@ -373,7 +371,7 @@ static const char *getCAssertFunctionName() { return "_assert"; } else if (triple.isOSSolaris()) { return "__assert_c99"; - } else if (triple.isMusl()) { + } else if (triple.isMusl() || triple.isGNUEnvironment()) { return "__assert_fail"; } else if (global.params.isNewlibEnvironment) { return "__assert_func"; @@ -387,7 +385,7 @@ static std::vector getCAssertFunctionParamTypes() { const auto uint = Type::tuns32; if (triple.isOSDarwin() || triple.isOSSolaris() || triple.isMusl() || - global.params.isUClibcEnvironment) { + global.params.isUClibcEnvironment || (triple.isOSGlibc() && triple.isGNUEnvironment())) { return {voidPtr, voidPtr, uint, voidPtr}; } if (triple.getEnvironment() == llvm::Triple::Android) { @@ -890,7 +888,7 @@ static void emitInstrumentationFn(const char *name) { // Grab the address of the calling function auto *caller = gIR->ir->CreateCall(GET_INTRINSIC_DECL(returnaddress), DtoConstInt(0)); - auto callee = DtoBitCast(gIR->topfunc(), getVoidPtrType()); + auto callee = gIR->topfunc(); gIR->ir->CreateCall(fn, {callee, caller}); } diff --git a/gen/statements.cpp b/gen/statements.cpp index f572a9ba5e3..267306fa9fd 100644 --- a/gen/statements.cpp +++ b/gen/statements.cpp @@ -262,14 +262,6 @@ class ToIRVisitor : public Visitor { Logger::println("Loading value for return"); returnValue = DtoLoad(funcType->getReturnType(), returnValue); } - - // can happen for classes - if (returnValue->getType() != funcType->getReturnType()) { - returnValue = - irs->ir->CreateBitCast(returnValue, funcType->getReturnType()); - IF_LOG Logger::cout() - << "return value after cast: " << *returnValue << '\n'; - } } } else { // no return value expression means it's a void function. diff --git a/gen/structs.cpp b/gen/structs.cpp index 19be48ec6db..bc1b9c9d150 100644 --- a/gen/structs.cpp +++ b/gen/structs.cpp @@ -152,7 +152,6 @@ LLValue *DtoUnpaddedStruct(Type *dty, LLValue *v) { fieldval = DtoUnpaddedStruct(fields[i]->type, fieldptr); } else { assert(!fields[i]->isBitFieldDeclaration()); - fieldptr = DtoBitCast(fieldptr, DtoPtrToType(fields[i]->type)); fieldval = DtoLoad(DtoType(fields[i]->type), fieldptr); } newval = DtoInsertValue(newval, fieldval, i); @@ -174,7 +173,6 @@ void DtoPaddedStruct(Type *dty, LLValue *v, LLValue *lval) { DtoPaddedStruct(fields[i]->type, fieldval, fieldptr); } else { assert(!fields[i]->isBitFieldDeclaration()); - fieldptr = DtoBitCast(fieldptr, DtoPtrToType(fields[i]->type)); DtoStoreZextI8(fieldval, fieldptr); } } diff --git a/gen/tocall.cpp b/gen/tocall.cpp index 58f0c2c7f9b..fc77c5f554d 100644 --- a/gen/tocall.cpp +++ b/gen/tocall.cpp @@ -16,6 +16,7 @@ #include "dmd/target.h" #include "dmd/template.h" #include "gen/abi/abi.h" +#include "gen/arrays.h" #include "gen/classes.h" #include "gen/dvalue.h" #include "gen/funcgenstate.h" @@ -101,17 +102,9 @@ static void addExplicitArguments(std::vector &args, AttrSet &attrs, Type *argType = argexps[i]->type; bool passByVal = gABI->passByVal(irFty.type, argType); -#if LDC_LLVM_VER >= 1400 llvm::AttrBuilder initialAttrs(getGlobalContext()); -#else - llvm::AttrBuilder initialAttrs; -#endif if (passByVal) { -#if LDC_LLVM_VER >= 1200 initialAttrs.addByValAttr(DtoType(argType)); -#else - initialAttrs.addAttribute(LLAttribute::ByVal); -#endif if (auto alignment = DtoAlignment(argType)) initialAttrs.addAlignmentAttr(alignment); } else { @@ -245,40 +238,11 @@ static LLValue *getTypeinfoArrayArgumentForDVarArg(Expressions *argexps, LLConstant *tiinits = LLConstantArray::get(typeinfoarraytype, vtypeinfos); typeinfomem->setInitializer(tiinits); - // put data in d-array - LLConstant *pinits[] = { - DtoConstSize_t(numVariadicArgs), - llvm::ConstantExpr::getBitCast(typeinfomem, getPtrToType(typeinfotype))}; - LLType *tiarrty = DtoType(arrayOf(getTypeInfoType())); - tiinits = LLConstantStruct::get(isaStruct(tiarrty), - llvm::ArrayRef(pinits)); - LLValue *typeinfoarrayparam = new llvm::GlobalVariable( - gIR->module, tiarrty, true, llvm::GlobalValue::InternalLinkage, tiinits, - "._arguments.array"); - - return DtoLoad(tiarrty, typeinfoarrayparam); + return DtoConstSlice(DtoConstSize_t(numVariadicArgs), typeinfomem); } //////////////////////////////////////////////////////////////////////////////// -static LLType *getPtrToAtomicType(LLType *type) { - switch (const size_t N = getTypeBitSize(type)) { - case 8: - case 16: - case 32: - case 64: - case 128: -#if LDC_LLVM_VER < 1800 - return LLType::getIntNPtrTy(gIR->context(), static_cast(N)); -#else - return LLType::getIntNTy(gIR->context(), static_cast(N)) - ->getPointerTo(); -#endif - default: - return nullptr; - } -} - static LLType *getAtomicType(LLType *type) { switch (const size_t N = getTypeBitSize(type)) { case 8: @@ -417,11 +381,9 @@ bool DtoLowerMagicIntrinsic(IRState *p, FuncDeclaration *fndecl, CallExp *e, LLValue *val = nullptr; if (pointeeType->isIntegerTy()) { val = DtoRVal(dval); - } else if (auto intPtrType = getPtrToAtomicType(pointeeType)) { - LLType *atype = getAtomicType(pointeeType); - ptr = DtoBitCast(ptr, intPtrType); + } else if (auto atype = getAtomicType(pointeeType)) { auto lval = makeLValue(exp1->loc, dval); - val = DtoLoad(atype, DtoBitCast(lval, intPtrType)); + val = DtoLoad(atype, lval); } else { error(e->loc, "atomic store only supports types of size 1/2/4/8/16 bytes, not `%s`", @@ -453,9 +415,8 @@ bool DtoLowerMagicIntrinsic(IRState *p, FuncDeclaration *fndecl, CallExp *e, Type *retType = exp->type->nextOf(); if (!pointeeType->isIntegerTy()) { - if (auto intPtrType = getPtrToAtomicType(pointeeType)) { - ptr = DtoBitCast(ptr, intPtrType); - loadedType = getAtomicType(pointeeType); + if (auto atype = getAtomicType(pointeeType)) { + loadedType = atype; } else { error(e->loc, "atomic load only supports types of size 1/2/4/8/16 bytes, " @@ -509,13 +470,11 @@ bool DtoLowerMagicIntrinsic(IRState *p, FuncDeclaration *fndecl, CallExp *e, if (pointeeType->isIntegerTy()) { cmp = DtoRVal(dcmp); val = DtoRVal(dval); - } else if (auto intPtrType = getPtrToAtomicType(pointeeType)) { - LLType *atype = getAtomicType(pointeeType); - ptr = DtoBitCast(ptr, intPtrType); + } else if (auto atype = getAtomicType(pointeeType)) { auto cmpLVal = makeLValue(exp2->loc, dcmp); - cmp = DtoLoad(atype, DtoBitCast(cmpLVal, intPtrType)); + cmp = DtoLoad(atype, cmpLVal); auto lval = makeLValue(exp3->loc, dval); - val = DtoLoad(atype, DtoBitCast(lval, intPtrType)); + val = DtoLoad(atype, lval); } else { error(e->loc, "`cmpxchg` only supports types of size 1/2/4/8/16 bytes, not `%s`", @@ -524,11 +483,9 @@ bool DtoLowerMagicIntrinsic(IRState *p, FuncDeclaration *fndecl, CallExp *e, } auto ret = - p->ir->CreateAtomicCmpXchg(ptr, cmp, val, -#if LDC_LLVM_VER >= 1300 - llvm::MaybeAlign(), // default alignment -#endif - successOrdering, failureOrdering); + p->ir->CreateAtomicCmpXchg(ptr, cmp, val, + llvm::MaybeAlign(), // default alignment + successOrdering, failureOrdering); ret->setWeak(isWeak); // we return a struct; allocate on stack and store to both fields manually @@ -536,8 +493,7 @@ bool DtoLowerMagicIntrinsic(IRState *p, FuncDeclaration *fndecl, CallExp *e, // because of i1) auto mem = DtoAlloca(e->type); llvm::Type* memty = DtoType(e->type); - DtoStore(p->ir->CreateExtractValue(ret, 0), - DtoBitCast(DtoGEP(memty, mem, 0u, 0), ptr->getType())); + DtoStore(p->ir->CreateExtractValue(ret, 0), DtoGEP(memty, mem, 0u, 0)); DtoStoreZextI8(p->ir->CreateExtractValue(ret, 1), DtoGEP(memty, mem, 0, 1)); result = new DLValue(e->type, mem); @@ -577,9 +533,7 @@ bool DtoLowerMagicIntrinsic(IRState *p, FuncDeclaration *fndecl, CallExp *e, LLValue *val = DtoRVal(exp2); LLValue *ret = p->ir->CreateAtomicRMW(llvm::AtomicRMWInst::BinOp(op), ptr, val, -#if LDC_LLVM_VER >= 1300 llvm::MaybeAlign(), // default alignment -#endif llvm::AtomicOrdering(atomicOrdering)); result = new DImValue(exp2->type, ret); return true; @@ -603,8 +557,9 @@ bool DtoLowerMagicIntrinsic(IRState *p, FuncDeclaration *fndecl, CallExp *e, unsigned bitmask = DtoSize_t()->getBitWidth() - 1; assert(bitmask == 31 || bitmask == 63); // auto q = cast(size_t*)ptr + (bitnum >> (64bit ? 6 : 5)); - LLValue *q = DtoBitCast(ptr, DtoSize_t()->getPointerTo()); - q = DtoGEP1(DtoSize_t(), q, p->ir->CreateLShr(bitnum, bitmask == 63 ? 6 : 5), "bitop.q"); + LLValue *q = + DtoGEP1(DtoSize_t(), ptr, + p->ir->CreateLShr(bitnum, bitmask == 63 ? 6 : 5), "bitop.q"); // auto mask = 1 << (bitnum & bitmask); LLValue *mask = @@ -756,7 +711,6 @@ class ImplicitArgumentsBuilder { return; size_t index = args.size(); - LLType *llArgType = *(llArgTypesBegin + index); if (dfnval && (dfnval->func->ident == Id::ensure || dfnval->func->ident == Id::require)) { @@ -776,12 +730,10 @@ class ImplicitArgumentsBuilder { } } } - LLValue *contextptr = DtoBitCast(thisptrLval, getVoidPtrType()); - args.push_back(contextptr); + args.push_back(thisptrLval); } else if (thiscall && dfnval && dfnval->vthis) { // ... or a normal 'this' argument - LLValue *thisarg = DtoBitCast(dfnval->vthis, llArgType); - args.push_back(thisarg); + args.push_back(dfnval->vthis); } else if (isDelegateCall) { // ... or a delegate context arg LLValue *ctxarg; @@ -790,13 +742,11 @@ class ImplicitArgumentsBuilder { } else { ctxarg = gIR->ir->CreateExtractValue(DtoRVal(fnval), 0, ".ptr"); } - ctxarg = DtoBitCast(ctxarg, llArgType); args.push_back(ctxarg); } else if (nestedcall) { // ... or a nested function context arg if (dfnval) { LLValue *contextptr = DtoNestedContext(loc, dfnval->func); - contextptr = DtoBitCast(contextptr, getVoidPtrType()); args.push_back(contextptr); } else { args.push_back(llvm::UndefValue::get(getVoidPtrType())); @@ -818,8 +768,7 @@ class ImplicitArgumentsBuilder { const auto selector = dfnval->func->objc.selector; assert(selector); LLGlobalVariable *selptr = gIR->objc.getMethVarRef(*selector); - args.push_back(DtoBitCast(DtoLoad(selptr->getValueType(), selptr), - getVoidPtrType())); + args.push_back(DtoLoad(selptr->getValueType(), selptr)); } } @@ -931,9 +880,7 @@ DValue *DtoCallFunction(const Loc &loc, Type *resulttype, DValue *fnval, if (irFty.arg_objcSelector) { // Use runtime msgSend function bitcasted as original call const char *msgSend = gABI->objcMsgSendFunc(resulttype, irFty); - LLType *t = callable->getType(); callable = getRuntimeFunction(loc, gIR->module, msgSend); - callable = DtoBitCast(callable, t); } // call the function @@ -983,9 +930,7 @@ DValue *DtoCallFunction(const Loc &loc, Type *resulttype, DValue *fnval, case TY::Tsarray: if (nextbase->ty == TY::Tvector && !tf->isref()) { - if (retValIsLVal) { - retllval = DtoBitCast(retllval, DtoType(pointerTo(rbase))); - } else { + if (!retValIsLVal) { // static arrays need to be dumped to memory; use vector alignment retllval = DtoAllocaDump(retllval, DtoType(rbase), DtoAlignment(nextbase), @@ -1076,15 +1021,9 @@ DValue *DtoCallFunction(const Loc &loc, Type *resulttype, DValue *fnval, call->setCallingConv(gABI->callingConv(tf, iab.hasContext)); } // merge in function attributes set in callOrInvoke -#if LDC_LLVM_VER >= 1400 auto attrbuildattribs = call->getAttributes().getFnAttrs(); attrlist = attrlist.addFnAttributes( gIR->context(), llvm::AttrBuilder(gIR->context(), attrbuildattribs)); -#else - attrlist = attrlist.addAttributes( - gIR->context(), LLAttributeList::FunctionIndex, - llvm::AttrBuilder(call->getAttributes(), LLAttributeList::FunctionIndex)); -#endif call->setAttributes(attrlist); // Special case for struct constructor calls: For temporaries, using the diff --git a/gen/toconstelem.cpp b/gen/toconstelem.cpp index f193a473e90..92f4fddab98 100644 --- a/gen/toconstelem.cpp +++ b/gen/toconstelem.cpp @@ -79,8 +79,7 @@ class ToConstElemVisitor : public Visitor { } if (TypeInfoDeclaration *ti = e->var->isTypeInfoDeclaration()) { - result = DtoTypeInfoOf(e->loc, ti->tinfo, /*base=*/false); - result = DtoBitCast(result, DtoType(e->type)); + result = DtoTypeInfoOf(e->loc, ti->tinfo); return; } @@ -176,13 +175,11 @@ class ToConstElemVisitor : public Visitor { } llvm::GlobalVariable *gvar = p->getCachedStringLiteral(e); - LLConstant *arrptr = DtoGEP(gvar->getValueType(), gvar, 0u, 0u); if (t->ty == TY::Tpointer) { - result = DtoBitCast(arrptr, DtoType(t)); + result = gvar; } else if (t->ty == TY::Tarray) { - LLConstant *clen = LLConstantInt::get(DtoSize_t(), e->len, false); - result = DtoConstSlice(clen, arrptr, e->type); + result = DtoConstSlice(DtoConstSize_t(e->len), gvar); } else { llvm_unreachable("Unknown type for StringExp."); } @@ -210,7 +207,6 @@ class ToConstElemVisitor : public Visitor { llResult = llvm::ConstantExpr::getGetElementPtr(DtoMemType(pointeeType), llBase, llOffset); } else { // need to cast base to i8* - llBase = DtoBitCast(llBase, getVoidPtrType()); LLConstant *llOffset = DtoConstSize_t(byteOffset); if (negateOffset) llOffset = llvm::ConstantExpr::getNeg(llOffset); @@ -218,7 +214,7 @@ class ToConstElemVisitor : public Visitor { llvm::ConstantExpr::getGetElementPtr(getI8Type(), llBase, llOffset); } - return DtoBitCast(llResult, DtoType(e->type)); + return llResult; } void visit(AddExp *e) override { @@ -323,7 +319,7 @@ class ToConstElemVisitor : public Visitor { if (type->ty == TY::Tarray || type->ty == TY::Tdelegate) { value = DtoGEP(irg->getType(), value, 0u, 1u); } - result = DtoBitCast(value, DtoType(tb)); + result = value; } else if (tb->ty == TY::Tclass && e->e1->type->ty == TY::Tclass && e->e1->op == EXP::classReference) { auto cd = static_cast(e->e1)->originalClass(); @@ -341,7 +337,7 @@ class ToConstElemVisitor : public Visitor { // offset pointer instance = DtoGEP(DtoType(e->e1->type), instance, 0, i_index); } - result = DtoBitCast(instance, DtoType(tb)); + result = instance; } else { goto Lerr; } @@ -384,14 +380,10 @@ class ToConstElemVisitor : public Visitor { } else { // Offset isn't a multiple of base type size, just cast to i8* and // apply the byte offset. - auto i8 = LLType::getInt8Ty(gIR->context()); result = llvm::ConstantExpr::getGetElementPtr( - i8, DtoBitCast(base, i8->getPointerTo()), - DtoConstSize_t(e->offset)); + getI8Type(), base, DtoConstSize_t(e->offset)); } } - - result = DtoBitCast(result, DtoType(e->type)); } ////////////////////////////////////////////////////////////////////////////// @@ -405,8 +397,7 @@ class ToConstElemVisitor : public Visitor { // address of global variable if (auto vexp = e->e1->isVarExp()) { - LLConstant *c = DtoConstSymbolAddress(e->loc, vexp->var); - result = c ? DtoBitCast(c, DtoType(e->type)) : nullptr; + result = DtoConstSymbolAddress(e->loc, vexp->var); return; } @@ -428,18 +419,16 @@ class ToConstElemVisitor : public Visitor { // gep LLConstant *idxs[2] = {DtoConstSize_t(0), index}; LLConstant *val = isaConstant(getIrGlobal(vd)->value); - val = DtoBitCast(val, DtoType(pointerTo(vd->type))); LLConstant *gep = llvm::ConstantExpr::getGetElementPtr( DtoType(vd->type), val, idxs, true); - // bitcast to requested type assert(e->type->toBasetype()->ty == TY::Tpointer); - result = DtoBitCast(gep, DtoType(e->type)); + result = gep; return; } if (auto se = e->e1->isStructLiteralExp()) { - result = p->getStructLiteralConstant(se); + result = p->getStructLiteralGlobal(se); if (result) { IF_LOG Logger::cout() << "Returning existing global: " << *result << '\n'; @@ -451,12 +440,12 @@ class ToConstElemVisitor : public Visitor { llvm::GlobalValue::InternalLinkage, nullptr, ".structliteral"); globalVar->setAlignment(llvm::MaybeAlign(DtoAlignment(se->type))); - p->setStructLiteralConstant(se, globalVar); + p->setStructLiteralGlobal(se, globalVar); llvm::Constant *constValue = toConstElem(se, p); - constValue = p->setGlobalVarInitializer(globalVar, constValue, nullptr); - p->setStructLiteralConstant(se, constValue); + globalVar = p->setGlobalVarInitializer(globalVar, constValue, nullptr); + p->setStructLiteralGlobal(se, globalVar); - result = constValue; + result = globalVar; return; } @@ -518,11 +507,6 @@ class ToConstElemVisitor : public Visitor { // extract D types Type *bt = e->type->toBasetype(); - Type *elemt = bt->nextOf(); - - // build llvm array type - LLArrayType *arrtype = - LLArrayType::get(DtoMemType(elemt), e->elements->length); // dynamic arrays can occur here as well ... bool dyn = (bt->ty != TY::Tsarray); @@ -541,18 +525,16 @@ class ToConstElemVisitor : public Visitor { llvm::GlobalValue::InternalLinkage, initval, ".dynarrayStorage"); gvar->setUnnamedAddr(canBeConst ? llvm::GlobalValue::UnnamedAddr::Global : llvm::GlobalValue::UnnamedAddr::None); - llvm::Constant *store = DtoBitCast(gvar, getPtrToType(arrtype)); if (bt->ty == TY::Tpointer) { // we need to return pointer to the static array. - result = store; + result = gvar; return; } // build a constant dynamic array reference with the .ptr field pointing // into store - LLConstant *globalstorePtr = DtoGEP(arrtype, store, 0u, 0u); - result = DtoConstSlice(DtoConstSize_t(e->elements->length), globalstorePtr); + result = DtoConstSlice(DtoConstSize_t(e->elements->length), gvar); } ////////////////////////////////////////////////////////////////////////////// @@ -605,14 +587,14 @@ class ToConstElemVisitor : public Visitor { DtoResolveClass(origClass); StructLiteralExp *value = e->value; - result = p->getStructLiteralConstant(value); + result = p->getStructLiteralGlobal(value); if (result) { IF_LOG Logger::cout() << "Using existing global: " << *result << '\n'; } else { auto globalVar = new llvm::GlobalVariable( p->module, getIrType(origClass->type)->isClass()->getMemoryLLType(), false, llvm::GlobalValue::InternalLinkage, nullptr, ".classref"); - p->setStructLiteralConstant(value, globalVar); + p->setStructLiteralGlobal(value, globalVar); std::map varInits; @@ -651,10 +633,10 @@ class ToConstElemVisitor : public Visitor { llvm::Constant *constValue = getIrAggr(origClass)->createInitializerConstant(varInits); - constValue = p->setGlobalVarInitializer(globalVar, constValue, nullptr); - p->setStructLiteralConstant(value, constValue); + globalVar = p->setGlobalVarInitializer(globalVar, constValue, nullptr); + p->setStructLiteralGlobal(value, globalVar); - result = constValue; + result = globalVar; } if (e->type->ty == TY::Tclass) { @@ -673,7 +655,6 @@ class ToConstElemVisitor : public Visitor { } assert(e->type->ty == TY::Tclass || e->type->ty == TY::Tenum); - result = DtoBitCast(result, DtoType(e->type)); } ////////////////////////////////////////////////////////////////////////////// @@ -710,11 +691,7 @@ class ToConstElemVisitor : public Visitor { // cast. // FIXME: Check DMD source to understand why two different ASTs are // constructed. -#if LDC_LLVM_VER >= 1200 const auto elementCount = llvm::ElementCount::getFixed(elemCount); -#else - const auto elementCount = llvm::ElementCount(elemCount, false); -#endif result = llvm::ConstantVector::getSplat( elementCount, toConstElem(optimize(e->e1, WANTvalue), p)); } @@ -732,8 +709,7 @@ class ToConstElemVisitor : public Visitor { return; } - result = DtoTypeInfoOf(e->loc, t, /*base=*/false); - result = DtoBitCast(result, DtoType(e->type)); + result = DtoTypeInfoOf(e->loc, t); } ////////////////////////////////////////////////////////////////////////////// diff --git a/gen/toir.cpp b/gen/toir.cpp index 8b8203f90a1..003143f142e 100644 --- a/gen/toir.cpp +++ b/gen/toir.cpp @@ -67,7 +67,6 @@ bool walkPostorder(Expression *e, StoppableVisitor *v); //////////////////////////////////////////////////////////////////////////////// static LLValue *write_zeroes(LLValue *mem, unsigned start, unsigned end) { - mem = DtoBitCast(mem, getVoidPtrType()); LLType *i8 = LLType::getInt8Ty(gIR->context()); LLValue *gep = DtoGEP1(i8, mem, start, ".padding"); DtoMemSetZero(i8, gep, DtoConstSize_t(end - start)); @@ -166,8 +165,7 @@ static void write_struct_literal(Loc loc, LLValue *mem, StructDeclaration *sd, } IF_LOG Logger::cout() << "merged IR value: " << *val << '\n'; - gIR->ir->CreateAlignedStore(val, DtoBitCast(ptr, getPtrToType(intType)), - llvm::MaybeAlign(1)); + gIR->ir->CreateAlignedStore(val, ptr, llvm::MaybeAlign(1)); offset += group.sizeInBytes; i += group.bitFields.size() - 1; // skip the other bit fields of the group @@ -191,8 +189,7 @@ static void write_struct_literal(Loc loc, LLValue *mem, StructDeclaration *sd, assert(vd == sd->vthis); IF_LOG Logger::println("initializing vthis"); LOG_SCOPE - DImValue val(vd->type, - DtoBitCast(DtoNestedContext(loc, sd), DtoType(vd->type))); + DImValue val(vd->type, DtoNestedContext(loc, sd)); DtoAssign(loc, field, &val, EXP::blit); } @@ -442,16 +439,15 @@ class ToElemVisitor : public Visitor { } llvm::GlobalVariable *gvar = p->getCachedStringLiteral(e); - LLConstant *arrptr = DtoGEP(gvar->getValueType(), gvar, 0u, 0u); if (dtype->ty == TY::Tarray) { - LLConstant *clen = LLConstantInt::get(DtoSize_t(), stringLength, false); - result = new DSliceValue(e->type, DtoConstSlice(clen, arrptr, dtype)); + result = new DSliceValue( + e->type, DtoConstSlice(DtoConstSize_t(stringLength), gvar)); } else if (dtype->ty == TY::Tsarray) { // array length matches string length with or without null terminator - result = new DLValue(e->type, DtoBitCast(gvar, DtoPtrToType(dtype))); + result = new DLValue(e->type, gvar); } else if (dtype->ty == TY::Tpointer) { - result = new DImValue(e->type, DtoBitCast(arrptr, DtoType(dtype))); + result = new DImValue(e->type, gvar); } else { llvm_unreachable("Unknown type for StringExp."); } @@ -799,9 +795,7 @@ class ToElemVisitor : public Visitor { if (canEmitVTableUnchangedAssumption && !dfnval->vtable && dfnval->vthis && dfnval->func->isVirtual()) { dfnval->vtable = - DtoLoad(getVoidPtrType(), - DtoBitCast(dfnval->vthis, getVoidPtrType()->getPointerTo()), - "saved_vtable"); + DtoLoad(getVoidPtrType(), dfnval->vthis, "saved_vtable"); } } @@ -812,9 +806,7 @@ class ToElemVisitor : public Visitor { // Reload vtable ptr. It's the first element so instead of GEP+load we can // do a void* load+bitcast (at this point in the code we don't have easy // access to the type of the class to do a GEP). - auto vtable = DtoLoad( - dfnval->vtable->getType(), - DtoBitCast(dfnval->vthis, dfnval->vtable->getType()->getPointerTo())); + auto vtable = DtoLoad(dfnval->vtable->getType(), dfnval->vthis); auto cmp = p->ir->CreateICmpEQ(vtable, dfnval->vtable); p->ir->CreateCall(GET_INTRINSIC_DECL(assume), {cmp}); } @@ -910,12 +902,11 @@ class ToElemVisitor : public Visitor { if (!offsetValue) { // Offset isn't a multiple of base type size, just cast to i8* and // apply the byte offset. - auto castBase = DtoBitCast(baseValue, getVoidPtrType()); if (target.ptrsize == 8) { - offsetValue = DtoGEP1i64(getI8Type(), castBase, e->offset); + offsetValue = DtoGEP1i64(getI8Type(), baseValue, e->offset); } else { offsetValue = - DtoGEP1(getI8Type(), castBase, static_cast(e->offset)); + DtoGEP1(getI8Type(), baseValue, static_cast(e->offset)); } } } @@ -945,7 +936,7 @@ class ToElemVisitor : public Visitor { LLConstant *addr = toConstElem(e, p); IF_LOG Logger::cout() << "returning address of struct literal global: " << addr << '\n'; - result = new DImValue(e->type, DtoBitCast(addr, DtoType(e->type))); + result = new DImValue(e->type, addr); return; } @@ -976,7 +967,7 @@ class ToElemVisitor : public Visitor { } IF_LOG Logger::cout() << "lval: " << *lval << '\n'; - result = new DImValue(e->type, DtoBitCast(lval, DtoType(e->type))); + result = new DImValue(e->type, lval); } ////////////////////////////////////////////////////////////////////////////// @@ -1004,16 +995,14 @@ class ToElemVisitor : public Visitor { // get the rvalue and return it as an lvalue LLValue *V = DtoRVal(e->e1); - result = new DLValue(e->type, DtoBitCast(V, DtoPtrToType(e->type))); + result = new DLValue(e->type, V); } static llvm::PointerType * getWithSamePointeeType(llvm::PointerType *p, unsigned addressSpace) { #if LDC_LLVM_VER >= 1700 return llvm::PointerType::get(p->getContext(), addressSpace); -#elif LDC_LLVM_VER >= 1300 - return llvm::PointerType::getWithSamePointeeType(p, addressSpace); #else - return p->getPointerElementType()->getPointerTo(addressSpace); + return llvm::PointerType::getWithSamePointeeType(p, addressSpace); #endif } @@ -1067,11 +1056,9 @@ class ToElemVisitor : public Visitor { } else ptrty = DtoType(e->type); - result = new DDcomputeLValue(e->type, i1ToI8(ptrty), - DtoBitCast(DtoLVal(d), DtoPtrToType(e->type))); + result = new DDcomputeLValue(e->type, i1ToI8(ptrty), DtoLVal(d)); } else { - LLValue *p = DtoBitCast(DtoLVal(ptr), DtoPtrToType(e->type)); - result = new DLValue(e->type, p); + result = new DLValue(e->type, DtoLVal(ptr)); } } else if (FuncDeclaration *fdecl = e->var->isFuncDeclaration()) { // This is a bit more convoluted than it would need to be, because it @@ -1140,7 +1127,7 @@ class ToElemVisitor : public Visitor { if (ident == Id::ensure || ident == Id::require) { Logger::println("contract this exp"); LLValue *v = p->func()->nestArg; // thisptr lvalue - result = new DLValue(e->type, DtoBitCast(v, DtoPtrToType(e->type))); + result = new DLValue(e->type, v); } else if (vd->toParent2() != p->func()->decl) { Logger::println("nested this exp"); result = @@ -1148,7 +1135,7 @@ class ToElemVisitor : public Visitor { } else { Logger::println("normal this exp"); LLValue *v = p->func()->thisArg; - result = new DLValue(e->type, DtoBitCast(v, DtoPtrToType(e->type))); + result = new DLValue(e->type, v); } } @@ -1192,7 +1179,7 @@ class ToElemVisitor : public Visitor { IF_LOG Logger::println("e1type: %s", e1type->toChars()); llvm_unreachable("Unknown IndexExp target."); } - result = new DLValue(e->type, DtoBitCast(arrptr, DtoPtrToType(e->type))); + result = new DLValue(e->type, arrptr); } ////////////////////////////////////////////////////////////////////////////// @@ -1289,10 +1276,6 @@ class ToElemVisitor : public Visitor { if (etype->ty == TY::Tsarray) { TypeSArray *tsa = static_cast(etype); elen = DtoConstSize_t(tsa->dim->toUInteger()); - - // in this case, we also need to make sure the pointer is cast to the - // innermost element type - eptr = DtoBitCast(eptr, DtoType(pointerTo(tsa->nextOf()))); } } @@ -1300,14 +1283,13 @@ class ToElemVisitor : public Visitor { // fixed-width slice to a static array. Type *const ety = e->type->toBasetype(); if (ety->ty == TY::Tsarray) { - result = new DLValue(e->type, DtoBitCast(eptr, DtoPtrToType(e->type))); + result = new DLValue(e->type, eptr); return; } assert(ety->ty == TY::Tarray); if (!elen) elen = DtoArrayLen(v); - eptr = DtoBitCast(eptr, DtoPtrToType(ety->nextOf())); result = new DSliceValue(e->type, elen, eptr); } @@ -1624,9 +1606,7 @@ class ToElemVisitor : public Visitor { // new AA else if (auto taa = ntype->isTypeAArray()) { LLFunction *func = getRuntimeFunction(e->loc, gIR->module, "_aaNew"); - LLValue *aaTypeInfo = - DtoBitCast(DtoTypeInfoOf(e->loc, stripModifiers(taa), /*base=*/false), - DtoType(getAssociativeArrayTypeInfoType())); + LLValue *aaTypeInfo = DtoTypeInfoOf(e->loc, stripModifiers(taa)); LLValue *aa = gIR->CreateCallOrInvoke(func, aaTypeInfo, "aa"); result = new DImValue(e->type, aa); } @@ -1823,8 +1803,7 @@ class ToElemVisitor : public Visitor { getIRMangledFuncName("_D9invariant12_d_invariantFC6ObjectZv", LINK::d); const auto fn = getRuntimeFunction(e->loc, gIR->module, fnMangle.c_str()); - const auto arg = - DtoBitCast(DtoRVal(cond), fn->getFunctionType()->getParamType(0)); + const auto arg = DtoRVal(cond); gIR->CreateCallOrInvoke(fn, arg); } else if (condty->ty == TY::Tpointer && @@ -1967,32 +1946,26 @@ class ToElemVisitor : public Visitor { e->func->toChars()); } - LLPointerType *int8ptrty = getPtrToType(LLType::getInt8Ty(gIR->context())); - assert(e->type->toBasetype()->ty == TY::Tdelegate); - LLType *dgty = DtoType(e->type); DValue *u = toElem(e->e1); - LLValue *uval; + LLValue *contextptr; if (DFuncValue *f = u->isFunc()) { assert(f->func); - LLValue *contextptr = DtoNestedContext(e->loc, f->func); - uval = DtoBitCast(contextptr, getVoidPtrType()); + contextptr = DtoNestedContext(e->loc, f->func); } else { - uval = (DtoIsInMemoryOnly(u->type) ? DtoLVal(u) : DtoRVal(u)); + contextptr = (DtoIsInMemoryOnly(u->type) ? DtoLVal(u) : DtoRVal(u)); } - IF_LOG Logger::cout() << "context = " << *uval << '\n'; - - LLValue *castcontext = DtoBitCast(uval, int8ptrty); + IF_LOG Logger::cout() << "context = " << *contextptr << '\n'; IF_LOG Logger::println("func: '%s'", e->func->toPrettyChars()); - LLValue *castfptr; + LLValue *fptr; if (e->e1->op != EXP::super_ && e->e1->op != EXP::dotType && e->func->isVirtual() && !e->func->isFinalFunc()) { - castfptr = DtoVirtualFunctionPointer(u, e->func).first; + fptr = DtoVirtualFunctionPointer(u, e->func).first; } else if (e->func->isAbstract()) { llvm_unreachable("Delegate to abstract method not implemented."); } else if (e->func->toParent()->isInterfaceDeclaration()) { @@ -2012,13 +1985,11 @@ class ToElemVisitor : public Visitor { } } - castfptr = DtoCallee(e->func); + fptr = DtoCallee(e->func); } - castfptr = DtoBitCast(castfptr, dgty->getContainedType(1)); - result = new DImValue( - e->type, DtoAggrPair(DtoType(e->type), castcontext, castfptr, ".dg")); + e->type, DtoAggrPair(DtoType(e->type), contextptr, fptr, ".dg")); } ////////////////////////////////////////////////////////////////////////////// @@ -2137,7 +2108,7 @@ class ToElemVisitor : public Visitor { DValue *u = toElem(e->e1); if (retPtr && u->type->toBasetype()->ty != TY::Tnoreturn) { LLValue *lval = makeLValue(e->loc, u); - DtoStore(lval, DtoBitCast(retPtr, lval->getType()->getPointerTo())); + DtoStore(lval, retPtr); } llvm::BranchInst::Create(condend, p->scopebb()); @@ -2145,7 +2116,7 @@ class ToElemVisitor : public Visitor { DValue *v = toElem(e->e2); if (retPtr && v->type->toBasetype()->ty != TY::Tnoreturn) { LLValue *lval = makeLValue(e->loc, v); - DtoStore(lval, DtoBitCast(retPtr, lval->getType()->getPointerTo())); + DtoStore(lval, retPtr); } llvm::BranchInst::Create(condend, p->scopebb()); @@ -2310,14 +2281,8 @@ class ToElemVisitor : public Visitor { LLFunction *callee = DtoCallee(fd, false); if (fd->isNested()) { - LLType *dgty = DtoType(e->type); - LLValue *cval = DtoNestedContext(e->loc, fd); - cval = DtoBitCast(cval, dgty->getContainedType(0)); - - LLValue *castfptr = DtoBitCast(callee, dgty->getContainedType(1)); - - result = new DImValue(e->type, DtoAggrPair(cval, castfptr, ".func")); + result = new DImValue(e->type, DtoAggrPair(cval, callee, ".func")); } else { result = new DFuncValue(e->type, fd, callee); } @@ -2370,7 +2335,6 @@ class ToElemVisitor : public Visitor { return; } - storage = DtoBitCast(storage, llElemType->getPointerTo()); if (arrayType->ty == TY::Tarray) { result = new DSliceValue(e->type, DtoConstSize_t(len), storage); } else if (arrayType->ty == TY::Tpointer) { @@ -2387,15 +2351,12 @@ class ToElemVisitor : public Visitor { auto global = new llvm::GlobalVariable(gIR->module, init->getType(), true, llvm::GlobalValue::InternalLinkage, init, ".immutablearray"); - result = new DSliceValue(arrayType, DtoConstSize_t(len), - DtoBitCast(global, getPtrToType(llElemType))); + result = new DSliceValue(arrayType, DtoConstSize_t(len), global); } else { DSliceValue *dynSlice = DtoNewDynArray( e->loc, arrayType, new DConstValue(Type::tsize_t, DtoConstSize_t(len)), false); - initializeArrayLiteral( - p, e, DtoBitCast(dynSlice->getPtr(), getPtrToType(llStoType)), - llStoType); + initializeArrayLiteral(p, e, dynSlice->getPtr(), llStoType); result = dynSlice; } } @@ -2419,7 +2380,6 @@ class ToElemVisitor : public Visitor { DtoMemSetZero(DtoType(e->type), dstMem); } else { LLValue *initsym = getIrAggr(sd)->getInitSymbol(); - initsym = DtoBitCast(initsym, DtoType(pointerTo(e->type))); assert(dstMem->getType() == initsym->getType()); DtoMemCpy(DtoType(e->type), dstMem, initsym); } @@ -2560,26 +2520,19 @@ class ToElemVisitor : public Visitor { llvm::Function *func = getRuntimeFunction(e->loc, gIR->module, "_d_assocarrayliteralTX"); - LLFunctionType *funcTy = func->getFunctionType(); - LLValue *aaTypeInfo = DtoBitCast( - DtoTypeInfoOf(e->loc, stripModifiers(aatype), /*base=*/false), - DtoType(getAssociativeArrayTypeInfoType())); + LLValue *aaTypeInfo = DtoTypeInfoOf(e->loc, stripModifiers(aatype)); LLConstant *initval = arrayConst(keysInits, indexType); LLConstant *globalstore = new LLGlobalVariable( gIR->module, initval->getType(), false, LLGlobalValue::InternalLinkage, initval, ".aaKeysStorage"); - LLConstant *slice = DtoGEP(initval->getType(), globalstore, 0u, 0u); - slice = DtoConstSlice(DtoConstSize_t(e->keys->length), slice); - LLValue *keysArray = DtoSlicePaint(slice, funcTy->getParamType(1)); + LLValue *keysArray = DtoConstSlice(DtoConstSize_t(e->keys->length), globalstore); initval = arrayConst(valuesInits, vtype); globalstore = new LLGlobalVariable(gIR->module, initval->getType(), false, LLGlobalValue::InternalLinkage, initval, ".aaValuesStorage"); - slice = DtoGEP(initval->getType(), globalstore, 0u, 0u); - slice = DtoConstSlice(DtoConstSize_t(e->keys->length), slice); - LLValue *valuesArray = DtoSlicePaint(slice, funcTy->getParamType(2)); + LLValue *valuesArray = DtoConstSlice(DtoConstSize_t(e->keys->length), globalstore); LLValue *aa = gIR->CreateCallOrInvoke(func, aaTypeInfo, keysArray, valuesArray, "aa"); @@ -2627,7 +2580,7 @@ class ToElemVisitor : public Visitor { DValue * dv = toElem(exp->e1); LLValue *val = makeLValue(exp->loc, dv); LLValue *v = DtoGEP(DtoType(dv->type), val, 0, index); - return new DLValue(exp->type, DtoBitCast(v, DtoPtrToType(exp->type))); + return new DLValue(exp->type, v); } void visit(DelegatePtrExp *e) override { @@ -2781,11 +2734,7 @@ class ToElemVisitor : public Visitor { DValue *val = toElem(e->e1); LLValue *llElement = getCastElement(val); if (auto llConstant = isaConstant(llElement)) { -#if LDC_LLVM_VER >= 1200 const auto elementCount = llvm::ElementCount::getFixed(N); -#else - const auto elementCount = llvm::ElementCount(N, false); -#endif auto vectorConstant = llvm::ConstantVector::getSplat(elementCount, llConstant); DtoStore(vectorConstant, dstMem); @@ -2855,10 +2804,9 @@ class ToElemVisitor : public Visitor { // member, so we have to add an extra layer of indirection. resultType = getInterfaceTypeInfoType(); LLType *pres = DtoType(pointerTo(resultType)); - typinf = DtoLoad(pres, DtoBitCast(typinf, pres->getPointerTo())); + typinf = DtoLoad(pres, typinf); } else { resultType = getClassInfoType(); - typinf = DtoBitCast(typinf, DtoType(pointerTo(resultType))); } result = new DLValue(resultType, typinf); diff --git a/gen/tollvm.cpp b/gen/tollvm.cpp index 981bf8680f9..18e8496d825 100644 --- a/gen/tollvm.cpp +++ b/gen/tollvm.cpp @@ -407,10 +407,6 @@ LLValue *DtoGEP1i64(LLType *pointeeTy, LLValue *ptr, uint64_t i0, const char *na //////////////////////////////////////////////////////////////////////////////// void DtoMemSet(LLValue *dst, LLValue *val, LLValue *nbytes, unsigned align) { - LLType *VoidPtrTy = getVoidPtrType(); - - dst = DtoBitCast(dst, VoidPtrTy); - gIR->ir->CreateMemSet(dst, val, nbytes, llvm::MaybeAlign(align), false /*isVolatile*/); } @@ -429,11 +425,6 @@ void DtoMemSetZero(LLType *type, LLValue *dst, unsigned align) { //////////////////////////////////////////////////////////////////////////////// void DtoMemCpy(LLValue *dst, LLValue *src, LLValue *nbytes, unsigned align) { - LLType *VoidPtrTy = getVoidPtrType(); - - dst = DtoBitCast(dst, VoidPtrTy); - src = DtoBitCast(src, VoidPtrTy); - auto A = llvm::MaybeAlign(align); gIR->ir->CreateMemCpy(dst, A, src, A, nbytes, false /*isVolatile*/); } @@ -459,9 +450,6 @@ LLValue *DtoMemCmp(LLValue *lhs, LLValue *rhs, LLValue *nbytes) { &gIR->module); } - lhs = DtoBitCast(lhs, VoidPtrTy); - rhs = DtoBitCast(rhs, VoidPtrTy); - return gIR->ir->CreateCall(fn, {lhs, rhs, nbytes}); } @@ -515,13 +503,13 @@ LLConstant *DtoConstFP(Type *t, const real_t value) { LLConstant *DtoConstCString(const char *str) { llvm::StringRef s(str ? str : ""); LLGlobalVariable *gvar = gIR->getCachedStringLiteral(s); - return DtoGEP(gvar->getValueType(), gvar, 0u, 0u); + return gvar; } LLConstant *DtoConstString(const char *str) { LLConstant *cString = DtoConstCString(str); LLConstant *length = DtoConstSize_t(str ? strlen(str) : 0); - return DtoConstSlice(length, cString, arrayOf(Type::tchar)); + return DtoConstSlice(length, cString); } //////////////////////////////////////////////////////////////////////////////// @@ -595,37 +583,7 @@ LLType *stripAddrSpaces(LLType *t) if (!pt) return t; -#if LDC_LLVM_VER >= 1700 return getVoidPtrType(); -#elif LDC_LLVM_VER >= 1400 - if (pt->isOpaque()) - return getVoidPtrType(); - else { - int indirections = 0; - while (t->isPointerTy()) { - indirections++; -// Disable [[deprecated]] warning on getPointerElementType. We solved the -// deprecation for versions >= LLVM 16 above (8 lines up). -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" - t = t->getPointerElementType(); -#pragma GCC diagnostic pop - } - while (indirections-- != 0) - t = t->getPointerTo(0); - } - return t; -#else - int indirections = 0; - while (t->isPointerTy()) { - indirections++; - t = t->getPointerElementType(); - } - while (indirections-- != 0) - t = t->getPointerTo(0); - - return t; -#endif } LLValue *DtoBitCast(LLValue *v, LLType *t, const llvm::Twine &name) { diff --git a/gen/trycatchfinally.cpp b/gen/trycatchfinally.cpp index 6c689bde12d..59d79213e78 100644 --- a/gen/trycatchfinally.cpp +++ b/gen/trycatchfinally.cpp @@ -270,8 +270,7 @@ void emitBeginCatchMSVC(IRState &irs, Catch *ctch, if (!isCPPclass) { auto enterCatchFn = getRuntimeFunction(ctch->loc, irs.module, "_d_eh_enter_catch"); - irs.CreateCallOrInvoke(enterCatchFn, DtoBitCast(exnObj, getVoidPtrType()), - clssInfo); + irs.CreateCallOrInvoke(enterCatchFn, exnObj, clssInfo); } } } @@ -730,9 +729,8 @@ llvm::BasicBlock *TryCatchFinallyScopes::emitLandingPad() { // "Call" llvm.eh.typeid.for, which gives us the eh selector value to // compare the landing pad selector value with. - llvm::Value *ehTypeId = - irs.ir->CreateCall(GET_INTRINSIC_DECL(eh_typeid_for), - DtoBitCast(cb.classInfoPtr, getVoidPtrType())); + llvm::Value *ehTypeId = irs.ir->CreateCall( + GET_INTRINSIC_DECL(eh_typeid_for), cb.classInfoPtr); // Compare the selector value from the unwinder against the expected // one and branch accordingly. diff --git a/gen/uda.cpp b/gen/uda.cpp index 897c4848ef2..c528b48a273 100644 --- a/gen/uda.cpp +++ b/gen/uda.cpp @@ -206,11 +206,7 @@ void applyAttrAllocSize(StructLiteralExp *sle, IrFunction *irFunc) { const auto llvmSizeIdx = sizeArgIdx + offset; const auto llvmNumIdx = numArgIdx + offset; -#if LDC_LLVM_VER >= 1400 llvm::AttrBuilder builder(getGlobalContext()); -#else - llvm::AttrBuilder builder; -#endif if (numArgIdx >= 0) { builder.addAllocSizeAttr(llvmSizeIdx, llvmNumIdx); } else { @@ -223,11 +219,7 @@ void applyAttrAllocSize(StructLiteralExp *sle, IrFunction *irFunc) { llvm::Function *func = irFunc->getLLVMFunc(); -#if LDC_LLVM_VER >= 1400 func->addFnAttrs(builder); -#else - func->addAttributes(LLAttributeList::FunctionIndex, builder); -#endif } // @llvmAttr("key", "value") @@ -417,9 +409,7 @@ bool parseCallingConvention(llvm::StringRef name, .Case("preserve_most", llvm::CallingConv::PreserveMost) .Case("preserve_all", llvm::CallingConv::PreserveAll) .Case("swiftcall", llvm::CallingConv::Swift) -#if LDC_LLVM_VER >= 1300 .Case("swiftasynccall", llvm::CallingConv::SwiftTail) -#endif // Names recognized in LLVM IR (see LLVM's // LLParser::parseOptionalCallingConv): @@ -458,9 +448,7 @@ bool parseCallingConvention(llvm::StringRef name, .Case("preserve_allcc", llvm::CallingConv::PreserveAll) .Case("ghccc", llvm::CallingConv::GHC) .Case("swiftcc", llvm::CallingConv::Swift) -#if LDC_LLVM_VER >= 1300 .Case("swifttailcc", llvm::CallingConv::SwiftTail) -#endif .Case("x86_intrcc", llvm::CallingConv::X86_INTR) #if LDC_LLVM_VER >= 1700 .Case("hhvmcc", llvm::CallingConv::DUMMY_HHVM) @@ -471,9 +459,7 @@ bool parseCallingConvention(llvm::StringRef name, #endif .Case("cxx_fast_tlscc", llvm::CallingConv::CXX_FAST_TLS) .Case("amdgpu_vs", llvm::CallingConv::AMDGPU_VS) -#if LDC_LLVM_VER >= 1200 .Case("amdgpu_gfx", llvm::CallingConv::AMDGPU_Gfx) -#endif .Case("amdgpu_ls", llvm::CallingConv::AMDGPU_LS) .Case("amdgpu_hs", llvm::CallingConv::AMDGPU_HS) .Case("amdgpu_es", llvm::CallingConv::AMDGPU_ES) @@ -552,17 +538,9 @@ void applyFuncDeclUDAs(FuncDeclaration *decl, IrFunction *irFunc) { if (ident == Id::udaAllocSize) { applyAttrAllocSize(sle, irFunc); } else if (ident == Id::udaLLVMAttr) { -#if LDC_LLVM_VER >= 1400 llvm::AttrBuilder attrs(getGlobalContext()); -#else - llvm::AttrBuilder attrs; -#endif applyAttrLLVMAttr(sle, attrs); -#if LDC_LLVM_VER >= 1400 func->addFnAttrs(attrs); -#else - func->addAttributes(LLAttributeList::FunctionIndex, attrs); -#endif } else if (ident == Id::udaHidden) { if (!decl->isExport()) // export visibility is stronger func->setVisibility(LLGlobalValue::HiddenVisibility); diff --git a/gen/variable_lifetime.cpp b/gen/variable_lifetime.cpp index 4accea82960..488739cd83d 100644 --- a/gen/variable_lifetime.cpp +++ b/gen/variable_lifetime.cpp @@ -51,7 +51,6 @@ void LocalVariableLifetimeAnnotator::addLocalVariable(llvm::Value *address, scopes.back().variables.emplace_back(size, address); // Emit lifetime start - address = irs.ir->CreateBitCast(address, allocaType); irs.CreateCallOrInvoke(getLLVMLifetimeStartFn(), {size, address}, "", true /*nothrow*/); } @@ -65,7 +64,6 @@ void LocalVariableLifetimeAnnotator::popScope() { auto size = var.first; auto address = var.second; - address = irs.ir->CreateBitCast(address, allocaType); assert(address); irs.CreateCallOrInvoke(getLLVMLifetimeEndFn(), {size, address}, "", diff --git a/ir/iraggr.cpp b/ir/iraggr.cpp index 03f8bb6a005..e99e3944a3f 100644 --- a/ir/iraggr.cpp +++ b/ir/iraggr.cpp @@ -60,7 +60,7 @@ bool IrAggr::useDLLImport() const { ////////////////////////////////////////////////////////////////////////////// -LLConstant *IrAggr::getInitSymbol(bool define) { +LLGlobalVariable *IrAggr::getInitSymbol(bool define) { #if LDC_LLVM_VER >= 1800 #define startswith starts_with #endif @@ -77,25 +77,22 @@ LLConstant *IrAggr::getInitSymbol(bool define) { // Only declare the symbol if it isn't yet, otherwise the init symbol of // built-in TypeInfos may clash with an existing base-typed forward // declaration when compiling the rt.util.typeinfo unittests. - auto initGlobal = gIR->module.getGlobalVariable(irMangle); - if (initGlobal) { - assert(!initGlobal->hasInitializer() && + init = gIR->module.getGlobalVariable(irMangle); + if (init) { + assert(!init->hasInitializer() && "existing init symbol not expected to be defined"); - assert((isBuiltinTypeInfo || - initGlobal->getValueType() == getLLStructType()) && + assert((isBuiltinTypeInfo || init->getValueType() == getLLStructType()) && "type of existing init symbol declaration doesn't match"); } else { // Init symbols of built-in TypeInfos need to be kept mutable as the type // is not declared as immutable on the D side, and e.g. synchronized() can // be used on the implicit monitor. const bool isConstant = !isBuiltinTypeInfo; - initGlobal = declareGlobal(aggrdecl->loc, gIR->module, getLLStructType(), - irMangle, isConstant, false, useDLLImport()); + init = declareGlobal(aggrdecl->loc, gIR->module, getLLStructType(), + irMangle, isConstant, false, useDLLImport()); } - initGlobal->setAlignment(llvm::MaybeAlign(DtoAlignment(type))); - - init = initGlobal; + init->setAlignment(llvm::MaybeAlign(DtoAlignment(type))); if (!define) define = defineOnDeclare(aggrdecl, /*isFunction=*/false); @@ -103,10 +100,8 @@ LLConstant *IrAggr::getInitSymbol(bool define) { if (define) { auto initConstant = getDefaultInit(); - auto initGlobal = llvm::dyn_cast(init); - if (initGlobal // NOT a bitcast pointer to helper global - && !initGlobal->hasInitializer()) { - init = gIR->setGlobalVarInitializer(initGlobal, initConstant, aggrdecl); + if (!init->hasInitializer()) { + init = gIR->setGlobalVarInitializer(init, initConstant, aggrdecl); } } diff --git a/ir/iraggr.h b/ir/iraggr.h index 8762626c479..db298f9fb76 100644 --- a/ir/iraggr.h +++ b/ir/iraggr.h @@ -58,7 +58,7 @@ class IrAggr { virtual ~IrAggr() = default; /// Creates the __initZ symbol lazily. - llvm::Constant *getInitSymbol(bool define = false); + llvm::GlobalVariable *getInitSymbol(bool define = false); /// Builds the __initZ initializer constant lazily. llvm::Constant *getDefaultInit(); /// Return the LLVM type of this Aggregate (w/o the reference for classes) @@ -84,7 +84,7 @@ class IrAggr { protected: /// Static default initializer global. - llvm::Constant *init = nullptr; + llvm::GlobalVariable *init = nullptr; /// Static default initializer constant. llvm::Constant *constInit = nullptr; diff --git a/ir/irclass.cpp b/ir/irclass.cpp index c1c9add2d62..34e1ef3d3e3 100644 --- a/ir/irclass.cpp +++ b/ir/irclass.cpp @@ -196,7 +196,6 @@ LLConstant *IrClass::getVtblInit() { if (!cd->isCPPclass()) { if (!suppressTypeInfo()) { c = getClassInfoSymbol(); - c = DtoBitCast(c, voidPtrType); } else { // use null if there are no TypeInfos c = llvm::Constant::getNullValue(voidPtrType); @@ -235,7 +234,7 @@ LLConstant *IrClass::getVtblInit() { } } - c = DtoBitCast(DtoCallee(fd), voidPtrType); + c = DtoCallee(fd); if (cd->isFuncHidden(fd) && !fd->isFuture()) { // fd is hidden from the view of this class. If fd overlaps with any @@ -381,7 +380,7 @@ LLConstant *IrClass::getClassInfoInit() { if (isInterface) { b.push_array(0, getNullValue(voidPtrPtr)); } else { - b.push_array(cd->vtbl.length, DtoBitCast(getVtblSymbol(), voidPtrPtr)); + b.push_array(cd->vtbl.length, getVtblSymbol()); } // Interface[] interfaces @@ -397,12 +396,11 @@ LLConstant *IrClass::getClassInfoInit() { // void* destructor assert(!isInterface || !cd->tidtor); - b.push_funcptr(cd->tidtor, Type::tvoidptr); + b.push_funcptr(cd->tidtor); // void function(Object) classInvariant assert(!isInterface || !cd->inv); - VarDeclaration *invVar = cinfo->fields[6]; - b.push_funcptr(cd->inv, invVar->type); + b.push_funcptr(cd->inv); // ClassFlags m_flags const auto flags = buildClassinfoFlags(cd); @@ -424,13 +422,12 @@ LLConstant *IrClass::getClassInfoInit() { b.push_null(offTiVar->type); // void function(Object) defaultConstructor - VarDeclaration *defConstructorVar = cinfo->fields[11]; CtorDeclaration *defConstructor = cd->defaultCtor; if (defConstructor && (defConstructor->storage_class & STCdisable)) { defConstructor = nullptr; } assert(!isInterface || !defConstructor); - b.push_funcptr(defConstructor, defConstructorVar->type); + b.push_funcptr(defConstructor); // immutable(void)* m_RTInfo if (cd->getRTInfo) { @@ -546,7 +543,7 @@ LLConstant *IrClass::getInterfaceVtblInit(BaseClass *b, llvm::Constant *c = llvm::ConstantExpr::getGetElementPtr( interfaceInfosZ->getValueType(), interfaceInfosZ, idxs, true); - constants.push_back(DtoBitCast(c, voidPtrTy)); + constants.push_back(c); } else { // use null if there are no TypeInfos constants.push_back(llvm::Constant::getNullValue(voidPtrTy)); @@ -579,7 +576,7 @@ LLConstant *IrClass::getInterfaceVtblInit(BaseClass *b, if (fd->interfaceVirtual) thunkOffset -= fd->interfaceVirtual->offset; if (thunkOffset == 0) { - constants.push_back(DtoBitCast(irFunc->getLLVMCallee(), voidPtrTy)); + constants.push_back(irFunc->getLLVMCallee()); continue; } @@ -656,10 +653,7 @@ LLConstant *IrClass::getInterfaceVtblInit(BaseClass *b, ? 0 : 1; LLValue *&thisArg = args[thisArgIndex]; - LLType *targetThisType = thisArg->getType(); - thisArg = DtoBitCast(thisArg, getVoidPtrType()); - thisArg = DtoGEP1(LLType::getInt8Ty(gIR->context()), thisArg, DtoConstInt(-thunkOffset)); - thisArg = DtoBitCast(thisArg, targetThisType); + thisArg = DtoGEP1(getI8Type(), thisArg, DtoConstInt(-thunkOffset)); // all calls that might be subject to inlining into a caller with debug // info should have debug info, too @@ -682,7 +676,7 @@ LLConstant *IrClass::getInterfaceVtblInit(BaseClass *b, gIR->funcGenStates.pop_back(); } - constants.push_back(DtoBitCast(thunk, voidPtrTy)); + constants.push_back(thunk); } // build the vtbl constant @@ -734,8 +728,6 @@ LLConstant *IrClass::getClassInfoInterfaces() { LLSmallVector constants; constants.reserve(cd->vtblInterfaces->length); - LLType *classinfo_type = DtoType(getClassInfoType()); - LLType *voidptrptr_type = DtoType(pointerTo(pointerTo(Type::tvoid))); LLStructType *interface_type = isaStruct(DtoType(interfacesArrayType->nextOf())); assert(interface_type); @@ -752,16 +744,14 @@ LLConstant *IrClass::getClassInfoInterfaces() { // classinfo LLConstant *ci = irinter->getClassInfoSymbol(); - ci = DtoBitCast(ci, classinfo_type); // vtbl LLConstant *vtb; // interface get a null if (cd->isInterfaceDeclaration()) { - vtb = DtoConstSlice(DtoConstSize_t(0), getNullValue(voidptrptr_type)); + vtb = DtoConstSlice(DtoConstSize_t(0), getNullValue(getVoidPtrType())); } else { vtb = getInterfaceVtblSymbol(it, i); - vtb = DtoBitCast(vtb, voidptrptr_type); auto vtblSize = itc->getVtblType()->getNumContainedTypes(); vtb = DtoConstSlice(DtoConstSize_t(vtblSize), vtb); } diff --git a/ir/irfuncty.cpp b/ir/irfuncty.cpp index 1528ccb9bca..a7f8adb4112 100644 --- a/ir/irfuncty.cpp +++ b/ir/irfuncty.cpp @@ -22,10 +22,7 @@ using namespace dmd; IrFuncTyArg::IrFuncTyArg(Type *t, bool bref) : type(t), ltype(t != Type::tvoid && bref ? DtoType(pointerTo(t)) : DtoType(t)), -#if LDC_LLVM_VER >= 1400 - attrs(getGlobalContext()), -#endif - byref(bref) { + attrs(getGlobalContext()), byref(bref) { mem.addRange(&type, sizeof(type)); } diff --git a/ir/irvar.cpp b/ir/irvar.cpp index 29739595714..fda8c5fd96d 100644 --- a/ir/irvar.cpp +++ b/ir/irvar.cpp @@ -139,7 +139,8 @@ void IrGlobal::define() { // Set the initializer, swapping out the variable if the types do not // match. auto gvar = llvm::cast(value); - value = gIR->setGlobalVarInitializer(gvar, initVal, V); + gvar = gIR->setGlobalVarInitializer(gvar, initVal, V); + value = gvar; // dllexport isn't supported for thread-local globals (MSVC++ neither); // don't let LLVM create a useless /EXPORT directive (yields the same linker diff --git a/ldc2.conf.in b/ldc2.conf.in index 1ec4ce854c4..0ef4caa07a2 100644 --- a/ldc2.conf.in +++ b/ldc2.conf.in @@ -28,7 +28,7 @@ default: ]; // default directories to be searched for libraries when linking lib-dirs = [ - "@CMAKE_BINARY_DIR@/lib@LIB_SUFFIX@", + "@CMAKE_BINARY_DIR@/lib@LIB_SUFFIX@",@OPTIONAL_COMPILER_RT_DIR@ ]; // default rpath when linking against the shared default libs rpath = "@SHARED_LIBS_RPATH@"; diff --git a/ldc2_install.conf.in b/ldc2_install.conf.in index 7536f8f0d4f..f8afa42613c 100644 --- a/ldc2_install.conf.in +++ b/ldc2_install.conf.in @@ -26,7 +26,7 @@ default: ]; // default directories to be searched for libraries when linking lib-dirs = [ - "@CMAKE_INSTALL_LIBDIR@", + "@CMAKE_INSTALL_LIBDIR@",@OPTIONAL_COMPILER_RT_DIR@ ]; // default rpath when linking against the shared default libs rpath = "@SHARED_LIBS_INSTALL_RPATH@"; diff --git a/ldc2_phobos.conf.in b/ldc2_phobos.conf.in index 9bc5b1aa484..f2c00634f16 100644 --- a/ldc2_phobos.conf.in +++ b/ldc2_phobos.conf.in @@ -29,7 +29,7 @@ default: ]; // default directories to be searched for libraries when linking lib-dirs = [ - "@CMAKE_BINARY_DIR@/lib@LIB_SUFFIX@", + "@CMAKE_BINARY_DIR@/lib@LIB_SUFFIX@",@OPTIONAL_COMPILER_RT_DIR@ ]; // default rpath when linking against the shared default libs rpath = "@SHARED_LIBS_RPATH@"; diff --git a/runtime/druntime/src/core/thread/fiber.d b/runtime/druntime/src/core/thread/fiber.d index 68ac62f7730..1f2ed97b634 100644 --- a/runtime/druntime/src/core/thread/fiber.d +++ b/runtime/druntime/src/core/thread/fiber.d @@ -237,6 +237,8 @@ private extern (C) void fiber_switchContext( void** oldp, void* newp ) nothrow @nogc; version (AArch64) extern (C) void fiber_trampoline() nothrow; + version (LoongArch64) + extern (C) void fiber_trampoline() nothrow; } else version (LDC_Windows) { @@ -1877,7 +1879,6 @@ private: // Like others, FP registers and return address ($r1) are kept // below the saved stack top (tstack) to hide from GC scanning. // fiber_switchContext expects newp sp to look like this: - // 10: $r21 (reserved) // 9: $r22 (frame pointer) // 8: $r23 // ... @@ -1893,8 +1894,8 @@ private: // Only need to set return address ($r1). Everything else is fine // zero initialized. - pstack -= size_t.sizeof * 11; // skip past space reserved for $r21-$r31 - push (cast(size_t) &fiber_entryPoint); + pstack -= size_t.sizeof * 10; // skip past space reserved for $r22-$r31 + push(cast(size_t) &fiber_trampoline); // see threadasm.S for docs pstack += size_t.sizeof; // adjust sp (newp) above lr } else version (AsmAArch64_Posix) diff --git a/runtime/druntime/src/core/threadasm.S b/runtime/druntime/src/core/threadasm.S index 762fd3fbd32..77d00674974 100644 --- a/runtime/druntime/src/core/threadasm.S +++ b/runtime/druntime/src/core/threadasm.S @@ -616,9 +616,10 @@ fiber_switchContext: */ .text .globl fiber_switchContext +.type fiber_switchContext, %function fiber_switchContext: .cfi_startproc - .cfi_undefined $ra + .cfi_undefined 1 # reserve space on stack addi.d $sp, $sp, -19 * 8 @@ -682,6 +683,28 @@ fiber_switchContext: jr $ra .cfi_endproc + +/** + * When generating any kind of backtrace (gdb, exception handling) for + * a function called in a Fiber, we need to tell the unwinder to stop + * at our Fiber main entry point, i.e. we need to mark the bottom of + * the call stack. This can be done by clearing the return address register $ra + * prior to calling fiber_entryPoint (i.e. in fiber_switchContext) or + * using a .cfi_undefined directive for the return address register in the + * Fiber entry point. cfi_undefined seems to yield better results in gdb. + * Unfortunately we can't place it into fiber_entryPoint using inline + * asm, so we use this trampoline instead. + */ + .text + .global CSYM(fiber_trampoline) + .p2align 2 + .type fiber_trampoline, %function +CSYM(fiber_trampoline): + .cfi_startproc + .cfi_undefined 1 + // fiber_entryPoint never returns + bl CSYM(fiber_entryPoint) + .cfi_endproc #elif defined(__arm__) && (defined(__ARM_EABI__) || defined(__APPLE__)) /************************************************************************************ * ARM ASM BITS diff --git a/runtime/druntime/src/ldc/intrinsics.di b/runtime/druntime/src/ldc/intrinsics.di index 5cfecca5c51..485ffc8c473 100644 --- a/runtime/druntime/src/ldc/intrinsics.di +++ b/runtime/druntime/src/ldc/intrinsics.di @@ -19,12 +19,7 @@ else static assert(false, "This module is only valid for LDC"); } - version (LDC_LLVM_1100) enum LLVM_version = 1100; -else version (LDC_LLVM_1101) enum LLVM_version = 1101; -else version (LDC_LLVM_1200) enum LLVM_version = 1200; -else version (LDC_LLVM_1300) enum LLVM_version = 1300; -else version (LDC_LLVM_1400) enum LLVM_version = 1400; -else version (LDC_LLVM_1500) enum LLVM_version = 1500; + version (LDC_LLVM_1500) enum LLVM_version = 1500; else version (LDC_LLVM_1600) enum LLVM_version = 1600; else version (LDC_LLVM_1700) enum LLVM_version = 1700; else version (LDC_LLVM_1800) enum LLVM_version = 1800; @@ -40,11 +35,6 @@ enum LLVM_atleast(int major) = (LLVM_version >= major * 100); nothrow: @nogc: -version(LDC_LLVM_OpaquePointers) - private enum p0i8 = "p0"; -else - private enum p0i8 = "p0i8"; - // // CODE GENERATOR INTRINSICS // @@ -57,7 +47,7 @@ pragma(LDC_intrinsic, "llvm.returnaddress") /// The 'llvm.frameaddress' intrinsic attempts to return the target-specific /// frame pointer value for the specified stack frame. -pragma(LDC_intrinsic, "llvm.frameaddress."~p0i8) +pragma(LDC_intrinsic, "llvm.frameaddress.p0") void* llvm_frameaddress(uint level); /// The 'llvm.stacksave' intrinsic is used to remember the current state of the @@ -84,7 +74,7 @@ pragma(LDC_intrinsic, "llvm.stackrestore") /// keep in cache. The cache type specifies whether the prefetch is performed on /// the data (1) or instruction (0) cache. The rw, locality and cache type /// arguments must be constant integers. -pragma(LDC_intrinsic, "llvm.prefetch."~p0i8) +pragma(LDC_intrinsic, "llvm.prefetch.p0") void llvm_prefetch(const(void)* ptr, uint rw, uint locality, uint cachetype) pure @safe; /// The 'llvm.pcmarker' intrinsic is a method to export a Program Counter (PC) @@ -148,7 +138,7 @@ pure: /// location to the destination location. /// Note that, unlike the standard libc function, the llvm.memcpy.* intrinsics do /// not return a value. -pragma(LDC_intrinsic, "llvm.memcpy."~p0i8~"."~p0i8~".i#") +pragma(LDC_intrinsic, "llvm.memcpy.p0.p0.i#") void llvm_memcpy(T)(void* dst, const(void)* src, T len, bool volatile_ = false) if (__traits(isIntegral, T)); @@ -158,7 +148,7 @@ pragma(LDC_intrinsic, "llvm.memcpy."~p0i8~"."~p0i8~".i#") /// intrinsic but allows the two memory locations to overlap. /// Note that, unlike the standard libc function, the llvm.memmove.* intrinsics /// do not return a value. -pragma(LDC_intrinsic, "llvm.memmove."~p0i8~"."~p0i8~".i#") +pragma(LDC_intrinsic, "llvm.memmove.p0.p0.i#") void llvm_memmove(T)(void* dst, const(void)* src, T len, bool volatile_ = false) if (__traits(isIntegral, T)); @@ -166,7 +156,7 @@ pragma(LDC_intrinsic, "llvm.memmove."~p0i8~"."~p0i8~".i#") /// value. /// Note that, unlike the standard libc function, the llvm.memset intrinsic does /// not return a value. -pragma(LDC_intrinsic, "llvm.memset."~p0i8~".i#") +pragma(LDC_intrinsic, "llvm.memset.p0.i#") void llvm_memset(T)(void* dst, ubyte val, T len, bool volatile_ = false) if (__traits(isIntegral, T)); diff --git a/runtime/druntime/src/ldc/profile.di b/runtime/druntime/src/ldc/profile.di index 57de7baf9d8..816b94f8c37 100644 --- a/runtime/druntime/src/ldc/profile.di +++ b/runtime/druntime/src/ldc/profile.di @@ -30,24 +30,17 @@ nothrow: extern(C++) struct ProfileData { ulong NameRef; ulong FuncHash; - static if (LLVM_version >= 1400) + private void* RelativeCounters; + inout(ulong)* Counters()() inout @property @trusted pure @nogc nothrow { - private void* RelativeCounters; - inout(ulong)* Counters()() inout @property @trusted pure @nogc nothrow + version (Win64) { - version (Win64) - { - // RelativeCounters apparenly needs to be treated as signed 32-bit offset?! - assert((cast(size_t) RelativeCounters) >> 32 == 0); - return cast(inout(ulong)*) ((cast(size_t) &this) + cast(int) RelativeCounters); - } - else - return cast(inout(ulong)*) ((cast(size_t) &this) + cast(size_t) RelativeCounters); + // RelativeCounters apparenly needs to be treated as signed 32-bit offset?! + assert((cast(size_t) RelativeCounters) >> 32 == 0); + return cast(inout(ulong)*) ((cast(size_t) &this) + cast(int) RelativeCounters); } - } - else - { - ulong* Counters; + else + return cast(inout(ulong)*) ((cast(size_t) &this) + cast(size_t) RelativeCounters); } static if (LLVM_version >= 1800) void* BitmapPtr; diff --git a/runtime/druntime/src/rt/lifetime.d b/runtime/druntime/src/rt/lifetime.d index c51ff93e799..f0d6453db69 100644 --- a/runtime/druntime/src/rt/lifetime.d +++ b/runtime/druntime/src/rt/lifetime.d @@ -2594,6 +2594,12 @@ unittest // test bug 14126 unittest { + version (D_Optimized) enum isOptimized = true; + else enum isOptimized = false; + + // LDC: disable with -O for LLVM < 17, see https://github.com/ldc-developers/ldc/issues/4538 + static if (!isOptimized || imported!"ldc.intrinsics".LLVM_version >= 1700) + { static struct S { S* thisptr; @@ -2605,4 +2611,5 @@ unittest { s.thisptr = &s; } + } } diff --git a/runtime/jit-rt/cpp-so/disassembler.cpp b/runtime/jit-rt/cpp-so/disassembler.cpp index 5375b7f56e5..ca91520faae 100644 --- a/runtime/jit-rt/cpp-so/disassembler.cpp +++ b/runtime/jit-rt/cpp-so/disassembler.cpp @@ -32,11 +32,7 @@ #include "llvm/MC/MCSubtargetInfo.h" #include "llvm/Object/ObjectFile.h" #include "llvm/Support/Error.h" -#if LDC_LLVM_VER >= 1400 #include "llvm/MC/TargetRegistry.h" -#else -#include "llvm/Support/TargetRegistry.h" -#endif #include "llvm/Target/TargetMachine.h" namespace { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index fc988f59560..39be385f5d6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,6 +1,4 @@ set( LDC2_BIN ${PROJECT_BINARY_DIR}/bin/${LDC_EXE} ) -set( LDCPROFDATA_BIN ${PROJECT_BINARY_DIR}/bin/ldc-profdata ) -set( LDCPROFGEN_BIN ${PROJECT_BINARY_DIR}/bin/ldc-profgen ) set( LDCPRUNECACHE_BIN ${PROJECT_BINARY_DIR}/bin/${LDCPRUNECACHE_EXE} ) set( LDCBUILDPLUGIN_BIN ${PROJECT_BINARY_DIR}/bin/${LDC_BUILD_PLUGIN_EXE} ) set( TIMETRACE2TXT_BIN ${PROJECT_BINARY_DIR}/bin/${TIMETRACE2TXT_EXE} ) @@ -13,6 +11,13 @@ if(WIN32) set(PYTHON_EXE python) endif() +set(TEST_COMPILER_RT_LIBRARIES "all" CACHE STRING "List of compiler-rt libraries to test, separated by ';'. Can be set to 'all' and 'none'") +if(TEST_COMPILER_RT_LIBRARIES STREQUAL "all") + set(TEST_COMPILER_RT_LIBRARIES "profile;xray;lsan;tsan;asan;msan;fuzzer") +elseif(TEST_COMPILER_RT_LIBRARIES STREQUAL "none") + set(TEST_COMPILER_RT_LIBRARIES "") +endif() + if(CMAKE_SIZEOF_VOID_P EQUAL 8) set( DEFAULT_TARGET_BITS 64 ) else() @@ -25,4 +30,3 @@ configure_file(runlit.py runlit.py COPYONLY) add_test(NAME lit-tests COMMAND ${PYTHON_EXE} runlit.py -v . ) - diff --git a/tests/PGO/indirect_calls.d b/tests/PGO/indirect_calls.d index 2e2319c3804..1104eb3fd53 100644 --- a/tests/PGO/indirect_calls.d +++ b/tests/PGO/indirect_calls.d @@ -3,7 +3,7 @@ // REQUIRES: PGO_RT // FIXME: fails with LLVM 13+, call remains indirect -// XFAIL: atleast_llvm1300 +// XFAIL: * // RUN: %ldc -c -output-ll -fprofile-instr-generate -of=%t.ll %s && FileCheck %s --check-prefix=PROFGEN < %t.ll diff --git a/tests/PGO/irbased_indirect_calls.d b/tests/PGO/irbased_indirect_calls.d index 3fa394dbe65..ed0ddf46b8e 100644 --- a/tests/PGO/irbased_indirect_calls.d +++ b/tests/PGO/irbased_indirect_calls.d @@ -3,7 +3,7 @@ // REQUIRES: PGO_RT // FIXME: fails with LLVM 13+ for Windows, call remains indirect -// XFAIL: Windows && atleast_llvm1300 +// XFAIL: Windows // RUN: %ldc -O3 -fprofile-generate=%t.profraw -run %s \ // RUN: && %profdata merge %t.profraw -o %t.profdata \ @@ -50,8 +50,8 @@ int main() { select_func(i); - // PROFUSE: [[REG1:%[0-9]+]] = load {{void \(\)\*, void \(\)\*\*|ptr, ptr}} @foo - // PROFUSE: [[REG2:%[0-9]+]] = icmp eq {{void \(\)\*|ptr}} [[REG1]], @hot + // PROFUSE: [[REG1:%[0-9]+]] = load ptr, ptr @foo + // PROFUSE: [[REG2:%[0-9]+]] = icmp eq ptr [[REG1]], @hot // PROFUSE: call void @hot() // PROFUSE: call void [[REG1]]() diff --git a/tests/PGO/lit.local.cfg b/tests/PGO/lit.local.cfg index d63318065a8..466a9bad6da 100644 --- a/tests/PGO/lit.local.cfg +++ b/tests/PGO/lit.local.cfg @@ -1,5 +1,3 @@ -from glob import glob - -# Add "PGO_RT" feature, if the `profile` compiler-rt library is available -if glob(os.path.join(config.ldc2_lib_dir, "*profile*")): +# Add "PGO_RT" feature +if 'profile' in config.enabled_rt_libs: config.available_features.add('PGO_RT') diff --git a/tests/PGO/sample_based.d b/tests/PGO/sample_based.d index 599a8c4df78..700a93fbd83 100644 --- a/tests/PGO/sample_based.d +++ b/tests/PGO/sample_based.d @@ -1,7 +1,5 @@ // Test basic use of sample-based PGO profile -// REQUIRES: atleast_llvm1500 - // RUN: split-file %s %t // RUN: %ldc -O2 -c -gline-tables-only -output-ll -of=%t.ll -fprofile-sample-use=%t/pgo-sample.prof %t/testcase.d && FileCheck %s < %t.ll diff --git a/tests/codegen/align.d b/tests/codegen/align.d index 71c5da07792..9e338a30763 100644 --- a/tests/codegen/align.d +++ b/tests/codegen/align.d @@ -18,14 +18,14 @@ static Inner globalInner; Outer passAndReturnOuterByVal(Outer arg) { return arg; } // CHECK: define{{.*}} void @{{.*}}_D5align23passAndReturnOuterByValFSQBh5OuterZQl /* the 32-bit x86 ABI substitutes the sret attribute by inreg */ -// CHECK-SAME: {{%align.Outer\*|ptr}} {{noalias sret.*|inreg noalias}} align 32 %.sret_arg +// CHECK-SAME: ptr {{noalias sret.*|inreg noalias}} align 32 %.sret_arg /* How the arg is passed by value is ABI-specific, but the pointer must be aligned. * When the argument is passed as a byte array and copied into a stack alloc, that stack alloca must be aligned. */ // CHECK: {{(align 32 %arg|%arg = alloca %align.Outer, align 32)}} Inner passAndReturnInnerByVal(Inner arg) { return arg; } // CHECK: define{{.*}} void @{{.*}}_D5align23passAndReturnInnerByValFSQBh5InnerZQl -// CHECK-SAME: {{%align.Inner\*|ptr}} {{noalias sret.*|inreg noalias}} align 32 %.sret_arg +// CHECK-SAME: ptr {{noalias sret.*|inreg noalias}} align 32 %.sret_arg // CHECK: {{(align 32 %arg|%arg = alloca %align.Inner, align 32)}} void main() { @@ -59,13 +59,13 @@ void main() { outer = passAndReturnOuterByVal(outer); // CHECK: call{{.*}} void @{{.*}}_D5align23passAndReturnOuterByValFSQBh5OuterZQl - // CHECK-SAME: {{%align.Outer\*|ptr}} {{noalias sret.*|inreg noalias}} align 32 %.sret_tmp + // CHECK-SAME: ptr {{noalias sret.*|inreg noalias}} align 32 %.sret_tmp // The argument is either passed by aligned (optimizer hint) pointer or as an array of i32/64 and copied into an aligned stack slot inside the callee. // CHECK-SAME: {{(align 32 %|\[[0-9]+ x i..\])}} inner = passAndReturnInnerByVal(inner); // CHECK: call{{.*}} void @{{.*}}_D5align23passAndReturnInnerByValFSQBh5InnerZQl - // CHECK-SAME: {{%align.Inner\*|ptr}} {{noalias sret.*|inreg noalias}} align 32 %.sret_tmp + // CHECK-SAME: ptr {{noalias sret.*|inreg noalias}} align 32 %.sret_tmp // The argument is either passed by aligned (optimizer hint) pointer or as an array of i32/64 and copied into an aligned stack slot inside the callee. // CHECK-SAME: {{(align 32 %|\[[0-9]+ x i..\])}} } diff --git a/tests/codegen/align_class.d b/tests/codegen/align_class.d index 6de4d449e37..66c21184ad4 100644 --- a/tests/codegen/align_class.d +++ b/tests/codegen/align_class.d @@ -4,14 +4,14 @@ struct S16 { align(16) short a; } class D { align(32) short d = 0xDD; } -// CHECK: %align_class.D = type <{ {{\[5 x i8\*\]\*, i8\*|ptr, ptr}}, [{{(16|24)}} x i8], i16 }> +// CHECK: %align_class.D = type <{ ptr, ptr, [{{(16|24)}} x i8], i16 }> class E : D { S16 s16 = S16(0xEE); } -// CHECK: %align_class.E = type { {{\[5 x i8\*\]\*, i8\*|ptr, ptr}}, [{{(16|24)}} x i8], i16, [14 x i8], %align_class.S16 } +// CHECK: %align_class.E = type { ptr, ptr, [{{(16|24)}} x i8], i16, [14 x i8], %align_class.S16 } class F : D { align(64) short f = 0xFF; } -// CHECK: %align_class.F = type <{ {{\[5 x i8\*\]\*, i8\*|ptr, ptr}}, [{{(16|24)}} x i8], i16, [30 x i8], i16 }> +// CHECK: %align_class.F = type <{ ptr, ptr, [{{(16|24)}} x i8], i16, [30 x i8], i16 }> extern(C++) class CppClass { align(32) short a = 0xAA; } -// CHECK: %align_class.CppClass = type <{ {{\[0 x i8\*\]\*|ptr}}, [{{(24|28)}} x i8], i16 }> +// CHECK: %align_class.CppClass = type <{ ptr, [{{(24|28)}} x i8], i16 }> void main() { diff --git a/tests/codegen/array_alloc_gh3041.d b/tests/codegen/array_alloc_gh3041.d index 75d74855b8b..ac1d6f46e3f 100644 --- a/tests/codegen/array_alloc_gh3041.d +++ b/tests/codegen/array_alloc_gh3041.d @@ -5,7 +5,7 @@ void[] foo() { // CHECK-NEXT: %.gc_mem = call {{.*}} @_d_newarrayT // CHECK-NEXT: %.ptr = extractvalue {{.*}} %.gc_mem, 1 - // CHECK-NEXT: %1 = insertvalue {{.*}} { i{{32|64}} 3, {{i8\*|ptr}} undef }, {{i8\*|ptr}} %.ptr, 1 + // CHECK-NEXT: %1 = insertvalue {{.*}} { i{{32|64}} 3, ptr undef }, ptr %.ptr, 1 // CHECK-NEXT: ret {{.*}} %1 return new void[3]; } diff --git a/tests/codegen/asm_gcc.d b/tests/codegen/asm_gcc.d index d18552a2f0b..2fd08b7a1b3 100644 --- a/tests/codegen/asm_gcc.d +++ b/tests/codegen/asm_gcc.d @@ -8,7 +8,7 @@ void cpuid() { uint max_extended_cpuid; // CHECK: %1 = call i32 asm sideeffect "cpuid", "={eax},{eax},~{ebx},~{ecx},~{edx}"(i32 -2147483648), !srcloc - // CHECK-NEXT: store i32 %1, {{i32\*|ptr}} %max_extended_cpuid + // CHECK-NEXT: store i32 %1, ptr %max_extended_cpuid asm { "cpuid" : "=eax" (max_extended_cpuid) : "eax" (0x8000_0000) : "ebx", "ecx", "edx"; } } @@ -23,13 +23,13 @@ void multipleOutput() // CHECK-NEXT: %4 = getelementptr {{.*}} %r, i32 0, i64 3 // CHECK-NEXT: %5 = call { i32, i32, i32, i32 } asm sideeffect "cpuid", "={eax},={ebx},={ecx},={edx},{eax}"(i32 2), !srcloc // CHECK-NEXT: %6 = extractvalue { i32, i32, i32, i32 } %5, 0 - // CHECK-NEXT: store i32 %6, {{i32\*|ptr}} %1 + // CHECK-NEXT: store i32 %6, ptr %1 // CHECK-NEXT: %7 = extractvalue { i32, i32, i32, i32 } %5, 1 - // CHECK-NEXT: store i32 %7, {{i32\*|ptr}} %2 + // CHECK-NEXT: store i32 %7, ptr %2 // CHECK-NEXT: %8 = extractvalue { i32, i32, i32, i32 } %5, 2 - // CHECK-NEXT: store i32 %8, {{i32\*|ptr}} %3 + // CHECK-NEXT: store i32 %8, ptr %3 // CHECK-NEXT: %9 = extractvalue { i32, i32, i32, i32 } %5, 3 - // CHECK-NEXT: store i32 %9, {{i32\*|ptr}} %4 + // CHECK-NEXT: store i32 %9, ptr %4 asm { "cpuid" : "=eax" (r[0]), "=ebx" (r[1]), "=ecx" (r[2]), "=edx" (r[3]) : "eax" (2); } } @@ -38,11 +38,11 @@ void indirectOutput(uint eax) { // CHECK-NEXT: %eax = alloca i32 // CHECK-NEXT: %r = alloca [4 x i32] - // CHECK-NEXT: store i32 %eax_arg, {{i32\*|ptr}} %eax + // CHECK-NEXT: store i32 %eax_arg, ptr %eax uint[4] r = void; - // CHECK-NEXT: %1 = load i32, {{i32\*|ptr}} %eax + // CHECK-NEXT: %1 = load i32, ptr %eax // CHECK-NEXT: call void asm sideeffect "cpuid - // CHECK-SAME: "=*m,{eax},~{eax},~{ebx},~{ecx},~{edx}"({{\[4 x i32\]\*|ptr}} {{(elementtype\(\[4 x i32\]\) )?}}%r, i32 %1), !srcloc + // CHECK-SAME: "=*m,{eax},~{eax},~{ebx},~{ecx},~{edx}"(ptr elementtype([4 x i32]) %r, i32 %1), !srcloc asm { `cpuid @@ -60,8 +60,8 @@ void indirectOutput(uint eax) void indirectInput(uint eax) { // CHECK-NEXT: %eax = alloca i32 - // CHECK-NEXT: store i32 %eax_arg, {{i32\*|ptr}} %eax - // CHECK-NEXT: call void asm sideeffect "movl %eax, $0", "*m,~{eax}"({{i32\*|ptr}} {{(elementtype\(i32\) )?}}%eax), !srcloc + // CHECK-NEXT: store i32 %eax_arg, ptr %eax + // CHECK-NEXT: call void asm sideeffect "movl %eax, $0", "*m,~{eax}"(ptr elementtype(i32) %eax), !srcloc asm { "movl %%eax, %0" : : "m" (eax) : "eax"; } } diff --git a/tests/codegen/assign_struct_init_without_stack.d b/tests/codegen/assign_struct_init_without_stack.d index 8f162fead5e..24f0d17ba89 100644 --- a/tests/codegen/assign_struct_init_without_stack.d +++ b/tests/codegen/assign_struct_init_without_stack.d @@ -83,7 +83,7 @@ void tupleassignByVal() // CHECK: alloca %assign_struct_init_without_stack.OpAssignStruct // CHECK: call void @llvm.memcpy // CHECK-NOT: memcpy - // CHECK: call{{.*}} {{%assign_struct_init_without_stack\.OpAssignStruct\*|ptr}} @{{.*}}_D32assign_struct_init_without_stack14OpAssignStruct__T8opAssignTSQCmQBhZQsMFNaNbNcNiNjNfQyZQBb + // CHECK: call{{.*}} ptr @{{.*}}_D32assign_struct_init_without_stack14OpAssignStruct__T8opAssignTSQCmQBhZQsMFNaNbNcNiNjNfQyZQBb // CHECK-NEXT: ret void } @@ -93,7 +93,7 @@ void tupleassignByRef() globalOpAssignStruct = globalOpAssignStruct2; // There should not be a memcpy. // CHECK-NOT: memcpy - // CHECK: call{{.*}} {{%assign_struct_init_without_stack\.OpAssignStruct\*|ptr}} @{{.*}}_D32assign_struct_init_without_stack14OpAssignStruct__T8opAssignTSQCmQBhZQsMFNaNbNcNiNjNfKQzZQBc + // CHECK: call{{.*}} ptr @{{.*}}_D32assign_struct_init_without_stack14OpAssignStruct__T8opAssignTSQCmQBhZQsMFNaNbNcNiNjNfKQzZQBc // CHECK-NEXT: ret void } diff --git a/tests/codegen/atomicrmw.d b/tests/codegen/atomicrmw.d index c5a77c94bd5..f48de68b860 100644 --- a/tests/codegen/atomicrmw.d +++ b/tests/codegen/atomicrmw.d @@ -8,33 +8,33 @@ void main() { r = atomicOp!"+="(x, uint(257)); assert(x == r); - // CHECK: = atomicrmw add {{i8\*|ptr}} + // CHECK: = atomicrmw add ptr r = atomicOp!"+="(x, int(-263)); assert(x == r); - // CHECK: = atomicrmw add {{i8\*|ptr}} + // CHECK: = atomicrmw add ptr r = atomicOp!"-="(x, ushort(257)); assert(x == r); - // CHECK: = atomicrmw sub {{i8\*|ptr}} + // CHECK: = atomicrmw sub ptr r = atomicOp!"-="(x, short(-263)); assert(x == r); - // CHECK: = atomicrmw sub {{i8\*|ptr}} + // CHECK: = atomicrmw sub ptr r = atomicOp!"&="(x, ubyte(255)); assert(x == r); - // CHECK: = atomicrmw and {{i8\*|ptr}} + // CHECK: = atomicrmw and ptr r = atomicOp!"|="(x, short(3)); assert(x == r); - // CHECK: = atomicrmw or {{i8\*|ptr}} + // CHECK: = atomicrmw or ptr r = atomicOp!"^="(x, int(3)); assert(x == r); - // CHECK: = atomicrmw xor {{i8\*|ptr}} + // CHECK: = atomicrmw xor ptr r = atomicOp!"+="(x, 1.0f); assert(x == r); - // CHECK: = cmpxchg weak {{i8\*|ptr}} + // CHECK: = cmpxchg weak ptr } diff --git a/tests/codegen/attr_param.d b/tests/codegen/attr_param.d index bf9a55dc954..c01fe950731 100644 --- a/tests/codegen/attr_param.d +++ b/tests/codegen/attr_param.d @@ -3,14 +3,14 @@ import ldc.attributes; // CHECK: define{{.*}} @{{.*}}3foo -// CHECK-SAME: {{i8\*|ptr}}{{.*}} noalias %p_arg +// CHECK-SAME: ptr{{.*}} noalias %p_arg void foo(@llvmAttr("noalias") void* p) {} // CHECK: define{{.*}} @{{.*}}3bar -// CHECK-SAME: {{float\*|ptr}}{{.*}} noalias %data_arg -// CHECK-SAME: {{\[16 x float\]\*|ptr}}{{.*}} noalias dereferenceable(64) %kernel +// CHECK-SAME: ptr{{.*}} noalias %data_arg +// CHECK-SAME: ptr{{.*}} noalias dereferenceable(64) %kernel void bar(@restrict float* data, @restrict ref const float[16] kernel) {} // CHECK: define{{.*}} @{{.*}}14classReference -// CHECK-SAME: {{%object.Object\*|ptr}}{{.*}} noalias %obj_arg +// CHECK-SAME: ptr{{.*}} noalias %obj_arg void classReference(@restrict Object obj) {} diff --git a/tests/codegen/casm.c b/tests/codegen/casm.c deleted file mode 100644 index 5a34f6d00b0..00000000000 --- a/tests/codegen/casm.c +++ /dev/null @@ -1,6 +0,0 @@ -// REQUIRES: target_X86 -// UNSUPPORTED: Windows -// RUN: %ldc -mtriple=x86_64-freebsd13 -output-ll -of=%t.ll %s && FileCheck %s < %t.ll - -// CHECK: module asm ".symver __qsort_r_compat, qsort_r@FBSD_1.0" -__asm__(".symver " "__qsort_r_compat" ", " "qsort_r" "@" "FBSD_1.0"); diff --git a/tests/codegen/casm.i b/tests/codegen/casm.i new file mode 100644 index 00000000000..33b0be8ee73 --- /dev/null +++ b/tests/codegen/casm.i @@ -0,0 +1,4 @@ +// RUN: %ldc -output-ll -of=%t.ll %s && FileCheck %s < %t.ll + +// CHECK: module asm ".symver __qsort_r_compat, qsort_r@FBSD_1.0" +asm(".symver " "__qsort_r_compat" ", " "qsort_r" "@" "FBSD_1.0"); diff --git a/tests/codegen/cmpxchg.d b/tests/codegen/cmpxchg.d index ce51270db81..2858a365b54 100644 --- a/tests/codegen/cmpxchg.d +++ b/tests/codegen/cmpxchg.d @@ -7,7 +7,7 @@ bool foo(int cmp) { static shared int g; // CHECK-NOT: ret - // CHECK: [[FOO1:%[0-9]]] = cmpxchg {{i32\*|ptr}} + // CHECK: [[FOO1:%[0-9]]] = cmpxchg ptr // CHECK-NEXT: [[FOO2:%[0-9]]] = extractvalue { i32, i1 } [[FOO1]], 1 // CHECK-NEXT: ret i1 [[FOO2]] return cas(&g, cmp, 456); @@ -19,7 +19,7 @@ double bar(double cmp) static shared double g; // CHECK-NOT: ret // CHECK: [[BAR1:%[0-9]]] = bitcast double %cmp_arg to i64 - // CHECK-NEXT: [[BAR2:%[0-9]]] = cmpxchg weak {{i64\*|ptr}} + // CHECK-NEXT: [[BAR2:%[0-9]]] = cmpxchg weak ptr casWeak(&g, &cmp, 456.0); // CHECK-NEXT: [[BAR3:%[0-9]]] = extractvalue { i64, i1 } [[BAR2]], 0 // CHECK-NEXT: [[BAR4:%[0-9]]] = bitcast i64 [[BAR3]] to double diff --git a/tests/codegen/const_cond.d b/tests/codegen/const_cond.d index 301b684532a..670afd393bf 100644 --- a/tests/codegen/const_cond.d +++ b/tests/codegen/const_cond.d @@ -18,8 +18,8 @@ void foo() // CHECK-NOT: %a = alloca // CHECK: %b = alloca // CHECK-NOT: br - // CHECK-NOT: store i32 1, {{i32\*|ptr}} %a - // CHECK: store i32 2, {{i32\*|ptr}} %b + // CHECK-NOT: store i32 1, ptr %a + // CHECK: store i32 2, ptr %b if (0) { int a = 1; @@ -34,7 +34,7 @@ void foo() void bar() { // CHECK-NOT: %a = alloca - // CHECK: store i32 2, {{i32\*|ptr}} %b + // CHECK: store i32 2, ptr %b if (0) { int a = 1; diff --git a/tests/codegen/const_cond_labels.d b/tests/codegen/const_cond_labels.d index 110bdd29cf3..f8a1caf2791 100644 --- a/tests/codegen/const_cond_labels.d +++ b/tests/codegen/const_cond_labels.d @@ -45,7 +45,7 @@ void bar(int a, int b) case 10: while (b) { L2: - // CHECK: store i32 10, {{i32\*|ptr}} %c + // CHECK: store i32 10, ptr %c c = 10; } default: assert(0); @@ -67,7 +67,7 @@ void without_goto(int a, int b) case 10: while (b) { L2: - // CHECK: store i32 10, {{i32\*|ptr}} %c + // CHECK: store i32 10, ptr %c c = 10; } default: assert(0); @@ -96,7 +96,7 @@ void fourth(int a, int b, int c) L3: foreach (i; 0..b) { - // CHECK: store i32 10, {{i32\*|ptr}} %d + // CHECK: store i32 10, ptr %d d = 10; } } @@ -112,11 +112,11 @@ void fourth(int a, int b, int c) void case_as_label(int a, int b) { // Note the `CHECK-NOT` trickery. - // CHECK-NOT: store i32 2, {{i32\*|ptr}} %c - // CHECK: store i32 3, {{i32\*|ptr}} %c - // CHECK-NOT: store i32 2, {{i32\*|ptr}} %c - // CHECK: store i32 4, {{i32\*|ptr}} %c - // CHECK-NOT: store i32 2, {{i32\*|ptr}} %c + // CHECK-NOT: store i32 2, ptr %c + // CHECK: store i32 3, ptr %c + // CHECK-NOT: store i32 2, ptr %c + // CHECK: store i32 4, ptr %c + // CHECK-NOT: store i32 2, ptr %c int c; switch (a) { case 1: @@ -145,8 +145,8 @@ void case_as_label(int a, int b) // CHECK-LABEL: @case_as_label2 void case_as_label2(int a, int b) { - // CHECK: store i32 2, {{i32\*|ptr}} %c - // CHECK: store i32 3, {{i32\*|ptr}} %c + // CHECK: store i32 2, ptr %c + // CHECK: store i32 3, ptr %c int c; final switch (a) { // Can't elide diff --git a/tests/codegen/const_struct.d b/tests/codegen/const_struct.d index 394dc864e02..8fae204422c 100644 --- a/tests/codegen/const_struct.d +++ b/tests/codegen/const_struct.d @@ -19,10 +19,10 @@ void testNested() { // CHECK: @.immutablearray{{.*}} = internal constant [4 x i32] // CHECK: @.immutablearray{{.*}} = internal constant [2 x float] // CHECK: @.immutablearray{{.*}} = internal constant [2 x double] -// CHECK: @.immutablearray{{.*}} = internal constant [2 x { i{{32|64}}, {{i8\*|ptr}} }] +// CHECK: @.immutablearray{{.*}} = internal constant [2 x { i{{32|64}}, ptr }] // CHECK: @.immutablearray{{.*}} = internal constant [1 x %const_struct.S2] -// CHECK: @.immutablearray{{.*}} = internal constant [2 x {{i32\*|ptr}}] {{.*}}globVar -// CHECK: @.immutablearray{{.*}} = internal constant [2 x {{void \(\)\*|ptr}}] {{.*}}Dmain +// CHECK: @.immutablearray{{.*}} = internal constant [2 x ptr] {{.*}}globVar +// CHECK: @.immutablearray{{.*}} = internal constant [2 x ptr] {{.*}}Dmain void main () { // Simple types @@ -34,7 +34,7 @@ void main () { // Complex type immutable S2[] aE = [ { [ { { 42 } }, { { 43 } }, { { 44 } } ] } ]; // Complex type with non-constant initializer - // CHECK: %.gc_mem = call { i{{32|64}}, {{i8\*|ptr}} } @_d_newarrayU + // CHECK: %.gc_mem = call { i{{32|64}}, ptr } @_d_newarrayU // CHECK-SAME: @{{.*}}_D29TypeInfo_yAS12const_struct2C06__initZ immutable C0[] aF = [ { new int(42) }, { new int(24) } ]; @@ -45,7 +45,7 @@ void main () { // Pointer arrays with non-const initializer immutable int localVar; immutable auto locA = [ &localVar, &localVar ]; - // CHECK: %.gc_mem{{.*}} = call { i{{32|64}}, {{i8\*|ptr}} } @_d_newarrayU + // CHECK: %.gc_mem{{.*}} = call { i{{32|64}}, ptr } @_d_newarrayU // CHECK-SAME: @{{.*}}_D13TypeInfo_yAPi6__initZ testNested(); diff --git a/tests/codegen/const_struct_export.d b/tests/codegen/const_struct_export.d index d5c68f2847a..322ad33a4be 100644 --- a/tests/codegen/const_struct_export.d +++ b/tests/codegen/const_struct_export.d @@ -1,9 +1,9 @@ // RUN: %ldc -c -output-ll -of=%t.ll %s && FileCheck %s < %t.ll -// CHECK: @.immutablearray{{.*}} = internal constant [2 x {{void \(\)\*|ptr}}] {{.*}}exportedFunction -// CHECK-NOT: @.immutablearray{{.*}} [2 x {{void \(\)\*|ptr}}] {{.*}}importedFunction -// CHECK: @.immutablearray{{.*}} = internal constant [2 x {{i32\*|ptr}}] {{.*}}exportedVariable -// CHECK-NOT: @.immutablearray{{.*}} [2 x {{i32\*|ptr}}] {{.*}}importedVariable +// CHECK: @.immutablearray{{.*}} = internal constant [2 x ptr] {{.*}}exportedFunction +// CHECK-NOT: @.immutablearray{{.*}} [2 x ptr] {{.*}}importedFunction +// CHECK: @.immutablearray{{.*}} = internal constant [2 x ptr] {{.*}}exportedVariable +// CHECK-NOT: @.immutablearray{{.*}} [2 x ptr] {{.*}}importedVariable export void exportedFunction() {} export void importedFunction(); @@ -13,8 +13,8 @@ export extern immutable int importedVariable; void foo () { immutable auto exportedFuncs = [ &exportedFunction, &exportedFunction ]; immutable auto importedFuncs = [ &importedFunction, &importedFunction ]; - // CHECK: store {{void \(\)\*|ptr}} @{{.*}}D19const_struct_export16importedFunctionFZv + // CHECK: store ptr @{{.*}}D19const_struct_export16importedFunctionFZv immutable auto exportedVars = [ &exportedVariable, &exportedVariable ]; immutable auto importedVars = [ &importedVariable, &importedVariable ]; - // CHECK: store {{i32\*|ptr}} @{{.*}}D19const_struct_export16importedVariable + // CHECK: store ptr @{{.*}}D19const_struct_export16importedVariable } diff --git a/tests/codegen/dcompute_cl_addrspaces_new.d b/tests/codegen/dcompute_cl_addrspaces.d similarity index 87% rename from tests/codegen/dcompute_cl_addrspaces_new.d rename to tests/codegen/dcompute_cl_addrspaces.d index ad28b905ff8..deff7e2f051 100644 --- a/tests/codegen/dcompute_cl_addrspaces_new.d +++ b/tests/codegen/dcompute_cl_addrspaces.d @@ -4,9 +4,9 @@ // UNSUPPORTED: atleast_llvm1800 && atmost_llvm1809 // REQUIRES: target_SPIRV && atleast_llvm1600 -// RUN: %ldc -c -opaque-pointers -mdcompute-targets=ocl-220 -m64 -mdcompute-file-prefix=addrspace_new -output-ll -output-o %s && FileCheck %s --check-prefix=LL < addrspace_new_ocl220_64.ll \ +// RUN: %ldc -c -mdcompute-targets=ocl-220 -m64 -mdcompute-file-prefix=addrspace_new -output-ll -output-o %s && FileCheck %s --check-prefix=LL < addrspace_new_ocl220_64.ll \ // RUN: && %llc addrspace_new_ocl220_64.ll -mtriple=spirv64-unknown-unknown -O0 -o - | FileCheck %s --check-prefix=SPT -@compute(CompileFor.deviceOnly) module dcompute_cl_addrspaces_new; +@compute(CompileFor.deviceOnly) module dcompute_cl_addrspaces; import ldc.dcompute; // LL: %"ldc.dcompute.Pointer!(AddrSpace.Private, float).Pointer" = type { ptr } diff --git a/tests/codegen/dcompute_cl_addrspaces_old.d b/tests/codegen/dcompute_cl_addrspaces_old.d deleted file mode 100644 index fc633c2c53e..00000000000 --- a/tests/codegen/dcompute_cl_addrspaces_old.d +++ /dev/null @@ -1,60 +0,0 @@ -// See GH issue #2709 - -// REQUIRES: target_SPIRV && atmost_llvm1509 -// RUN: %ldc -c -mdcompute-targets=ocl-220 -m64 -mdcompute-file-prefix=addrspace_old -output-ll -output-o %s && FileCheck %s --check-prefix=LL < addrspace_ocl220_64.ll \ -// RUN: && %llvm-spirv -to-text addrspace_old_ocl220_64.spv && FileCheck %s --check-prefix=SPT < addrspace_ocl220_64.spt -@compute(CompileFor.deviceOnly) module dcompute_cl_addrspaces_old; -import ldc.dcompute; - -// LL: %"ldc.dcompute.Pointer!(AddrSpace.Private, float).Pointer" = type { float* } -// LL: %"ldc.dcompute.Pointer!(AddrSpace.Global, float).Pointer" = type { float addrspace(1)* } -// LL: %"ldc.dcompute.Pointer!(AddrSpace.Shared, float).Pointer" = type { float addrspace(2)* } -// LL: %"ldc.dcompute.Pointer!(AddrSpace.Constant, immutable(float)).Pointer" = type { float addrspace(3)* } -// LL: %"ldc.dcompute.Pointer!(AddrSpace.Generic, float).Pointer" = type { float addrspace(4)* } - -// SPT-DAG: 2 TypeVoid [[VOID_ID:[0-9]+]] -// SPT-DAG: 3 TypeFloat [[FLOAT_ID:[0-9]+]] 32 - -//See section 3.7 of the SPIR-V Specification for the numbers in the 4th column. -// SPT-DAG: 4 TypePointer [[SHARED_FLOAT_POINTER_ID:[0-9]+]] 4 [[FLOAT_ID]] -// SPT-DAG: 4 TypePointer [[CONSTANT_FLOAT_POINTER_ID:[0-9]+]] 0 [[FLOAT_ID]] -// SPT-DAG: 4 TypePointer [[GLOBAL_FLOAT_POINTER_ID:[0-9]+]] 5 [[FLOAT_ID]] -// SPT-DAG: 4 TypePointer [[GENERIC_FLOAT_POINTER_ID:[0-9]+]] 8 [[FLOAT_ID]] -// SPT-DAG: 4 TypePointer [[PRIVATE_FLOAT_POINTER_ID:[0-9]+]] 7 [[FLOAT_ID]] - -//void function({ T addrspace(n)* }) -// SPT-DAG: 4 TypeFunction [[FOO_PRIVATE:[0-9]+]] [[VOID_ID]] [[PRIVATE_FLOAT_POINTER_ID]] -// SPT-DAG: 4 TypeFunction [[FOO_GLOBAL:[0-9]+]] [[VOID_ID]] [[GLOBAL_FLOAT_POINTER_ID]] -// SPT-DAG: 4 TypeFunction [[FOO_SHARED:[0-9]+]] [[VOID_ID]] [[SHARED_FLOAT_POINTER_ID]] -// SPT-DAG: 4 TypeFunction [[FOO_CONSTANT:[0-9]+]] [[VOID_ID]] [[CONSTANT_FLOAT_POINTER_ID]] -// SPT-DAG: 4 TypeFunction [[FOO_GENERIC:[0-9]+]] [[VOID_ID]] [[GENERIC_FLOAT_POINTER_ID]] - -void foo(PrivatePointer!float f) { - // LL: load float, float* - // SPT-DAG: 5 Function [[VOID_ID]] {{[0-9]+}} 0 [[FOO_PRIVATE]] - float g = *f; -} - -void foo(GlobalPointer!float f) { - // LL: load float, float addrspace(1)* - // SPT-DAG: 5 Function [[VOID_ID]] {{[0-9]+}} 0 [[FOO_GLOBAL]] - float g = *f; -} - -void foo(SharedPointer!float f) { - // LL: load float, float addrspace(2)* - // SPT-DAG: 5 Function [[VOID_ID]] {{[0-9]+}} 0 [[FOO_SHARED]] - float g = *f; -} - -void foo(ConstantPointer!float f) { - // LL: load float, float addrspace(3)* - // SPT-DAG: 5 Function [[VOID_ID]] {{[0-9]+}} 0 [[FOO_CONSTANT]] - float g = *f; -} - -void foo(GenericPointer!float f) { - // LL: load float, float addrspace(4)* - // SPT-DAG: 5 Function [[VOID_ID]] {{[0-9]+}} 0 [[FOO_GENERIC]] - float g = *f; -} diff --git a/tests/codegen/dcompute_cl_images.d b/tests/codegen/dcompute_cl_images.d index 892039d5fbf..e9ccc1d8146 100644 --- a/tests/codegen/dcompute_cl_images.d +++ b/tests/codegen/dcompute_cl_images.d @@ -11,9 +11,9 @@ import ldc.dcompute; import ldc.opencl; -// CHECK-DAG: %"ldc.dcompute.Pointer!(AddrSpace.Global, image1d_ro_t).Pointer" = type { {{%opencl.image1d_ro_t addrspace\(1\)\*|ptr addrspace\(1\)}} } -// CHECK-DAG: %"ldc.dcompute.Pointer!(AddrSpace.Global, image1d_wo_t).Pointer" = type { {{%opencl.image1d_wo_t addrspace\(1\)\*|ptr addrspace\(1\)}} } -// CHECK-DAG: %"ldc.dcompute.Pointer!(AddrSpace.Shared, sampler_t).Pointer" = type { {{%opencl.sampler_t addrspace\(2\)\*|ptr addrspace\(2\)}} } +// CHECK-DAG: %"ldc.dcompute.Pointer!(AddrSpace.Global, image1d_ro_t).Pointer" = type { ptr addrspace(1) } +// CHECK-DAG: %"ldc.dcompute.Pointer!(AddrSpace.Global, image1d_wo_t).Pointer" = type { ptr addrspace(1) } +// CHECK-DAG: %"ldc.dcompute.Pointer!(AddrSpace.Shared, sampler_t).Pointer" = type { ptr addrspace(2) } pragma(mangle,"__translate_sampler_initializer") Sampler makeSampler(int); @@ -26,16 +26,16 @@ pragma(mangle,"_Z12write_imagef11ocl_image1d_woiDv4_f") @kernel void img(GlobalPointer!image1d_ro_t src, GlobalPointer!image1d_wo_t dst) { -// CHECK: %{{[0-9+]}} = call spir_func {{%opencl.sampler_t addrspace\(2\)\*|ptr addrspace\(2\)}} @__translate_sampler_initializer(i32 0) {{.*}} -// CHECK: %{{[0-9+]}} = call spir_func <4 x float> @_Z11read_imagef11ocl_image1d_ro11ocl_sampleri({{%opencl.image1d_ro_t addrspace\(1\)\*|ptr addrspace\(1\)}} %.DComputePointerRewrite_arg, {{%opencl.sampler_t addrspace\(2\)\*|ptr addrspace\(2\)}} %.DComputePointerRewrite_arg1, i32 0) {{.*}} +// CHECK: %{{[0-9+]}} = call spir_func ptr addrspace(2) @__translate_sampler_initializer(i32 0) {{.*}} +// CHECK: %{{[0-9+]}} = call spir_func <4 x float> @_Z11read_imagef11ocl_image1d_ro11ocl_sampleri(ptr addrspace(1) %.DComputePointerRewrite_arg, ptr addrspace(2) %.DComputePointerRewrite_arg1, i32 0) {{.*}} auto x = src.read(makeSampler(0), 0); -// CHECK: call spir_func void @_Z12write_imagef11ocl_image1d_woiDv4_f({{%opencl.image1d_wo_t addrspace\(1\)\*|ptr addrspace\(1\)}} %.DComputePointerRewrite_arg2, i32 0, <4 x float> %{{[0-9+]}}) +// CHECK: call spir_func void @_Z12write_imagef11ocl_image1d_woiDv4_f(ptr addrspace(1) %.DComputePointerRewrite_arg2, i32 0, <4 x float> %{{[0-9+]}}) dst.write(0,x); } @kernel void img2(GlobalPointer!image1d_ro_t src, Sampler samp) { -// CHECK: %{{[0-9+]}} = call spir_func <4 x float> @_Z11read_imagef11ocl_image1d_ro11ocl_sampleri({{%opencl.image1d_ro_t addrspace\(1\)\*|ptr addrspace\(1\)}} %.DComputePointerRewrite_arg, {{%opencl.sampler_t addrspace\(2\)\*|ptr addrspace\(2\)}} %.DComputePointerRewrite_arg1, i32 0) {{.*}} +// CHECK: %{{[0-9+]}} = call spir_func <4 x float> @_Z11read_imagef11ocl_image1d_ro11ocl_sampleri(ptr addrspace(1) %.DComputePointerRewrite_arg, ptr addrspace(2) %.DComputePointerRewrite_arg1, i32 0) {{.*}} auto x = src.read(samp, 0); } diff --git a/tests/codegen/dcompute_cu_addrspaces.d b/tests/codegen/dcompute_cu_addrspaces.d index ab158b060aa..3a5f808fe28 100644 --- a/tests/codegen/dcompute_cu_addrspaces.d +++ b/tests/codegen/dcompute_cu_addrspaces.d @@ -3,34 +3,34 @@ @compute(CompileFor.deviceOnly) module dcompute_cu_addrspaces; import ldc.dcompute; -// LL: %"ldc.dcompute.Pointer!(AddrSpace.Private, float).Pointer" = type { {{float addrspace\(5\)\*|ptr addrspace\(5\)}} } -// LL: %"ldc.dcompute.Pointer!(AddrSpace.Global, float).Pointer" = type { {{float addrspace\(1\)\*|ptr addrspace\(1\)}} } -// LL: %"ldc.dcompute.Pointer!(AddrSpace.Shared, float).Pointer" = type { {{float addrspace\(3\)\*|ptr addrspace\(3\)}} } -// LL: %"ldc.dcompute.Pointer!(AddrSpace.Constant, immutable(float)).Pointer" = type { {{float addrspace\(4\)\*|ptr addrspace\(4\)}} } -// LL: %"ldc.dcompute.Pointer!(AddrSpace.Generic, float).Pointer" = type { {{float\*|ptr}} } +// LL: %"ldc.dcompute.Pointer!(AddrSpace.Private, float).Pointer" = type { ptr addrspace(5) } +// LL: %"ldc.dcompute.Pointer!(AddrSpace.Global, float).Pointer" = type { ptr addrspace(1) } +// LL: %"ldc.dcompute.Pointer!(AddrSpace.Shared, float).Pointer" = type { ptr addrspace(3) } +// LL: %"ldc.dcompute.Pointer!(AddrSpace.Constant, immutable(float)).Pointer" = type { ptr addrspace(4) } +// LL: %"ldc.dcompute.Pointer!(AddrSpace.Generic, float).Pointer" = type { ptr } void foo(PrivatePointer!float f) { - // LL: load float, {{float addrspace\(5\)\*|ptr addrspace\(5\)}} + // LL: load float, ptr addrspace(5) // PTX: ld.local.f32 float g = *f; } void foo(GlobalPointer!float f) { - // LL: load float, {{float addrspace\(1\)\*|ptr addrspace\(1\)}} + // LL: load float, ptr addrspace(1) // PTX: ld.global.f32 float g = *f; } void foo(SharedPointer!float f) { - // LL: load float, {{float addrspace\(3\)\*|ptr addrspace\(3\)}} + // LL: load float, ptr addrspace(3) // PTX: ld.shared.f32 float g = *f; } void foo(ConstantPointer!float f) { - // LL: load float, {{float addrspace\(4\)\*|ptr addrspace\(4\)}} + // LL: load float, ptr addrspace(4) // PTX: ld.const.f32 float g = *f; } void foo(GenericPointer!float f) { - // LL: load float, {{float\*|ptr}} + // LL: load float, ptr // PTX: ld.f32 float g = *f; } diff --git a/tests/codegen/dcompute_host_and_device.d b/tests/codegen/dcompute_host_and_device.d index 2819e9ae40e..351344d53ec 100644 --- a/tests/codegen/dcompute_host_and_device.d +++ b/tests/codegen/dcompute_host_and_device.d @@ -28,34 +28,34 @@ void foo(GlobalPointer!float x_in) { PrivatePointer!float private_x; ConstantPointer!float const_x; - // LL: [[s_load_reg:%[0-9]*]] = load {{float\*|ptr}}, {{float\*\*|ptr}} {{%[0-9]*}} - // LL: [[s_addr_reg:%[0-9]*]] = load {{float\*|ptr}}, {{float\*\*|ptr}} {{%[0-9]*}} - // LL: [[s_store_reg:%[0-9]*]] = load float, {{float\*|ptr}} [[s_addr_reg]] - // LL: store float [[s_store_reg]], {{float\*|ptr}} [[s_load_reg]] + // LL: [[s_load_reg:%[0-9]*]] = load ptr, ptr {{%[0-9]*}} + // LL: [[s_addr_reg:%[0-9]*]] = load ptr, ptr {{%[0-9]*}} + // LL: [[s_store_reg:%[0-9]*]] = load float, ptr [[s_addr_reg]] + // LL: store float [[s_store_reg]], ptr [[s_load_reg]] *shared_x = *x_in; - // LL: [[p_load_reg:%[0-9]*]] = load {{float\*|ptr}}, {{float\*\*|ptr}} {{%[0-9]*}} - // LL: [[p_addr_reg:%[0-9]*]] = load {{float\*|ptr}}, {{float\*\*|ptr}} {{%[0-9]*}} - // LL: [[p_store_reg:%[0-9]*]] = load float, {{float\*|ptr}} [[p_addr_reg]] - // LL: store float [[p_store_reg]], {{float\*|ptr}} [[p_load_reg]] + // LL: [[p_load_reg:%[0-9]*]] = load ptr, ptr {{%[0-9]*}} + // LL: [[p_addr_reg:%[0-9]*]] = load ptr, ptr {{%[0-9]*}} + // LL: [[p_store_reg:%[0-9]*]] = load float, ptr [[p_addr_reg]] + // LL: store float [[p_store_reg]], ptr [[p_load_reg]] *private_x = *x_in; - // LL: [[c_load_reg:%[0-9]*]] = load {{float\*|ptr}}, {{float\*\*|ptr}} {{%[0-9]*}} - // LL: [[c_addr_reg:%[0-9]*]] = load {{float\*|ptr}}, {{float\*\*|ptr}} {{%[0-9]*}} - // LL: [[c_store_reg:%[0-9]*]] = load float, {{float\*|ptr}} [[c_addr_reg]] - // LL: store float [[c_store_reg]], {{float\*|ptr}} [[c_load_reg]] + // LL: [[c_load_reg:%[0-9]*]] = load ptr, ptr {{%[0-9]*}} + // LL: [[c_addr_reg:%[0-9]*]] = load ptr, ptr {{%[0-9]*}} + // LL: [[c_store_reg:%[0-9]*]] = load float, ptr [[c_addr_reg]] + // LL: store float [[c_store_reg]], ptr [[c_load_reg]] *x_in = *const_x; - // LL: [[g1_load_reg:%[0-9]*]] = load {{float\*|ptr}}, {{float\*\*|ptr}} {{%[0-9]*}} - // LL: [[g1_addr_reg:%[0-9]*]] = load {{float\*|ptr}}, {{float\*\*|ptr}} {{%[0-9]*}} - // LL: [[g1_store_reg:%[0-9]*]] = load float, {{float\*|ptr}} [[g1_addr_reg]] - // LL: store float [[g1_store_reg]], {{float\*|ptr}} [[g1_load_reg]] + // LL: [[g1_load_reg:%[0-9]*]] = load ptr, ptr {{%[0-9]*}} + // LL: [[g1_addr_reg:%[0-9]*]] = load ptr, ptr {{%[0-9]*}} + // LL: [[g1_store_reg:%[0-9]*]] = load float, ptr [[g1_addr_reg]] + // LL: store float [[g1_store_reg]], ptr [[g1_load_reg]] *x_in = *shared_x; - // LL: [[g2_load_reg:%[0-9]*]] = load {{float\*|ptr}}, {{float\*\*|ptr}} {{%[0-9]*}} - // LL: [[g2_addr_reg:%[0-9]*]] = load {{float\*|ptr}}, {{float\*\*|ptr}} {{%[0-9]*}} - // LL: [[g2_store_reg:%[0-9]*]] = load float, {{float\*|ptr}} [[g2_addr_reg]] - // LL: store float [[g2_store_reg]], {{float\*|ptr}} [[g2_load_reg]] + // LL: [[g2_load_reg:%[0-9]*]] = load ptr, ptr {{%[0-9]*}} + // LL: [[g2_addr_reg:%[0-9]*]] = load ptr, ptr {{%[0-9]*}} + // LL: [[g2_store_reg:%[0-9]*]] = load float, ptr [[g2_addr_reg]] + // LL: store float [[g2_store_reg]], ptr [[g2_load_reg]] *x_in = *private_x; } diff --git a/tests/codegen/dllimport_gh3926.d b/tests/codegen/dllimport_gh3926.d index 1888496ee19..8e7e6f7ce97 100644 --- a/tests/codegen/dllimport_gh3926.d +++ b/tests/codegen/dllimport_gh3926.d @@ -24,8 +24,8 @@ void foo() // should set the vptr: // CHECK: if: -// CHECK-NEXT: store {{\[[0-9]+ x i8\*\]\*|ptr}} @_D15TypeInfo_Struct6__vtblZ, +// CHECK-NEXT: store ptr @_D15TypeInfo_Struct6__vtblZ, // should set m_init.ptr: // CHECK: if1: -// CHECK-NEXT: store {{i8\*|ptr}} {{.*}}@_D3std7variant__T8VariantN{{.*}}6__initZ, {{.*}}TypeInfo_S3std7variant__T8VariantN{{.*}}6__initZ +// CHECK-NEXT: store ptr {{.*}}@_D3std7variant__T8VariantN{{.*}}6__initZ, {{.*}}TypeInfo_S3std7variant__T8VariantN{{.*}}6__initZ diff --git a/tests/codegen/func_contracts_gh1543.d b/tests/codegen/func_contracts_gh1543.d index dfe72f79023..4efd0b91167 100644 --- a/tests/codegen/func_contracts_gh1543.d +++ b/tests/codegen/func_contracts_gh1543.d @@ -27,19 +27,19 @@ class Bar // Bar.failMe codegen order = function, in-contract __require function, out-contract __ensure function // CHECK-LABEL: define {{.*}} @{{.*}}Bar6failMe -// CHECK-SAME: {{i32\*|ptr}} dereferenceable(4) %some -// CHECK: store i32 0, {{i32\*|ptr}} %some +// CHECK-SAME: ptr dereferenceable(4) %some +// CHECK: store i32 0, ptr %some // CHECK: call {{.*}} @{{.*}}Bar6failMeMFJiZ9__require // CHECK: call {{.*}} @{{.*}}Bar6failMeMFJiZ8__ensure // CHECK: } // CHECK-LABEL: define {{.*}} @{{.*}}Bar6failMeMFJiZ9__require -// CHECK-SAME: {{i32\*|ptr}} dereferenceable(4) %some +// CHECK-SAME: ptr dereferenceable(4) %some // CHECK-NOT: store {{.*}} %some // CHECK: } // CHECK-LABEL: define {{.*}} @{{.*}}Bar6failMeMFJiZ8__ensure -// CHECK-SAME: {{i32\*|ptr}} dereferenceable(4) %some +// CHECK-SAME: ptr dereferenceable(4) %some // CHECK-NOT: store {{.*}} %some // CHECK: } diff --git a/tests/codegen/funcptr_harvard_gh4432.d b/tests/codegen/funcptr_harvard_gh4432.d index 9400d982b07..74f50d758de 100644 --- a/tests/codegen/funcptr_harvard_gh4432.d +++ b/tests/codegen/funcptr_harvard_gh4432.d @@ -8,9 +8,9 @@ alias FP = void function(); alias DG = void delegate(); -// CHECK: @_D22funcptr_harvard_gh44328globalFPPFZv = global {{void \(\) addrspace\(1\)\*|ptr addrspace\(1\)}} @_D22funcptr_harvard_gh44323barFZv, align 2 +// CHECK: @_D22funcptr_harvard_gh44328globalFPPFZv = global ptr addrspace(1) @_D22funcptr_harvard_gh44323barFZv, align 2 __gshared FP globalFP = &bar; -// CHECK: @_D22funcptr_harvard_gh443217globalDataPointerPPFZv = global {{void \(\) addrspace\(1\)\*\*|ptr}} @_D22funcptr_harvard_gh44328globalFPPFZv, align 2 +// CHECK: @_D22funcptr_harvard_gh443217globalDataPointerPPFZv = global ptr @_D22funcptr_harvard_gh44328globalFPPFZv, align 2 __gshared FP* globalDataPointer = &globalFP; // CHECK: define void @_D22funcptr_harvard_gh44323fooFPFZvDQeZv({{.*}} addrspace(1){{\*?}} %fp_arg, { {{.*}} addrspace(1){{\*?}} } %dg_arg) addrspace(1) diff --git a/tests/codegen/gh2865.d b/tests/codegen/gh2865.d index 37d58d18b8d..28c1fa038d0 100644 --- a/tests/codegen/gh2865.d +++ b/tests/codegen/gh2865.d @@ -7,7 +7,7 @@ void foo() { // M64: %1 = getelementptr inbounds i8,{{.*}}_D6gh28653fooFZv{{.*}}, i64 -10 // M32: %1 = getelementptr inbounds i8,{{.*}}_D6gh28653fooFZv{{.*}}, i32 -10 - // M64-NEXT: %2 = ptrtoint {{i8\*|ptr}} %1 to i64 - // M32-NEXT: %2 = ptrtoint {{i8\*|ptr}} %1 to i32 + // M64-NEXT: %2 = ptrtoint ptr %1 to i64 + // M32-NEXT: %2 = ptrtoint ptr %1 to i32 auto addr = (cast(size_t) &foo) - 10; } diff --git a/tests/codegen/gh3692.d b/tests/codegen/gh3692.d index 8ba5b19369f..15ac41375ae 100644 --- a/tests/codegen/gh3692.d +++ b/tests/codegen/gh3692.d @@ -1,48 +1,44 @@ // https://github.com/ldc-developers/ldc/issues/3692 // REQUIRES: target_X86 -// REQUIRES: atmost_llvm1409 // RUN: %ldc -mtriple=x86_64-linux-gnu -output-ll -of=%t.ll %s // RUN: FileCheck %s < %t.ll // D `int[3]` rewritten to LL `{ i64, i32 }` for SysV ABI - mismatching size and alignment -// CHECK: define void @_D6gh36924takeFG3iZv({ i64, i32 } %a_arg) +// CHECK-LABEL: define void @_D6gh36924takeFG3iZv({ i64, i32 } %a_arg) void take(int[3] a) { // the `{ i64, i32 }` size is 16 bytes, so we need a padded alloca (with 8-bytes alignment) - // CHECK-NEXT: %.BaseBitcastABIRewrite_param_storage = alloca { i64, i32 }, align 8 - // CHECK-NEXT: store { i64, i32 } %a_arg, { i64, i32 }* %.BaseBitcastABIRewrite_param_storage - // CHECK-NEXT: %a = bitcast { i64, i32 }* %.BaseBitcastABIRewrite_param_storage to [3 x i32]* + // CHECK-NEXT: = alloca { i64, i32 }, align 8 } -// CHECK: define void @_D6gh36924passFZv() +// CHECK-LABEL: define void @_D6gh36924passFZv() void pass() { // CHECK-NEXT: %arrayliteral = alloca [3 x i32], align 4 // we need an extra padded alloca with proper alignment // CHECK-NEXT: %.BaseBitcastABIRewrite_padded_arg_storage = alloca { i64, i32 }, align 8 - // CHECK: %.BaseBitcastABIRewrite_arg = load { i64, i32 }, { i64, i32 }* %.BaseBitcastABIRewrite_padded_arg_storage + // CHECK: %.BaseBitcastABIRewrite_arg = load { i64, i32 }, ptr %.BaseBitcastABIRewrite_padded_arg_storage take([1, 2, 3]); } // D `int[4]` rewritten to LL `{ i64, i64 }` for SysV ABI - mismatching alignment only -// CHECK: define void @_D6gh36925take4FG4iZv({ i64, i64 } %a_arg) +// CHECK-LABEL: define void @_D6gh36925take4FG4iZv({ i64, i64 } %a_arg) void take4(int[4] a) { // the alloca should have 8-bytes alignment, even though a.alignof == 4 // CHECK-NEXT: %a = alloca [4 x i32], align 8 - // CHECK-NEXT: %1 = bitcast [4 x i32]* %a to { i64, i64 }* - // CHECK-NEXT: store { i64, i64 } %a_arg, { i64, i64 }* %1 + // CHECK: store { i64, i64 } %a_arg, ptr % } -// CHECK: define void @_D6gh36925pass4FZv() +// CHECK-LABEL: define void @_D6gh36925pass4FZv() void pass4() { // CHECK-NEXT: %arrayliteral = alloca [4 x i32], align 4 // we need an extra alloca with 8-bytes alignment // CHECK-NEXT: %.BaseBitcastABIRewrite_padded_arg_storage = alloca { i64, i64 }, align 8 - // CHECK: %.BaseBitcastABIRewrite_arg = load { i64, i64 }, { i64, i64 }* %.BaseBitcastABIRewrite_padded_arg_storage + // CHECK: %.BaseBitcastABIRewrite_arg = load { i64, i64 }, ptr %.BaseBitcastABIRewrite_padded_arg_storage take4([1, 2, 3, 4]); } diff --git a/tests/codegen/gh3692_llvm15.d b/tests/codegen/gh3692_llvm15.d deleted file mode 100644 index 718635f8af2..00000000000 --- a/tests/codegen/gh3692_llvm15.d +++ /dev/null @@ -1,45 +0,0 @@ -// https://github.com/ldc-developers/ldc/issues/3692 - -// REQUIRES: target_X86 -// REQUIRES: atleast_llvm1500 -// RUN: %ldc -mtriple=x86_64-linux-gnu -output-ll -of=%t.ll %s -// RUN: FileCheck %s < %t.ll - - -// D `int[3]` rewritten to LL `{ i64, i32 }` for SysV ABI - mismatching size and alignment -// CHECK-LABEL: define void @_D13gh3692_llvm154takeFG3iZv({ i64, i32 } %a_arg) -void take(int[3] a) -{ - // the `{ i64, i32 }` size is 16 bytes, so we need a padded alloca (with 8-bytes alignment) - // CHECK-NEXT: = alloca { i64, i32 }, align 8 -} - -// CHECK-LABEL: define void @_D13gh3692_llvm154passFZv() -void pass() -{ - // CHECK-NEXT: %arrayliteral = alloca [3 x i32], align 4 - // we need an extra padded alloca with proper alignment - // CHECK-NEXT: %.BaseBitcastABIRewrite_padded_arg_storage = alloca { i64, i32 }, align 8 - // CHECK: %.BaseBitcastABIRewrite_arg = load { i64, i32 }, {{\{ i64, i32 \}\*|ptr}} %.BaseBitcastABIRewrite_padded_arg_storage - take([1, 2, 3]); -} - - -// D `int[4]` rewritten to LL `{ i64, i64 }` for SysV ABI - mismatching alignment only -// CHECK-LABEL: define void @_D13gh3692_llvm155take4FG4iZv({ i64, i64 } %a_arg) -void take4(int[4] a) -{ - // the alloca should have 8-bytes alignment, even though a.alignof == 4 - // CHECK-NEXT: %a = alloca [4 x i32], align 8 - // CHECK: store { i64, i64 } %a_arg, {{\{ i64, i64 \}\*|ptr}} % -} - -// CHECK-LABEL: define void @_D13gh3692_llvm155pass4FZv() -void pass4() -{ - // CHECK-NEXT: %arrayliteral = alloca [4 x i32], align 4 - // we need an extra alloca with 8-bytes alignment - // CHECK-NEXT: %.BaseBitcastABIRewrite_padded_arg_storage = alloca { i64, i64 }, align 8 - // CHECK: %.BaseBitcastABIRewrite_arg = load { i64, i64 }, {{\{ i64, i64 \}\*|ptr}} %.BaseBitcastABIRewrite_padded_arg_storage - take4([1, 2, 3, 4]); -} diff --git a/tests/codegen/in_place_construct.d b/tests/codegen/in_place_construct.d index dd0892785b4..610c1341cc3 100644 --- a/tests/codegen/in_place_construct.d +++ b/tests/codegen/in_place_construct.d @@ -12,14 +12,14 @@ struct S S returnLiteral() { // make sure the literal is emitted directly into the sret pointee - // CHECK: %1 = getelementptr inbounds {{.*}}%in_place_construct.S{{\*|, ptr}} %.sret_arg, i32 0, i32 0 - // CHECK: store i64 1, {{i64\*|ptr}} %1 - // CHECK: %2 = getelementptr inbounds {{.*}}%in_place_construct.S{{\*|, ptr}} %.sret_arg, i32 0, i32 1 - // CHECK: store i64 2, {{i64\*|ptr}} %2 - // CHECK: %3 = getelementptr inbounds {{.*}}%in_place_construct.S{{\*|, ptr}} %.sret_arg, i32 0, i32 2 - // CHECK: store i64 3, {{i64\*|ptr}} %3 - // CHECK: %4 = getelementptr inbounds {{.*}}%in_place_construct.S{{\*|, ptr}} %.sret_arg, i32 0, i32 3 - // CHECK: store i64 4, {{i64\*|ptr}} %4 + // CHECK: %1 = getelementptr inbounds {{.*}}%in_place_construct.S, ptr %.sret_arg, i32 0, i32 0 + // CHECK: store i64 1, ptr %1 + // CHECK: %2 = getelementptr inbounds {{.*}}%in_place_construct.S, ptr %.sret_arg, i32 0, i32 1 + // CHECK: store i64 2, ptr %2 + // CHECK: %3 = getelementptr inbounds {{.*}}%in_place_construct.S, ptr %.sret_arg, i32 0, i32 2 + // CHECK: store i64 3, ptr %3 + // CHECK: %4 = getelementptr inbounds {{.*}}%in_place_construct.S, ptr %.sret_arg, i32 0, i32 3 + // CHECK: store i64 4, ptr %4 return S(1, 2, 3, 4); } @@ -28,7 +28,7 @@ S returnRValue() { // make sure the sret pointer is forwarded // CHECK: call {{.*}}_D18in_place_construct13returnLiteralFZSQBm1S - // CHECK-SAME: {{%in_place_construct.S\*|ptr}} {{.*}} %.sret_arg + // CHECK-SAME: ptr {{.*}} %.sret_arg return returnLiteral(); } @@ -36,7 +36,7 @@ S returnRValue() S returnNRVO() { // make sure NRVO zero-initializes the sret pointee directly - // CHECK: call void @llvm.memset.{{.*}}({{i8\*|ptr}}{{.*}}, i8 0, + // CHECK: call void @llvm.memset.{{.*}}(ptr{{.*}}, i8 0, const S r; return r; } @@ -47,13 +47,13 @@ out { assert(__result.a == 0); } do { // make sure NRVO zero-initializes the sret pointee directly - // CHECK: call void @llvm.memset.{{.*}}({{i8\*|ptr}}{{.*}}, i8 0, + // CHECK: call void @llvm.memset.{{.*}}(ptr{{.*}}, i8 0, const S r; return r; // make sure `__result` inside the out contract is just an alias to the sret pointee - // CHECK: %{{1|2}} = getelementptr inbounds {{.*}}%in_place_construct.S{{\*|, ptr}} %.sret_arg, i32 0, i32 0 - // CHECK: %{{2|3}} = load {{.*}}{{i64\*|ptr}} %{{1|2}} + // CHECK: %{{1|2}} = getelementptr inbounds {{.*}}%in_place_construct.S, ptr %.sret_arg, i32 0, i32 0 + // CHECK: %{{2|3}} = load {{.*}}ptr %{{1|2}} // CHECK: %{{3|4}} = icmp eq i64 %{{2|3}}, 0 } @@ -66,25 +66,25 @@ void structs() // CHECK: %c = alloca %in_place_construct.S // make sure the literal is emitted directly into the lvalue - // CHECK: %1 = getelementptr inbounds {{.*}}%in_place_construct.S{{\*|, ptr}} %literal, i32 0, i32 0 - // CHECK: store i64 5, {{i64\*|ptr}} %1 - // CHECK: %2 = getelementptr inbounds {{.*}}%in_place_construct.S{{\*|, ptr}} %literal, i32 0, i32 1 - // CHECK: store i64 6, {{i64\*|ptr}} %2 - // CHECK: %3 = getelementptr inbounds {{.*}}%in_place_construct.S{{\*|, ptr}} %literal, i32 0, i32 2 - // CHECK: store i64 7, {{i64\*|ptr}} %3 - // CHECK: %4 = getelementptr inbounds {{.*}}%in_place_construct.S{{\*|, ptr}} %literal, i32 0, i32 3 - // CHECK: store i64 8, {{i64\*|ptr}} %4 + // CHECK: %1 = getelementptr inbounds {{.*}}%in_place_construct.S, ptr %literal, i32 0, i32 0 + // CHECK: store i64 5, ptr %1 + // CHECK: %2 = getelementptr inbounds {{.*}}%in_place_construct.S, ptr %literal, i32 0, i32 1 + // CHECK: store i64 6, ptr %2 + // CHECK: %3 = getelementptr inbounds {{.*}}%in_place_construct.S, ptr %literal, i32 0, i32 2 + // CHECK: store i64 7, ptr %3 + // CHECK: %4 = getelementptr inbounds {{.*}}%in_place_construct.S, ptr %literal, i32 0, i32 3 + // CHECK: store i64 8, ptr %4 const literal = S(5, 6, 7, 8); // make sure the variables are in-place constructed via sret // CHECK: call {{.*}}_D18in_place_construct13returnLiteralFZSQBm1S - // CHECK-SAME: {{(%in_place_construct\.S\*|ptr).*}} %a + // CHECK-SAME: ptr{{.*}} %a const a = returnLiteral(); // CHECK: call {{.*}}_D18in_place_construct12returnRValueFZSQBl1S - // CHECK-SAME: {{(%in_place_construct\.S\*|ptr).*}} %b + // CHECK-SAME: ptr{{.*}} %b const b = returnRValue(); // CHECK: call {{.*}}_D18in_place_construct10returnNRVOFZSQBj1S - // CHECK-SAME: {{(%in_place_construct\.S\*|ptr).*}} %c + // CHECK-SAME: ptr{{.*}} %c const c = returnNRVO(); withOutContract(); @@ -96,7 +96,7 @@ void staticArrays() // CHECK: %sa = alloca [2 x i32] // make sure static array literals are in-place constructed too - // CHECK: store [2 x i32] [i32 1, i32 2], {{\[2 x i32\]\*|ptr}} %sa + // CHECK: store [2 x i32] [i32 1, i32 2], ptr %sa const(int[2]) sa = [ 1, 2 ]; } @@ -106,7 +106,7 @@ struct Container { S s; } void hierarchyOfLiterals() { // CHECK: %sa = alloca [1 x %in_place_construct.Container] - // CHECK: store [1 x %in_place_construct.Container] [%in_place_construct.Container { %in_place_construct.S { i64 11, i64 12, i64 13, i64 14 } }], {{\[1 x %in_place_construct.Container\]\*|ptr}} %sa + // CHECK: store [1 x %in_place_construct.Container] [%in_place_construct.Container { %in_place_construct.S { i64 11, i64 12, i64 13, i64 14 } }], ptr %sa Container[1] sa = [ Container(S(11, 12, 13, 14)) ]; } diff --git a/tests/codegen/in_place_construct_asm.d b/tests/codegen/in_place_construct_asm.d index e726d76d835..346d65e3de2 100644 --- a/tests/codegen/in_place_construct_asm.d +++ b/tests/codegen/in_place_construct_asm.d @@ -9,7 +9,7 @@ import ldc.llvmasm; // CHECK-LABEL: define{{.*}} @{{.*}}_D22in_place_construct_asm14inlineAssemblyFkkZv void inlineAssembly(uint eax, uint ecx) { - // CHECK: store %"ldc.llvmasm.__asmtuple_t!(uint, uint, uint, uint).__asmtuple_t" %3, {{%"ldc.llvmasm.__asmtuple_t!\(uint, uint, uint, uint\).__asmtuple_t"\*|ptr}} %r + // CHECK: store %"ldc.llvmasm.__asmtuple_t!(uint, uint, uint, uint).__asmtuple_t" %3, ptr %r auto r = __asmtuple!(uint, uint, uint, uint) ("cpuid", "={eax},={ebx},={ecx},={edx},{eax},{ecx}", eax, ecx); } diff --git a/tests/codegen/inbounds.d b/tests/codegen/inbounds.d index 024795f7e87..d50939f71b9 100644 --- a/tests/codegen/inbounds.d +++ b/tests/codegen/inbounds.d @@ -65,27 +65,27 @@ float foo8(S s) { // IndexExp in dynamic array with const exp // CHECK-LABEL: @foo9 int foo9(int[] a) { - // CHECK: getelementptr inbounds i32, {{i32\*|ptr}} + // CHECK: getelementptr inbounds i32, ptr return a[1]; } // IndexExp in dynamic array with variable exp // CHECK-LABEL: @foo10 int foo10(int[] a, int i) { - // CHECK: getelementptr inbounds i32, {{i32\*|ptr}} + // CHECK: getelementptr inbounds i32, ptr return a[i]; } // SliceExp for static array with const lower bound // CHECK-LABEL: @foo11 int[] foo11(ref int[3] a) { - // CHECK: getelementptr inbounds i32, {{i32\*|ptr}} + // CHECK: getelementptr inbounds i32, ptr return a[1 .. $]; } // SliceExp for dynamic array with variable lower bound // CHECK-LABEL: @foo12 int[] foo12(int[] a, int i) { - // CHECK: getelementptr inbounds i32, {{i32\*|ptr}} + // CHECK: getelementptr inbounds i32, ptr return a[i .. $]; } diff --git a/tests/codegen/inline_ir.d b/tests/codegen/inline_ir.d index 11ba7b71039..e30f8a0f0f6 100644 --- a/tests/codegen/inline_ir.d +++ b/tests/codegen/inline_ir.d @@ -2,19 +2,13 @@ import ldc.llvmasm; import ldc.intrinsics; -version (LDC_LLVM_OpaquePointers) -{ - alias __irEx!("", "store i32 %1, ptr %0, !nontemporal !0", "!0 = !{i32 1}", void, int*, int) nontemporalStore; - alias __irEx!("!0 = !{i32 1}", "%i = load i32, ptr %0, !nontemporal !0\nret i32 %i", "", int, const int*) nontemporalLoad; -} -else -{ - alias __irEx!("", "store i32 %1, i32* %0, !nontemporal !0", "!0 = !{i32 1}", void, int*, int) nontemporalStore; - alias __irEx!("!0 = !{i32 1}", "%i = load i32, i32* %0, !nontemporal !0\nret i32 %i", "", int, const int*) nontemporalLoad; -} + +alias __irEx!("", "store i32 %1, ptr %0, !nontemporal !0", "!0 = !{i32 1}", void, int*, int) nontemporalStore; +alias __irEx!("!0 = !{i32 1}", "%i = load i32, ptr %0, !nontemporal !0\nret i32 %i", "", int, const int*) nontemporalLoad; + int foo(const int* src) { - // CHECK: %{{.*}} = load i32, {{i32\*|ptr}} {{.*}} !nontemporal ![[METADATA:[0-9]+]] + // CHECK: %{{.*}} = load i32, ptr {{.*}} !nontemporal ![[METADATA:[0-9]+]] return nontemporalLoad(src); } diff --git a/tests/codegen/inputs/mangling_definitions.d b/tests/codegen/inputs/mangling_definitions.d index 6234999bfc4..a6ed0fec9e8 100644 --- a/tests/codegen/inputs/mangling_definitions.d +++ b/tests/codegen/inputs/mangling_definitions.d @@ -46,6 +46,8 @@ version(AsmX86) else static assert(naked_cppFunc.mangleof == "_ZN15cpp_naked_funcs13naked_cppFuncEd"); - int naked_dFunc(double a) { asm { naked; ret; } } - static assert(naked_dFunc.mangleof == "_D11definitions11naked_dFuncFdZi"); + // Pass an int instead of a double due to x86 calling convetion + // See: https://github.com/ldc-developers/ldc/pull/4661 + int naked_dFunc(int a) { asm { naked; ret; } } + static assert(naked_dFunc.mangleof == "_D11definitions11naked_dFuncFiZi"); } diff --git a/tests/codegen/llvm_used_1.d b/tests/codegen/llvm_used_1.d index 1043d18deb9..2be0346d14f 100644 --- a/tests/codegen/llvm_used_1.d +++ b/tests/codegen/llvm_used_1.d @@ -10,7 +10,7 @@ // There was a bug where llvm.used was emitted more than once, whose symptom was that suffixed versions would appear: e.g. `@llvm.used.3`. // Expect 2 llvm.used entries, for both ModuleInfo refs. // LLVM-NOT: @llvm.used. -// LLVM: @llvm.used = appending global [2 x {{i8\*|ptr}}] +// LLVM: @llvm.used = appending global [2 x ptr] // LLVM-NOT: @llvm.used. // EXECUTE: ctor diff --git a/tests/codegen/mangling.d b/tests/codegen/mangling.d index d79793192a6..054d4d79926 100644 --- a/tests/codegen/mangling.d +++ b/tests/codegen/mangling.d @@ -61,8 +61,8 @@ version(AsmX86) extern(C++, decl_cpp_naked_funcs) pragma(mangle, nakedCppFuncMangle) int decl_naked_cppFunc(double a); - pragma(mangle, "_D11definitions11naked_dFuncFdZi") - int decl_naked_dFunc(double a); + pragma(mangle, "_D11definitions11naked_dFuncFiZi") + int decl_naked_dFunc(int a); } // Interfacing with C via pragma(mangle, …), without having to take care @@ -84,7 +84,7 @@ void main() { decl_naked_cFunc(1.0); decl_naked_cppFunc(2.0); - decl_naked_dFunc(3.0); + decl_naked_dFunc(3); } assert(decl_cos(0.0) == 1.0); diff --git a/tests/codegen/nested_lazy_gh2302.d b/tests/codegen/nested_lazy_gh2302.d index 40ec05cb841..3999d935901 100644 --- a/tests/codegen/nested_lazy_gh2302.d +++ b/tests/codegen/nested_lazy_gh2302.d @@ -6,7 +6,7 @@ void foo() auto impl(T)(lazy T field) { // Make sure `field` is a closure variable with delegate type (LL struct). - // CHECK: %nest.impl = type { { {{i8\*|ptr}}, {{i32 \(i8\*\)\*|ptr}} } } + // CHECK: %nest.impl = type { { ptr, ptr } } auto ff() { return field; } auto a = field; return ff() + a; diff --git a/tests/codegen/newlib_assert.d b/tests/codegen/newlib_assert.d index 6695111508e..a6aecd081a9 100644 --- a/tests/codegen/newlib_assert.d +++ b/tests/codegen/newlib_assert.d @@ -5,4 +5,4 @@ extern (C) void main() assert(false); } -// CHECK: declare void @__assert_func({{i8\*|ptr}}, i32, {{i8\*|ptr}}, {{i8\*|ptr}}) +// CHECK: declare void @__assert_func(ptr, i32, ptr, ptr) diff --git a/tests/codegen/simd_alignment.d b/tests/codegen/simd_alignment.d index 8f74a0879ae..3f4a300c2c1 100644 --- a/tests/codegen/simd_alignment.d +++ b/tests/codegen/simd_alignment.d @@ -20,7 +20,7 @@ S17237 globalStruct; // CHECK-LABEL: define <8 x i32> @foo( extern(C) int8 foo(S17237* s) { - // CHECK: %[[GEP:[0-9]]] = getelementptr {{.*}}S17237{{\*|, ptr}} %s_arg - // CHECK: = load <8 x i32>, {{<8 x i32>\*|ptr}} %[[GEP]], align 32 + // CHECK: %[[GEP:[0-9]]] = getelementptr {{.*}}S17237, ptr %s_arg + // CHECK: = load <8 x i32>, ptr %[[GEP]], align 32 return s.c; } diff --git a/tests/codegen/static_array_init.d b/tests/codegen/static_array_init.d index 03ffef5f061..3e912e01135 100644 --- a/tests/codegen/static_array_init.d +++ b/tests/codegen/static_array_init.d @@ -7,7 +7,7 @@ void bytes_scalar() // CHECK: define {{.*}}_D17static_array_init12bytes_scalarFZv // CHECK-NEXT: %myBytes = alloca [32 x i8], align 1 - // CHECK: call void @llvm.memset{{.*}}({{i8\*|ptr}}{{[a-z0-9 ]*}} {{%.*}}, i8 123, i{{(32|64)}} 32 + // CHECK: call void @llvm.memset{{.*}}(ptr{{[a-z0-9 ]*}} {{%.*}}, i8 123, i{{(32|64)}} 32 } void bytes_scalar(byte arg) @@ -17,9 +17,9 @@ void bytes_scalar(byte arg) // CHECK: define {{.*}}_D17static_array_init12bytes_scalarFgZv // CHECK-NEXT: %arg = alloca i8, align 1 // CHECK-NEXT: %myBytes = alloca [32 x i8], align 1 - // CHECK-NEXT: store i8 %arg_arg, {{i8\*|ptr}} %arg - // CHECK: {{%.}} = load {{.*}}{{i8\*|ptr}} %arg - // CHECK-NEXT: call void @llvm.memset{{.*}}({{i8\*|ptr}}{{[a-z0-9 ]*}} {{%.*}}, i8 {{%.}}, i{{(32|64)}} 32 + // CHECK-NEXT: store i8 %arg_arg, ptr %arg + // CHECK: {{%.}} = load {{.*}}ptr %arg + // CHECK-NEXT: call void @llvm.memset{{.*}}(ptr{{[a-z0-9 ]*}} {{%.*}}, i8 {{%.}}, i{{(32|64)}} 32 } void ints_scalar() @@ -28,9 +28,9 @@ void ints_scalar() // CHECK: define {{.*}}_D17static_array_init11ints_scalarFZv // CHECK: arrayinit.cond: - // CHECK-NEXT: %[[I1:[0-9]+]] = load {{.*}}{{i(32|64)\*|ptr}} %arrayinit.itr + // CHECK-NEXT: %[[I1:[0-9]+]] = load {{.*}}ptr %arrayinit.itr // CHECK-NEXT: %arrayinit.condition = icmp ne i{{(32|64)}} %[[I1]], 32 - // CHECK: store i32 123, {{i32\*|ptr}} %arrayinit.arrayelem + // CHECK: store i32 123, ptr %arrayinit.arrayelem } void ints_scalar(int arg) @@ -39,10 +39,10 @@ void ints_scalar(int arg) // CHECK: define {{.*}}_D17static_array_init11ints_scalarFiZv // CHECK: arrayinit.cond: - // CHECK-NEXT: %[[I2:[0-9]+]] = load {{.*}}{{i(32|64)\*|ptr}} %arrayinit.itr + // CHECK-NEXT: %[[I2:[0-9]+]] = load {{.*}}ptr %arrayinit.itr // CHECK-NEXT: %arrayinit.condition = icmp ne i{{(32|64)}} %[[I2]], 32 - // CHECK: %[[E2:[0-9]+]] = load {{.*}}{{i32\*|ptr}} %arg - // CHECK-NEXT: store i32 %[[E2]], {{i32\*|ptr}} %arrayinit.arrayelem + // CHECK: %[[E2:[0-9]+]] = load {{.*}}ptr %arg + // CHECK-NEXT: store i32 %[[E2]], ptr %arrayinit.arrayelem } void bytes() @@ -51,7 +51,7 @@ void bytes() // CHECK: define {{.*}}_D17static_array_init5bytesFZv // CHECK-NEXT: %myBytes = alloca [4 x i8], align 1 - // CHECK-NEXT: store [4 x i8] c"\01\02\03\04", {{\[4 x i8\]\*|ptr}} %myBytes + // CHECK-NEXT: store [4 x i8] c"\01\02\03\04", ptr %myBytes } void bytes(byte[] arg) @@ -60,7 +60,7 @@ void bytes(byte[] arg) // CHECK: define {{.*}}_D17static_array_init5bytesFAgZv // CHECK: %myBytes = alloca [4 x i8], align 1 - // CHECK: call void @llvm.memcpy{{.*}}({{(i8\*|ptr)[a-z0-9 ]*}} %{{.*}}, {{(i8\*|ptr)[a-z0-9 ]*}} %.ptr, i{{(32|64)}} 4 + // CHECK: call void @llvm.memcpy{{.*}}(ptr{{[a-z0-9 ]*}} %{{.*}}, ptr{{[a-z0-9 ]*}} %.ptr, i{{(32|64)}} 4 } void ints() @@ -69,7 +69,7 @@ void ints() // CHECK: define {{.*}}_D17static_array_init4intsFZv // CHECK-NEXT: %myInts = alloca [4 x i32], align 4 - // CHECK-NEXT: store [4 x i32] [i32 1, i32 2, i32 3, i32 4], {{\[4 x i32\]\*|ptr}} %myInts + // CHECK-NEXT: store [4 x i32] [i32 1, i32 2, i32 3, i32 4], ptr %myInts } void ints(ref int[4] arg) @@ -78,7 +78,7 @@ void ints(ref int[4] arg) // CHECK: define {{.*}}_D17static_array_init4intsFKG4iZv // CHECK-NEXT: %myInts = alloca [4 x i32], align 4 - // CHECK: call void @llvm.memcpy{{.*}}({{(i8\*|ptr)[a-z0-9 ]*}} %{{.*}}, {{(i8\*|ptr)[a-z0-9 ]*}} %{{.*}}, i{{(32|64)}} 16 + // CHECK: call void @llvm.memcpy{{.*}}(ptr{{[a-z0-9 ]*}} %{{.*}}, ptr{{[a-z0-9 ]*}} %{{.*}}, i{{(32|64)}} 16 } void bytes_scalar_2d() @@ -87,7 +87,7 @@ void bytes_scalar_2d() // CHECK: define {{.*}}_D17static_array_init15bytes_scalar_2dFZv // CHECK-NEXT: %myBytes = alloca [8 x [4 x i8]], align 1 - // CHECK: call void @llvm.memset{{.*}}({{(i8\*|ptr)[a-z0-9 ]*}} %{{.*}}, i8 123, i{{(32|64)}} 32 + // CHECK: call void @llvm.memset{{.*}}(ptr{{[a-z0-9 ]*}} %{{.*}}, i8 123, i{{(32|64)}} 32 } void ints_scalar_2d(immutable int arg) @@ -96,8 +96,8 @@ void ints_scalar_2d(immutable int arg) // CHECK: define {{.*}}_D17static_array_init14ints_scalar_2dFyiZv // CHECK: arrayinit.cond: - // CHECK-NEXT: %[[I3:[0-9]+]] = load {{.*}}{{i(32|64)\*|ptr}} %arrayinit.itr + // CHECK-NEXT: %[[I3:[0-9]+]] = load {{.*}}ptr %arrayinit.itr // CHECK-NEXT: %arrayinit.condition = icmp ne i{{(32|64)}} %[[I3]], 32 - // CHECK: %[[E3:[0-9]+]] = load {{.*}}{{i32\*|ptr}} %arg - // CHECK-NEXT: store i32 %[[E3]], {{i32\*|ptr}} %arrayinit.arrayelem + // CHECK: %[[E3:[0-9]+]] = load {{.*}}ptr %arg + // CHECK-NEXT: store i32 %[[E3]], ptr %arrayinit.arrayelem } diff --git a/tests/codegen/static_typeid_gh1540.d b/tests/codegen/static_typeid_gh1540.d index 178004ffdef..55b4fb2a31b 100644 --- a/tests/codegen/static_typeid_gh1540.d +++ b/tests/codegen/static_typeid_gh1540.d @@ -16,13 +16,13 @@ struct S } // CHECK-DAG: _D{{.*}}1C7__ClassZ{{\"?}} = global %object.TypeInfo_Class -// CHECK-DAG: _D{{.*}}classvarC14TypeInfo_Class{{\"?}} = thread_local global {{%object.TypeInfo_Class\*|ptr}} {{.*}}1C7__ClassZ +// CHECK-DAG: _D{{.*}}classvarC14TypeInfo_Class{{\"?}} = thread_local global ptr {{.*}}1C7__ClassZ auto classvar = typeid(C); // CHECK-DAG: _D{{.*}}TypeInfo_C{{.*}}1I6__initZ{{\"?}} = linkonce_odr global %object.TypeInfo_Interface -// CHECK-DAG: _D{{.*}}interfacevarC18TypeInfo_Interface{{\"?}} = thread_local global {{%object.TypeInfo_Interface\*|ptr}} {{.*}}TypeInfo_C{{.*}}1I6__initZ +// CHECK-DAG: _D{{.*}}interfacevarC18TypeInfo_Interface{{\"?}} = thread_local global ptr {{.*}}TypeInfo_C{{.*}}1I6__initZ auto interfacevar = typeid(I); // CHECK-DAG: _D{{.*}}TypeInfo_S{{.*}}1S6__initZ{{\"?}} = linkonce_odr global %object.TypeInfo_Struct -// CHECK-DAG: _D{{.*}}structvarC15TypeInfo_Struct{{\"?}} = thread_local global {{%object.TypeInfo_Struct\*|ptr}} {{.*}}TypeInfo_S{{.*}}1S6__initZ +// CHECK-DAG: _D{{.*}}structvarC15TypeInfo_Struct{{\"?}} = thread_local global ptr {{.*}}TypeInfo_S{{.*}}1S6__initZ auto structvar = typeid(S); diff --git a/tests/codegen/vector_init.d b/tests/codegen/vector_init.d index 8a73c02a2c2..5494e054872 100644 --- a/tests/codegen/vector_init.d +++ b/tests/codegen/vector_init.d @@ -29,9 +29,9 @@ void foo() // CHECK-NEXT: %v16 = alloca <16 x i8> // CHECK-NEXT: %s8 = alloca <8 x i16> // CHECK-NEXT: %d2 = alloca <2 x double> - // CHECK-NEXT: store <16 x i8> zeroinitializer, {{<16 x i8>\*|ptr}} %v16 - // CHECK-NEXT: store <8 x i16> , {{<8 x i16>\*|ptr}} %s8 - // CHECK-NEXT: store <2 x double> , {{<2 x double>\*|ptr}} %d2 + // CHECK-NEXT: store <16 x i8> zeroinitializer, ptr %v16 + // CHECK-NEXT: store <8 x i16> , ptr %s8 + // CHECK-NEXT: store <2 x double> , ptr %d2 // CHECK-NEXT: ret void void16 v16; short8 s8 = [1, 2, 3, 4, 5, 6, 7, 8]; @@ -45,10 +45,10 @@ void bar(const ref float[4] floats, const ref int[4] ints) alias float4 = __vector(float[4]); alias int4 = __vector(int[4]); - // CHECK: call void @llvm.memcpy{{.*}}({{i8\*|ptr}}{{[^,]*}} %{{.*}} + // CHECK: call void @llvm.memcpy{{.*}}(ptr{{[^,]*}} %{{.*}} auto f = cast(float4) floats; - // CHECK: call void @llvm.memcpy{{.*}}({{i8\*|ptr}}{{[^,]*}} %{{.*}} + // CHECK: call void @llvm.memcpy{{.*}}(ptr{{[^,]*}} %{{.*}} auto i = cast(int4) ints; // CHECK: fptosi diff --git a/tests/codegen/wasi.d b/tests/codegen/wasi.d index 2e33d5b4e24..c5c7b8fe444 100644 --- a/tests/codegen/wasi.d +++ b/tests/codegen/wasi.d @@ -21,8 +21,8 @@ extern int declaredGlobal; // make sure the ModuleInfo ref is emitted into the __minfo section: -// CHECK: @_D4wasi11__moduleRefZ = linkonce_odr hidden global {{%object\.ModuleInfo\*|ptr}} {{.*}}, section "__minfo" -// CHECK: @llvm.used = appending global [1 x {{i8\*|ptr}}] [{{i8\*|ptr}} {{.*}}@_D4wasi11__moduleRefZ +// CHECK: @_D4wasi11__moduleRefZ = linkonce_odr hidden global ptr {{.*}}, section "__minfo" +// CHECK: @llvm.used = appending global [1 x ptr] [ptr {{.*}}@_D4wasi11__moduleRefZ // test the magic linker symbols via linkability of the following: diff --git a/tests/codegen/wasm_emscripten.d b/tests/codegen/wasm_emscripten.d new file mode 100644 index 00000000000..8b00a4ee9d1 --- /dev/null +++ b/tests/codegen/wasm_emscripten.d @@ -0,0 +1,11 @@ +// REQUIRES: target_WebAssembly + +// RUN: %ldc -mtriple=wasm32-unknown-emscripten -w -output-ll -of=%t.ll %s +// RUN: FileCheck %s < %t.ll + +// test predefined versions: +version (Emscripten) {} else static assert(0); + +void foo() {} + +// CHECK: target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-f128:64-n32:64-S128-ni:1:10:20" diff --git a/tests/compilable/no-integrated-as.d b/tests/compilable/no-integrated-as.d index 7e665836a8f..2e2aeef13f6 100644 --- a/tests/compilable/no-integrated-as.d +++ b/tests/compilable/no-integrated-as.d @@ -7,7 +7,7 @@ * LLVM 12: .section __minfo,"aw",@progbits * LLVM 13: .section __minfo,"awR",@progbits,unique,1 */ -// UNSUPPORTED: Linux && atleast_llvm1300 +// UNSUPPORTED: Linux module noIntegratedAs; diff --git a/tests/driver/fwarn-stack-size.d b/tests/driver/fwarn-stack-size.d index ba028c10a07..20743571c56 100644 --- a/tests/driver/fwarn-stack-size.d +++ b/tests/driver/fwarn-stack-size.d @@ -1,7 +1,5 @@ // Test --fwarn-stack-trace functionality -// REQUIRES: atleast_llvm1300 - // RUN: %ldc -c --fwarn-stack-size=2000 %s // RUN: %ldc -c --fwarn-stack-size=200 %s 2>&1 | FileCheck %s diff --git a/tests/instrument/finstrument_functions.d b/tests/instrument/finstrument_functions.d index 37e29b4aaba..352bd6ede09 100644 --- a/tests/instrument/finstrument_functions.d +++ b/tests/instrument/finstrument_functions.d @@ -2,9 +2,9 @@ void fun0 () { // CHECK-LABEL: define{{.*}} @{{.*}}4fun0FZv - // CHECK: [[RET1:%[0-9]]] = call {{i8\*|ptr}} @llvm.returnaddress(i32 0) + // CHECK: [[RET1:%[0-9]]] = call ptr @llvm.returnaddress(i32 0) // CHECK: call void @__cyg_profile_func_enter{{.*}}4fun0FZv{{.*}}[[RET1]] - // CHECK: [[RET2:%[0-9]]] = call {{i8\*|ptr}} @llvm.returnaddress(i32 0) + // CHECK: [[RET2:%[0-9]]] = call ptr @llvm.returnaddress(i32 0) // CHECK: call void @__cyg_profile_func_exit{{.*}}4fun0FZv{{.*}}[[RET2]] // CHECK-NEXT: ret return; diff --git a/tests/instrument/lit.local.cfg b/tests/instrument/lit.local.cfg index e97afbfa4bc..71968d9cd78 100644 --- a/tests/instrument/lit.local.cfg +++ b/tests/instrument/lit.local.cfg @@ -1,7 +1,5 @@ import platform -from glob import glob - -# Add "XRay_RT" feature on non-Windows, if the compiler-rt libraries are available +# Add "XRay_RT" feature on non-Windows if (platform.system() != 'Windows'): - if glob(os.path.join(config.ldc2_lib_dir, "*xray*")): + if 'xray' in config.enabled_rt_libs: config.available_features.add('XRay_RT') diff --git a/tests/lit.site.cfg.in b/tests/lit.site.cfg.in index 1e966a78805..b9c723803f8 100644 --- a/tests/lit.site.cfg.in +++ b/tests/lit.site.cfg.in @@ -37,6 +37,7 @@ config.ldc_host_arch = "@LLVM_NATIVE_ARCH@" config.ldc_with_lld = @LDC_WITH_LLD@ config.spirv_enabled = @LLVM_SPIRV_FOUND@ config.rt_supports_sanitizers = @RT_SUPPORT_SANITIZERS@ +config.enabled_rt_libs = set("@TEST_COMPILER_RT_LIBRARIES@".split(';')) config.shared_rt_libs_only = "@BUILD_SHARED_LIBS@" == "ON" config.name = 'LDC' @@ -86,7 +87,7 @@ config.available_features.add("llvm%d" % config.llvm_version) # config.llvm_version: 309, 400, 500, ... # plusoneable_llvmversion: 39, 40, 50, ... plusoneable_llvmversion = config.llvm_version // 10 + config.llvm_version%10 -for version in range(60, plusoneable_llvmversion+1): +for version in range(140, plusoneable_llvmversion+1): config.available_features.add("atleast_llvm%d0%d" % (version//10, version%10)) for version in range(plusoneable_llvmversion, 201): config.available_features.add("atmost_llvm%d0%d" % (version//10, version%10)) diff --git a/tests/plugins/addFuncEntryCall/Makefile b/tests/plugins/addFuncEntryCall/Makefile index e2c15152167..d934387e651 100644 --- a/tests/plugins/addFuncEntryCall/Makefile +++ b/tests/plugins/addFuncEntryCall/Makefile @@ -7,7 +7,6 @@ LLVM_CONFIG ?= llvm-config CXXFLAGS ?= -O3 CXXFLAGS += $(shell $(LLVM_CONFIG) --cxxflags) -fno-rtti -fpic -CXXFLAGS += -DLLVM_VERSION=$(LLVM_VERSION) # Remove all warning flags (they may or may not be supported by the compiler) CXXFLAGS := $(filter-out -W%,$(CXXFLAGS)) CXXFLAGS := $(filter-out -fcolor-diagnostics,$(CXXFLAGS)) diff --git a/tests/plugins/addFuncEntryCall/addFuncEntryCallPass.cpp b/tests/plugins/addFuncEntryCall/addFuncEntryCallPass.cpp index 72899b0ca31..c7c92f7e7eb 100644 --- a/tests/plugins/addFuncEntryCall/addFuncEntryCallPass.cpp +++ b/tests/plugins/addFuncEntryCall/addFuncEntryCallPass.cpp @@ -11,11 +11,9 @@ #include "llvm/IR/Function.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/Module.h" - -#if LLVM_VERSION < 1500 // legacy pass manager -#include "llvm/IR/LegacyPassManager.h" -#include "llvm/Transforms/IPO/PassManagerBuilder.h" -#endif +#include "llvm/IR/PassManager.h" +#include "llvm/Passes/PassBuilder.h" +#include "llvm/Passes/PassPlugin.h" using namespace llvm; @@ -54,26 +52,6 @@ bool FuncEntryCallPass::runOnFunction(Function &F) { } -#if LLVM_VERSION < 1500 // legacy pass manager - -static void addFuncEntryCallPass(const PassManagerBuilder &, - legacy::PassManagerBase &PM) { - PM.add(new FuncEntryCallPass()); -} -// Registration of the plugin's pass is done by the plugin's static constructor. -static RegisterStandardPasses - RegisterFuncEntryCallPass0(PassManagerBuilder::EP_EnabledOnOptLevel0, - addFuncEntryCallPass); - -#endif - - -#if LLVM_VERSION >= 1400 // new pass manager - -#include "llvm/IR/PassManager.h" -#include "llvm/Passes/PassBuilder.h" -#include "llvm/Passes/PassPlugin.h" - namespace { struct MyPass : public PassInfoMixin { @@ -111,5 +89,3 @@ llvmGetPassPluginInfo() { } }; } - -#endif // LLVM 14+ diff --git a/tests/plugins/addFuncEntryCall/testPlugin.d b/tests/plugins/addFuncEntryCall/testPlugin.d index dfda8098355..ac7733fad90 100644 --- a/tests/plugins/addFuncEntryCall/testPlugin.d +++ b/tests/plugins/addFuncEntryCall/testPlugin.d @@ -1,8 +1,7 @@ // REQUIRES: Plugins -// REQUIRES: atleast_llvm1400 // RUN: %gnu_make -f %S/Makefile -// RUN: %ldc --passmanager=new -c -output-ll -plugin=./addFuncEntryCallPass.so -of=%t.ll %s +// RUN: %ldc -c -output-ll -plugin=./addFuncEntryCallPass.so -of=%t.ll %s // RUN: FileCheck %s < %t.ll // CHECK: define {{.*}}testfunction diff --git a/tests/plugins/addFuncEntryCall/testPluginLegacy.d b/tests/plugins/addFuncEntryCall/testPluginLegacy.d deleted file mode 100644 index 98b49a4c030..00000000000 --- a/tests/plugins/addFuncEntryCall/testPluginLegacy.d +++ /dev/null @@ -1,15 +0,0 @@ -// REQUIRES: Plugins -// REQUIRES: atmost_llvm1409 - -// RUN: %gnu_make -f %S/Makefile -// RUN: %ldc --passmanager=legacy -c -output-ll -plugin=./addFuncEntryCallPass.so -of=%t.ll %s -// RUN: FileCheck %s < %t.ll - -// CHECK: define {{.*}}testfunction -int testfunction(int i) -{ - // CHECK-NEXT: call {{.*}}__test_funcentrycall - return i * 2; -} - -// CHECK-DAG: declare {{.*}}__test_funcentrycall diff --git a/tests/plugins/lit.local.cfg b/tests/plugins/lit.local.cfg index eb5b296e587..7fc1ad4047d 100644 --- a/tests/plugins/lit.local.cfg +++ b/tests/plugins/lit.local.cfg @@ -11,7 +11,6 @@ import glob if (config.plugins_supported): config.available_features.add('Plugins') config.environment['LLVM_CONFIG'] = os.path.join(config.llvm_tools_dir, 'llvm-config') - config.environment['LLVM_VERSION'] = str(config.llvm_version) # Set feature that tells us that the just-built LDC is ABI compatible with the host D compiler # For our tets, the required ABI compatibility seems OK since at least LDC 1.30. diff --git a/tests/sanitizers/fsanitize_thread.d b/tests/sanitizers/fsanitize_thread.d index e7e30a210f2..b883c6864c4 100644 --- a/tests/sanitizers/fsanitize_thread.d +++ b/tests/sanitizers/fsanitize_thread.d @@ -3,7 +3,7 @@ // RUN: %ldc -c -output-ll -g -fsanitize=thread -of=%t.ll %s && FileCheck %s < %t.ll // LLVM 14+: no more __tsan_func_{entry,exit} calls -// REQUIRES: atmost_llvm1309 +// XFAIL: * // CHECK: ; Function Attrs:{{.*}} sanitize_thread // CHECK-NEXT: define {{.*}}D16fsanitize_thread3foo diff --git a/tests/sanitizers/lit.local.cfg b/tests/sanitizers/lit.local.cfg index 82e35e28dd0..bdac8304b84 100644 --- a/tests/sanitizers/lit.local.cfg +++ b/tests/sanitizers/lit.local.cfg @@ -1,32 +1,31 @@ import os import platform -from glob import glob sys = platform.system() -# Add "LSan" feature, if the compiler-rt library is available -if glob(os.path.join(config.ldc2_lib_dir, "*lsan*")): +# Add "LSan" feature +if 'lsan' in config.enabled_rt_libs: config.available_features.add('LSan') # FreeBSD TSan doesn't seem to work, # Linux TSan currently only works with static druntime, # and there's no Windows TSan (yet?). if (sys != 'FreeBSD') and (sys != 'Windows') and not (sys == 'Linux' and config.shared_rt_libs_only): - if glob(os.path.join(config.ldc2_lib_dir, "*tsan*")): + if 'tsan' in config.enabled_rt_libs: config.available_features.add('TSan') # FreeBSD ASan and MSan don't cope well with ASLR (might do with FreeBSD 14 according to https://github.com/llvm/llvm-project/pull/73439) if sys != 'FreeBSD': - if glob(os.path.join(config.ldc2_lib_dir, "*asan*")): + if 'asan' in config.enabled_rt_libs: config.available_features.add('ASan') # MSan is supported on Linux, FreeBSD (modulo ASLR issue), and OpenBSD: https://clang.llvm.org/docs/MemorySanitizer.html#supported-platforms if (sys == 'Linux') or (sys == 'OpenBSD'): - if glob(os.path.join(config.ldc2_lib_dir, "*msan*")): + if 'msan' in config.enabled_rt_libs: config.available_features.add('MSan') -# Add "Fuzzer" feature, assuming the compiler-rt library is available -if glob(os.path.join(config.ldc2_lib_dir, "*fuzzer*")): +# Add "Fuzzer" feature +if 'fuzzer' in config.enabled_rt_libs: config.available_features.add('Fuzzer') if 'ASan' in config.available_features: diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index facb939472d..3fc3f035d06 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -1,6 +1,8 @@ # "tools" are supposed to be packaged with LDC. # (in contrast to "utils" which are only used for building / testing) +option(LDC_BUNDLE_LLVM_TOOLS "Build and install ldc-profgen and ldc-profdata utilities instead of using their llvm counterparts" TRUE) + ############################################################################# # Build ldc-prune-cache set(LDCPRUNECACHE_EXE ldc-prune-cache) @@ -23,47 +25,56 @@ build_d_executable( ) install(PROGRAMS ${LDCPRUNECACHE_EXE_FULL} DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) -############################################################################# -# Build ldc-profdata for converting profile data formats (source version depends on LLVM version) -set(LDCPROFDATA_SRC ldc-profdata/llvm-profdata-${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}.cpp) -if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LDCPROFDATA_SRC}) - add_executable(ldc-profdata ${LDCPROFDATA_SRC}) - set_target_properties( +if(LDC_BUNDLE_LLVM_TOOLS) + ############################################################################# + # Build ldc-profdata for converting profile data formats (source version depends on LLVM version) + set(LDCPROFDATA_SRC ldc-profdata/llvm-profdata-${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}.cpp) + if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LDCPROFDATA_SRC}) + add_executable(ldc-profdata ${LDCPROFDATA_SRC}) + set_target_properties( ldc-profdata PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin COMPILE_FLAGS "${LLVM_CXXFLAGS} ${LDC_CXXFLAGS}" LINK_FLAGS "${SANITIZE_LDFLAGS}" + ) + target_link_libraries(ldc-profdata ${LLVM_LIBRARIES} ${CMAKE_DL_LIBS} ${LLVM_LDFLAGS}) + install(TARGETS ldc-profdata DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) + + # Set path to executable, used by the lit testsuite. + set(LDCPROFDATA_BIN ${PROJECT_BINARY_DIR}/bin/ldc-profdata PARENT_SCOPE) + else() + message(WARNING "ldc-profdata source (${LDCPROFDATA_SRC}) not found") + endif() + + ############################################################################# + # Build ldc-profgen utility that generates a profile data file from given perf script + # data files for sample-based profile guided optimization (-fprofile-sample-use). + # https://llvm.org/docs/CommandGuide/llvm-profgen.html + # The source in ldc-profgen/ldc-profgen-xx.x is an unmodified copy of llvm's llvm-profgen source dir. + macro(add_llvm_tool llvm_name) + string(REPLACE "llvm-" "ldc-" ldc_name ${llvm_name}) + message(STATUS "Configuring ${ldc_name} build target") + add_executable(${ldc_name} ${ARGN}) + set_target_properties( + ${ldc_name} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin + COMPILE_FLAGS "${LLVM_CXXFLAGS} ${LDC_CXXFLAGS}" + LINK_FLAGS "${SANITIZE_LDFLAGS}" ) - target_link_libraries(ldc-profdata ${LLVM_LIBRARIES} ${CMAKE_DL_LIBS} ${LLVM_LDFLAGS}) - install(TARGETS ldc-profdata DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) + target_link_libraries(${ldc_name} ${LLVM_LIBRARIES} ${CMAKE_DL_LIBS} ${LLVM_LDFLAGS}) + install(TARGETS ${ldc_name} DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) + endmacro() + if (IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/ldc-profgen/ldc-profgen-${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}) + add_subdirectory(ldc-profgen/ldc-profgen-${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}) + # Set path to executable, potentially to be used by the lit tests in the future + set(LDCPROFGEN_BIN ${PROJECT_BINARY_DIR}/bin/ldc-profgen PARENT_SCOPE) + else() + message(WARNING "ldc-profgen source not found (${CMAKE_CURRENT_SOURCE_DIR}/ldc-profgen/ldc-profgen-${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR})") + endif() else() - message(WARNING "ldc-profdata source (${LDCPROFDATA_SRC}) not found") -endif() - -############################################################################# -# Build ldc-profgen utility that generates a profile data file from given perf script -# data files for sample-based profile guided optimization (-fprofile-sample-use). -# https://llvm.org/docs/CommandGuide/llvm-profgen.html -# The source in ldc-profgen/ldc-profgen-xx.x is an unmodified copy of llvm's llvm-profgen source dir. -if(LDC_LLVM_VER GREATER_EQUAL 1400) - macro(add_llvm_tool llvm_name) - string(REPLACE "llvm-" "ldc-" ldc_name ${llvm_name}) - message(STATUS "Configuring ${ldc_name} build target") - add_executable(${ldc_name} ${ARGN}) - set_target_properties( - ${ldc_name} PROPERTIES - RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin - COMPILE_FLAGS "${LLVM_CXXFLAGS} ${LDC_CXXFLAGS}" - LINK_FLAGS "${SANITIZE_LDFLAGS}" - ) - target_link_libraries(${ldc_name} ${LLVM_LIBRARIES} ${CMAKE_DL_LIBS} ${LLVM_LDFLAGS}) - install(TARGETS ${ldc_name} DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) - endmacro() - if (IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/ldc-profgen/ldc-profgen-${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}) - add_subdirectory(ldc-profgen/ldc-profgen-${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}) - else() - message(WARNING "ldc-profgen source not found (${CMAKE_CURRENT_SOURCE_DIR}/ldc-profgen/ldc-profgen-${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR})") - endif() + message(STATUS "Using llvm provided llvm-profdata and llvm-profgen") + set(LDCPROFDATA_BIN ${LLVM_ROOT_DIR}/bin/llvm-profdata PARENT_SCOPE) + set(LDCPROFGEN_BIN ${LLVM_ROOT_DIR}/bin/llvm-profgen PARENT_SCOPE) endif() ############################################################################# diff --git a/tools/ldc-profdata/llvm-profdata-11.0.cpp b/tools/ldc-profdata/llvm-profdata-11.0.cpp deleted file mode 100644 index 843f072a61c..00000000000 --- a/tools/ldc-profdata/llvm-profdata-11.0.cpp +++ /dev/null @@ -1,1344 +0,0 @@ -//===- llvm-profdata.cpp - LLVM profile data tool -------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// llvm-profdata merges .profdata files. -// -//===----------------------------------------------------------------------===// - -#include "llvm/ADT/SmallSet.h" -#include "llvm/ADT/SmallVector.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/IR/LLVMContext.h" -#include "llvm/ProfileData/InstrProfReader.h" -#include "llvm/ProfileData/InstrProfWriter.h" -#include "llvm/ProfileData/ProfileCommon.h" -#include "llvm/ProfileData/SampleProfReader.h" -#include "llvm/ProfileData/SampleProfWriter.h" -#include "llvm/Support/CommandLine.h" -#include "llvm/Support/Errc.h" -#include "llvm/Support/FileSystem.h" -#include "llvm/Support/Format.h" -#include "llvm/Support/FormattedStream.h" -#include "llvm/Support/InitLLVM.h" -#include "llvm/Support/MemoryBuffer.h" -#include "llvm/Support/Path.h" -#include "llvm/Support/ThreadPool.h" -#include "llvm/Support/Threading.h" -#include "llvm/Support/WithColor.h" -#include "llvm/Support/raw_ostream.h" -#include - -using namespace llvm; - -enum ProfileFormat { - PF_None = 0, - PF_Text, - PF_Compact_Binary, - PF_Ext_Binary, - PF_GCC, - PF_Binary -}; - -static void warn(Twine Message, std::string Whence = "", - std::string Hint = "") { - WithColor::warning(); - if (!Whence.empty()) - errs() << Whence << ": "; - errs() << Message << "\n"; - if (!Hint.empty()) - WithColor::note() << Hint << "\n"; -} - -static void exitWithError(Twine Message, std::string Whence = "", - std::string Hint = "") { - WithColor::error(); - if (!Whence.empty()) - errs() << Whence << ": "; - errs() << Message << "\n"; - if (!Hint.empty()) - WithColor::note() << Hint << "\n"; - ::exit(1); -} - -static void exitWithError(Error E, StringRef Whence = "") { - if (E.isA()) { - handleAllErrors(std::move(E), [&](const InstrProfError &IPE) { - instrprof_error instrError = IPE.get(); - StringRef Hint = ""; - if (instrError == instrprof_error::unrecognized_format) { - // Hint for common error of forgetting --sample for sample profiles. - Hint = "Perhaps you forgot to use the --sample option?"; - } - exitWithError(IPE.message(), std::string(Whence), std::string(Hint)); - }); - } - - exitWithError(toString(std::move(E)), std::string(Whence)); -} - -static void exitWithErrorCode(std::error_code EC, StringRef Whence = "") { - exitWithError(EC.message(), std::string(Whence)); -} - -namespace { -enum ProfileKinds { instr, sample }; -enum FailureMode { failIfAnyAreInvalid, failIfAllAreInvalid }; -} - -static void warnOrExitGivenError(FailureMode FailMode, std::error_code EC, - StringRef Whence = "") { - if (FailMode == failIfAnyAreInvalid) - exitWithErrorCode(EC, Whence); - else - warn(EC.message(), std::string(Whence)); -} - -static void handleMergeWriterError(Error E, StringRef WhenceFile = "", - StringRef WhenceFunction = "", - bool ShowHint = true) { - if (!WhenceFile.empty()) - errs() << WhenceFile << ": "; - if (!WhenceFunction.empty()) - errs() << WhenceFunction << ": "; - - auto IPE = instrprof_error::success; - E = handleErrors(std::move(E), - [&IPE](std::unique_ptr E) -> Error { - IPE = E->get(); - return Error(std::move(E)); - }); - errs() << toString(std::move(E)) << "\n"; - - if (ShowHint) { - StringRef Hint = ""; - if (IPE != instrprof_error::success) { - switch (IPE) { - case instrprof_error::hash_mismatch: - case instrprof_error::count_mismatch: - case instrprof_error::value_site_count_mismatch: - Hint = "Make sure that all profile data to be merged is generated " - "from the same binary."; - break; - default: - break; - } - } - - if (!Hint.empty()) - errs() << Hint << "\n"; - } -} - -namespace { -/// A remapper from original symbol names to new symbol names based on a file -/// containing a list of mappings from old name to new name. -class SymbolRemapper { - std::unique_ptr File; - DenseMap RemappingTable; - -public: - /// Build a SymbolRemapper from a file containing a list of old/new symbols. - static std::unique_ptr create(StringRef InputFile) { - auto BufOrError = MemoryBuffer::getFileOrSTDIN(InputFile); - if (!BufOrError) - exitWithErrorCode(BufOrError.getError(), InputFile); - - auto Remapper = std::make_unique(); - Remapper->File = std::move(BufOrError.get()); - - for (line_iterator LineIt(*Remapper->File, /*SkipBlanks=*/true, '#'); - !LineIt.is_at_eof(); ++LineIt) { - std::pair Parts = LineIt->split(' '); - if (Parts.first.empty() || Parts.second.empty() || - Parts.second.count(' ')) { - exitWithError("unexpected line in remapping file", - (InputFile + ":" + Twine(LineIt.line_number())).str(), - "expected 'old_symbol new_symbol'"); - } - Remapper->RemappingTable.insert(Parts); - } - return Remapper; - } - - /// Attempt to map the given old symbol into a new symbol. - /// - /// \return The new symbol, or \p Name if no such symbol was found. - StringRef operator()(StringRef Name) { - StringRef New = RemappingTable.lookup(Name); - return New.empty() ? Name : New; - } -}; -} - -struct WeightedFile { - std::string Filename; - uint64_t Weight; -}; -typedef SmallVector WeightedFileVector; - -/// Keep track of merged data and reported errors. -struct WriterContext { - std::mutex Lock; - InstrProfWriter Writer; - std::vector> Errors; - std::mutex &ErrLock; - SmallSet &WriterErrorCodes; - - WriterContext(bool IsSparse, std::mutex &ErrLock, - SmallSet &WriterErrorCodes) - : Lock(), Writer(IsSparse), Errors(), ErrLock(ErrLock), - WriterErrorCodes(WriterErrorCodes) {} -}; - -/// Computer the overlap b/w profile BaseFilename and TestFileName, -/// and store the program level result to Overlap. -static void overlapInput(const std::string &BaseFilename, - const std::string &TestFilename, WriterContext *WC, - OverlapStats &Overlap, - const OverlapFuncFilters &FuncFilter, - raw_fd_ostream &OS, bool IsCS) { - auto ReaderOrErr = InstrProfReader::create(TestFilename); - if (Error E = ReaderOrErr.takeError()) { - // Skip the empty profiles by returning sliently. - instrprof_error IPE = InstrProfError::take(std::move(E)); - if (IPE != instrprof_error::empty_raw_profile) - WC->Errors.emplace_back(make_error(IPE), TestFilename); - return; - } - - auto Reader = std::move(ReaderOrErr.get()); - for (auto &I : *Reader) { - OverlapStats FuncOverlap(OverlapStats::FunctionLevel); - FuncOverlap.setFuncInfo(I.Name, I.Hash); - - WC->Writer.overlapRecord(std::move(I), Overlap, FuncOverlap, FuncFilter); - FuncOverlap.dump(OS); - } -} - -/// Load an input into a writer context. -static void loadInput(const WeightedFile &Input, SymbolRemapper *Remapper, - WriterContext *WC) { - std::unique_lock CtxGuard{WC->Lock}; - - // Copy the filename, because llvm::ThreadPool copied the input "const - // WeightedFile &" by value, making a reference to the filename within it - // invalid outside of this packaged task. - std::string Filename = Input.Filename; - - auto ReaderOrErr = InstrProfReader::create(Input.Filename); - if (Error E = ReaderOrErr.takeError()) { - // Skip the empty profiles by returning sliently. - instrprof_error IPE = InstrProfError::take(std::move(E)); - if (IPE != instrprof_error::empty_raw_profile) - WC->Errors.emplace_back(make_error(IPE), Filename); - return; - } - - auto Reader = std::move(ReaderOrErr.get()); - bool IsIRProfile = Reader->isIRLevelProfile(); - bool HasCSIRProfile = Reader->hasCSIRLevelProfile(); - if (WC->Writer.setIsIRLevelProfile(IsIRProfile, HasCSIRProfile)) { - WC->Errors.emplace_back( - make_error( - "Merge IR generated profile with Clang generated profile.", - std::error_code()), - Filename); - return; - } - - for (auto &I : *Reader) { - if (Remapper) - I.Name = (*Remapper)(I.Name); - const StringRef FuncName = I.Name; - bool Reported = false; - WC->Writer.addRecord(std::move(I), Input.Weight, [&](Error E) { - if (Reported) { - consumeError(std::move(E)); - return; - } - Reported = true; - // Only show hint the first time an error occurs. - instrprof_error IPE = InstrProfError::take(std::move(E)); - std::unique_lock ErrGuard{WC->ErrLock}; - bool firstTime = WC->WriterErrorCodes.insert(IPE).second; - handleMergeWriterError(make_error(IPE), Input.Filename, - FuncName, firstTime); - }); - } - if (Reader->hasError()) - if (Error E = Reader->getError()) - WC->Errors.emplace_back(std::move(E), Filename); -} - -/// Merge the \p Src writer context into \p Dst. -static void mergeWriterContexts(WriterContext *Dst, WriterContext *Src) { - for (auto &ErrorPair : Src->Errors) - Dst->Errors.push_back(std::move(ErrorPair)); - Src->Errors.clear(); - - Dst->Writer.mergeRecordsFromWriter(std::move(Src->Writer), [&](Error E) { - instrprof_error IPE = InstrProfError::take(std::move(E)); - std::unique_lock ErrGuard{Dst->ErrLock}; - bool firstTime = Dst->WriterErrorCodes.insert(IPE).second; - if (firstTime) - warn(toString(make_error(IPE))); - }); -} - -static void writeInstrProfile(StringRef OutputFilename, - ProfileFormat OutputFormat, - InstrProfWriter &Writer) { - std::error_code EC; - raw_fd_ostream Output(OutputFilename.data(), EC, sys::fs::OF_None); - if (EC) - exitWithErrorCode(EC, OutputFilename); - - if (OutputFormat == PF_Text) { - if (Error E = Writer.writeText(Output)) - exitWithError(std::move(E)); - } else { - Writer.write(Output); - } -} - -static void mergeInstrProfile(const WeightedFileVector &Inputs, - SymbolRemapper *Remapper, - StringRef OutputFilename, - ProfileFormat OutputFormat, bool OutputSparse, - unsigned NumThreads, FailureMode FailMode) { - if (OutputFilename.compare("-") == 0) - exitWithError("Cannot write indexed profdata format to stdout."); - - if (OutputFormat != PF_Binary && OutputFormat != PF_Compact_Binary && - OutputFormat != PF_Ext_Binary && OutputFormat != PF_Text) - exitWithError("Unknown format is specified."); - - std::mutex ErrorLock; - SmallSet WriterErrorCodes; - - // If NumThreads is not specified, auto-detect a good default. - if (NumThreads == 0) - NumThreads = std::min(hardware_concurrency().compute_thread_count(), - unsigned((Inputs.size() + 1) / 2)); - // FIXME: There's a bug here, where setting NumThreads = Inputs.size() fails - // the merge_empty_profile.test because the InstrProfWriter.ProfileKind isn't - // merged, thus the emitted file ends up with a PF_Unknown kind. - - // Initialize the writer contexts. - SmallVector, 4> Contexts; - for (unsigned I = 0; I < NumThreads; ++I) - Contexts.emplace_back(std::make_unique( - OutputSparse, ErrorLock, WriterErrorCodes)); - - if (NumThreads == 1) { - for (const auto &Input : Inputs) - loadInput(Input, Remapper, Contexts[0].get()); - } else { - ThreadPool Pool(hardware_concurrency(NumThreads)); - - // Load the inputs in parallel (N/NumThreads serial steps). - unsigned Ctx = 0; - for (const auto &Input : Inputs) { - Pool.async(loadInput, Input, Remapper, Contexts[Ctx].get()); - Ctx = (Ctx + 1) % NumThreads; - } - Pool.wait(); - - // Merge the writer contexts together (~ lg(NumThreads) serial steps). - unsigned Mid = Contexts.size() / 2; - unsigned End = Contexts.size(); - assert(Mid > 0 && "Expected more than one context"); - do { - for (unsigned I = 0; I < Mid; ++I) - Pool.async(mergeWriterContexts, Contexts[I].get(), - Contexts[I + Mid].get()); - Pool.wait(); - if (End & 1) { - Pool.async(mergeWriterContexts, Contexts[0].get(), - Contexts[End - 1].get()); - Pool.wait(); - } - End = Mid; - Mid /= 2; - } while (Mid > 0); - } - - // Handle deferred errors encountered during merging. If the number of errors - // is equal to the number of inputs the merge failed. - unsigned NumErrors = 0; - for (std::unique_ptr &WC : Contexts) { - for (auto &ErrorPair : WC->Errors) { - ++NumErrors; - warn(toString(std::move(ErrorPair.first)), ErrorPair.second); - } - } - if (NumErrors == Inputs.size() || - (NumErrors > 0 && FailMode == failIfAnyAreInvalid)) - exitWithError("No profiles could be merged."); - - writeInstrProfile(OutputFilename, OutputFormat, Contexts[0]->Writer); -} - -/// Make a copy of the given function samples with all symbol names remapped -/// by the provided symbol remapper. -static sampleprof::FunctionSamples -remapSamples(const sampleprof::FunctionSamples &Samples, - SymbolRemapper &Remapper, sampleprof_error &Error) { - sampleprof::FunctionSamples Result; - Result.setName(Remapper(Samples.getName())); - Result.addTotalSamples(Samples.getTotalSamples()); - Result.addHeadSamples(Samples.getHeadSamples()); - for (const auto &BodySample : Samples.getBodySamples()) { - Result.addBodySamples(BodySample.first.LineOffset, - BodySample.first.Discriminator, - BodySample.second.getSamples()); - for (const auto &Target : BodySample.second.getCallTargets()) { - Result.addCalledTargetSamples(BodySample.first.LineOffset, - BodySample.first.Discriminator, - Remapper(Target.first()), Target.second); - } - } - for (const auto &CallsiteSamples : Samples.getCallsiteSamples()) { - sampleprof::FunctionSamplesMap &Target = - Result.functionSamplesAt(CallsiteSamples.first); - for (const auto &Callsite : CallsiteSamples.second) { - sampleprof::FunctionSamples Remapped = - remapSamples(Callsite.second, Remapper, Error); - MergeResult(Error, - Target[std::string(Remapped.getName())].merge(Remapped)); - } - } - return Result; -} - -static sampleprof::SampleProfileFormat FormatMap[] = { - sampleprof::SPF_None, - sampleprof::SPF_Text, - sampleprof::SPF_Compact_Binary, - sampleprof::SPF_Ext_Binary, - sampleprof::SPF_GCC, - sampleprof::SPF_Binary}; - -static std::unique_ptr -getInputFileBuf(const StringRef &InputFile) { - if (InputFile == "") - return {}; - - auto BufOrError = MemoryBuffer::getFileOrSTDIN(InputFile); - if (!BufOrError) - exitWithErrorCode(BufOrError.getError(), InputFile); - - return std::move(*BufOrError); -} - -static void populateProfileSymbolList(MemoryBuffer *Buffer, - sampleprof::ProfileSymbolList &PSL) { - if (!Buffer) - return; - - SmallVector SymbolVec; - StringRef Data = Buffer->getBuffer(); - Data.split(SymbolVec, '\n', /*MaxSplit=*/-1, /*KeepEmpty=*/false); - - for (StringRef symbol : SymbolVec) - PSL.add(symbol); -} - -static void handleExtBinaryWriter(sampleprof::SampleProfileWriter &Writer, - ProfileFormat OutputFormat, - MemoryBuffer *Buffer, - sampleprof::ProfileSymbolList &WriterList, - bool CompressAllSections, bool UseMD5, - bool GenPartialProfile) { - populateProfileSymbolList(Buffer, WriterList); - if (WriterList.size() > 0 && OutputFormat != PF_Ext_Binary) - warn("Profile Symbol list is not empty but the output format is not " - "ExtBinary format. The list will be lost in the output. "); - - Writer.setProfileSymbolList(&WriterList); - - if (CompressAllSections) { - if (OutputFormat != PF_Ext_Binary) - warn("-compress-all-section is ignored. Specify -extbinary to enable it"); - else - Writer.setToCompressAllSections(); - } - if (UseMD5) { - if (OutputFormat != PF_Ext_Binary) - warn("-use-md5 is ignored. Specify -extbinary to enable it"); - else - Writer.setUseMD5(); - } - if (GenPartialProfile) { - if (OutputFormat != PF_Ext_Binary) - warn("-gen-partial-profile is ignored. Specify -extbinary to enable it"); - else - Writer.setPartialProfile(); - } -} - -static void -mergeSampleProfile(const WeightedFileVector &Inputs, SymbolRemapper *Remapper, - StringRef OutputFilename, ProfileFormat OutputFormat, - StringRef ProfileSymbolListFile, bool CompressAllSections, - bool UseMD5, bool GenPartialProfile, FailureMode FailMode) { - using namespace sampleprof; - StringMap ProfileMap; - SmallVector, 5> Readers; - LLVMContext Context; - sampleprof::ProfileSymbolList WriterList; - for (const auto &Input : Inputs) { - auto ReaderOrErr = SampleProfileReader::create(Input.Filename, Context); - if (std::error_code EC = ReaderOrErr.getError()) { - warnOrExitGivenError(FailMode, EC, Input.Filename); - continue; - } - - // We need to keep the readers around until after all the files are - // read so that we do not lose the function names stored in each - // reader's memory. The function names are needed to write out the - // merged profile map. - Readers.push_back(std::move(ReaderOrErr.get())); - const auto Reader = Readers.back().get(); - if (std::error_code EC = Reader->read()) { - warnOrExitGivenError(FailMode, EC, Input.Filename); - Readers.pop_back(); - continue; - } - - StringMap &Profiles = Reader->getProfiles(); - for (StringMap::iterator I = Profiles.begin(), - E = Profiles.end(); - I != E; ++I) { - sampleprof_error Result = sampleprof_error::success; - FunctionSamples Remapped = - Remapper ? remapSamples(I->second, *Remapper, Result) - : FunctionSamples(); - FunctionSamples &Samples = Remapper ? Remapped : I->second; - StringRef FName = Samples.getName(); - MergeResult(Result, ProfileMap[FName].merge(Samples, Input.Weight)); - if (Result != sampleprof_error::success) { - std::error_code EC = make_error_code(Result); - handleMergeWriterError(errorCodeToError(EC), Input.Filename, FName); - } - } - - std::unique_ptr ReaderList = - Reader->getProfileSymbolList(); - if (ReaderList) - WriterList.merge(*ReaderList); - } - auto WriterOrErr = - SampleProfileWriter::create(OutputFilename, FormatMap[OutputFormat]); - if (std::error_code EC = WriterOrErr.getError()) - exitWithErrorCode(EC, OutputFilename); - - auto Writer = std::move(WriterOrErr.get()); - // WriterList will have StringRef refering to string in Buffer. - // Make sure Buffer lives as long as WriterList. - auto Buffer = getInputFileBuf(ProfileSymbolListFile); - handleExtBinaryWriter(*Writer, OutputFormat, Buffer.get(), WriterList, - CompressAllSections, UseMD5, GenPartialProfile); - Writer->write(ProfileMap); -} - -static WeightedFile parseWeightedFile(const StringRef &WeightedFilename) { - StringRef WeightStr, FileName; - std::tie(WeightStr, FileName) = WeightedFilename.split(','); - - uint64_t Weight; - if (WeightStr.getAsInteger(10, Weight) || Weight < 1) - exitWithError("Input weight must be a positive integer."); - - return {std::string(FileName), Weight}; -} - -static void addWeightedInput(WeightedFileVector &WNI, const WeightedFile &WF) { - StringRef Filename = WF.Filename; - uint64_t Weight = WF.Weight; - - // If it's STDIN just pass it on. - if (Filename == "-") { - WNI.push_back({std::string(Filename), Weight}); - return; - } - - llvm::sys::fs::file_status Status; - llvm::sys::fs::status(Filename, Status); - if (!llvm::sys::fs::exists(Status)) - exitWithErrorCode(make_error_code(errc::no_such_file_or_directory), - Filename); - // If it's a source file, collect it. - if (llvm::sys::fs::is_regular_file(Status)) { - WNI.push_back({std::string(Filename), Weight}); - return; - } - - if (llvm::sys::fs::is_directory(Status)) { - std::error_code EC; - for (llvm::sys::fs::recursive_directory_iterator F(Filename, EC), E; - F != E && !EC; F.increment(EC)) { - if (llvm::sys::fs::is_regular_file(F->path())) { - addWeightedInput(WNI, {F->path(), Weight}); - } - } - if (EC) - exitWithErrorCode(EC, Filename); - } -} - -static void parseInputFilenamesFile(MemoryBuffer *Buffer, - WeightedFileVector &WFV) { - if (!Buffer) - return; - - SmallVector Entries; - StringRef Data = Buffer->getBuffer(); - Data.split(Entries, '\n', /*MaxSplit=*/-1, /*KeepEmpty=*/false); - for (const StringRef &FileWeightEntry : Entries) { - StringRef SanitizedEntry = FileWeightEntry.trim(" \t\v\f\r"); - // Skip comments. - if (SanitizedEntry.startswith("#")) - continue; - // If there's no comma, it's an unweighted profile. - else if (SanitizedEntry.find(',') == StringRef::npos) - addWeightedInput(WFV, {std::string(SanitizedEntry), 1}); - else - addWeightedInput(WFV, parseWeightedFile(SanitizedEntry)); - } -} - -static int merge_main(int argc, const char *argv[]) { - cl::list InputFilenames(cl::Positional, - cl::desc("")); - cl::list WeightedInputFilenames("weighted-input", - cl::desc(",")); - cl::opt InputFilenamesFile( - "input-files", cl::init(""), - cl::desc("Path to file containing newline-separated " - "[,] entries")); - cl::alias InputFilenamesFileA("f", cl::desc("Alias for --input-files"), - cl::aliasopt(InputFilenamesFile)); - cl::opt DumpInputFileList( - "dump-input-file-list", cl::init(false), cl::Hidden, - cl::desc("Dump the list of input files and their weights, then exit")); - cl::opt RemappingFile("remapping-file", cl::value_desc("file"), - cl::desc("Symbol remapping file")); - cl::alias RemappingFileA("r", cl::desc("Alias for --remapping-file"), - cl::aliasopt(RemappingFile)); - cl::opt OutputFilename("output", cl::value_desc("output"), - cl::init("-"), cl::Required, - cl::desc("Output file")); - cl::alias OutputFilenameA("o", cl::desc("Alias for --output"), - cl::aliasopt(OutputFilename)); - cl::opt ProfileKind( - cl::desc("Profile kind:"), cl::init(instr), - cl::values(clEnumVal(instr, "Instrumentation profile (default)"), - clEnumVal(sample, "Sample profile"))); - cl::opt OutputFormat( - cl::desc("Format of output profile"), cl::init(PF_Binary), - cl::values( - clEnumValN(PF_Binary, "binary", "Binary encoding (default)"), - clEnumValN(PF_Compact_Binary, "compbinary", - "Compact binary encoding"), - clEnumValN(PF_Ext_Binary, "extbinary", "Extensible binary encoding"), - clEnumValN(PF_Text, "text", "Text encoding"), - clEnumValN(PF_GCC, "gcc", - "GCC encoding (only meaningful for -sample)"))); - cl::opt FailureMode( - "failure-mode", cl::init(failIfAnyAreInvalid), cl::desc("Failure mode:"), - cl::values(clEnumValN(failIfAnyAreInvalid, "any", - "Fail if any profile is invalid."), - clEnumValN(failIfAllAreInvalid, "all", - "Fail only if all profiles are invalid."))); - cl::opt OutputSparse("sparse", cl::init(false), - cl::desc("Generate a sparse profile (only meaningful for -instr)")); - cl::opt NumThreads( - "num-threads", cl::init(0), - cl::desc("Number of merge threads to use (default: autodetect)")); - cl::alias NumThreadsA("j", cl::desc("Alias for --num-threads"), - cl::aliasopt(NumThreads)); - cl::opt ProfileSymbolListFile( - "prof-sym-list", cl::init(""), - cl::desc("Path to file containing the list of function symbols " - "used to populate profile symbol list")); - cl::opt CompressAllSections( - "compress-all-sections", cl::init(false), cl::Hidden, - cl::desc("Compress all sections when writing the profile (only " - "meaningful for -extbinary)")); - cl::opt UseMD5( - "use-md5", cl::init(false), cl::Hidden, - cl::desc("Choose to use MD5 to represent string in name table (only " - "meaningful for -extbinary)")); - cl::opt GenPartialProfile( - "gen-partial-profile", cl::init(false), cl::Hidden, - cl::desc("Generate a partial profile (only meaningful for -extbinary)")); - - cl::ParseCommandLineOptions(argc, argv, "LLVM profile data merger\n"); - - WeightedFileVector WeightedInputs; - for (StringRef Filename : InputFilenames) - addWeightedInput(WeightedInputs, {std::string(Filename), 1}); - for (StringRef WeightedFilename : WeightedInputFilenames) - addWeightedInput(WeightedInputs, parseWeightedFile(WeightedFilename)); - - // Make sure that the file buffer stays alive for the duration of the - // weighted input vector's lifetime. - auto Buffer = getInputFileBuf(InputFilenamesFile); - parseInputFilenamesFile(Buffer.get(), WeightedInputs); - - if (WeightedInputs.empty()) - exitWithError("No input files specified. See " + - sys::path::filename(argv[0]) + " -help"); - - if (DumpInputFileList) { - for (auto &WF : WeightedInputs) - outs() << WF.Weight << "," << WF.Filename << "\n"; - return 0; - } - - std::unique_ptr Remapper; - if (!RemappingFile.empty()) - Remapper = SymbolRemapper::create(RemappingFile); - - if (ProfileKind == instr) - mergeInstrProfile(WeightedInputs, Remapper.get(), OutputFilename, - OutputFormat, OutputSparse, NumThreads, FailureMode); - else - mergeSampleProfile(WeightedInputs, Remapper.get(), OutputFilename, - OutputFormat, ProfileSymbolListFile, CompressAllSections, - UseMD5, GenPartialProfile, FailureMode); - - return 0; -} - -/// Computer the overlap b/w profile BaseFilename and profile TestFilename. -static void overlapInstrProfile(const std::string &BaseFilename, - const std::string &TestFilename, - const OverlapFuncFilters &FuncFilter, - raw_fd_ostream &OS, bool IsCS) { - std::mutex ErrorLock; - SmallSet WriterErrorCodes; - WriterContext Context(false, ErrorLock, WriterErrorCodes); - WeightedFile WeightedInput{BaseFilename, 1}; - OverlapStats Overlap; - Error E = Overlap.accumulateCounts(BaseFilename, TestFilename, IsCS); - if (E) - exitWithError(std::move(E), "Error in getting profile count sums"); - if (Overlap.Base.CountSum < 1.0f) { - OS << "Sum of edge counts for profile " << BaseFilename << " is 0.\n"; - exit(0); - } - if (Overlap.Test.CountSum < 1.0f) { - OS << "Sum of edge counts for profile " << TestFilename << " is 0.\n"; - exit(0); - } - loadInput(WeightedInput, nullptr, &Context); - overlapInput(BaseFilename, TestFilename, &Context, Overlap, FuncFilter, OS, - IsCS); - Overlap.dump(OS); -} - -static int overlap_main(int argc, const char *argv[]) { - cl::opt BaseFilename(cl::Positional, cl::Required, - cl::desc("")); - cl::opt TestFilename(cl::Positional, cl::Required, - cl::desc("")); - cl::opt Output("output", cl::value_desc("output"), cl::init("-"), - cl::desc("Output file")); - cl::alias OutputA("o", cl::desc("Alias for --output"), cl::aliasopt(Output)); - cl::opt IsCS("cs", cl::init(false), - cl::desc("For context sensitive counts")); - cl::opt ValueCutoff( - "value-cutoff", cl::init(-1), - cl::desc( - "Function level overlap information for every function in test " - "profile with max count value greater then the parameter value")); - cl::opt FuncNameFilter( - "function", - cl::desc("Function level overlap information for matching functions")); - cl::ParseCommandLineOptions(argc, argv, "LLVM profile data overlap tool\n"); - - std::error_code EC; - raw_fd_ostream OS(Output.data(), EC, sys::fs::OF_Text); - if (EC) - exitWithErrorCode(EC, Output); - - overlapInstrProfile(BaseFilename, TestFilename, - OverlapFuncFilters{ValueCutoff, FuncNameFilter}, OS, - IsCS); - - return 0; -} - -typedef struct ValueSitesStats { - ValueSitesStats() - : TotalNumValueSites(0), TotalNumValueSitesWithValueProfile(0), - TotalNumValues(0) {} - uint64_t TotalNumValueSites; - uint64_t TotalNumValueSitesWithValueProfile; - uint64_t TotalNumValues; - std::vector ValueSitesHistogram; -} ValueSitesStats; - -static void traverseAllValueSites(const InstrProfRecord &Func, uint32_t VK, - ValueSitesStats &Stats, raw_fd_ostream &OS, - InstrProfSymtab *Symtab) { - uint32_t NS = Func.getNumValueSites(VK); - Stats.TotalNumValueSites += NS; - for (size_t I = 0; I < NS; ++I) { - uint32_t NV = Func.getNumValueDataForSite(VK, I); - std::unique_ptr VD = Func.getValueForSite(VK, I); - Stats.TotalNumValues += NV; - if (NV) { - Stats.TotalNumValueSitesWithValueProfile++; - if (NV > Stats.ValueSitesHistogram.size()) - Stats.ValueSitesHistogram.resize(NV, 0); - Stats.ValueSitesHistogram[NV - 1]++; - } - - uint64_t SiteSum = 0; - for (uint32_t V = 0; V < NV; V++) - SiteSum += VD[V].Count; - if (SiteSum == 0) - SiteSum = 1; - - for (uint32_t V = 0; V < NV; V++) { - OS << "\t[ " << format("%2u", I) << ", "; - if (Symtab == nullptr) - OS << format("%4" PRIu64, VD[V].Value); - else - OS << Symtab->getFuncName(VD[V].Value); - OS << ", " << format("%10" PRId64, VD[V].Count) << " ] (" - << format("%.2f%%", (VD[V].Count * 100.0 / SiteSum)) << ")\n"; - } - } -} - -static void showValueSitesStats(raw_fd_ostream &OS, uint32_t VK, - ValueSitesStats &Stats) { - OS << " Total number of sites: " << Stats.TotalNumValueSites << "\n"; - OS << " Total number of sites with values: " - << Stats.TotalNumValueSitesWithValueProfile << "\n"; - OS << " Total number of profiled values: " << Stats.TotalNumValues << "\n"; - - OS << " Value sites histogram:\n\tNumTargets, SiteCount\n"; - for (unsigned I = 0; I < Stats.ValueSitesHistogram.size(); I++) { - if (Stats.ValueSitesHistogram[I] > 0) - OS << "\t" << I + 1 << ", " << Stats.ValueSitesHistogram[I] << "\n"; - } -} - -static int showInstrProfile(const std::string &Filename, bool ShowCounts, - uint32_t TopN, bool ShowIndirectCallTargets, - bool ShowMemOPSizes, bool ShowDetailedSummary, - std::vector DetailedSummaryCutoffs, - bool ShowAllFunctions, bool ShowCS, - uint64_t ValueCutoff, bool OnlyListBelow, - const std::string &ShowFunction, bool TextFormat, - raw_fd_ostream &OS) { - auto ReaderOrErr = InstrProfReader::create(Filename); - std::vector Cutoffs = std::move(DetailedSummaryCutoffs); - if (ShowDetailedSummary && Cutoffs.empty()) { - Cutoffs = {800000, 900000, 950000, 990000, 999000, 999900, 999990}; - } - InstrProfSummaryBuilder Builder(std::move(Cutoffs)); - if (Error E = ReaderOrErr.takeError()) - exitWithError(std::move(E), Filename); - - auto Reader = std::move(ReaderOrErr.get()); - bool IsIRInstr = Reader->isIRLevelProfile(); - size_t ShownFunctions = 0; - size_t BelowCutoffFunctions = 0; - int NumVPKind = IPVK_Last - IPVK_First + 1; - std::vector VPStats(NumVPKind); - - auto MinCmp = [](const std::pair &v1, - const std::pair &v2) { - return v1.second > v2.second; - }; - - std::priority_queue, - std::vector>, - decltype(MinCmp)> - HottestFuncs(MinCmp); - - if (!TextFormat && OnlyListBelow) { - OS << "The list of functions with the maximum counter less than " - << ValueCutoff << ":\n"; - } - - // Add marker so that IR-level instrumentation round-trips properly. - if (TextFormat && IsIRInstr) - OS << ":ir\n"; - - for (const auto &Func : *Reader) { - if (Reader->isIRLevelProfile()) { - bool FuncIsCS = NamedInstrProfRecord::hasCSFlagInHash(Func.Hash); - if (FuncIsCS != ShowCS) - continue; - } - bool Show = - ShowAllFunctions || (!ShowFunction.empty() && - Func.Name.find(ShowFunction) != Func.Name.npos); - - bool doTextFormatDump = (Show && TextFormat); - - if (doTextFormatDump) { - InstrProfSymtab &Symtab = Reader->getSymtab(); - InstrProfWriter::writeRecordInText(Func.Name, Func.Hash, Func, Symtab, - OS); - continue; - } - - assert(Func.Counts.size() > 0 && "function missing entry counter"); - Builder.addRecord(Func); - - uint64_t FuncMax = 0; - uint64_t FuncSum = 0; - for (size_t I = 0, E = Func.Counts.size(); I < E; ++I) { - FuncMax = std::max(FuncMax, Func.Counts[I]); - FuncSum += Func.Counts[I]; - } - - if (FuncMax < ValueCutoff) { - ++BelowCutoffFunctions; - if (OnlyListBelow) { - OS << " " << Func.Name << ": (Max = " << FuncMax - << " Sum = " << FuncSum << ")\n"; - } - continue; - } else if (OnlyListBelow) - continue; - - if (TopN) { - if (HottestFuncs.size() == TopN) { - if (HottestFuncs.top().second < FuncMax) { - HottestFuncs.pop(); - HottestFuncs.emplace(std::make_pair(std::string(Func.Name), FuncMax)); - } - } else - HottestFuncs.emplace(std::make_pair(std::string(Func.Name), FuncMax)); - } - - if (Show) { - if (!ShownFunctions) - OS << "Counters:\n"; - - ++ShownFunctions; - - OS << " " << Func.Name << ":\n" - << " Hash: " << format("0x%016" PRIx64, Func.Hash) << "\n" - << " Counters: " << Func.Counts.size() << "\n"; - if (!IsIRInstr) - OS << " Function count: " << Func.Counts[0] << "\n"; - - if (ShowIndirectCallTargets) - OS << " Indirect Call Site Count: " - << Func.getNumValueSites(IPVK_IndirectCallTarget) << "\n"; - - uint32_t NumMemOPCalls = Func.getNumValueSites(IPVK_MemOPSize); - if (ShowMemOPSizes && NumMemOPCalls > 0) - OS << " Number of Memory Intrinsics Calls: " << NumMemOPCalls - << "\n"; - - if (ShowCounts) { - OS << " Block counts: ["; - size_t Start = (IsIRInstr ? 0 : 1); - for (size_t I = Start, E = Func.Counts.size(); I < E; ++I) { - OS << (I == Start ? "" : ", ") << Func.Counts[I]; - } - OS << "]\n"; - } - - if (ShowIndirectCallTargets) { - OS << " Indirect Target Results:\n"; - traverseAllValueSites(Func, IPVK_IndirectCallTarget, - VPStats[IPVK_IndirectCallTarget], OS, - &(Reader->getSymtab())); - } - - if (ShowMemOPSizes && NumMemOPCalls > 0) { - OS << " Memory Intrinsic Size Results:\n"; - traverseAllValueSites(Func, IPVK_MemOPSize, VPStats[IPVK_MemOPSize], OS, - nullptr); - } - } - } - if (Reader->hasError()) - exitWithError(Reader->getError(), Filename); - - if (TextFormat) - return 0; - std::unique_ptr PS(Builder.getSummary()); - OS << "Instrumentation level: " - << (Reader->isIRLevelProfile() ? "IR" : "Front-end") << "\n"; - if (ShowAllFunctions || !ShowFunction.empty()) - OS << "Functions shown: " << ShownFunctions << "\n"; - OS << "Total functions: " << PS->getNumFunctions() << "\n"; - if (ValueCutoff > 0) { - OS << "Number of functions with maximum count (< " << ValueCutoff - << "): " << BelowCutoffFunctions << "\n"; - OS << "Number of functions with maximum count (>= " << ValueCutoff - << "): " << PS->getNumFunctions() - BelowCutoffFunctions << "\n"; - } - OS << "Maximum function count: " << PS->getMaxFunctionCount() << "\n"; - OS << "Maximum internal block count: " << PS->getMaxInternalCount() << "\n"; - - if (TopN) { - std::vector> SortedHottestFuncs; - while (!HottestFuncs.empty()) { - SortedHottestFuncs.emplace_back(HottestFuncs.top()); - HottestFuncs.pop(); - } - OS << "Top " << TopN - << " functions with the largest internal block counts: \n"; - for (auto &hotfunc : llvm::reverse(SortedHottestFuncs)) - OS << " " << hotfunc.first << ", max count = " << hotfunc.second << "\n"; - } - - if (ShownFunctions && ShowIndirectCallTargets) { - OS << "Statistics for indirect call sites profile:\n"; - showValueSitesStats(OS, IPVK_IndirectCallTarget, - VPStats[IPVK_IndirectCallTarget]); - } - - if (ShownFunctions && ShowMemOPSizes) { - OS << "Statistics for memory intrinsic calls sizes profile:\n"; - showValueSitesStats(OS, IPVK_MemOPSize, VPStats[IPVK_MemOPSize]); - } - - if (ShowDetailedSummary) { - OS << "Total number of blocks: " << PS->getNumCounts() << "\n"; - OS << "Total count: " << PS->getTotalCount() << "\n"; - PS->printDetailedSummary(OS); - } - return 0; -} - -static void showSectionInfo(sampleprof::SampleProfileReader *Reader, - raw_fd_ostream &OS) { - if (!Reader->dumpSectionInfo(OS)) { - WithColor::warning() << "-show-sec-info-only is only supported for " - << "sample profile in extbinary format and is " - << "ignored for other formats.\n"; - return; - } -} - -namespace { -struct HotFuncInfo { - StringRef FuncName; - uint64_t TotalCount; - double TotalCountPercent; - uint64_t MaxCount; - uint64_t EntryCount; - - HotFuncInfo() - : FuncName(), TotalCount(0), TotalCountPercent(0.0f), MaxCount(0), - EntryCount(0) {} - - HotFuncInfo(StringRef FN, uint64_t TS, double TSP, uint64_t MS, uint64_t ES) - : FuncName(FN), TotalCount(TS), TotalCountPercent(TSP), MaxCount(MS), - EntryCount(ES) {} -}; -} // namespace - -// Print out detailed information about hot functions in PrintValues vector. -// Users specify titles and offset of every columns through ColumnTitle and -// ColumnOffset. The size of ColumnTitle and ColumnOffset need to be the same -// and at least 4. Besides, users can optionally give a HotFuncMetric string to -// print out or let it be an empty string. -static void dumpHotFunctionList(const std::vector &ColumnTitle, - const std::vector &ColumnOffset, - const std::vector &PrintValues, - uint64_t HotFuncCount, uint64_t TotalFuncCount, - uint64_t HotProfCount, uint64_t TotalProfCount, - const std::string &HotFuncMetric, - raw_fd_ostream &OS) { - assert(ColumnOffset.size() == ColumnTitle.size()); - assert(ColumnTitle.size() >= 4); - assert(TotalFuncCount > 0); - double TotalProfPercent = 0; - if (TotalProfCount > 0) - TotalProfPercent = ((double)HotProfCount) / TotalProfCount * 100; - - formatted_raw_ostream FOS(OS); - FOS << HotFuncCount << " out of " << TotalFuncCount - << " functions with profile (" - << format("%.2f%%", (((double)HotFuncCount) / TotalFuncCount * 100)) - << ") are considered hot functions"; - if (!HotFuncMetric.empty()) - FOS << " (" << HotFuncMetric << ")"; - FOS << ".\n"; - FOS << HotProfCount << " out of " << TotalProfCount << " profile counts (" - << format("%.2f%%", TotalProfPercent) << ") are from hot functions.\n"; - - for (size_t I = 0; I < ColumnTitle.size(); ++I) { - FOS.PadToColumn(ColumnOffset[I]); - FOS << ColumnTitle[I]; - } - FOS << "\n"; - - for (const HotFuncInfo &R : PrintValues) { - FOS.PadToColumn(ColumnOffset[0]); - FOS << R.TotalCount << " (" << format("%.2f%%", R.TotalCountPercent) << ")"; - FOS.PadToColumn(ColumnOffset[1]); - FOS << R.MaxCount; - FOS.PadToColumn(ColumnOffset[2]); - FOS << R.EntryCount; - FOS.PadToColumn(ColumnOffset[3]); - FOS << R.FuncName << "\n"; - } - return; -} - -static int -showHotFunctionList(const StringMap &Profiles, - ProfileSummary &PS, raw_fd_ostream &OS) { - using namespace sampleprof; - - const uint32_t HotFuncCutoff = 990000; - auto &SummaryVector = PS.getDetailedSummary(); - uint64_t MinCountThreshold = 0; - for (const ProfileSummaryEntry &SummaryEntry : SummaryVector) { - if (SummaryEntry.Cutoff == HotFuncCutoff) { - MinCountThreshold = SummaryEntry.MinCount; - break; - } - } - assert(MinCountThreshold != 0); - - // Traverse all functions in the profile and keep only hot functions. - // The following loop also calculates the sum of total samples of all - // functions. - std::multimap, - std::greater> - HotFunc; - uint64_t ProfileTotalSample = 0; - uint64_t HotFuncSample = 0; - uint64_t HotFuncCount = 0; - uint64_t MaxCount = 0; - for (const auto &I : Profiles) { - const FunctionSamples &FuncProf = I.second; - ProfileTotalSample += FuncProf.getTotalSamples(); - MaxCount = FuncProf.getMaxCountInside(); - - // MinCountThreshold is a block/line threshold computed for a given cutoff. - // We intentionally compare the maximum sample count in a function with this - // threshold to get an approximate threshold for hot functions. - if (MaxCount >= MinCountThreshold) { - HotFunc.emplace(FuncProf.getTotalSamples(), - std::make_pair(&(I.second), MaxCount)); - HotFuncSample += FuncProf.getTotalSamples(); - ++HotFuncCount; - } - } - - std::vector ColumnTitle{"Total sample (%)", "Max sample", - "Entry sample", "Function name"}; - std::vector ColumnOffset{0, 24, 42, 58}; - std::string Metric = - std::string("max sample >= ") + std::to_string(MinCountThreshold); - std::vector PrintValues; - for (const auto &FuncPair : HotFunc) { - const FunctionSamples &Func = *FuncPair.second.first; - double TotalSamplePercent = - (ProfileTotalSample > 0) - ? (Func.getTotalSamples() * 100.0) / ProfileTotalSample - : 0; - PrintValues.emplace_back(HotFuncInfo( - Func.getFuncName(), Func.getTotalSamples(), TotalSamplePercent, - FuncPair.second.second, Func.getEntrySamples())); - } - dumpHotFunctionList(ColumnTitle, ColumnOffset, PrintValues, HotFuncCount, - Profiles.size(), HotFuncSample, ProfileTotalSample, - Metric, OS); - - return 0; -} - -static int showSampleProfile(const std::string &Filename, bool ShowCounts, - bool ShowAllFunctions, bool ShowDetailedSummary, - const std::string &ShowFunction, - bool ShowProfileSymbolList, - bool ShowSectionInfoOnly, bool ShowHotFuncList, - raw_fd_ostream &OS) { - using namespace sampleprof; - LLVMContext Context; - auto ReaderOrErr = SampleProfileReader::create(Filename, Context); - if (std::error_code EC = ReaderOrErr.getError()) - exitWithErrorCode(EC, Filename); - - auto Reader = std::move(ReaderOrErr.get()); - - if (ShowSectionInfoOnly) { - showSectionInfo(Reader.get(), OS); - return 0; - } - - if (std::error_code EC = Reader->read()) - exitWithErrorCode(EC, Filename); - - if (ShowAllFunctions || ShowFunction.empty()) - Reader->dump(OS); - else - Reader->dumpFunctionProfile(ShowFunction, OS); - - if (ShowProfileSymbolList) { - std::unique_ptr ReaderList = - Reader->getProfileSymbolList(); - ReaderList->dump(OS); - } - - if (ShowDetailedSummary) { - auto &PS = Reader->getSummary(); - PS.printSummary(OS); - PS.printDetailedSummary(OS); - } - - if (ShowHotFuncList) - showHotFunctionList(Reader->getProfiles(), Reader->getSummary(), OS); - - return 0; -} - -static int show_main(int argc, const char *argv[]) { - cl::opt Filename(cl::Positional, cl::Required, - cl::desc("")); - - cl::opt ShowCounts("counts", cl::init(false), - cl::desc("Show counter values for shown functions")); - cl::opt TextFormat( - "text", cl::init(false), - cl::desc("Show instr profile data in text dump format")); - cl::opt ShowIndirectCallTargets( - "ic-targets", cl::init(false), - cl::desc("Show indirect call site target values for shown functions")); - cl::opt ShowMemOPSizes( - "memop-sizes", cl::init(false), - cl::desc("Show the profiled sizes of the memory intrinsic calls " - "for shown functions")); - cl::opt ShowDetailedSummary("detailed-summary", cl::init(false), - cl::desc("Show detailed profile summary")); - cl::list DetailedSummaryCutoffs( - cl::CommaSeparated, "detailed-summary-cutoffs", - cl::desc( - "Cutoff percentages (times 10000) for generating detailed summary"), - cl::value_desc("800000,901000,999999")); - cl::opt ShowHotFuncList( - "hot-func-list", cl::init(false), - cl::desc("Show profile summary of a list of hot functions")); - cl::opt ShowAllFunctions("all-functions", cl::init(false), - cl::desc("Details for every function")); - cl::opt ShowCS("showcs", cl::init(false), - cl::desc("Show context sensitive counts")); - cl::opt ShowFunction("function", - cl::desc("Details for matching functions")); - - cl::opt OutputFilename("output", cl::value_desc("output"), - cl::init("-"), cl::desc("Output file")); - cl::alias OutputFilenameA("o", cl::desc("Alias for --output"), - cl::aliasopt(OutputFilename)); - cl::opt ProfileKind( - cl::desc("Profile kind:"), cl::init(instr), - cl::values(clEnumVal(instr, "Instrumentation profile (default)"), - clEnumVal(sample, "Sample profile"))); - cl::opt TopNFunctions( - "topn", cl::init(0), - cl::desc("Show the list of functions with the largest internal counts")); - cl::opt ValueCutoff( - "value-cutoff", cl::init(0), - cl::desc("Set the count value cutoff. Functions with the maximum count " - "less than this value will not be printed out. (Default is 0)")); - cl::opt OnlyListBelow( - "list-below-cutoff", cl::init(false), - cl::desc("Only output names of functions whose max count values are " - "below the cutoff value")); - cl::opt ShowProfileSymbolList( - "show-prof-sym-list", cl::init(false), - cl::desc("Show profile symbol list if it exists in the profile. ")); - cl::opt ShowSectionInfoOnly( - "show-sec-info-only", cl::init(false), - cl::desc("Show the information of each section in the sample profile. " - "The flag is only usable when the sample profile is in " - "extbinary format")); - - cl::ParseCommandLineOptions(argc, argv, "LLVM profile data summary\n"); - - if (OutputFilename.empty()) - OutputFilename = "-"; - - if (!Filename.compare(OutputFilename)) { - errs() << sys::path::filename(argv[0]) - << ": Input file name cannot be the same as the output file name!\n"; - return 1; - } - - std::error_code EC; - raw_fd_ostream OS(OutputFilename.data(), EC, sys::fs::OF_Text); - if (EC) - exitWithErrorCode(EC, OutputFilename); - - if (ShowAllFunctions && !ShowFunction.empty()) - WithColor::warning() << "-function argument ignored: showing all functions\n"; - - if (ProfileKind == instr) - return showInstrProfile(Filename, ShowCounts, TopNFunctions, - ShowIndirectCallTargets, ShowMemOPSizes, - ShowDetailedSummary, DetailedSummaryCutoffs, - ShowAllFunctions, ShowCS, ValueCutoff, - OnlyListBelow, ShowFunction, TextFormat, OS); - else - return showSampleProfile(Filename, ShowCounts, ShowAllFunctions, - ShowDetailedSummary, ShowFunction, - ShowProfileSymbolList, ShowSectionInfoOnly, - ShowHotFuncList, OS); -} - -int main(int argc, const char *argv[]) { - InitLLVM X(argc, argv); - - StringRef ProgName(sys::path::filename(argv[0])); - if (argc > 1) { - int (*func)(int, const char *[]) = nullptr; - - if (strcmp(argv[1], "merge") == 0) - func = merge_main; - else if (strcmp(argv[1], "show") == 0) - func = show_main; - else if (strcmp(argv[1], "overlap") == 0) - func = overlap_main; - - if (func) { - std::string Invocation(ProgName.str() + " " + argv[1]); - argv[1] = Invocation.c_str(); - return func(argc - 1, argv + 1); - } - - if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "-help") == 0 || - strcmp(argv[1], "--help") == 0) { - - errs() << "OVERVIEW: LLVM profile data tools\n\n" - << "USAGE: " << ProgName << " [args...]\n" - << "USAGE: " << ProgName << " -help\n\n" - << "See each individual command --help for more details.\n" - << "Available commands: merge, show, overlap\n"; - return 0; - } - } - - if (argc < 2) - errs() << ProgName << ": No command specified!\n"; - else - errs() << ProgName << ": Unknown command!\n"; - - errs() << "USAGE: " << ProgName << " [args...]\n"; - return 1; -} diff --git a/tools/ldc-profdata/llvm-profdata-11.1.cpp b/tools/ldc-profdata/llvm-profdata-11.1.cpp deleted file mode 100644 index 843f072a61c..00000000000 --- a/tools/ldc-profdata/llvm-profdata-11.1.cpp +++ /dev/null @@ -1,1344 +0,0 @@ -//===- llvm-profdata.cpp - LLVM profile data tool -------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// llvm-profdata merges .profdata files. -// -//===----------------------------------------------------------------------===// - -#include "llvm/ADT/SmallSet.h" -#include "llvm/ADT/SmallVector.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/IR/LLVMContext.h" -#include "llvm/ProfileData/InstrProfReader.h" -#include "llvm/ProfileData/InstrProfWriter.h" -#include "llvm/ProfileData/ProfileCommon.h" -#include "llvm/ProfileData/SampleProfReader.h" -#include "llvm/ProfileData/SampleProfWriter.h" -#include "llvm/Support/CommandLine.h" -#include "llvm/Support/Errc.h" -#include "llvm/Support/FileSystem.h" -#include "llvm/Support/Format.h" -#include "llvm/Support/FormattedStream.h" -#include "llvm/Support/InitLLVM.h" -#include "llvm/Support/MemoryBuffer.h" -#include "llvm/Support/Path.h" -#include "llvm/Support/ThreadPool.h" -#include "llvm/Support/Threading.h" -#include "llvm/Support/WithColor.h" -#include "llvm/Support/raw_ostream.h" -#include - -using namespace llvm; - -enum ProfileFormat { - PF_None = 0, - PF_Text, - PF_Compact_Binary, - PF_Ext_Binary, - PF_GCC, - PF_Binary -}; - -static void warn(Twine Message, std::string Whence = "", - std::string Hint = "") { - WithColor::warning(); - if (!Whence.empty()) - errs() << Whence << ": "; - errs() << Message << "\n"; - if (!Hint.empty()) - WithColor::note() << Hint << "\n"; -} - -static void exitWithError(Twine Message, std::string Whence = "", - std::string Hint = "") { - WithColor::error(); - if (!Whence.empty()) - errs() << Whence << ": "; - errs() << Message << "\n"; - if (!Hint.empty()) - WithColor::note() << Hint << "\n"; - ::exit(1); -} - -static void exitWithError(Error E, StringRef Whence = "") { - if (E.isA()) { - handleAllErrors(std::move(E), [&](const InstrProfError &IPE) { - instrprof_error instrError = IPE.get(); - StringRef Hint = ""; - if (instrError == instrprof_error::unrecognized_format) { - // Hint for common error of forgetting --sample for sample profiles. - Hint = "Perhaps you forgot to use the --sample option?"; - } - exitWithError(IPE.message(), std::string(Whence), std::string(Hint)); - }); - } - - exitWithError(toString(std::move(E)), std::string(Whence)); -} - -static void exitWithErrorCode(std::error_code EC, StringRef Whence = "") { - exitWithError(EC.message(), std::string(Whence)); -} - -namespace { -enum ProfileKinds { instr, sample }; -enum FailureMode { failIfAnyAreInvalid, failIfAllAreInvalid }; -} - -static void warnOrExitGivenError(FailureMode FailMode, std::error_code EC, - StringRef Whence = "") { - if (FailMode == failIfAnyAreInvalid) - exitWithErrorCode(EC, Whence); - else - warn(EC.message(), std::string(Whence)); -} - -static void handleMergeWriterError(Error E, StringRef WhenceFile = "", - StringRef WhenceFunction = "", - bool ShowHint = true) { - if (!WhenceFile.empty()) - errs() << WhenceFile << ": "; - if (!WhenceFunction.empty()) - errs() << WhenceFunction << ": "; - - auto IPE = instrprof_error::success; - E = handleErrors(std::move(E), - [&IPE](std::unique_ptr E) -> Error { - IPE = E->get(); - return Error(std::move(E)); - }); - errs() << toString(std::move(E)) << "\n"; - - if (ShowHint) { - StringRef Hint = ""; - if (IPE != instrprof_error::success) { - switch (IPE) { - case instrprof_error::hash_mismatch: - case instrprof_error::count_mismatch: - case instrprof_error::value_site_count_mismatch: - Hint = "Make sure that all profile data to be merged is generated " - "from the same binary."; - break; - default: - break; - } - } - - if (!Hint.empty()) - errs() << Hint << "\n"; - } -} - -namespace { -/// A remapper from original symbol names to new symbol names based on a file -/// containing a list of mappings from old name to new name. -class SymbolRemapper { - std::unique_ptr File; - DenseMap RemappingTable; - -public: - /// Build a SymbolRemapper from a file containing a list of old/new symbols. - static std::unique_ptr create(StringRef InputFile) { - auto BufOrError = MemoryBuffer::getFileOrSTDIN(InputFile); - if (!BufOrError) - exitWithErrorCode(BufOrError.getError(), InputFile); - - auto Remapper = std::make_unique(); - Remapper->File = std::move(BufOrError.get()); - - for (line_iterator LineIt(*Remapper->File, /*SkipBlanks=*/true, '#'); - !LineIt.is_at_eof(); ++LineIt) { - std::pair Parts = LineIt->split(' '); - if (Parts.first.empty() || Parts.second.empty() || - Parts.second.count(' ')) { - exitWithError("unexpected line in remapping file", - (InputFile + ":" + Twine(LineIt.line_number())).str(), - "expected 'old_symbol new_symbol'"); - } - Remapper->RemappingTable.insert(Parts); - } - return Remapper; - } - - /// Attempt to map the given old symbol into a new symbol. - /// - /// \return The new symbol, or \p Name if no such symbol was found. - StringRef operator()(StringRef Name) { - StringRef New = RemappingTable.lookup(Name); - return New.empty() ? Name : New; - } -}; -} - -struct WeightedFile { - std::string Filename; - uint64_t Weight; -}; -typedef SmallVector WeightedFileVector; - -/// Keep track of merged data and reported errors. -struct WriterContext { - std::mutex Lock; - InstrProfWriter Writer; - std::vector> Errors; - std::mutex &ErrLock; - SmallSet &WriterErrorCodes; - - WriterContext(bool IsSparse, std::mutex &ErrLock, - SmallSet &WriterErrorCodes) - : Lock(), Writer(IsSparse), Errors(), ErrLock(ErrLock), - WriterErrorCodes(WriterErrorCodes) {} -}; - -/// Computer the overlap b/w profile BaseFilename and TestFileName, -/// and store the program level result to Overlap. -static void overlapInput(const std::string &BaseFilename, - const std::string &TestFilename, WriterContext *WC, - OverlapStats &Overlap, - const OverlapFuncFilters &FuncFilter, - raw_fd_ostream &OS, bool IsCS) { - auto ReaderOrErr = InstrProfReader::create(TestFilename); - if (Error E = ReaderOrErr.takeError()) { - // Skip the empty profiles by returning sliently. - instrprof_error IPE = InstrProfError::take(std::move(E)); - if (IPE != instrprof_error::empty_raw_profile) - WC->Errors.emplace_back(make_error(IPE), TestFilename); - return; - } - - auto Reader = std::move(ReaderOrErr.get()); - for (auto &I : *Reader) { - OverlapStats FuncOverlap(OverlapStats::FunctionLevel); - FuncOverlap.setFuncInfo(I.Name, I.Hash); - - WC->Writer.overlapRecord(std::move(I), Overlap, FuncOverlap, FuncFilter); - FuncOverlap.dump(OS); - } -} - -/// Load an input into a writer context. -static void loadInput(const WeightedFile &Input, SymbolRemapper *Remapper, - WriterContext *WC) { - std::unique_lock CtxGuard{WC->Lock}; - - // Copy the filename, because llvm::ThreadPool copied the input "const - // WeightedFile &" by value, making a reference to the filename within it - // invalid outside of this packaged task. - std::string Filename = Input.Filename; - - auto ReaderOrErr = InstrProfReader::create(Input.Filename); - if (Error E = ReaderOrErr.takeError()) { - // Skip the empty profiles by returning sliently. - instrprof_error IPE = InstrProfError::take(std::move(E)); - if (IPE != instrprof_error::empty_raw_profile) - WC->Errors.emplace_back(make_error(IPE), Filename); - return; - } - - auto Reader = std::move(ReaderOrErr.get()); - bool IsIRProfile = Reader->isIRLevelProfile(); - bool HasCSIRProfile = Reader->hasCSIRLevelProfile(); - if (WC->Writer.setIsIRLevelProfile(IsIRProfile, HasCSIRProfile)) { - WC->Errors.emplace_back( - make_error( - "Merge IR generated profile with Clang generated profile.", - std::error_code()), - Filename); - return; - } - - for (auto &I : *Reader) { - if (Remapper) - I.Name = (*Remapper)(I.Name); - const StringRef FuncName = I.Name; - bool Reported = false; - WC->Writer.addRecord(std::move(I), Input.Weight, [&](Error E) { - if (Reported) { - consumeError(std::move(E)); - return; - } - Reported = true; - // Only show hint the first time an error occurs. - instrprof_error IPE = InstrProfError::take(std::move(E)); - std::unique_lock ErrGuard{WC->ErrLock}; - bool firstTime = WC->WriterErrorCodes.insert(IPE).second; - handleMergeWriterError(make_error(IPE), Input.Filename, - FuncName, firstTime); - }); - } - if (Reader->hasError()) - if (Error E = Reader->getError()) - WC->Errors.emplace_back(std::move(E), Filename); -} - -/// Merge the \p Src writer context into \p Dst. -static void mergeWriterContexts(WriterContext *Dst, WriterContext *Src) { - for (auto &ErrorPair : Src->Errors) - Dst->Errors.push_back(std::move(ErrorPair)); - Src->Errors.clear(); - - Dst->Writer.mergeRecordsFromWriter(std::move(Src->Writer), [&](Error E) { - instrprof_error IPE = InstrProfError::take(std::move(E)); - std::unique_lock ErrGuard{Dst->ErrLock}; - bool firstTime = Dst->WriterErrorCodes.insert(IPE).second; - if (firstTime) - warn(toString(make_error(IPE))); - }); -} - -static void writeInstrProfile(StringRef OutputFilename, - ProfileFormat OutputFormat, - InstrProfWriter &Writer) { - std::error_code EC; - raw_fd_ostream Output(OutputFilename.data(), EC, sys::fs::OF_None); - if (EC) - exitWithErrorCode(EC, OutputFilename); - - if (OutputFormat == PF_Text) { - if (Error E = Writer.writeText(Output)) - exitWithError(std::move(E)); - } else { - Writer.write(Output); - } -} - -static void mergeInstrProfile(const WeightedFileVector &Inputs, - SymbolRemapper *Remapper, - StringRef OutputFilename, - ProfileFormat OutputFormat, bool OutputSparse, - unsigned NumThreads, FailureMode FailMode) { - if (OutputFilename.compare("-") == 0) - exitWithError("Cannot write indexed profdata format to stdout."); - - if (OutputFormat != PF_Binary && OutputFormat != PF_Compact_Binary && - OutputFormat != PF_Ext_Binary && OutputFormat != PF_Text) - exitWithError("Unknown format is specified."); - - std::mutex ErrorLock; - SmallSet WriterErrorCodes; - - // If NumThreads is not specified, auto-detect a good default. - if (NumThreads == 0) - NumThreads = std::min(hardware_concurrency().compute_thread_count(), - unsigned((Inputs.size() + 1) / 2)); - // FIXME: There's a bug here, where setting NumThreads = Inputs.size() fails - // the merge_empty_profile.test because the InstrProfWriter.ProfileKind isn't - // merged, thus the emitted file ends up with a PF_Unknown kind. - - // Initialize the writer contexts. - SmallVector, 4> Contexts; - for (unsigned I = 0; I < NumThreads; ++I) - Contexts.emplace_back(std::make_unique( - OutputSparse, ErrorLock, WriterErrorCodes)); - - if (NumThreads == 1) { - for (const auto &Input : Inputs) - loadInput(Input, Remapper, Contexts[0].get()); - } else { - ThreadPool Pool(hardware_concurrency(NumThreads)); - - // Load the inputs in parallel (N/NumThreads serial steps). - unsigned Ctx = 0; - for (const auto &Input : Inputs) { - Pool.async(loadInput, Input, Remapper, Contexts[Ctx].get()); - Ctx = (Ctx + 1) % NumThreads; - } - Pool.wait(); - - // Merge the writer contexts together (~ lg(NumThreads) serial steps). - unsigned Mid = Contexts.size() / 2; - unsigned End = Contexts.size(); - assert(Mid > 0 && "Expected more than one context"); - do { - for (unsigned I = 0; I < Mid; ++I) - Pool.async(mergeWriterContexts, Contexts[I].get(), - Contexts[I + Mid].get()); - Pool.wait(); - if (End & 1) { - Pool.async(mergeWriterContexts, Contexts[0].get(), - Contexts[End - 1].get()); - Pool.wait(); - } - End = Mid; - Mid /= 2; - } while (Mid > 0); - } - - // Handle deferred errors encountered during merging. If the number of errors - // is equal to the number of inputs the merge failed. - unsigned NumErrors = 0; - for (std::unique_ptr &WC : Contexts) { - for (auto &ErrorPair : WC->Errors) { - ++NumErrors; - warn(toString(std::move(ErrorPair.first)), ErrorPair.second); - } - } - if (NumErrors == Inputs.size() || - (NumErrors > 0 && FailMode == failIfAnyAreInvalid)) - exitWithError("No profiles could be merged."); - - writeInstrProfile(OutputFilename, OutputFormat, Contexts[0]->Writer); -} - -/// Make a copy of the given function samples with all symbol names remapped -/// by the provided symbol remapper. -static sampleprof::FunctionSamples -remapSamples(const sampleprof::FunctionSamples &Samples, - SymbolRemapper &Remapper, sampleprof_error &Error) { - sampleprof::FunctionSamples Result; - Result.setName(Remapper(Samples.getName())); - Result.addTotalSamples(Samples.getTotalSamples()); - Result.addHeadSamples(Samples.getHeadSamples()); - for (const auto &BodySample : Samples.getBodySamples()) { - Result.addBodySamples(BodySample.first.LineOffset, - BodySample.first.Discriminator, - BodySample.second.getSamples()); - for (const auto &Target : BodySample.second.getCallTargets()) { - Result.addCalledTargetSamples(BodySample.first.LineOffset, - BodySample.first.Discriminator, - Remapper(Target.first()), Target.second); - } - } - for (const auto &CallsiteSamples : Samples.getCallsiteSamples()) { - sampleprof::FunctionSamplesMap &Target = - Result.functionSamplesAt(CallsiteSamples.first); - for (const auto &Callsite : CallsiteSamples.second) { - sampleprof::FunctionSamples Remapped = - remapSamples(Callsite.second, Remapper, Error); - MergeResult(Error, - Target[std::string(Remapped.getName())].merge(Remapped)); - } - } - return Result; -} - -static sampleprof::SampleProfileFormat FormatMap[] = { - sampleprof::SPF_None, - sampleprof::SPF_Text, - sampleprof::SPF_Compact_Binary, - sampleprof::SPF_Ext_Binary, - sampleprof::SPF_GCC, - sampleprof::SPF_Binary}; - -static std::unique_ptr -getInputFileBuf(const StringRef &InputFile) { - if (InputFile == "") - return {}; - - auto BufOrError = MemoryBuffer::getFileOrSTDIN(InputFile); - if (!BufOrError) - exitWithErrorCode(BufOrError.getError(), InputFile); - - return std::move(*BufOrError); -} - -static void populateProfileSymbolList(MemoryBuffer *Buffer, - sampleprof::ProfileSymbolList &PSL) { - if (!Buffer) - return; - - SmallVector SymbolVec; - StringRef Data = Buffer->getBuffer(); - Data.split(SymbolVec, '\n', /*MaxSplit=*/-1, /*KeepEmpty=*/false); - - for (StringRef symbol : SymbolVec) - PSL.add(symbol); -} - -static void handleExtBinaryWriter(sampleprof::SampleProfileWriter &Writer, - ProfileFormat OutputFormat, - MemoryBuffer *Buffer, - sampleprof::ProfileSymbolList &WriterList, - bool CompressAllSections, bool UseMD5, - bool GenPartialProfile) { - populateProfileSymbolList(Buffer, WriterList); - if (WriterList.size() > 0 && OutputFormat != PF_Ext_Binary) - warn("Profile Symbol list is not empty but the output format is not " - "ExtBinary format. The list will be lost in the output. "); - - Writer.setProfileSymbolList(&WriterList); - - if (CompressAllSections) { - if (OutputFormat != PF_Ext_Binary) - warn("-compress-all-section is ignored. Specify -extbinary to enable it"); - else - Writer.setToCompressAllSections(); - } - if (UseMD5) { - if (OutputFormat != PF_Ext_Binary) - warn("-use-md5 is ignored. Specify -extbinary to enable it"); - else - Writer.setUseMD5(); - } - if (GenPartialProfile) { - if (OutputFormat != PF_Ext_Binary) - warn("-gen-partial-profile is ignored. Specify -extbinary to enable it"); - else - Writer.setPartialProfile(); - } -} - -static void -mergeSampleProfile(const WeightedFileVector &Inputs, SymbolRemapper *Remapper, - StringRef OutputFilename, ProfileFormat OutputFormat, - StringRef ProfileSymbolListFile, bool CompressAllSections, - bool UseMD5, bool GenPartialProfile, FailureMode FailMode) { - using namespace sampleprof; - StringMap ProfileMap; - SmallVector, 5> Readers; - LLVMContext Context; - sampleprof::ProfileSymbolList WriterList; - for (const auto &Input : Inputs) { - auto ReaderOrErr = SampleProfileReader::create(Input.Filename, Context); - if (std::error_code EC = ReaderOrErr.getError()) { - warnOrExitGivenError(FailMode, EC, Input.Filename); - continue; - } - - // We need to keep the readers around until after all the files are - // read so that we do not lose the function names stored in each - // reader's memory. The function names are needed to write out the - // merged profile map. - Readers.push_back(std::move(ReaderOrErr.get())); - const auto Reader = Readers.back().get(); - if (std::error_code EC = Reader->read()) { - warnOrExitGivenError(FailMode, EC, Input.Filename); - Readers.pop_back(); - continue; - } - - StringMap &Profiles = Reader->getProfiles(); - for (StringMap::iterator I = Profiles.begin(), - E = Profiles.end(); - I != E; ++I) { - sampleprof_error Result = sampleprof_error::success; - FunctionSamples Remapped = - Remapper ? remapSamples(I->second, *Remapper, Result) - : FunctionSamples(); - FunctionSamples &Samples = Remapper ? Remapped : I->second; - StringRef FName = Samples.getName(); - MergeResult(Result, ProfileMap[FName].merge(Samples, Input.Weight)); - if (Result != sampleprof_error::success) { - std::error_code EC = make_error_code(Result); - handleMergeWriterError(errorCodeToError(EC), Input.Filename, FName); - } - } - - std::unique_ptr ReaderList = - Reader->getProfileSymbolList(); - if (ReaderList) - WriterList.merge(*ReaderList); - } - auto WriterOrErr = - SampleProfileWriter::create(OutputFilename, FormatMap[OutputFormat]); - if (std::error_code EC = WriterOrErr.getError()) - exitWithErrorCode(EC, OutputFilename); - - auto Writer = std::move(WriterOrErr.get()); - // WriterList will have StringRef refering to string in Buffer. - // Make sure Buffer lives as long as WriterList. - auto Buffer = getInputFileBuf(ProfileSymbolListFile); - handleExtBinaryWriter(*Writer, OutputFormat, Buffer.get(), WriterList, - CompressAllSections, UseMD5, GenPartialProfile); - Writer->write(ProfileMap); -} - -static WeightedFile parseWeightedFile(const StringRef &WeightedFilename) { - StringRef WeightStr, FileName; - std::tie(WeightStr, FileName) = WeightedFilename.split(','); - - uint64_t Weight; - if (WeightStr.getAsInteger(10, Weight) || Weight < 1) - exitWithError("Input weight must be a positive integer."); - - return {std::string(FileName), Weight}; -} - -static void addWeightedInput(WeightedFileVector &WNI, const WeightedFile &WF) { - StringRef Filename = WF.Filename; - uint64_t Weight = WF.Weight; - - // If it's STDIN just pass it on. - if (Filename == "-") { - WNI.push_back({std::string(Filename), Weight}); - return; - } - - llvm::sys::fs::file_status Status; - llvm::sys::fs::status(Filename, Status); - if (!llvm::sys::fs::exists(Status)) - exitWithErrorCode(make_error_code(errc::no_such_file_or_directory), - Filename); - // If it's a source file, collect it. - if (llvm::sys::fs::is_regular_file(Status)) { - WNI.push_back({std::string(Filename), Weight}); - return; - } - - if (llvm::sys::fs::is_directory(Status)) { - std::error_code EC; - for (llvm::sys::fs::recursive_directory_iterator F(Filename, EC), E; - F != E && !EC; F.increment(EC)) { - if (llvm::sys::fs::is_regular_file(F->path())) { - addWeightedInput(WNI, {F->path(), Weight}); - } - } - if (EC) - exitWithErrorCode(EC, Filename); - } -} - -static void parseInputFilenamesFile(MemoryBuffer *Buffer, - WeightedFileVector &WFV) { - if (!Buffer) - return; - - SmallVector Entries; - StringRef Data = Buffer->getBuffer(); - Data.split(Entries, '\n', /*MaxSplit=*/-1, /*KeepEmpty=*/false); - for (const StringRef &FileWeightEntry : Entries) { - StringRef SanitizedEntry = FileWeightEntry.trim(" \t\v\f\r"); - // Skip comments. - if (SanitizedEntry.startswith("#")) - continue; - // If there's no comma, it's an unweighted profile. - else if (SanitizedEntry.find(',') == StringRef::npos) - addWeightedInput(WFV, {std::string(SanitizedEntry), 1}); - else - addWeightedInput(WFV, parseWeightedFile(SanitizedEntry)); - } -} - -static int merge_main(int argc, const char *argv[]) { - cl::list InputFilenames(cl::Positional, - cl::desc("")); - cl::list WeightedInputFilenames("weighted-input", - cl::desc(",")); - cl::opt InputFilenamesFile( - "input-files", cl::init(""), - cl::desc("Path to file containing newline-separated " - "[,] entries")); - cl::alias InputFilenamesFileA("f", cl::desc("Alias for --input-files"), - cl::aliasopt(InputFilenamesFile)); - cl::opt DumpInputFileList( - "dump-input-file-list", cl::init(false), cl::Hidden, - cl::desc("Dump the list of input files and their weights, then exit")); - cl::opt RemappingFile("remapping-file", cl::value_desc("file"), - cl::desc("Symbol remapping file")); - cl::alias RemappingFileA("r", cl::desc("Alias for --remapping-file"), - cl::aliasopt(RemappingFile)); - cl::opt OutputFilename("output", cl::value_desc("output"), - cl::init("-"), cl::Required, - cl::desc("Output file")); - cl::alias OutputFilenameA("o", cl::desc("Alias for --output"), - cl::aliasopt(OutputFilename)); - cl::opt ProfileKind( - cl::desc("Profile kind:"), cl::init(instr), - cl::values(clEnumVal(instr, "Instrumentation profile (default)"), - clEnumVal(sample, "Sample profile"))); - cl::opt OutputFormat( - cl::desc("Format of output profile"), cl::init(PF_Binary), - cl::values( - clEnumValN(PF_Binary, "binary", "Binary encoding (default)"), - clEnumValN(PF_Compact_Binary, "compbinary", - "Compact binary encoding"), - clEnumValN(PF_Ext_Binary, "extbinary", "Extensible binary encoding"), - clEnumValN(PF_Text, "text", "Text encoding"), - clEnumValN(PF_GCC, "gcc", - "GCC encoding (only meaningful for -sample)"))); - cl::opt FailureMode( - "failure-mode", cl::init(failIfAnyAreInvalid), cl::desc("Failure mode:"), - cl::values(clEnumValN(failIfAnyAreInvalid, "any", - "Fail if any profile is invalid."), - clEnumValN(failIfAllAreInvalid, "all", - "Fail only if all profiles are invalid."))); - cl::opt OutputSparse("sparse", cl::init(false), - cl::desc("Generate a sparse profile (only meaningful for -instr)")); - cl::opt NumThreads( - "num-threads", cl::init(0), - cl::desc("Number of merge threads to use (default: autodetect)")); - cl::alias NumThreadsA("j", cl::desc("Alias for --num-threads"), - cl::aliasopt(NumThreads)); - cl::opt ProfileSymbolListFile( - "prof-sym-list", cl::init(""), - cl::desc("Path to file containing the list of function symbols " - "used to populate profile symbol list")); - cl::opt CompressAllSections( - "compress-all-sections", cl::init(false), cl::Hidden, - cl::desc("Compress all sections when writing the profile (only " - "meaningful for -extbinary)")); - cl::opt UseMD5( - "use-md5", cl::init(false), cl::Hidden, - cl::desc("Choose to use MD5 to represent string in name table (only " - "meaningful for -extbinary)")); - cl::opt GenPartialProfile( - "gen-partial-profile", cl::init(false), cl::Hidden, - cl::desc("Generate a partial profile (only meaningful for -extbinary)")); - - cl::ParseCommandLineOptions(argc, argv, "LLVM profile data merger\n"); - - WeightedFileVector WeightedInputs; - for (StringRef Filename : InputFilenames) - addWeightedInput(WeightedInputs, {std::string(Filename), 1}); - for (StringRef WeightedFilename : WeightedInputFilenames) - addWeightedInput(WeightedInputs, parseWeightedFile(WeightedFilename)); - - // Make sure that the file buffer stays alive for the duration of the - // weighted input vector's lifetime. - auto Buffer = getInputFileBuf(InputFilenamesFile); - parseInputFilenamesFile(Buffer.get(), WeightedInputs); - - if (WeightedInputs.empty()) - exitWithError("No input files specified. See " + - sys::path::filename(argv[0]) + " -help"); - - if (DumpInputFileList) { - for (auto &WF : WeightedInputs) - outs() << WF.Weight << "," << WF.Filename << "\n"; - return 0; - } - - std::unique_ptr Remapper; - if (!RemappingFile.empty()) - Remapper = SymbolRemapper::create(RemappingFile); - - if (ProfileKind == instr) - mergeInstrProfile(WeightedInputs, Remapper.get(), OutputFilename, - OutputFormat, OutputSparse, NumThreads, FailureMode); - else - mergeSampleProfile(WeightedInputs, Remapper.get(), OutputFilename, - OutputFormat, ProfileSymbolListFile, CompressAllSections, - UseMD5, GenPartialProfile, FailureMode); - - return 0; -} - -/// Computer the overlap b/w profile BaseFilename and profile TestFilename. -static void overlapInstrProfile(const std::string &BaseFilename, - const std::string &TestFilename, - const OverlapFuncFilters &FuncFilter, - raw_fd_ostream &OS, bool IsCS) { - std::mutex ErrorLock; - SmallSet WriterErrorCodes; - WriterContext Context(false, ErrorLock, WriterErrorCodes); - WeightedFile WeightedInput{BaseFilename, 1}; - OverlapStats Overlap; - Error E = Overlap.accumulateCounts(BaseFilename, TestFilename, IsCS); - if (E) - exitWithError(std::move(E), "Error in getting profile count sums"); - if (Overlap.Base.CountSum < 1.0f) { - OS << "Sum of edge counts for profile " << BaseFilename << " is 0.\n"; - exit(0); - } - if (Overlap.Test.CountSum < 1.0f) { - OS << "Sum of edge counts for profile " << TestFilename << " is 0.\n"; - exit(0); - } - loadInput(WeightedInput, nullptr, &Context); - overlapInput(BaseFilename, TestFilename, &Context, Overlap, FuncFilter, OS, - IsCS); - Overlap.dump(OS); -} - -static int overlap_main(int argc, const char *argv[]) { - cl::opt BaseFilename(cl::Positional, cl::Required, - cl::desc("")); - cl::opt TestFilename(cl::Positional, cl::Required, - cl::desc("")); - cl::opt Output("output", cl::value_desc("output"), cl::init("-"), - cl::desc("Output file")); - cl::alias OutputA("o", cl::desc("Alias for --output"), cl::aliasopt(Output)); - cl::opt IsCS("cs", cl::init(false), - cl::desc("For context sensitive counts")); - cl::opt ValueCutoff( - "value-cutoff", cl::init(-1), - cl::desc( - "Function level overlap information for every function in test " - "profile with max count value greater then the parameter value")); - cl::opt FuncNameFilter( - "function", - cl::desc("Function level overlap information for matching functions")); - cl::ParseCommandLineOptions(argc, argv, "LLVM profile data overlap tool\n"); - - std::error_code EC; - raw_fd_ostream OS(Output.data(), EC, sys::fs::OF_Text); - if (EC) - exitWithErrorCode(EC, Output); - - overlapInstrProfile(BaseFilename, TestFilename, - OverlapFuncFilters{ValueCutoff, FuncNameFilter}, OS, - IsCS); - - return 0; -} - -typedef struct ValueSitesStats { - ValueSitesStats() - : TotalNumValueSites(0), TotalNumValueSitesWithValueProfile(0), - TotalNumValues(0) {} - uint64_t TotalNumValueSites; - uint64_t TotalNumValueSitesWithValueProfile; - uint64_t TotalNumValues; - std::vector ValueSitesHistogram; -} ValueSitesStats; - -static void traverseAllValueSites(const InstrProfRecord &Func, uint32_t VK, - ValueSitesStats &Stats, raw_fd_ostream &OS, - InstrProfSymtab *Symtab) { - uint32_t NS = Func.getNumValueSites(VK); - Stats.TotalNumValueSites += NS; - for (size_t I = 0; I < NS; ++I) { - uint32_t NV = Func.getNumValueDataForSite(VK, I); - std::unique_ptr VD = Func.getValueForSite(VK, I); - Stats.TotalNumValues += NV; - if (NV) { - Stats.TotalNumValueSitesWithValueProfile++; - if (NV > Stats.ValueSitesHistogram.size()) - Stats.ValueSitesHistogram.resize(NV, 0); - Stats.ValueSitesHistogram[NV - 1]++; - } - - uint64_t SiteSum = 0; - for (uint32_t V = 0; V < NV; V++) - SiteSum += VD[V].Count; - if (SiteSum == 0) - SiteSum = 1; - - for (uint32_t V = 0; V < NV; V++) { - OS << "\t[ " << format("%2u", I) << ", "; - if (Symtab == nullptr) - OS << format("%4" PRIu64, VD[V].Value); - else - OS << Symtab->getFuncName(VD[V].Value); - OS << ", " << format("%10" PRId64, VD[V].Count) << " ] (" - << format("%.2f%%", (VD[V].Count * 100.0 / SiteSum)) << ")\n"; - } - } -} - -static void showValueSitesStats(raw_fd_ostream &OS, uint32_t VK, - ValueSitesStats &Stats) { - OS << " Total number of sites: " << Stats.TotalNumValueSites << "\n"; - OS << " Total number of sites with values: " - << Stats.TotalNumValueSitesWithValueProfile << "\n"; - OS << " Total number of profiled values: " << Stats.TotalNumValues << "\n"; - - OS << " Value sites histogram:\n\tNumTargets, SiteCount\n"; - for (unsigned I = 0; I < Stats.ValueSitesHistogram.size(); I++) { - if (Stats.ValueSitesHistogram[I] > 0) - OS << "\t" << I + 1 << ", " << Stats.ValueSitesHistogram[I] << "\n"; - } -} - -static int showInstrProfile(const std::string &Filename, bool ShowCounts, - uint32_t TopN, bool ShowIndirectCallTargets, - bool ShowMemOPSizes, bool ShowDetailedSummary, - std::vector DetailedSummaryCutoffs, - bool ShowAllFunctions, bool ShowCS, - uint64_t ValueCutoff, bool OnlyListBelow, - const std::string &ShowFunction, bool TextFormat, - raw_fd_ostream &OS) { - auto ReaderOrErr = InstrProfReader::create(Filename); - std::vector Cutoffs = std::move(DetailedSummaryCutoffs); - if (ShowDetailedSummary && Cutoffs.empty()) { - Cutoffs = {800000, 900000, 950000, 990000, 999000, 999900, 999990}; - } - InstrProfSummaryBuilder Builder(std::move(Cutoffs)); - if (Error E = ReaderOrErr.takeError()) - exitWithError(std::move(E), Filename); - - auto Reader = std::move(ReaderOrErr.get()); - bool IsIRInstr = Reader->isIRLevelProfile(); - size_t ShownFunctions = 0; - size_t BelowCutoffFunctions = 0; - int NumVPKind = IPVK_Last - IPVK_First + 1; - std::vector VPStats(NumVPKind); - - auto MinCmp = [](const std::pair &v1, - const std::pair &v2) { - return v1.second > v2.second; - }; - - std::priority_queue, - std::vector>, - decltype(MinCmp)> - HottestFuncs(MinCmp); - - if (!TextFormat && OnlyListBelow) { - OS << "The list of functions with the maximum counter less than " - << ValueCutoff << ":\n"; - } - - // Add marker so that IR-level instrumentation round-trips properly. - if (TextFormat && IsIRInstr) - OS << ":ir\n"; - - for (const auto &Func : *Reader) { - if (Reader->isIRLevelProfile()) { - bool FuncIsCS = NamedInstrProfRecord::hasCSFlagInHash(Func.Hash); - if (FuncIsCS != ShowCS) - continue; - } - bool Show = - ShowAllFunctions || (!ShowFunction.empty() && - Func.Name.find(ShowFunction) != Func.Name.npos); - - bool doTextFormatDump = (Show && TextFormat); - - if (doTextFormatDump) { - InstrProfSymtab &Symtab = Reader->getSymtab(); - InstrProfWriter::writeRecordInText(Func.Name, Func.Hash, Func, Symtab, - OS); - continue; - } - - assert(Func.Counts.size() > 0 && "function missing entry counter"); - Builder.addRecord(Func); - - uint64_t FuncMax = 0; - uint64_t FuncSum = 0; - for (size_t I = 0, E = Func.Counts.size(); I < E; ++I) { - FuncMax = std::max(FuncMax, Func.Counts[I]); - FuncSum += Func.Counts[I]; - } - - if (FuncMax < ValueCutoff) { - ++BelowCutoffFunctions; - if (OnlyListBelow) { - OS << " " << Func.Name << ": (Max = " << FuncMax - << " Sum = " << FuncSum << ")\n"; - } - continue; - } else if (OnlyListBelow) - continue; - - if (TopN) { - if (HottestFuncs.size() == TopN) { - if (HottestFuncs.top().second < FuncMax) { - HottestFuncs.pop(); - HottestFuncs.emplace(std::make_pair(std::string(Func.Name), FuncMax)); - } - } else - HottestFuncs.emplace(std::make_pair(std::string(Func.Name), FuncMax)); - } - - if (Show) { - if (!ShownFunctions) - OS << "Counters:\n"; - - ++ShownFunctions; - - OS << " " << Func.Name << ":\n" - << " Hash: " << format("0x%016" PRIx64, Func.Hash) << "\n" - << " Counters: " << Func.Counts.size() << "\n"; - if (!IsIRInstr) - OS << " Function count: " << Func.Counts[0] << "\n"; - - if (ShowIndirectCallTargets) - OS << " Indirect Call Site Count: " - << Func.getNumValueSites(IPVK_IndirectCallTarget) << "\n"; - - uint32_t NumMemOPCalls = Func.getNumValueSites(IPVK_MemOPSize); - if (ShowMemOPSizes && NumMemOPCalls > 0) - OS << " Number of Memory Intrinsics Calls: " << NumMemOPCalls - << "\n"; - - if (ShowCounts) { - OS << " Block counts: ["; - size_t Start = (IsIRInstr ? 0 : 1); - for (size_t I = Start, E = Func.Counts.size(); I < E; ++I) { - OS << (I == Start ? "" : ", ") << Func.Counts[I]; - } - OS << "]\n"; - } - - if (ShowIndirectCallTargets) { - OS << " Indirect Target Results:\n"; - traverseAllValueSites(Func, IPVK_IndirectCallTarget, - VPStats[IPVK_IndirectCallTarget], OS, - &(Reader->getSymtab())); - } - - if (ShowMemOPSizes && NumMemOPCalls > 0) { - OS << " Memory Intrinsic Size Results:\n"; - traverseAllValueSites(Func, IPVK_MemOPSize, VPStats[IPVK_MemOPSize], OS, - nullptr); - } - } - } - if (Reader->hasError()) - exitWithError(Reader->getError(), Filename); - - if (TextFormat) - return 0; - std::unique_ptr PS(Builder.getSummary()); - OS << "Instrumentation level: " - << (Reader->isIRLevelProfile() ? "IR" : "Front-end") << "\n"; - if (ShowAllFunctions || !ShowFunction.empty()) - OS << "Functions shown: " << ShownFunctions << "\n"; - OS << "Total functions: " << PS->getNumFunctions() << "\n"; - if (ValueCutoff > 0) { - OS << "Number of functions with maximum count (< " << ValueCutoff - << "): " << BelowCutoffFunctions << "\n"; - OS << "Number of functions with maximum count (>= " << ValueCutoff - << "): " << PS->getNumFunctions() - BelowCutoffFunctions << "\n"; - } - OS << "Maximum function count: " << PS->getMaxFunctionCount() << "\n"; - OS << "Maximum internal block count: " << PS->getMaxInternalCount() << "\n"; - - if (TopN) { - std::vector> SortedHottestFuncs; - while (!HottestFuncs.empty()) { - SortedHottestFuncs.emplace_back(HottestFuncs.top()); - HottestFuncs.pop(); - } - OS << "Top " << TopN - << " functions with the largest internal block counts: \n"; - for (auto &hotfunc : llvm::reverse(SortedHottestFuncs)) - OS << " " << hotfunc.first << ", max count = " << hotfunc.second << "\n"; - } - - if (ShownFunctions && ShowIndirectCallTargets) { - OS << "Statistics for indirect call sites profile:\n"; - showValueSitesStats(OS, IPVK_IndirectCallTarget, - VPStats[IPVK_IndirectCallTarget]); - } - - if (ShownFunctions && ShowMemOPSizes) { - OS << "Statistics for memory intrinsic calls sizes profile:\n"; - showValueSitesStats(OS, IPVK_MemOPSize, VPStats[IPVK_MemOPSize]); - } - - if (ShowDetailedSummary) { - OS << "Total number of blocks: " << PS->getNumCounts() << "\n"; - OS << "Total count: " << PS->getTotalCount() << "\n"; - PS->printDetailedSummary(OS); - } - return 0; -} - -static void showSectionInfo(sampleprof::SampleProfileReader *Reader, - raw_fd_ostream &OS) { - if (!Reader->dumpSectionInfo(OS)) { - WithColor::warning() << "-show-sec-info-only is only supported for " - << "sample profile in extbinary format and is " - << "ignored for other formats.\n"; - return; - } -} - -namespace { -struct HotFuncInfo { - StringRef FuncName; - uint64_t TotalCount; - double TotalCountPercent; - uint64_t MaxCount; - uint64_t EntryCount; - - HotFuncInfo() - : FuncName(), TotalCount(0), TotalCountPercent(0.0f), MaxCount(0), - EntryCount(0) {} - - HotFuncInfo(StringRef FN, uint64_t TS, double TSP, uint64_t MS, uint64_t ES) - : FuncName(FN), TotalCount(TS), TotalCountPercent(TSP), MaxCount(MS), - EntryCount(ES) {} -}; -} // namespace - -// Print out detailed information about hot functions in PrintValues vector. -// Users specify titles and offset of every columns through ColumnTitle and -// ColumnOffset. The size of ColumnTitle and ColumnOffset need to be the same -// and at least 4. Besides, users can optionally give a HotFuncMetric string to -// print out or let it be an empty string. -static void dumpHotFunctionList(const std::vector &ColumnTitle, - const std::vector &ColumnOffset, - const std::vector &PrintValues, - uint64_t HotFuncCount, uint64_t TotalFuncCount, - uint64_t HotProfCount, uint64_t TotalProfCount, - const std::string &HotFuncMetric, - raw_fd_ostream &OS) { - assert(ColumnOffset.size() == ColumnTitle.size()); - assert(ColumnTitle.size() >= 4); - assert(TotalFuncCount > 0); - double TotalProfPercent = 0; - if (TotalProfCount > 0) - TotalProfPercent = ((double)HotProfCount) / TotalProfCount * 100; - - formatted_raw_ostream FOS(OS); - FOS << HotFuncCount << " out of " << TotalFuncCount - << " functions with profile (" - << format("%.2f%%", (((double)HotFuncCount) / TotalFuncCount * 100)) - << ") are considered hot functions"; - if (!HotFuncMetric.empty()) - FOS << " (" << HotFuncMetric << ")"; - FOS << ".\n"; - FOS << HotProfCount << " out of " << TotalProfCount << " profile counts (" - << format("%.2f%%", TotalProfPercent) << ") are from hot functions.\n"; - - for (size_t I = 0; I < ColumnTitle.size(); ++I) { - FOS.PadToColumn(ColumnOffset[I]); - FOS << ColumnTitle[I]; - } - FOS << "\n"; - - for (const HotFuncInfo &R : PrintValues) { - FOS.PadToColumn(ColumnOffset[0]); - FOS << R.TotalCount << " (" << format("%.2f%%", R.TotalCountPercent) << ")"; - FOS.PadToColumn(ColumnOffset[1]); - FOS << R.MaxCount; - FOS.PadToColumn(ColumnOffset[2]); - FOS << R.EntryCount; - FOS.PadToColumn(ColumnOffset[3]); - FOS << R.FuncName << "\n"; - } - return; -} - -static int -showHotFunctionList(const StringMap &Profiles, - ProfileSummary &PS, raw_fd_ostream &OS) { - using namespace sampleprof; - - const uint32_t HotFuncCutoff = 990000; - auto &SummaryVector = PS.getDetailedSummary(); - uint64_t MinCountThreshold = 0; - for (const ProfileSummaryEntry &SummaryEntry : SummaryVector) { - if (SummaryEntry.Cutoff == HotFuncCutoff) { - MinCountThreshold = SummaryEntry.MinCount; - break; - } - } - assert(MinCountThreshold != 0); - - // Traverse all functions in the profile and keep only hot functions. - // The following loop also calculates the sum of total samples of all - // functions. - std::multimap, - std::greater> - HotFunc; - uint64_t ProfileTotalSample = 0; - uint64_t HotFuncSample = 0; - uint64_t HotFuncCount = 0; - uint64_t MaxCount = 0; - for (const auto &I : Profiles) { - const FunctionSamples &FuncProf = I.second; - ProfileTotalSample += FuncProf.getTotalSamples(); - MaxCount = FuncProf.getMaxCountInside(); - - // MinCountThreshold is a block/line threshold computed for a given cutoff. - // We intentionally compare the maximum sample count in a function with this - // threshold to get an approximate threshold for hot functions. - if (MaxCount >= MinCountThreshold) { - HotFunc.emplace(FuncProf.getTotalSamples(), - std::make_pair(&(I.second), MaxCount)); - HotFuncSample += FuncProf.getTotalSamples(); - ++HotFuncCount; - } - } - - std::vector ColumnTitle{"Total sample (%)", "Max sample", - "Entry sample", "Function name"}; - std::vector ColumnOffset{0, 24, 42, 58}; - std::string Metric = - std::string("max sample >= ") + std::to_string(MinCountThreshold); - std::vector PrintValues; - for (const auto &FuncPair : HotFunc) { - const FunctionSamples &Func = *FuncPair.second.first; - double TotalSamplePercent = - (ProfileTotalSample > 0) - ? (Func.getTotalSamples() * 100.0) / ProfileTotalSample - : 0; - PrintValues.emplace_back(HotFuncInfo( - Func.getFuncName(), Func.getTotalSamples(), TotalSamplePercent, - FuncPair.second.second, Func.getEntrySamples())); - } - dumpHotFunctionList(ColumnTitle, ColumnOffset, PrintValues, HotFuncCount, - Profiles.size(), HotFuncSample, ProfileTotalSample, - Metric, OS); - - return 0; -} - -static int showSampleProfile(const std::string &Filename, bool ShowCounts, - bool ShowAllFunctions, bool ShowDetailedSummary, - const std::string &ShowFunction, - bool ShowProfileSymbolList, - bool ShowSectionInfoOnly, bool ShowHotFuncList, - raw_fd_ostream &OS) { - using namespace sampleprof; - LLVMContext Context; - auto ReaderOrErr = SampleProfileReader::create(Filename, Context); - if (std::error_code EC = ReaderOrErr.getError()) - exitWithErrorCode(EC, Filename); - - auto Reader = std::move(ReaderOrErr.get()); - - if (ShowSectionInfoOnly) { - showSectionInfo(Reader.get(), OS); - return 0; - } - - if (std::error_code EC = Reader->read()) - exitWithErrorCode(EC, Filename); - - if (ShowAllFunctions || ShowFunction.empty()) - Reader->dump(OS); - else - Reader->dumpFunctionProfile(ShowFunction, OS); - - if (ShowProfileSymbolList) { - std::unique_ptr ReaderList = - Reader->getProfileSymbolList(); - ReaderList->dump(OS); - } - - if (ShowDetailedSummary) { - auto &PS = Reader->getSummary(); - PS.printSummary(OS); - PS.printDetailedSummary(OS); - } - - if (ShowHotFuncList) - showHotFunctionList(Reader->getProfiles(), Reader->getSummary(), OS); - - return 0; -} - -static int show_main(int argc, const char *argv[]) { - cl::opt Filename(cl::Positional, cl::Required, - cl::desc("")); - - cl::opt ShowCounts("counts", cl::init(false), - cl::desc("Show counter values for shown functions")); - cl::opt TextFormat( - "text", cl::init(false), - cl::desc("Show instr profile data in text dump format")); - cl::opt ShowIndirectCallTargets( - "ic-targets", cl::init(false), - cl::desc("Show indirect call site target values for shown functions")); - cl::opt ShowMemOPSizes( - "memop-sizes", cl::init(false), - cl::desc("Show the profiled sizes of the memory intrinsic calls " - "for shown functions")); - cl::opt ShowDetailedSummary("detailed-summary", cl::init(false), - cl::desc("Show detailed profile summary")); - cl::list DetailedSummaryCutoffs( - cl::CommaSeparated, "detailed-summary-cutoffs", - cl::desc( - "Cutoff percentages (times 10000) for generating detailed summary"), - cl::value_desc("800000,901000,999999")); - cl::opt ShowHotFuncList( - "hot-func-list", cl::init(false), - cl::desc("Show profile summary of a list of hot functions")); - cl::opt ShowAllFunctions("all-functions", cl::init(false), - cl::desc("Details for every function")); - cl::opt ShowCS("showcs", cl::init(false), - cl::desc("Show context sensitive counts")); - cl::opt ShowFunction("function", - cl::desc("Details for matching functions")); - - cl::opt OutputFilename("output", cl::value_desc("output"), - cl::init("-"), cl::desc("Output file")); - cl::alias OutputFilenameA("o", cl::desc("Alias for --output"), - cl::aliasopt(OutputFilename)); - cl::opt ProfileKind( - cl::desc("Profile kind:"), cl::init(instr), - cl::values(clEnumVal(instr, "Instrumentation profile (default)"), - clEnumVal(sample, "Sample profile"))); - cl::opt TopNFunctions( - "topn", cl::init(0), - cl::desc("Show the list of functions with the largest internal counts")); - cl::opt ValueCutoff( - "value-cutoff", cl::init(0), - cl::desc("Set the count value cutoff. Functions with the maximum count " - "less than this value will not be printed out. (Default is 0)")); - cl::opt OnlyListBelow( - "list-below-cutoff", cl::init(false), - cl::desc("Only output names of functions whose max count values are " - "below the cutoff value")); - cl::opt ShowProfileSymbolList( - "show-prof-sym-list", cl::init(false), - cl::desc("Show profile symbol list if it exists in the profile. ")); - cl::opt ShowSectionInfoOnly( - "show-sec-info-only", cl::init(false), - cl::desc("Show the information of each section in the sample profile. " - "The flag is only usable when the sample profile is in " - "extbinary format")); - - cl::ParseCommandLineOptions(argc, argv, "LLVM profile data summary\n"); - - if (OutputFilename.empty()) - OutputFilename = "-"; - - if (!Filename.compare(OutputFilename)) { - errs() << sys::path::filename(argv[0]) - << ": Input file name cannot be the same as the output file name!\n"; - return 1; - } - - std::error_code EC; - raw_fd_ostream OS(OutputFilename.data(), EC, sys::fs::OF_Text); - if (EC) - exitWithErrorCode(EC, OutputFilename); - - if (ShowAllFunctions && !ShowFunction.empty()) - WithColor::warning() << "-function argument ignored: showing all functions\n"; - - if (ProfileKind == instr) - return showInstrProfile(Filename, ShowCounts, TopNFunctions, - ShowIndirectCallTargets, ShowMemOPSizes, - ShowDetailedSummary, DetailedSummaryCutoffs, - ShowAllFunctions, ShowCS, ValueCutoff, - OnlyListBelow, ShowFunction, TextFormat, OS); - else - return showSampleProfile(Filename, ShowCounts, ShowAllFunctions, - ShowDetailedSummary, ShowFunction, - ShowProfileSymbolList, ShowSectionInfoOnly, - ShowHotFuncList, OS); -} - -int main(int argc, const char *argv[]) { - InitLLVM X(argc, argv); - - StringRef ProgName(sys::path::filename(argv[0])); - if (argc > 1) { - int (*func)(int, const char *[]) = nullptr; - - if (strcmp(argv[1], "merge") == 0) - func = merge_main; - else if (strcmp(argv[1], "show") == 0) - func = show_main; - else if (strcmp(argv[1], "overlap") == 0) - func = overlap_main; - - if (func) { - std::string Invocation(ProgName.str() + " " + argv[1]); - argv[1] = Invocation.c_str(); - return func(argc - 1, argv + 1); - } - - if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "-help") == 0 || - strcmp(argv[1], "--help") == 0) { - - errs() << "OVERVIEW: LLVM profile data tools\n\n" - << "USAGE: " << ProgName << " [args...]\n" - << "USAGE: " << ProgName << " -help\n\n" - << "See each individual command --help for more details.\n" - << "Available commands: merge, show, overlap\n"; - return 0; - } - } - - if (argc < 2) - errs() << ProgName << ": No command specified!\n"; - else - errs() << ProgName << ": Unknown command!\n"; - - errs() << "USAGE: " << ProgName << " [args...]\n"; - return 1; -} diff --git a/tools/ldc-profdata/llvm-profdata-12.0.cpp b/tools/ldc-profdata/llvm-profdata-12.0.cpp deleted file mode 100644 index 7e53c30c757..00000000000 --- a/tools/ldc-profdata/llvm-profdata-12.0.cpp +++ /dev/null @@ -1,2498 +0,0 @@ -//===- llvm-profdata.cpp - LLVM profile data tool -------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// llvm-profdata merges .profdata files. -// -//===----------------------------------------------------------------------===// - -#include "llvm/ADT/SmallSet.h" -#include "llvm/ADT/SmallVector.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/IR/LLVMContext.h" -#include "llvm/ProfileData/InstrProfReader.h" -#include "llvm/ProfileData/InstrProfWriter.h" -#include "llvm/ProfileData/ProfileCommon.h" -#include "llvm/ProfileData/SampleProfReader.h" -#include "llvm/ProfileData/SampleProfWriter.h" -#include "llvm/Support/CommandLine.h" -#include "llvm/Support/Errc.h" -#include "llvm/Support/FileSystem.h" -#include "llvm/Support/Format.h" -#include "llvm/Support/FormattedStream.h" -#include "llvm/Support/InitLLVM.h" -#include "llvm/Support/MemoryBuffer.h" -#include "llvm/Support/Path.h" -#include "llvm/Support/ThreadPool.h" -#include "llvm/Support/Threading.h" -#include "llvm/Support/WithColor.h" -#include "llvm/Support/raw_ostream.h" -#include - -using namespace llvm; - -enum ProfileFormat { - PF_None = 0, - PF_Text, - PF_Compact_Binary, - PF_Ext_Binary, - PF_GCC, - PF_Binary -}; - -static void warn(Twine Message, std::string Whence = "", - std::string Hint = "") { - WithColor::warning(); - if (!Whence.empty()) - errs() << Whence << ": "; - errs() << Message << "\n"; - if (!Hint.empty()) - WithColor::note() << Hint << "\n"; -} - -static void exitWithError(Twine Message, std::string Whence = "", - std::string Hint = "") { - WithColor::error(); - if (!Whence.empty()) - errs() << Whence << ": "; - errs() << Message << "\n"; - if (!Hint.empty()) - WithColor::note() << Hint << "\n"; - ::exit(1); -} - -static void exitWithError(Error E, StringRef Whence = "") { - if (E.isA()) { - handleAllErrors(std::move(E), [&](const InstrProfError &IPE) { - instrprof_error instrError = IPE.get(); - StringRef Hint = ""; - if (instrError == instrprof_error::unrecognized_format) { - // Hint for common error of forgetting --sample for sample profiles. - Hint = "Perhaps you forgot to use the --sample option?"; - } - exitWithError(IPE.message(), std::string(Whence), std::string(Hint)); - }); - } - - exitWithError(toString(std::move(E)), std::string(Whence)); -} - -static void exitWithErrorCode(std::error_code EC, StringRef Whence = "") { - exitWithError(EC.message(), std::string(Whence)); -} - -namespace { -enum ProfileKinds { instr, sample }; -enum FailureMode { failIfAnyAreInvalid, failIfAllAreInvalid }; -} - -static void warnOrExitGivenError(FailureMode FailMode, std::error_code EC, - StringRef Whence = "") { - if (FailMode == failIfAnyAreInvalid) - exitWithErrorCode(EC, Whence); - else - warn(EC.message(), std::string(Whence)); -} - -static void handleMergeWriterError(Error E, StringRef WhenceFile = "", - StringRef WhenceFunction = "", - bool ShowHint = true) { - if (!WhenceFile.empty()) - errs() << WhenceFile << ": "; - if (!WhenceFunction.empty()) - errs() << WhenceFunction << ": "; - - auto IPE = instrprof_error::success; - E = handleErrors(std::move(E), - [&IPE](std::unique_ptr E) -> Error { - IPE = E->get(); - return Error(std::move(E)); - }); - errs() << toString(std::move(E)) << "\n"; - - if (ShowHint) { - StringRef Hint = ""; - if (IPE != instrprof_error::success) { - switch (IPE) { - case instrprof_error::hash_mismatch: - case instrprof_error::count_mismatch: - case instrprof_error::value_site_count_mismatch: - Hint = "Make sure that all profile data to be merged is generated " - "from the same binary."; - break; - default: - break; - } - } - - if (!Hint.empty()) - errs() << Hint << "\n"; - } -} - -namespace { -/// A remapper from original symbol names to new symbol names based on a file -/// containing a list of mappings from old name to new name. -class SymbolRemapper { - std::unique_ptr File; - DenseMap RemappingTable; - -public: - /// Build a SymbolRemapper from a file containing a list of old/new symbols. - static std::unique_ptr create(StringRef InputFile) { - auto BufOrError = MemoryBuffer::getFileOrSTDIN(InputFile); - if (!BufOrError) - exitWithErrorCode(BufOrError.getError(), InputFile); - - auto Remapper = std::make_unique(); - Remapper->File = std::move(BufOrError.get()); - - for (line_iterator LineIt(*Remapper->File, /*SkipBlanks=*/true, '#'); - !LineIt.is_at_eof(); ++LineIt) { - std::pair Parts = LineIt->split(' '); - if (Parts.first.empty() || Parts.second.empty() || - Parts.second.count(' ')) { - exitWithError("unexpected line in remapping file", - (InputFile + ":" + Twine(LineIt.line_number())).str(), - "expected 'old_symbol new_symbol'"); - } - Remapper->RemappingTable.insert(Parts); - } - return Remapper; - } - - /// Attempt to map the given old symbol into a new symbol. - /// - /// \return The new symbol, or \p Name if no such symbol was found. - StringRef operator()(StringRef Name) { - StringRef New = RemappingTable.lookup(Name); - return New.empty() ? Name : New; - } -}; -} - -struct WeightedFile { - std::string Filename; - uint64_t Weight; -}; -typedef SmallVector WeightedFileVector; - -/// Keep track of merged data and reported errors. -struct WriterContext { - std::mutex Lock; - InstrProfWriter Writer; - std::vector> Errors; - std::mutex &ErrLock; - SmallSet &WriterErrorCodes; - - WriterContext(bool IsSparse, std::mutex &ErrLock, - SmallSet &WriterErrorCodes) - : Lock(), Writer(IsSparse), Errors(), ErrLock(ErrLock), - WriterErrorCodes(WriterErrorCodes) {} -}; - -/// Computer the overlap b/w profile BaseFilename and TestFileName, -/// and store the program level result to Overlap. -static void overlapInput(const std::string &BaseFilename, - const std::string &TestFilename, WriterContext *WC, - OverlapStats &Overlap, - const OverlapFuncFilters &FuncFilter, - raw_fd_ostream &OS, bool IsCS) { - auto ReaderOrErr = InstrProfReader::create(TestFilename); - if (Error E = ReaderOrErr.takeError()) { - // Skip the empty profiles by returning sliently. - instrprof_error IPE = InstrProfError::take(std::move(E)); - if (IPE != instrprof_error::empty_raw_profile) - WC->Errors.emplace_back(make_error(IPE), TestFilename); - return; - } - - auto Reader = std::move(ReaderOrErr.get()); - for (auto &I : *Reader) { - OverlapStats FuncOverlap(OverlapStats::FunctionLevel); - FuncOverlap.setFuncInfo(I.Name, I.Hash); - - WC->Writer.overlapRecord(std::move(I), Overlap, FuncOverlap, FuncFilter); - FuncOverlap.dump(OS); - } -} - -/// Load an input into a writer context. -static void loadInput(const WeightedFile &Input, SymbolRemapper *Remapper, - WriterContext *WC) { - std::unique_lock CtxGuard{WC->Lock}; - - // Copy the filename, because llvm::ThreadPool copied the input "const - // WeightedFile &" by value, making a reference to the filename within it - // invalid outside of this packaged task. - std::string Filename = Input.Filename; - - auto ReaderOrErr = InstrProfReader::create(Input.Filename); - if (Error E = ReaderOrErr.takeError()) { - // Skip the empty profiles by returning sliently. - instrprof_error IPE = InstrProfError::take(std::move(E)); - if (IPE != instrprof_error::empty_raw_profile) - WC->Errors.emplace_back(make_error(IPE), Filename); - return; - } - - auto Reader = std::move(ReaderOrErr.get()); - bool IsIRProfile = Reader->isIRLevelProfile(); - bool HasCSIRProfile = Reader->hasCSIRLevelProfile(); - if (WC->Writer.setIsIRLevelProfile(IsIRProfile, HasCSIRProfile)) { - WC->Errors.emplace_back( - make_error( - "Merge IR generated profile with Clang generated profile.", - std::error_code()), - Filename); - return; - } - WC->Writer.setInstrEntryBBEnabled(Reader->instrEntryBBEnabled()); - - for (auto &I : *Reader) { - if (Remapper) - I.Name = (*Remapper)(I.Name); - const StringRef FuncName = I.Name; - bool Reported = false; - WC->Writer.addRecord(std::move(I), Input.Weight, [&](Error E) { - if (Reported) { - consumeError(std::move(E)); - return; - } - Reported = true; - // Only show hint the first time an error occurs. - instrprof_error IPE = InstrProfError::take(std::move(E)); - std::unique_lock ErrGuard{WC->ErrLock}; - bool firstTime = WC->WriterErrorCodes.insert(IPE).second; - handleMergeWriterError(make_error(IPE), Input.Filename, - FuncName, firstTime); - }); - } - if (Reader->hasError()) - if (Error E = Reader->getError()) - WC->Errors.emplace_back(std::move(E), Filename); -} - -/// Merge the \p Src writer context into \p Dst. -static void mergeWriterContexts(WriterContext *Dst, WriterContext *Src) { - for (auto &ErrorPair : Src->Errors) - Dst->Errors.push_back(std::move(ErrorPair)); - Src->Errors.clear(); - - Dst->Writer.mergeRecordsFromWriter(std::move(Src->Writer), [&](Error E) { - instrprof_error IPE = InstrProfError::take(std::move(E)); - std::unique_lock ErrGuard{Dst->ErrLock}; - bool firstTime = Dst->WriterErrorCodes.insert(IPE).second; - if (firstTime) - warn(toString(make_error(IPE))); - }); -} - -static void writeInstrProfile(StringRef OutputFilename, - ProfileFormat OutputFormat, - InstrProfWriter &Writer) { - std::error_code EC; - raw_fd_ostream Output(OutputFilename.data(), EC, - OutputFormat == PF_Text ? sys::fs::OF_Text - : sys::fs::OF_None); - if (EC) - exitWithErrorCode(EC, OutputFilename); - - if (OutputFormat == PF_Text) { - if (Error E = Writer.writeText(Output)) - exitWithError(std::move(E)); - } else { - Writer.write(Output); - } -} - -static void mergeInstrProfile(const WeightedFileVector &Inputs, - SymbolRemapper *Remapper, - StringRef OutputFilename, - ProfileFormat OutputFormat, bool OutputSparse, - unsigned NumThreads, FailureMode FailMode) { - if (OutputFilename.compare("-") == 0) - exitWithError("Cannot write indexed profdata format to stdout."); - - if (OutputFormat != PF_Binary && OutputFormat != PF_Compact_Binary && - OutputFormat != PF_Ext_Binary && OutputFormat != PF_Text) - exitWithError("Unknown format is specified."); - - std::mutex ErrorLock; - SmallSet WriterErrorCodes; - - // If NumThreads is not specified, auto-detect a good default. - if (NumThreads == 0) - NumThreads = std::min(hardware_concurrency().compute_thread_count(), - unsigned((Inputs.size() + 1) / 2)); - // FIXME: There's a bug here, where setting NumThreads = Inputs.size() fails - // the merge_empty_profile.test because the InstrProfWriter.ProfileKind isn't - // merged, thus the emitted file ends up with a PF_Unknown kind. - - // Initialize the writer contexts. - SmallVector, 4> Contexts; - for (unsigned I = 0; I < NumThreads; ++I) - Contexts.emplace_back(std::make_unique( - OutputSparse, ErrorLock, WriterErrorCodes)); - - if (NumThreads == 1) { - for (const auto &Input : Inputs) - loadInput(Input, Remapper, Contexts[0].get()); - } else { - ThreadPool Pool(hardware_concurrency(NumThreads)); - - // Load the inputs in parallel (N/NumThreads serial steps). - unsigned Ctx = 0; - for (const auto &Input : Inputs) { - Pool.async(loadInput, Input, Remapper, Contexts[Ctx].get()); - Ctx = (Ctx + 1) % NumThreads; - } - Pool.wait(); - - // Merge the writer contexts together (~ lg(NumThreads) serial steps). - unsigned Mid = Contexts.size() / 2; - unsigned End = Contexts.size(); - assert(Mid > 0 && "Expected more than one context"); - do { - for (unsigned I = 0; I < Mid; ++I) - Pool.async(mergeWriterContexts, Contexts[I].get(), - Contexts[I + Mid].get()); - Pool.wait(); - if (End & 1) { - Pool.async(mergeWriterContexts, Contexts[0].get(), - Contexts[End - 1].get()); - Pool.wait(); - } - End = Mid; - Mid /= 2; - } while (Mid > 0); - } - - // Handle deferred errors encountered during merging. If the number of errors - // is equal to the number of inputs the merge failed. - unsigned NumErrors = 0; - for (std::unique_ptr &WC : Contexts) { - for (auto &ErrorPair : WC->Errors) { - ++NumErrors; - warn(toString(std::move(ErrorPair.first)), ErrorPair.second); - } - } - if (NumErrors == Inputs.size() || - (NumErrors > 0 && FailMode == failIfAnyAreInvalid)) - exitWithError("No profiles could be merged."); - - writeInstrProfile(OutputFilename, OutputFormat, Contexts[0]->Writer); -} - -/// The profile entry for a function in instrumentation profile. -struct InstrProfileEntry { - uint64_t MaxCount = 0; - float ZeroCounterRatio = 0.0; - InstrProfRecord *ProfRecord; - InstrProfileEntry(InstrProfRecord *Record); - InstrProfileEntry() = default; -}; - -InstrProfileEntry::InstrProfileEntry(InstrProfRecord *Record) { - ProfRecord = Record; - uint64_t CntNum = Record->Counts.size(); - uint64_t ZeroCntNum = 0; - for (size_t I = 0; I < CntNum; ++I) { - MaxCount = std::max(MaxCount, Record->Counts[I]); - ZeroCntNum += !Record->Counts[I]; - } - ZeroCounterRatio = (float)ZeroCntNum / CntNum; -} - -/// Either set all the counters in the instr profile entry \p IFE to -1 -/// in order to drop the profile or scale up the counters in \p IFP to -/// be above hot threshold. We use the ratio of zero counters in the -/// profile of a function to decide the profile is helpful or harmful -/// for performance, and to choose whether to scale up or drop it. -static void updateInstrProfileEntry(InstrProfileEntry &IFE, - uint64_t HotInstrThreshold, - float ZeroCounterThreshold) { - InstrProfRecord *ProfRecord = IFE.ProfRecord; - if (!IFE.MaxCount || IFE.ZeroCounterRatio > ZeroCounterThreshold) { - // If all or most of the counters of the function are zero, the - // profile is unaccountable and shuld be dropped. Reset all the - // counters to be -1 and PGO profile-use will drop the profile. - // All counters being -1 also implies that the function is hot so - // PGO profile-use will also set the entry count metadata to be - // above hot threshold. - for (size_t I = 0; I < ProfRecord->Counts.size(); ++I) - ProfRecord->Counts[I] = -1; - return; - } - - // Scale up the MaxCount to be multiple times above hot threshold. - const unsigned MultiplyFactor = 3; - uint64_t Numerator = HotInstrThreshold * MultiplyFactor; - uint64_t Denominator = IFE.MaxCount; - ProfRecord->scale(Numerator, Denominator, [&](instrprof_error E) { - warn(toString(make_error(E))); - }); -} - -const uint64_t ColdPercentileIdx = 15; -const uint64_t HotPercentileIdx = 11; - -/// Adjust the instr profile in \p WC based on the sample profile in -/// \p Reader. -static void -adjustInstrProfile(std::unique_ptr &WC, - std::unique_ptr &Reader, - unsigned SupplMinSizeThreshold, float ZeroCounterThreshold, - unsigned InstrProfColdThreshold) { - // Function to its entry in instr profile. - StringMap InstrProfileMap; - InstrProfSummaryBuilder IPBuilder(ProfileSummaryBuilder::DefaultCutoffs); - for (auto &PD : WC->Writer.getProfileData()) { - // Populate IPBuilder. - for (const auto &PDV : PD.getValue()) { - InstrProfRecord Record = PDV.second; - IPBuilder.addRecord(Record); - } - - // If a function has multiple entries in instr profile, skip it. - if (PD.getValue().size() != 1) - continue; - - // Initialize InstrProfileMap. - InstrProfRecord *R = &PD.getValue().begin()->second; - InstrProfileMap[PD.getKey()] = InstrProfileEntry(R); - } - - ProfileSummary InstrPS = *IPBuilder.getSummary(); - ProfileSummary SamplePS = Reader->getSummary(); - - // Compute cold thresholds for instr profile and sample profile. - uint64_t ColdSampleThreshold = - ProfileSummaryBuilder::getEntryForPercentile( - SamplePS.getDetailedSummary(), - ProfileSummaryBuilder::DefaultCutoffs[ColdPercentileIdx]) - .MinCount; - uint64_t HotInstrThreshold = - ProfileSummaryBuilder::getEntryForPercentile( - InstrPS.getDetailedSummary(), - ProfileSummaryBuilder::DefaultCutoffs[HotPercentileIdx]) - .MinCount; - uint64_t ColdInstrThreshold = - InstrProfColdThreshold - ? InstrProfColdThreshold - : ProfileSummaryBuilder::getEntryForPercentile( - InstrPS.getDetailedSummary(), - ProfileSummaryBuilder::DefaultCutoffs[ColdPercentileIdx]) - .MinCount; - - // Find hot/warm functions in sample profile which is cold in instr profile - // and adjust the profiles of those functions in the instr profile. - for (const auto &PD : Reader->getProfiles()) { - StringRef FName = PD.getKey(); - const sampleprof::FunctionSamples &FS = PD.getValue(); - auto It = InstrProfileMap.find(FName); - if (FS.getHeadSamples() > ColdSampleThreshold && - It != InstrProfileMap.end() && - It->second.MaxCount <= ColdInstrThreshold && - FS.getBodySamples().size() >= SupplMinSizeThreshold) { - updateInstrProfileEntry(It->second, HotInstrThreshold, - ZeroCounterThreshold); - } - } -} - -/// The main function to supplement instr profile with sample profile. -/// \Inputs contains the instr profile. \p SampleFilename specifies the -/// sample profile. \p OutputFilename specifies the output profile name. -/// \p OutputFormat specifies the output profile format. \p OutputSparse -/// specifies whether to generate sparse profile. \p SupplMinSizeThreshold -/// specifies the minimal size for the functions whose profile will be -/// adjusted. \p ZeroCounterThreshold is the threshold to check whether -/// a function contains too many zero counters and whether its profile -/// should be dropped. \p InstrProfColdThreshold is the user specified -/// cold threshold which will override the cold threshold got from the -/// instr profile summary. -static void supplementInstrProfile( - const WeightedFileVector &Inputs, StringRef SampleFilename, - StringRef OutputFilename, ProfileFormat OutputFormat, bool OutputSparse, - unsigned SupplMinSizeThreshold, float ZeroCounterThreshold, - unsigned InstrProfColdThreshold) { - if (OutputFilename.compare("-") == 0) - exitWithError("Cannot write indexed profdata format to stdout."); - if (Inputs.size() != 1) - exitWithError("Expect one input to be an instr profile."); - if (Inputs[0].Weight != 1) - exitWithError("Expect instr profile doesn't have weight."); - - StringRef InstrFilename = Inputs[0].Filename; - - // Read sample profile. - LLVMContext Context; - auto ReaderOrErr = - sampleprof::SampleProfileReader::create(SampleFilename.str(), Context); - if (std::error_code EC = ReaderOrErr.getError()) - exitWithErrorCode(EC, SampleFilename); - auto Reader = std::move(ReaderOrErr.get()); - if (std::error_code EC = Reader->read()) - exitWithErrorCode(EC, SampleFilename); - - // Read instr profile. - std::mutex ErrorLock; - SmallSet WriterErrorCodes; - auto WC = std::make_unique(OutputSparse, ErrorLock, - WriterErrorCodes); - loadInput(Inputs[0], nullptr, WC.get()); - if (WC->Errors.size() > 0) - exitWithError(std::move(WC->Errors[0].first), InstrFilename); - - adjustInstrProfile(WC, Reader, SupplMinSizeThreshold, ZeroCounterThreshold, - InstrProfColdThreshold); - writeInstrProfile(OutputFilename, OutputFormat, WC->Writer); -} - -/// Make a copy of the given function samples with all symbol names remapped -/// by the provided symbol remapper. -static sampleprof::FunctionSamples -remapSamples(const sampleprof::FunctionSamples &Samples, - SymbolRemapper &Remapper, sampleprof_error &Error) { - sampleprof::FunctionSamples Result; - Result.setName(Remapper(Samples.getName())); - Result.addTotalSamples(Samples.getTotalSamples()); - Result.addHeadSamples(Samples.getHeadSamples()); - for (const auto &BodySample : Samples.getBodySamples()) { - Result.addBodySamples(BodySample.first.LineOffset, - BodySample.first.Discriminator, - BodySample.second.getSamples()); - for (const auto &Target : BodySample.second.getCallTargets()) { - Result.addCalledTargetSamples(BodySample.first.LineOffset, - BodySample.first.Discriminator, - Remapper(Target.first()), Target.second); - } - } - for (const auto &CallsiteSamples : Samples.getCallsiteSamples()) { - sampleprof::FunctionSamplesMap &Target = - Result.functionSamplesAt(CallsiteSamples.first); - for (const auto &Callsite : CallsiteSamples.second) { - sampleprof::FunctionSamples Remapped = - remapSamples(Callsite.second, Remapper, Error); - MergeResult(Error, - Target[std::string(Remapped.getName())].merge(Remapped)); - } - } - return Result; -} - -static sampleprof::SampleProfileFormat FormatMap[] = { - sampleprof::SPF_None, - sampleprof::SPF_Text, - sampleprof::SPF_Compact_Binary, - sampleprof::SPF_Ext_Binary, - sampleprof::SPF_GCC, - sampleprof::SPF_Binary}; - -static std::unique_ptr -getInputFileBuf(const StringRef &InputFile) { - if (InputFile == "") - return {}; - - auto BufOrError = MemoryBuffer::getFileOrSTDIN(InputFile); - if (!BufOrError) - exitWithErrorCode(BufOrError.getError(), InputFile); - - return std::move(*BufOrError); -} - -static void populateProfileSymbolList(MemoryBuffer *Buffer, - sampleprof::ProfileSymbolList &PSL) { - if (!Buffer) - return; - - SmallVector SymbolVec; - StringRef Data = Buffer->getBuffer(); - Data.split(SymbolVec, '\n', /*MaxSplit=*/-1, /*KeepEmpty=*/false); - - for (StringRef symbol : SymbolVec) - PSL.add(symbol); -} - -static void handleExtBinaryWriter(sampleprof::SampleProfileWriter &Writer, - ProfileFormat OutputFormat, - MemoryBuffer *Buffer, - sampleprof::ProfileSymbolList &WriterList, - bool CompressAllSections, bool UseMD5, - bool GenPartialProfile) { - populateProfileSymbolList(Buffer, WriterList); - if (WriterList.size() > 0 && OutputFormat != PF_Ext_Binary) - warn("Profile Symbol list is not empty but the output format is not " - "ExtBinary format. The list will be lost in the output. "); - - Writer.setProfileSymbolList(&WriterList); - - if (CompressAllSections) { - if (OutputFormat != PF_Ext_Binary) - warn("-compress-all-section is ignored. Specify -extbinary to enable it"); - else - Writer.setToCompressAllSections(); - } - if (UseMD5) { - if (OutputFormat != PF_Ext_Binary) - warn("-use-md5 is ignored. Specify -extbinary to enable it"); - else - Writer.setUseMD5(); - } - if (GenPartialProfile) { - if (OutputFormat != PF_Ext_Binary) - warn("-gen-partial-profile is ignored. Specify -extbinary to enable it"); - else - Writer.setPartialProfile(); - } -} - -static void -mergeSampleProfile(const WeightedFileVector &Inputs, SymbolRemapper *Remapper, - StringRef OutputFilename, ProfileFormat OutputFormat, - StringRef ProfileSymbolListFile, bool CompressAllSections, - bool UseMD5, bool GenPartialProfile, FailureMode FailMode) { - using namespace sampleprof; - StringMap ProfileMap; - SmallVector, 5> Readers; - LLVMContext Context; - sampleprof::ProfileSymbolList WriterList; - Optional ProfileIsProbeBased; - for (const auto &Input : Inputs) { - auto ReaderOrErr = SampleProfileReader::create(Input.Filename, Context); - if (std::error_code EC = ReaderOrErr.getError()) { - warnOrExitGivenError(FailMode, EC, Input.Filename); - continue; - } - - // We need to keep the readers around until after all the files are - // read so that we do not lose the function names stored in each - // reader's memory. The function names are needed to write out the - // merged profile map. - Readers.push_back(std::move(ReaderOrErr.get())); - const auto Reader = Readers.back().get(); - if (std::error_code EC = Reader->read()) { - warnOrExitGivenError(FailMode, EC, Input.Filename); - Readers.pop_back(); - continue; - } - - StringMap &Profiles = Reader->getProfiles(); - if (ProfileIsProbeBased && - ProfileIsProbeBased != FunctionSamples::ProfileIsProbeBased) - exitWithError( - "cannot merge probe-based profile with non-probe-based profile"); - ProfileIsProbeBased = FunctionSamples::ProfileIsProbeBased; - for (StringMap::iterator I = Profiles.begin(), - E = Profiles.end(); - I != E; ++I) { - sampleprof_error Result = sampleprof_error::success; - FunctionSamples Remapped = - Remapper ? remapSamples(I->second, *Remapper, Result) - : FunctionSamples(); - FunctionSamples &Samples = Remapper ? Remapped : I->second; - StringRef FName = Samples.getNameWithContext(true); - MergeResult(Result, ProfileMap[FName].merge(Samples, Input.Weight)); - if (Result != sampleprof_error::success) { - std::error_code EC = make_error_code(Result); - handleMergeWriterError(errorCodeToError(EC), Input.Filename, FName); - } - } - - std::unique_ptr ReaderList = - Reader->getProfileSymbolList(); - if (ReaderList) - WriterList.merge(*ReaderList); - } - auto WriterOrErr = - SampleProfileWriter::create(OutputFilename, FormatMap[OutputFormat]); - if (std::error_code EC = WriterOrErr.getError()) - exitWithErrorCode(EC, OutputFilename); - - auto Writer = std::move(WriterOrErr.get()); - // WriterList will have StringRef refering to string in Buffer. - // Make sure Buffer lives as long as WriterList. - auto Buffer = getInputFileBuf(ProfileSymbolListFile); - handleExtBinaryWriter(*Writer, OutputFormat, Buffer.get(), WriterList, - CompressAllSections, UseMD5, GenPartialProfile); - Writer->write(ProfileMap); -} - -static WeightedFile parseWeightedFile(const StringRef &WeightedFilename) { - StringRef WeightStr, FileName; - std::tie(WeightStr, FileName) = WeightedFilename.split(','); - - uint64_t Weight; - if (WeightStr.getAsInteger(10, Weight) || Weight < 1) - exitWithError("Input weight must be a positive integer."); - - return {std::string(FileName), Weight}; -} - -static void addWeightedInput(WeightedFileVector &WNI, const WeightedFile &WF) { - StringRef Filename = WF.Filename; - uint64_t Weight = WF.Weight; - - // If it's STDIN just pass it on. - if (Filename == "-") { - WNI.push_back({std::string(Filename), Weight}); - return; - } - - llvm::sys::fs::file_status Status; - llvm::sys::fs::status(Filename, Status); - if (!llvm::sys::fs::exists(Status)) - exitWithErrorCode(make_error_code(errc::no_such_file_or_directory), - Filename); - // If it's a source file, collect it. - if (llvm::sys::fs::is_regular_file(Status)) { - WNI.push_back({std::string(Filename), Weight}); - return; - } - - if (llvm::sys::fs::is_directory(Status)) { - std::error_code EC; - for (llvm::sys::fs::recursive_directory_iterator F(Filename, EC), E; - F != E && !EC; F.increment(EC)) { - if (llvm::sys::fs::is_regular_file(F->path())) { - addWeightedInput(WNI, {F->path(), Weight}); - } - } - if (EC) - exitWithErrorCode(EC, Filename); - } -} - -static void parseInputFilenamesFile(MemoryBuffer *Buffer, - WeightedFileVector &WFV) { - if (!Buffer) - return; - - SmallVector Entries; - StringRef Data = Buffer->getBuffer(); - Data.split(Entries, '\n', /*MaxSplit=*/-1, /*KeepEmpty=*/false); - for (const StringRef &FileWeightEntry : Entries) { - StringRef SanitizedEntry = FileWeightEntry.trim(" \t\v\f\r"); - // Skip comments. - if (SanitizedEntry.startswith("#")) - continue; - // If there's no comma, it's an unweighted profile. - else if (SanitizedEntry.find(',') == StringRef::npos) - addWeightedInput(WFV, {std::string(SanitizedEntry), 1}); - else - addWeightedInput(WFV, parseWeightedFile(SanitizedEntry)); - } -} - -static int merge_main(int argc, const char *argv[]) { - cl::list InputFilenames(cl::Positional, - cl::desc("")); - cl::list WeightedInputFilenames("weighted-input", - cl::desc(",")); - cl::opt InputFilenamesFile( - "input-files", cl::init(""), - cl::desc("Path to file containing newline-separated " - "[,] entries")); - cl::alias InputFilenamesFileA("f", cl::desc("Alias for --input-files"), - cl::aliasopt(InputFilenamesFile)); - cl::opt DumpInputFileList( - "dump-input-file-list", cl::init(false), cl::Hidden, - cl::desc("Dump the list of input files and their weights, then exit")); - cl::opt RemappingFile("remapping-file", cl::value_desc("file"), - cl::desc("Symbol remapping file")); - cl::alias RemappingFileA("r", cl::desc("Alias for --remapping-file"), - cl::aliasopt(RemappingFile)); - cl::opt OutputFilename("output", cl::value_desc("output"), - cl::init("-"), cl::Required, - cl::desc("Output file")); - cl::alias OutputFilenameA("o", cl::desc("Alias for --output"), - cl::aliasopt(OutputFilename)); - cl::opt ProfileKind( - cl::desc("Profile kind:"), cl::init(instr), - cl::values(clEnumVal(instr, "Instrumentation profile (default)"), - clEnumVal(sample, "Sample profile"))); - cl::opt OutputFormat( - cl::desc("Format of output profile"), cl::init(PF_Binary), - cl::values( - clEnumValN(PF_Binary, "binary", "Binary encoding (default)"), - clEnumValN(PF_Compact_Binary, "compbinary", - "Compact binary encoding"), - clEnumValN(PF_Ext_Binary, "extbinary", "Extensible binary encoding"), - clEnumValN(PF_Text, "text", "Text encoding"), - clEnumValN(PF_GCC, "gcc", - "GCC encoding (only meaningful for -sample)"))); - cl::opt FailureMode( - "failure-mode", cl::init(failIfAnyAreInvalid), cl::desc("Failure mode:"), - cl::values(clEnumValN(failIfAnyAreInvalid, "any", - "Fail if any profile is invalid."), - clEnumValN(failIfAllAreInvalid, "all", - "Fail only if all profiles are invalid."))); - cl::opt OutputSparse("sparse", cl::init(false), - cl::desc("Generate a sparse profile (only meaningful for -instr)")); - cl::opt NumThreads( - "num-threads", cl::init(0), - cl::desc("Number of merge threads to use (default: autodetect)")); - cl::alias NumThreadsA("j", cl::desc("Alias for --num-threads"), - cl::aliasopt(NumThreads)); - cl::opt ProfileSymbolListFile( - "prof-sym-list", cl::init(""), - cl::desc("Path to file containing the list of function symbols " - "used to populate profile symbol list")); - cl::opt CompressAllSections( - "compress-all-sections", cl::init(false), cl::Hidden, - cl::desc("Compress all sections when writing the profile (only " - "meaningful for -extbinary)")); - cl::opt UseMD5( - "use-md5", cl::init(false), cl::Hidden, - cl::desc("Choose to use MD5 to represent string in name table (only " - "meaningful for -extbinary)")); - cl::opt GenPartialProfile( - "gen-partial-profile", cl::init(false), cl::Hidden, - cl::desc("Generate a partial profile (only meaningful for -extbinary)")); - cl::opt SupplInstrWithSample( - "supplement-instr-with-sample", cl::init(""), cl::Hidden, - cl::desc("Supplement an instr profile with sample profile, to correct " - "the profile unrepresentativeness issue. The sample " - "profile is the input of the flag. Output will be in instr " - "format (The flag only works with -instr)")); - cl::opt ZeroCounterThreshold( - "zero-counter-threshold", cl::init(0.7), cl::Hidden, - cl::desc("For the function which is cold in instr profile but hot in " - "sample profile, if the ratio of the number of zero counters " - "divided by the the total number of counters is above the " - "threshold, the profile of the function will be regarded as " - "being harmful for performance and will be dropped. ")); - cl::opt SupplMinSizeThreshold( - "suppl-min-size-threshold", cl::init(10), cl::Hidden, - cl::desc("If the size of a function is smaller than the threshold, " - "assume it can be inlined by PGO early inliner and it won't " - "be adjusted based on sample profile. ")); - cl::opt InstrProfColdThreshold( - "instr-prof-cold-threshold", cl::init(0), cl::Hidden, - cl::desc("User specified cold threshold for instr profile which will " - "override the cold threshold got from profile summary. ")); - - cl::ParseCommandLineOptions(argc, argv, "LLVM profile data merger\n"); - - WeightedFileVector WeightedInputs; - for (StringRef Filename : InputFilenames) - addWeightedInput(WeightedInputs, {std::string(Filename), 1}); - for (StringRef WeightedFilename : WeightedInputFilenames) - addWeightedInput(WeightedInputs, parseWeightedFile(WeightedFilename)); - - // Make sure that the file buffer stays alive for the duration of the - // weighted input vector's lifetime. - auto Buffer = getInputFileBuf(InputFilenamesFile); - parseInputFilenamesFile(Buffer.get(), WeightedInputs); - - if (WeightedInputs.empty()) - exitWithError("No input files specified. See " + - sys::path::filename(argv[0]) + " -help"); - - if (DumpInputFileList) { - for (auto &WF : WeightedInputs) - outs() << WF.Weight << "," << WF.Filename << "\n"; - return 0; - } - - std::unique_ptr Remapper; - if (!RemappingFile.empty()) - Remapper = SymbolRemapper::create(RemappingFile); - - if (!SupplInstrWithSample.empty()) { - if (ProfileKind != instr) - exitWithError( - "-supplement-instr-with-sample can only work with -instr. "); - - supplementInstrProfile(WeightedInputs, SupplInstrWithSample, OutputFilename, - OutputFormat, OutputSparse, SupplMinSizeThreshold, - ZeroCounterThreshold, InstrProfColdThreshold); - return 0; - } - - if (ProfileKind == instr) - mergeInstrProfile(WeightedInputs, Remapper.get(), OutputFilename, - OutputFormat, OutputSparse, NumThreads, FailureMode); - else - mergeSampleProfile(WeightedInputs, Remapper.get(), OutputFilename, - OutputFormat, ProfileSymbolListFile, CompressAllSections, - UseMD5, GenPartialProfile, FailureMode); - - return 0; -} - -/// Computer the overlap b/w profile BaseFilename and profile TestFilename. -static void overlapInstrProfile(const std::string &BaseFilename, - const std::string &TestFilename, - const OverlapFuncFilters &FuncFilter, - raw_fd_ostream &OS, bool IsCS) { - std::mutex ErrorLock; - SmallSet WriterErrorCodes; - WriterContext Context(false, ErrorLock, WriterErrorCodes); - WeightedFile WeightedInput{BaseFilename, 1}; - OverlapStats Overlap; - Error E = Overlap.accumulateCounts(BaseFilename, TestFilename, IsCS); - if (E) - exitWithError(std::move(E), "Error in getting profile count sums"); - if (Overlap.Base.CountSum < 1.0f) { - OS << "Sum of edge counts for profile " << BaseFilename << " is 0.\n"; - exit(0); - } - if (Overlap.Test.CountSum < 1.0f) { - OS << "Sum of edge counts for profile " << TestFilename << " is 0.\n"; - exit(0); - } - loadInput(WeightedInput, nullptr, &Context); - overlapInput(BaseFilename, TestFilename, &Context, Overlap, FuncFilter, OS, - IsCS); - Overlap.dump(OS); -} - -namespace { -struct SampleOverlapStats { - StringRef BaseName; - StringRef TestName; - // Number of overlap units - uint64_t OverlapCount; - // Total samples of overlap units - uint64_t OverlapSample; - // Number of and total samples of units that only present in base or test - // profile - uint64_t BaseUniqueCount; - uint64_t BaseUniqueSample; - uint64_t TestUniqueCount; - uint64_t TestUniqueSample; - // Number of units and total samples in base or test profile - uint64_t BaseCount; - uint64_t BaseSample; - uint64_t TestCount; - uint64_t TestSample; - // Number of and total samples of units that present in at least one profile - uint64_t UnionCount; - uint64_t UnionSample; - // Weighted similarity - double Similarity; - // For SampleOverlapStats instances representing functions, weights of the - // function in base and test profiles - double BaseWeight; - double TestWeight; - - SampleOverlapStats() - : OverlapCount(0), OverlapSample(0), BaseUniqueCount(0), - BaseUniqueSample(0), TestUniqueCount(0), TestUniqueSample(0), - BaseCount(0), BaseSample(0), TestCount(0), TestSample(0), UnionCount(0), - UnionSample(0), Similarity(0.0), BaseWeight(0.0), TestWeight(0.0) {} -}; -} // end anonymous namespace - -namespace { -struct FuncSampleStats { - uint64_t SampleSum; - uint64_t MaxSample; - uint64_t HotBlockCount; - FuncSampleStats() : SampleSum(0), MaxSample(0), HotBlockCount(0) {} - FuncSampleStats(uint64_t SampleSum, uint64_t MaxSample, - uint64_t HotBlockCount) - : SampleSum(SampleSum), MaxSample(MaxSample), - HotBlockCount(HotBlockCount) {} -}; -} // end anonymous namespace - -namespace { -enum MatchStatus { MS_Match, MS_FirstUnique, MS_SecondUnique, MS_None }; - -// Class for updating merging steps for two sorted maps. The class should be -// instantiated with a map iterator type. -template class MatchStep { -public: - MatchStep() = delete; - - MatchStep(T FirstIter, T FirstEnd, T SecondIter, T SecondEnd) - : FirstIter(FirstIter), FirstEnd(FirstEnd), SecondIter(SecondIter), - SecondEnd(SecondEnd), Status(MS_None) {} - - bool areBothFinished() const { - return (FirstIter == FirstEnd && SecondIter == SecondEnd); - } - - bool isFirstFinished() const { return FirstIter == FirstEnd; } - - bool isSecondFinished() const { return SecondIter == SecondEnd; } - - /// Advance one step based on the previous match status unless the previous - /// status is MS_None. Then update Status based on the comparison between two - /// container iterators at the current step. If the previous status is - /// MS_None, it means two iterators are at the beginning and no comparison has - /// been made, so we simply update Status without advancing the iterators. - void updateOneStep(); - - T getFirstIter() const { return FirstIter; } - - T getSecondIter() const { return SecondIter; } - - MatchStatus getMatchStatus() const { return Status; } - -private: - // Current iterator and end iterator of the first container. - T FirstIter; - T FirstEnd; - // Current iterator and end iterator of the second container. - T SecondIter; - T SecondEnd; - // Match status of the current step. - MatchStatus Status; -}; -} // end anonymous namespace - -template void MatchStep::updateOneStep() { - switch (Status) { - case MS_Match: - ++FirstIter; - ++SecondIter; - break; - case MS_FirstUnique: - ++FirstIter; - break; - case MS_SecondUnique: - ++SecondIter; - break; - case MS_None: - break; - } - - // Update Status according to iterators at the current step. - if (areBothFinished()) - return; - if (FirstIter != FirstEnd && - (SecondIter == SecondEnd || FirstIter->first < SecondIter->first)) - Status = MS_FirstUnique; - else if (SecondIter != SecondEnd && - (FirstIter == FirstEnd || SecondIter->first < FirstIter->first)) - Status = MS_SecondUnique; - else - Status = MS_Match; -} - -// Return the sum of line/block samples, the max line/block sample, and the -// number of line/block samples above the given threshold in a function -// including its inlinees. -static void getFuncSampleStats(const sampleprof::FunctionSamples &Func, - FuncSampleStats &FuncStats, - uint64_t HotThreshold) { - for (const auto &L : Func.getBodySamples()) { - uint64_t Sample = L.second.getSamples(); - FuncStats.SampleSum += Sample; - FuncStats.MaxSample = std::max(FuncStats.MaxSample, Sample); - if (Sample >= HotThreshold) - ++FuncStats.HotBlockCount; - } - - for (const auto &C : Func.getCallsiteSamples()) { - for (const auto &F : C.second) - getFuncSampleStats(F.second, FuncStats, HotThreshold); - } -} - -/// Predicate that determines if a function is hot with a given threshold. We -/// keep it separate from its callsites for possible extension in the future. -static bool isFunctionHot(const FuncSampleStats &FuncStats, - uint64_t HotThreshold) { - // We intentionally compare the maximum sample count in a function with the - // HotThreshold to get an approximate determination on hot functions. - return (FuncStats.MaxSample >= HotThreshold); -} - -namespace { -class SampleOverlapAggregator { -public: - SampleOverlapAggregator(const std::string &BaseFilename, - const std::string &TestFilename, - double LowSimilarityThreshold, double Epsilon, - const OverlapFuncFilters &FuncFilter) - : BaseFilename(BaseFilename), TestFilename(TestFilename), - LowSimilarityThreshold(LowSimilarityThreshold), Epsilon(Epsilon), - FuncFilter(FuncFilter) {} - - /// Detect 0-sample input profile and report to output stream. This interface - /// should be called after loadProfiles(). - bool detectZeroSampleProfile(raw_fd_ostream &OS) const; - - /// Write out function-level similarity statistics for functions specified by - /// options --function, --value-cutoff, and --similarity-cutoff. - void dumpFuncSimilarity(raw_fd_ostream &OS) const; - - /// Write out program-level similarity and overlap statistics. - void dumpProgramSummary(raw_fd_ostream &OS) const; - - /// Write out hot-function and hot-block statistics for base_profile, - /// test_profile, and their overlap. For both cases, the overlap HO is - /// calculated as follows: - /// Given the number of functions (or blocks) that are hot in both profiles - /// HCommon and the number of functions (or blocks) that are hot in at - /// least one profile HUnion, HO = HCommon / HUnion. - void dumpHotFuncAndBlockOverlap(raw_fd_ostream &OS) const; - - /// This function tries matching functions in base and test profiles. For each - /// pair of matched functions, it aggregates the function-level - /// similarity into a profile-level similarity. It also dump function-level - /// similarity information of functions specified by --function, - /// --value-cutoff, and --similarity-cutoff options. The program-level - /// similarity PS is computed as follows: - /// Given function-level similarity FS(A) for all function A, the - /// weight of function A in base profile WB(A), and the weight of function - /// A in test profile WT(A), compute PS(base_profile, test_profile) = - /// sum_A(FS(A) * avg(WB(A), WT(A))) ranging in [0.0f to 1.0f] with 0.0 - /// meaning no-overlap. - void computeSampleProfileOverlap(raw_fd_ostream &OS); - - /// Initialize ProfOverlap with the sum of samples in base and test - /// profiles. This function also computes and keeps the sum of samples and - /// max sample counts of each function in BaseStats and TestStats for later - /// use to avoid re-computations. - void initializeSampleProfileOverlap(); - - /// Load profiles specified by BaseFilename and TestFilename. - std::error_code loadProfiles(); - -private: - SampleOverlapStats ProfOverlap; - SampleOverlapStats HotFuncOverlap; - SampleOverlapStats HotBlockOverlap; - std::string BaseFilename; - std::string TestFilename; - std::unique_ptr BaseReader; - std::unique_ptr TestReader; - // BaseStats and TestStats hold FuncSampleStats for each function, with - // function name as the key. - StringMap BaseStats; - StringMap TestStats; - // Low similarity threshold in floating point number - double LowSimilarityThreshold; - // Block samples above BaseHotThreshold or TestHotThreshold are considered hot - // for tracking hot blocks. - uint64_t BaseHotThreshold; - uint64_t TestHotThreshold; - // A small threshold used to round the results of floating point accumulations - // to resolve imprecision. - const double Epsilon; - std::multimap> - FuncSimilarityDump; - // FuncFilter carries specifications in options --value-cutoff and - // --function. - OverlapFuncFilters FuncFilter; - // Column offsets for printing the function-level details table. - static const unsigned int TestWeightCol = 15; - static const unsigned int SimilarityCol = 30; - static const unsigned int OverlapCol = 43; - static const unsigned int BaseUniqueCol = 53; - static const unsigned int TestUniqueCol = 67; - static const unsigned int BaseSampleCol = 81; - static const unsigned int TestSampleCol = 96; - static const unsigned int FuncNameCol = 111; - - /// Return a similarity of two line/block sample counters in the same - /// function in base and test profiles. The line/block-similarity BS(i) is - /// computed as follows: - /// For an offsets i, given the sample count at i in base profile BB(i), - /// the sample count at i in test profile BT(i), the sum of sample counts - /// in this function in base profile SB, and the sum of sample counts in - /// this function in test profile ST, compute BS(i) = 1.0 - fabs(BB(i)/SB - - /// BT(i)/ST), ranging in [0.0f to 1.0f] with 0.0 meaning no-overlap. - double computeBlockSimilarity(uint64_t BaseSample, uint64_t TestSample, - const SampleOverlapStats &FuncOverlap) const; - - void updateHotBlockOverlap(uint64_t BaseSample, uint64_t TestSample, - uint64_t HotBlockCount); - - void getHotFunctions(const StringMap &ProfStats, - StringMap &HotFunc, - uint64_t HotThreshold) const; - - void computeHotFuncOverlap(); - - /// This function updates statistics in FuncOverlap, HotBlockOverlap, and - /// Difference for two sample units in a matched function according to the - /// given match status. - void updateOverlapStatsForFunction(uint64_t BaseSample, uint64_t TestSample, - uint64_t HotBlockCount, - SampleOverlapStats &FuncOverlap, - double &Difference, MatchStatus Status); - - /// This function updates statistics in FuncOverlap, HotBlockOverlap, and - /// Difference for unmatched callees that only present in one profile in a - /// matched caller function. - void updateForUnmatchedCallee(const sampleprof::FunctionSamples &Func, - SampleOverlapStats &FuncOverlap, - double &Difference, MatchStatus Status); - - /// This function updates sample overlap statistics of an overlap function in - /// base and test profile. It also calculates a function-internal similarity - /// FIS as follows: - /// For offsets i that have samples in at least one profile in this - /// function A, given BS(i) returned by computeBlockSimilarity(), compute - /// FIS(A) = (2.0 - sum_i(1.0 - BS(i))) / 2, ranging in [0.0f to 1.0f] with - /// 0.0 meaning no overlap. - double computeSampleFunctionInternalOverlap( - const sampleprof::FunctionSamples &BaseFunc, - const sampleprof::FunctionSamples &TestFunc, - SampleOverlapStats &FuncOverlap); - - /// Function-level similarity (FS) is a weighted value over function internal - /// similarity (FIS). This function computes a function's FS from its FIS by - /// applying the weight. - double weightForFuncSimilarity(double FuncSimilarity, uint64_t BaseFuncSample, - uint64_t TestFuncSample) const; - - /// The function-level similarity FS(A) for a function A is computed as - /// follows: - /// Compute a function-internal similarity FIS(A) by - /// computeSampleFunctionInternalOverlap(). Then, with the weight of - /// function A in base profile WB(A), and the weight of function A in test - /// profile WT(A), compute FS(A) = FIS(A) * (1.0 - fabs(WB(A) - WT(A))) - /// ranging in [0.0f to 1.0f] with 0.0 meaning no overlap. - double - computeSampleFunctionOverlap(const sampleprof::FunctionSamples *BaseFunc, - const sampleprof::FunctionSamples *TestFunc, - SampleOverlapStats *FuncOverlap, - uint64_t BaseFuncSample, - uint64_t TestFuncSample); - - /// Profile-level similarity (PS) is a weighted aggregate over function-level - /// similarities (FS). This method weights the FS value by the function - /// weights in the base and test profiles for the aggregation. - double weightByImportance(double FuncSimilarity, uint64_t BaseFuncSample, - uint64_t TestFuncSample) const; -}; -} // end anonymous namespace - -bool SampleOverlapAggregator::detectZeroSampleProfile( - raw_fd_ostream &OS) const { - bool HaveZeroSample = false; - if (ProfOverlap.BaseSample == 0) { - OS << "Sum of sample counts for profile " << BaseFilename << " is 0.\n"; - HaveZeroSample = true; - } - if (ProfOverlap.TestSample == 0) { - OS << "Sum of sample counts for profile " << TestFilename << " is 0.\n"; - HaveZeroSample = true; - } - return HaveZeroSample; -} - -double SampleOverlapAggregator::computeBlockSimilarity( - uint64_t BaseSample, uint64_t TestSample, - const SampleOverlapStats &FuncOverlap) const { - double BaseFrac = 0.0; - double TestFrac = 0.0; - if (FuncOverlap.BaseSample > 0) - BaseFrac = static_cast(BaseSample) / FuncOverlap.BaseSample; - if (FuncOverlap.TestSample > 0) - TestFrac = static_cast(TestSample) / FuncOverlap.TestSample; - return 1.0 - std::fabs(BaseFrac - TestFrac); -} - -void SampleOverlapAggregator::updateHotBlockOverlap(uint64_t BaseSample, - uint64_t TestSample, - uint64_t HotBlockCount) { - bool IsBaseHot = (BaseSample >= BaseHotThreshold); - bool IsTestHot = (TestSample >= TestHotThreshold); - if (!IsBaseHot && !IsTestHot) - return; - - HotBlockOverlap.UnionCount += HotBlockCount; - if (IsBaseHot) - HotBlockOverlap.BaseCount += HotBlockCount; - if (IsTestHot) - HotBlockOverlap.TestCount += HotBlockCount; - if (IsBaseHot && IsTestHot) - HotBlockOverlap.OverlapCount += HotBlockCount; -} - -void SampleOverlapAggregator::getHotFunctions( - const StringMap &ProfStats, - StringMap &HotFunc, uint64_t HotThreshold) const { - for (const auto &F : ProfStats) { - if (isFunctionHot(F.second, HotThreshold)) - HotFunc.try_emplace(F.first(), F.second); - } -} - -void SampleOverlapAggregator::computeHotFuncOverlap() { - StringMap BaseHotFunc; - getHotFunctions(BaseStats, BaseHotFunc, BaseHotThreshold); - HotFuncOverlap.BaseCount = BaseHotFunc.size(); - - StringMap TestHotFunc; - getHotFunctions(TestStats, TestHotFunc, TestHotThreshold); - HotFuncOverlap.TestCount = TestHotFunc.size(); - HotFuncOverlap.UnionCount = HotFuncOverlap.TestCount; - - for (const auto &F : BaseHotFunc) { - if (TestHotFunc.count(F.first())) - ++HotFuncOverlap.OverlapCount; - else - ++HotFuncOverlap.UnionCount; - } -} - -void SampleOverlapAggregator::updateOverlapStatsForFunction( - uint64_t BaseSample, uint64_t TestSample, uint64_t HotBlockCount, - SampleOverlapStats &FuncOverlap, double &Difference, MatchStatus Status) { - assert(Status != MS_None && - "Match status should be updated before updating overlap statistics"); - if (Status == MS_FirstUnique) { - TestSample = 0; - FuncOverlap.BaseUniqueSample += BaseSample; - } else if (Status == MS_SecondUnique) { - BaseSample = 0; - FuncOverlap.TestUniqueSample += TestSample; - } else { - ++FuncOverlap.OverlapCount; - } - - FuncOverlap.UnionSample += std::max(BaseSample, TestSample); - FuncOverlap.OverlapSample += std::min(BaseSample, TestSample); - Difference += - 1.0 - computeBlockSimilarity(BaseSample, TestSample, FuncOverlap); - updateHotBlockOverlap(BaseSample, TestSample, HotBlockCount); -} - -void SampleOverlapAggregator::updateForUnmatchedCallee( - const sampleprof::FunctionSamples &Func, SampleOverlapStats &FuncOverlap, - double &Difference, MatchStatus Status) { - assert((Status == MS_FirstUnique || Status == MS_SecondUnique) && - "Status must be either of the two unmatched cases"); - FuncSampleStats FuncStats; - if (Status == MS_FirstUnique) { - getFuncSampleStats(Func, FuncStats, BaseHotThreshold); - updateOverlapStatsForFunction(FuncStats.SampleSum, 0, - FuncStats.HotBlockCount, FuncOverlap, - Difference, Status); - } else { - getFuncSampleStats(Func, FuncStats, TestHotThreshold); - updateOverlapStatsForFunction(0, FuncStats.SampleSum, - FuncStats.HotBlockCount, FuncOverlap, - Difference, Status); - } -} - -double SampleOverlapAggregator::computeSampleFunctionInternalOverlap( - const sampleprof::FunctionSamples &BaseFunc, - const sampleprof::FunctionSamples &TestFunc, - SampleOverlapStats &FuncOverlap) { - - using namespace sampleprof; - - double Difference = 0; - - // Accumulate Difference for regular line/block samples in the function. - // We match them through sort-merge join algorithm because - // FunctionSamples::getBodySamples() returns a map of sample counters ordered - // by their offsets. - MatchStep BlockIterStep( - BaseFunc.getBodySamples().cbegin(), BaseFunc.getBodySamples().cend(), - TestFunc.getBodySamples().cbegin(), TestFunc.getBodySamples().cend()); - BlockIterStep.updateOneStep(); - while (!BlockIterStep.areBothFinished()) { - uint64_t BaseSample = - BlockIterStep.isFirstFinished() - ? 0 - : BlockIterStep.getFirstIter()->second.getSamples(); - uint64_t TestSample = - BlockIterStep.isSecondFinished() - ? 0 - : BlockIterStep.getSecondIter()->second.getSamples(); - updateOverlapStatsForFunction(BaseSample, TestSample, 1, FuncOverlap, - Difference, BlockIterStep.getMatchStatus()); - - BlockIterStep.updateOneStep(); - } - - // Accumulate Difference for callsite lines in the function. We match - // them through sort-merge algorithm because - // FunctionSamples::getCallsiteSamples() returns a map of callsite records - // ordered by their offsets. - MatchStep CallsiteIterStep( - BaseFunc.getCallsiteSamples().cbegin(), - BaseFunc.getCallsiteSamples().cend(), - TestFunc.getCallsiteSamples().cbegin(), - TestFunc.getCallsiteSamples().cend()); - CallsiteIterStep.updateOneStep(); - while (!CallsiteIterStep.areBothFinished()) { - MatchStatus CallsiteStepStatus = CallsiteIterStep.getMatchStatus(); - assert(CallsiteStepStatus != MS_None && - "Match status should be updated before entering loop body"); - - if (CallsiteStepStatus != MS_Match) { - auto Callsite = (CallsiteStepStatus == MS_FirstUnique) - ? CallsiteIterStep.getFirstIter() - : CallsiteIterStep.getSecondIter(); - for (const auto &F : Callsite->second) - updateForUnmatchedCallee(F.second, FuncOverlap, Difference, - CallsiteStepStatus); - } else { - // There may be multiple inlinees at the same offset, so we need to try - // matching all of them. This match is implemented through sort-merge - // algorithm because callsite records at the same offset are ordered by - // function names. - MatchStep CalleeIterStep( - CallsiteIterStep.getFirstIter()->second.cbegin(), - CallsiteIterStep.getFirstIter()->second.cend(), - CallsiteIterStep.getSecondIter()->second.cbegin(), - CallsiteIterStep.getSecondIter()->second.cend()); - CalleeIterStep.updateOneStep(); - while (!CalleeIterStep.areBothFinished()) { - MatchStatus CalleeStepStatus = CalleeIterStep.getMatchStatus(); - if (CalleeStepStatus != MS_Match) { - auto Callee = (CalleeStepStatus == MS_FirstUnique) - ? CalleeIterStep.getFirstIter() - : CalleeIterStep.getSecondIter(); - updateForUnmatchedCallee(Callee->second, FuncOverlap, Difference, - CalleeStepStatus); - } else { - // An inlined function can contain other inlinees inside, so compute - // the Difference recursively. - Difference += 2.0 - 2 * computeSampleFunctionInternalOverlap( - CalleeIterStep.getFirstIter()->second, - CalleeIterStep.getSecondIter()->second, - FuncOverlap); - } - CalleeIterStep.updateOneStep(); - } - } - CallsiteIterStep.updateOneStep(); - } - - // Difference reflects the total differences of line/block samples in this - // function and ranges in [0.0f to 2.0f]. Take (2.0 - Difference) / 2 to - // reflect the similarity between function profiles in [0.0f to 1.0f]. - return (2.0 - Difference) / 2; -} - -double SampleOverlapAggregator::weightForFuncSimilarity( - double FuncInternalSimilarity, uint64_t BaseFuncSample, - uint64_t TestFuncSample) const { - // Compute the weight as the distance between the function weights in two - // profiles. - double BaseFrac = 0.0; - double TestFrac = 0.0; - assert(ProfOverlap.BaseSample > 0 && - "Total samples in base profile should be greater than 0"); - BaseFrac = static_cast(BaseFuncSample) / ProfOverlap.BaseSample; - assert(ProfOverlap.TestSample > 0 && - "Total samples in test profile should be greater than 0"); - TestFrac = static_cast(TestFuncSample) / ProfOverlap.TestSample; - double WeightDistance = std::fabs(BaseFrac - TestFrac); - - // Take WeightDistance into the similarity. - return FuncInternalSimilarity * (1 - WeightDistance); -} - -double -SampleOverlapAggregator::weightByImportance(double FuncSimilarity, - uint64_t BaseFuncSample, - uint64_t TestFuncSample) const { - - double BaseFrac = 0.0; - double TestFrac = 0.0; - assert(ProfOverlap.BaseSample > 0 && - "Total samples in base profile should be greater than 0"); - BaseFrac = static_cast(BaseFuncSample) / ProfOverlap.BaseSample / 2.0; - assert(ProfOverlap.TestSample > 0 && - "Total samples in test profile should be greater than 0"); - TestFrac = static_cast(TestFuncSample) / ProfOverlap.TestSample / 2.0; - return FuncSimilarity * (BaseFrac + TestFrac); -} - -double SampleOverlapAggregator::computeSampleFunctionOverlap( - const sampleprof::FunctionSamples *BaseFunc, - const sampleprof::FunctionSamples *TestFunc, - SampleOverlapStats *FuncOverlap, uint64_t BaseFuncSample, - uint64_t TestFuncSample) { - // Default function internal similarity before weighted, meaning two functions - // has no overlap. - const double DefaultFuncInternalSimilarity = 0; - double FuncSimilarity; - double FuncInternalSimilarity; - - // If BaseFunc or TestFunc is nullptr, it means the functions do not overlap. - // In this case, we use DefaultFuncInternalSimilarity as the function internal - // similarity. - if (!BaseFunc || !TestFunc) { - FuncInternalSimilarity = DefaultFuncInternalSimilarity; - } else { - assert(FuncOverlap != nullptr && - "FuncOverlap should be provided in this case"); - FuncInternalSimilarity = computeSampleFunctionInternalOverlap( - *BaseFunc, *TestFunc, *FuncOverlap); - // Now, FuncInternalSimilarity may be a little less than 0 due to - // imprecision of floating point accumulations. Make it zero if the - // difference is below Epsilon. - FuncInternalSimilarity = (std::fabs(FuncInternalSimilarity - 0) < Epsilon) - ? 0 - : FuncInternalSimilarity; - } - FuncSimilarity = weightForFuncSimilarity(FuncInternalSimilarity, - BaseFuncSample, TestFuncSample); - return FuncSimilarity; -} - -void SampleOverlapAggregator::computeSampleProfileOverlap(raw_fd_ostream &OS) { - using namespace sampleprof; - - StringMap BaseFuncProf; - const auto &BaseProfiles = BaseReader->getProfiles(); - for (const auto &BaseFunc : BaseProfiles) { - BaseFuncProf.try_emplace(BaseFunc.second.getName(), &(BaseFunc.second)); - } - ProfOverlap.UnionCount = BaseFuncProf.size(); - - const auto &TestProfiles = TestReader->getProfiles(); - for (const auto &TestFunc : TestProfiles) { - SampleOverlapStats FuncOverlap; - FuncOverlap.TestName = TestFunc.second.getName(); - assert(TestStats.count(FuncOverlap.TestName) && - "TestStats should have records for all functions in test profile " - "except inlinees"); - FuncOverlap.TestSample = TestStats[FuncOverlap.TestName].SampleSum; - - const auto Match = BaseFuncProf.find(FuncOverlap.TestName); - if (Match == BaseFuncProf.end()) { - const FuncSampleStats &FuncStats = TestStats[FuncOverlap.TestName]; - ++ProfOverlap.TestUniqueCount; - ProfOverlap.TestUniqueSample += FuncStats.SampleSum; - FuncOverlap.TestUniqueSample = FuncStats.SampleSum; - - updateHotBlockOverlap(0, FuncStats.SampleSum, FuncStats.HotBlockCount); - - double FuncSimilarity = computeSampleFunctionOverlap( - nullptr, nullptr, nullptr, 0, FuncStats.SampleSum); - ProfOverlap.Similarity += - weightByImportance(FuncSimilarity, 0, FuncStats.SampleSum); - - ++ProfOverlap.UnionCount; - ProfOverlap.UnionSample += FuncStats.SampleSum; - } else { - ++ProfOverlap.OverlapCount; - - // Two functions match with each other. Compute function-level overlap and - // aggregate them into profile-level overlap. - FuncOverlap.BaseName = Match->second->getName(); - assert(BaseStats.count(FuncOverlap.BaseName) && - "BaseStats should have records for all functions in base profile " - "except inlinees"); - FuncOverlap.BaseSample = BaseStats[FuncOverlap.BaseName].SampleSum; - - FuncOverlap.Similarity = computeSampleFunctionOverlap( - Match->second, &TestFunc.second, &FuncOverlap, FuncOverlap.BaseSample, - FuncOverlap.TestSample); - ProfOverlap.Similarity += - weightByImportance(FuncOverlap.Similarity, FuncOverlap.BaseSample, - FuncOverlap.TestSample); - ProfOverlap.OverlapSample += FuncOverlap.OverlapSample; - ProfOverlap.UnionSample += FuncOverlap.UnionSample; - - // Accumulate the percentage of base unique and test unique samples into - // ProfOverlap. - ProfOverlap.BaseUniqueSample += FuncOverlap.BaseUniqueSample; - ProfOverlap.TestUniqueSample += FuncOverlap.TestUniqueSample; - - // Remove matched base functions for later reporting functions not found - // in test profile. - BaseFuncProf.erase(Match); - } - - // Print function-level similarity information if specified by options. - assert(TestStats.count(FuncOverlap.TestName) && - "TestStats should have records for all functions in test profile " - "except inlinees"); - if (TestStats[FuncOverlap.TestName].MaxSample >= FuncFilter.ValueCutoff || - (Match != BaseFuncProf.end() && - FuncOverlap.Similarity < LowSimilarityThreshold) || - (Match != BaseFuncProf.end() && !FuncFilter.NameFilter.empty() && - FuncOverlap.BaseName.find(FuncFilter.NameFilter) != - FuncOverlap.BaseName.npos)) { - assert(ProfOverlap.BaseSample > 0 && - "Total samples in base profile should be greater than 0"); - FuncOverlap.BaseWeight = - static_cast(FuncOverlap.BaseSample) / ProfOverlap.BaseSample; - assert(ProfOverlap.TestSample > 0 && - "Total samples in test profile should be greater than 0"); - FuncOverlap.TestWeight = - static_cast(FuncOverlap.TestSample) / ProfOverlap.TestSample; - FuncSimilarityDump.emplace(FuncOverlap.BaseWeight, FuncOverlap); - } - } - - // Traverse through functions in base profile but not in test profile. - for (const auto &F : BaseFuncProf) { - assert(BaseStats.count(F.second->getName()) && - "BaseStats should have records for all functions in base profile " - "except inlinees"); - const FuncSampleStats &FuncStats = BaseStats[F.second->getName()]; - ++ProfOverlap.BaseUniqueCount; - ProfOverlap.BaseUniqueSample += FuncStats.SampleSum; - - updateHotBlockOverlap(FuncStats.SampleSum, 0, FuncStats.HotBlockCount); - - double FuncSimilarity = computeSampleFunctionOverlap( - nullptr, nullptr, nullptr, FuncStats.SampleSum, 0); - ProfOverlap.Similarity += - weightByImportance(FuncSimilarity, FuncStats.SampleSum, 0); - - ProfOverlap.UnionSample += FuncStats.SampleSum; - } - - // Now, ProfSimilarity may be a little greater than 1 due to imprecision - // of floating point accumulations. Make it 1.0 if the difference is below - // Epsilon. - ProfOverlap.Similarity = (std::fabs(ProfOverlap.Similarity - 1) < Epsilon) - ? 1 - : ProfOverlap.Similarity; - - computeHotFuncOverlap(); -} - -void SampleOverlapAggregator::initializeSampleProfileOverlap() { - const auto &BaseProf = BaseReader->getProfiles(); - for (const auto &I : BaseProf) { - ++ProfOverlap.BaseCount; - FuncSampleStats FuncStats; - getFuncSampleStats(I.second, FuncStats, BaseHotThreshold); - ProfOverlap.BaseSample += FuncStats.SampleSum; - BaseStats.try_emplace(I.second.getName(), FuncStats); - } - - const auto &TestProf = TestReader->getProfiles(); - for (const auto &I : TestProf) { - ++ProfOverlap.TestCount; - FuncSampleStats FuncStats; - getFuncSampleStats(I.second, FuncStats, TestHotThreshold); - ProfOverlap.TestSample += FuncStats.SampleSum; - TestStats.try_emplace(I.second.getName(), FuncStats); - } - - ProfOverlap.BaseName = StringRef(BaseFilename); - ProfOverlap.TestName = StringRef(TestFilename); -} - -void SampleOverlapAggregator::dumpFuncSimilarity(raw_fd_ostream &OS) const { - using namespace sampleprof; - - if (FuncSimilarityDump.empty()) - return; - - formatted_raw_ostream FOS(OS); - FOS << "Function-level details:\n"; - FOS << "Base weight"; - FOS.PadToColumn(TestWeightCol); - FOS << "Test weight"; - FOS.PadToColumn(SimilarityCol); - FOS << "Similarity"; - FOS.PadToColumn(OverlapCol); - FOS << "Overlap"; - FOS.PadToColumn(BaseUniqueCol); - FOS << "Base unique"; - FOS.PadToColumn(TestUniqueCol); - FOS << "Test unique"; - FOS.PadToColumn(BaseSampleCol); - FOS << "Base samples"; - FOS.PadToColumn(TestSampleCol); - FOS << "Test samples"; - FOS.PadToColumn(FuncNameCol); - FOS << "Function name\n"; - for (const auto &F : FuncSimilarityDump) { - double OverlapPercent = - F.second.UnionSample > 0 - ? static_cast(F.second.OverlapSample) / F.second.UnionSample - : 0; - double BaseUniquePercent = - F.second.BaseSample > 0 - ? static_cast(F.second.BaseUniqueSample) / - F.second.BaseSample - : 0; - double TestUniquePercent = - F.second.TestSample > 0 - ? static_cast(F.second.TestUniqueSample) / - F.second.TestSample - : 0; - - FOS << format("%.2f%%", F.second.BaseWeight * 100); - FOS.PadToColumn(TestWeightCol); - FOS << format("%.2f%%", F.second.TestWeight * 100); - FOS.PadToColumn(SimilarityCol); - FOS << format("%.2f%%", F.second.Similarity * 100); - FOS.PadToColumn(OverlapCol); - FOS << format("%.2f%%", OverlapPercent * 100); - FOS.PadToColumn(BaseUniqueCol); - FOS << format("%.2f%%", BaseUniquePercent * 100); - FOS.PadToColumn(TestUniqueCol); - FOS << format("%.2f%%", TestUniquePercent * 100); - FOS.PadToColumn(BaseSampleCol); - FOS << F.second.BaseSample; - FOS.PadToColumn(TestSampleCol); - FOS << F.second.TestSample; - FOS.PadToColumn(FuncNameCol); - FOS << F.second.TestName << "\n"; - } -} - -void SampleOverlapAggregator::dumpProgramSummary(raw_fd_ostream &OS) const { - OS << "Profile overlap infomation for base_profile: " << ProfOverlap.BaseName - << " and test_profile: " << ProfOverlap.TestName << "\nProgram level:\n"; - - OS << " Whole program profile similarity: " - << format("%.3f%%", ProfOverlap.Similarity * 100) << "\n"; - - assert(ProfOverlap.UnionSample > 0 && - "Total samples in two profile should be greater than 0"); - double OverlapPercent = - static_cast(ProfOverlap.OverlapSample) / ProfOverlap.UnionSample; - assert(ProfOverlap.BaseSample > 0 && - "Total samples in base profile should be greater than 0"); - double BaseUniquePercent = static_cast(ProfOverlap.BaseUniqueSample) / - ProfOverlap.BaseSample; - assert(ProfOverlap.TestSample > 0 && - "Total samples in test profile should be greater than 0"); - double TestUniquePercent = static_cast(ProfOverlap.TestUniqueSample) / - ProfOverlap.TestSample; - - OS << " Whole program sample overlap: " - << format("%.3f%%", OverlapPercent * 100) << "\n"; - OS << " percentage of samples unique in base profile: " - << format("%.3f%%", BaseUniquePercent * 100) << "\n"; - OS << " percentage of samples unique in test profile: " - << format("%.3f%%", TestUniquePercent * 100) << "\n"; - OS << " total samples in base profile: " << ProfOverlap.BaseSample << "\n" - << " total samples in test profile: " << ProfOverlap.TestSample << "\n"; - - assert(ProfOverlap.UnionCount > 0 && - "There should be at least one function in two input profiles"); - double FuncOverlapPercent = - static_cast(ProfOverlap.OverlapCount) / ProfOverlap.UnionCount; - OS << " Function overlap: " << format("%.3f%%", FuncOverlapPercent * 100) - << "\n"; - OS << " overlap functions: " << ProfOverlap.OverlapCount << "\n"; - OS << " functions unique in base profile: " << ProfOverlap.BaseUniqueCount - << "\n"; - OS << " functions unique in test profile: " << ProfOverlap.TestUniqueCount - << "\n"; -} - -void SampleOverlapAggregator::dumpHotFuncAndBlockOverlap( - raw_fd_ostream &OS) const { - assert(HotFuncOverlap.UnionCount > 0 && - "There should be at least one hot function in two input profiles"); - OS << " Hot-function overlap: " - << format("%.3f%%", static_cast(HotFuncOverlap.OverlapCount) / - HotFuncOverlap.UnionCount * 100) - << "\n"; - OS << " overlap hot functions: " << HotFuncOverlap.OverlapCount << "\n"; - OS << " hot functions unique in base profile: " - << HotFuncOverlap.BaseCount - HotFuncOverlap.OverlapCount << "\n"; - OS << " hot functions unique in test profile: " - << HotFuncOverlap.TestCount - HotFuncOverlap.OverlapCount << "\n"; - - assert(HotBlockOverlap.UnionCount > 0 && - "There should be at least one hot block in two input profiles"); - OS << " Hot-block overlap: " - << format("%.3f%%", static_cast(HotBlockOverlap.OverlapCount) / - HotBlockOverlap.UnionCount * 100) - << "\n"; - OS << " overlap hot blocks: " << HotBlockOverlap.OverlapCount << "\n"; - OS << " hot blocks unique in base profile: " - << HotBlockOverlap.BaseCount - HotBlockOverlap.OverlapCount << "\n"; - OS << " hot blocks unique in test profile: " - << HotBlockOverlap.TestCount - HotBlockOverlap.OverlapCount << "\n"; -} - -std::error_code SampleOverlapAggregator::loadProfiles() { - using namespace sampleprof; - - LLVMContext Context; - auto BaseReaderOrErr = SampleProfileReader::create(BaseFilename, Context); - if (std::error_code EC = BaseReaderOrErr.getError()) - exitWithErrorCode(EC, BaseFilename); - - auto TestReaderOrErr = SampleProfileReader::create(TestFilename, Context); - if (std::error_code EC = TestReaderOrErr.getError()) - exitWithErrorCode(EC, TestFilename); - - BaseReader = std::move(BaseReaderOrErr.get()); - TestReader = std::move(TestReaderOrErr.get()); - - if (std::error_code EC = BaseReader->read()) - exitWithErrorCode(EC, BaseFilename); - if (std::error_code EC = TestReader->read()) - exitWithErrorCode(EC, TestFilename); - if (BaseReader->profileIsProbeBased() != TestReader->profileIsProbeBased()) - exitWithError( - "cannot compare probe-based profile with non-probe-based profile"); - - // Load BaseHotThreshold and TestHotThreshold as 99-percentile threshold in - // profile summary. - const uint64_t HotCutoff = 990000; - ProfileSummary &BasePS = BaseReader->getSummary(); - for (const auto &SummaryEntry : BasePS.getDetailedSummary()) { - if (SummaryEntry.Cutoff == HotCutoff) { - BaseHotThreshold = SummaryEntry.MinCount; - break; - } - } - - ProfileSummary &TestPS = TestReader->getSummary(); - for (const auto &SummaryEntry : TestPS.getDetailedSummary()) { - if (SummaryEntry.Cutoff == HotCutoff) { - TestHotThreshold = SummaryEntry.MinCount; - break; - } - } - return std::error_code(); -} - -void overlapSampleProfile(const std::string &BaseFilename, - const std::string &TestFilename, - const OverlapFuncFilters &FuncFilter, - uint64_t SimilarityCutoff, raw_fd_ostream &OS) { - using namespace sampleprof; - - // We use 0.000005 to initialize OverlapAggr.Epsilon because the final metrics - // report 2--3 places after decimal point in percentage numbers. - SampleOverlapAggregator OverlapAggr( - BaseFilename, TestFilename, - static_cast(SimilarityCutoff) / 1000000, 0.000005, FuncFilter); - if (std::error_code EC = OverlapAggr.loadProfiles()) - exitWithErrorCode(EC); - - OverlapAggr.initializeSampleProfileOverlap(); - if (OverlapAggr.detectZeroSampleProfile(OS)) - return; - - OverlapAggr.computeSampleProfileOverlap(OS); - - OverlapAggr.dumpProgramSummary(OS); - OverlapAggr.dumpHotFuncAndBlockOverlap(OS); - OverlapAggr.dumpFuncSimilarity(OS); -} - -static int overlap_main(int argc, const char *argv[]) { - cl::opt BaseFilename(cl::Positional, cl::Required, - cl::desc("")); - cl::opt TestFilename(cl::Positional, cl::Required, - cl::desc("")); - cl::opt Output("output", cl::value_desc("output"), cl::init("-"), - cl::desc("Output file")); - cl::alias OutputA("o", cl::desc("Alias for --output"), cl::aliasopt(Output)); - cl::opt IsCS("cs", cl::init(false), - cl::desc("For context sensitive counts")); - cl::opt ValueCutoff( - "value-cutoff", cl::init(-1), - cl::desc( - "Function level overlap information for every function in test " - "profile with max count value greater then the parameter value")); - cl::opt FuncNameFilter( - "function", - cl::desc("Function level overlap information for matching functions")); - cl::opt SimilarityCutoff( - "similarity-cutoff", cl::init(0), - cl::desc( - "For sample profiles, list function names for overlapped functions " - "with similarities below the cutoff (percentage times 10000).")); - cl::opt ProfileKind( - cl::desc("Profile kind:"), cl::init(instr), - cl::values(clEnumVal(instr, "Instrumentation profile (default)"), - clEnumVal(sample, "Sample profile"))); - cl::ParseCommandLineOptions(argc, argv, "LLVM profile data overlap tool\n"); - - std::error_code EC; - raw_fd_ostream OS(Output.data(), EC, sys::fs::OF_Text); - if (EC) - exitWithErrorCode(EC, Output); - - if (ProfileKind == instr) - overlapInstrProfile(BaseFilename, TestFilename, - OverlapFuncFilters{ValueCutoff, FuncNameFilter}, OS, - IsCS); - else - overlapSampleProfile(BaseFilename, TestFilename, - OverlapFuncFilters{ValueCutoff, FuncNameFilter}, - SimilarityCutoff, OS); - - return 0; -} - -typedef struct ValueSitesStats { - ValueSitesStats() - : TotalNumValueSites(0), TotalNumValueSitesWithValueProfile(0), - TotalNumValues(0) {} - uint64_t TotalNumValueSites; - uint64_t TotalNumValueSitesWithValueProfile; - uint64_t TotalNumValues; - std::vector ValueSitesHistogram; -} ValueSitesStats; - -static void traverseAllValueSites(const InstrProfRecord &Func, uint32_t VK, - ValueSitesStats &Stats, raw_fd_ostream &OS, - InstrProfSymtab *Symtab) { - uint32_t NS = Func.getNumValueSites(VK); - Stats.TotalNumValueSites += NS; - for (size_t I = 0; I < NS; ++I) { - uint32_t NV = Func.getNumValueDataForSite(VK, I); - std::unique_ptr VD = Func.getValueForSite(VK, I); - Stats.TotalNumValues += NV; - if (NV) { - Stats.TotalNumValueSitesWithValueProfile++; - if (NV > Stats.ValueSitesHistogram.size()) - Stats.ValueSitesHistogram.resize(NV, 0); - Stats.ValueSitesHistogram[NV - 1]++; - } - - uint64_t SiteSum = 0; - for (uint32_t V = 0; V < NV; V++) - SiteSum += VD[V].Count; - if (SiteSum == 0) - SiteSum = 1; - - for (uint32_t V = 0; V < NV; V++) { - OS << "\t[ " << format("%2u", I) << ", "; - if (Symtab == nullptr) - OS << format("%4" PRIu64, VD[V].Value); - else - OS << Symtab->getFuncName(VD[V].Value); - OS << ", " << format("%10" PRId64, VD[V].Count) << " ] (" - << format("%.2f%%", (VD[V].Count * 100.0 / SiteSum)) << ")\n"; - } - } -} - -static void showValueSitesStats(raw_fd_ostream &OS, uint32_t VK, - ValueSitesStats &Stats) { - OS << " Total number of sites: " << Stats.TotalNumValueSites << "\n"; - OS << " Total number of sites with values: " - << Stats.TotalNumValueSitesWithValueProfile << "\n"; - OS << " Total number of profiled values: " << Stats.TotalNumValues << "\n"; - - OS << " Value sites histogram:\n\tNumTargets, SiteCount\n"; - for (unsigned I = 0; I < Stats.ValueSitesHistogram.size(); I++) { - if (Stats.ValueSitesHistogram[I] > 0) - OS << "\t" << I + 1 << ", " << Stats.ValueSitesHistogram[I] << "\n"; - } -} - -static int showInstrProfile(const std::string &Filename, bool ShowCounts, - uint32_t TopN, bool ShowIndirectCallTargets, - bool ShowMemOPSizes, bool ShowDetailedSummary, - std::vector DetailedSummaryCutoffs, - bool ShowAllFunctions, bool ShowCS, - uint64_t ValueCutoff, bool OnlyListBelow, - const std::string &ShowFunction, bool TextFormat, - raw_fd_ostream &OS) { - auto ReaderOrErr = InstrProfReader::create(Filename); - std::vector Cutoffs = std::move(DetailedSummaryCutoffs); - if (ShowDetailedSummary && Cutoffs.empty()) { - Cutoffs = {800000, 900000, 950000, 990000, 999000, 999900, 999990}; - } - InstrProfSummaryBuilder Builder(std::move(Cutoffs)); - if (Error E = ReaderOrErr.takeError()) - exitWithError(std::move(E), Filename); - - auto Reader = std::move(ReaderOrErr.get()); - bool IsIRInstr = Reader->isIRLevelProfile(); - size_t ShownFunctions = 0; - size_t BelowCutoffFunctions = 0; - int NumVPKind = IPVK_Last - IPVK_First + 1; - std::vector VPStats(NumVPKind); - - auto MinCmp = [](const std::pair &v1, - const std::pair &v2) { - return v1.second > v2.second; - }; - - std::priority_queue, - std::vector>, - decltype(MinCmp)> - HottestFuncs(MinCmp); - - if (!TextFormat && OnlyListBelow) { - OS << "The list of functions with the maximum counter less than " - << ValueCutoff << ":\n"; - } - - // Add marker so that IR-level instrumentation round-trips properly. - if (TextFormat && IsIRInstr) - OS << ":ir\n"; - - for (const auto &Func : *Reader) { - if (Reader->isIRLevelProfile()) { - bool FuncIsCS = NamedInstrProfRecord::hasCSFlagInHash(Func.Hash); - if (FuncIsCS != ShowCS) - continue; - } - bool Show = - ShowAllFunctions || (!ShowFunction.empty() && - Func.Name.find(ShowFunction) != Func.Name.npos); - - bool doTextFormatDump = (Show && TextFormat); - - if (doTextFormatDump) { - InstrProfSymtab &Symtab = Reader->getSymtab(); - InstrProfWriter::writeRecordInText(Func.Name, Func.Hash, Func, Symtab, - OS); - continue; - } - - assert(Func.Counts.size() > 0 && "function missing entry counter"); - Builder.addRecord(Func); - - uint64_t FuncMax = 0; - uint64_t FuncSum = 0; - for (size_t I = 0, E = Func.Counts.size(); I < E; ++I) { - if (Func.Counts[I] == (uint64_t)-1) - continue; - FuncMax = std::max(FuncMax, Func.Counts[I]); - FuncSum += Func.Counts[I]; - } - - if (FuncMax < ValueCutoff) { - ++BelowCutoffFunctions; - if (OnlyListBelow) { - OS << " " << Func.Name << ": (Max = " << FuncMax - << " Sum = " << FuncSum << ")\n"; - } - continue; - } else if (OnlyListBelow) - continue; - - if (TopN) { - if (HottestFuncs.size() == TopN) { - if (HottestFuncs.top().second < FuncMax) { - HottestFuncs.pop(); - HottestFuncs.emplace(std::make_pair(std::string(Func.Name), FuncMax)); - } - } else - HottestFuncs.emplace(std::make_pair(std::string(Func.Name), FuncMax)); - } - - if (Show) { - if (!ShownFunctions) - OS << "Counters:\n"; - - ++ShownFunctions; - - OS << " " << Func.Name << ":\n" - << " Hash: " << format("0x%016" PRIx64, Func.Hash) << "\n" - << " Counters: " << Func.Counts.size() << "\n"; - if (!IsIRInstr) - OS << " Function count: " << Func.Counts[0] << "\n"; - - if (ShowIndirectCallTargets) - OS << " Indirect Call Site Count: " - << Func.getNumValueSites(IPVK_IndirectCallTarget) << "\n"; - - uint32_t NumMemOPCalls = Func.getNumValueSites(IPVK_MemOPSize); - if (ShowMemOPSizes && NumMemOPCalls > 0) - OS << " Number of Memory Intrinsics Calls: " << NumMemOPCalls - << "\n"; - - if (ShowCounts) { - OS << " Block counts: ["; - size_t Start = (IsIRInstr ? 0 : 1); - for (size_t I = Start, E = Func.Counts.size(); I < E; ++I) { - OS << (I == Start ? "" : ", ") << Func.Counts[I]; - } - OS << "]\n"; - } - - if (ShowIndirectCallTargets) { - OS << " Indirect Target Results:\n"; - traverseAllValueSites(Func, IPVK_IndirectCallTarget, - VPStats[IPVK_IndirectCallTarget], OS, - &(Reader->getSymtab())); - } - - if (ShowMemOPSizes && NumMemOPCalls > 0) { - OS << " Memory Intrinsic Size Results:\n"; - traverseAllValueSites(Func, IPVK_MemOPSize, VPStats[IPVK_MemOPSize], OS, - nullptr); - } - } - } - if (Reader->hasError()) - exitWithError(Reader->getError(), Filename); - - if (TextFormat) - return 0; - std::unique_ptr PS(Builder.getSummary()); - bool IsIR = Reader->isIRLevelProfile(); - OS << "Instrumentation level: " << (IsIR ? "IR" : "Front-end"); - if (IsIR) - OS << " entry_first = " << Reader->instrEntryBBEnabled(); - OS << "\n"; - if (ShowAllFunctions || !ShowFunction.empty()) - OS << "Functions shown: " << ShownFunctions << "\n"; - OS << "Total functions: " << PS->getNumFunctions() << "\n"; - if (ValueCutoff > 0) { - OS << "Number of functions with maximum count (< " << ValueCutoff - << "): " << BelowCutoffFunctions << "\n"; - OS << "Number of functions with maximum count (>= " << ValueCutoff - << "): " << PS->getNumFunctions() - BelowCutoffFunctions << "\n"; - } - OS << "Maximum function count: " << PS->getMaxFunctionCount() << "\n"; - OS << "Maximum internal block count: " << PS->getMaxInternalCount() << "\n"; - - if (TopN) { - std::vector> SortedHottestFuncs; - while (!HottestFuncs.empty()) { - SortedHottestFuncs.emplace_back(HottestFuncs.top()); - HottestFuncs.pop(); - } - OS << "Top " << TopN - << " functions with the largest internal block counts: \n"; - for (auto &hotfunc : llvm::reverse(SortedHottestFuncs)) - OS << " " << hotfunc.first << ", max count = " << hotfunc.second << "\n"; - } - - if (ShownFunctions && ShowIndirectCallTargets) { - OS << "Statistics for indirect call sites profile:\n"; - showValueSitesStats(OS, IPVK_IndirectCallTarget, - VPStats[IPVK_IndirectCallTarget]); - } - - if (ShownFunctions && ShowMemOPSizes) { - OS << "Statistics for memory intrinsic calls sizes profile:\n"; - showValueSitesStats(OS, IPVK_MemOPSize, VPStats[IPVK_MemOPSize]); - } - - if (ShowDetailedSummary) { - OS << "Total number of blocks: " << PS->getNumCounts() << "\n"; - OS << "Total count: " << PS->getTotalCount() << "\n"; - PS->printDetailedSummary(OS); - } - return 0; -} - -static void showSectionInfo(sampleprof::SampleProfileReader *Reader, - raw_fd_ostream &OS) { - if (!Reader->dumpSectionInfo(OS)) { - WithColor::warning() << "-show-sec-info-only is only supported for " - << "sample profile in extbinary format and is " - << "ignored for other formats.\n"; - return; - } -} - -namespace { -struct HotFuncInfo { - StringRef FuncName; - uint64_t TotalCount; - double TotalCountPercent; - uint64_t MaxCount; - uint64_t EntryCount; - - HotFuncInfo() - : FuncName(), TotalCount(0), TotalCountPercent(0.0f), MaxCount(0), - EntryCount(0) {} - - HotFuncInfo(StringRef FN, uint64_t TS, double TSP, uint64_t MS, uint64_t ES) - : FuncName(FN), TotalCount(TS), TotalCountPercent(TSP), MaxCount(MS), - EntryCount(ES) {} -}; -} // namespace - -// Print out detailed information about hot functions in PrintValues vector. -// Users specify titles and offset of every columns through ColumnTitle and -// ColumnOffset. The size of ColumnTitle and ColumnOffset need to be the same -// and at least 4. Besides, users can optionally give a HotFuncMetric string to -// print out or let it be an empty string. -static void dumpHotFunctionList(const std::vector &ColumnTitle, - const std::vector &ColumnOffset, - const std::vector &PrintValues, - uint64_t HotFuncCount, uint64_t TotalFuncCount, - uint64_t HotProfCount, uint64_t TotalProfCount, - const std::string &HotFuncMetric, - raw_fd_ostream &OS) { - assert(ColumnOffset.size() == ColumnTitle.size() && - "ColumnOffset and ColumnTitle should have the same size"); - assert(ColumnTitle.size() >= 4 && - "ColumnTitle should have at least 4 elements"); - assert(TotalFuncCount > 0 && - "There should be at least one function in the profile"); - double TotalProfPercent = 0; - if (TotalProfCount > 0) - TotalProfPercent = static_cast(HotProfCount) / TotalProfCount * 100; - - formatted_raw_ostream FOS(OS); - FOS << HotFuncCount << " out of " << TotalFuncCount - << " functions with profile (" - << format("%.2f%%", - (static_cast(HotFuncCount) / TotalFuncCount * 100)) - << ") are considered hot functions"; - if (!HotFuncMetric.empty()) - FOS << " (" << HotFuncMetric << ")"; - FOS << ".\n"; - FOS << HotProfCount << " out of " << TotalProfCount << " profile counts (" - << format("%.2f%%", TotalProfPercent) << ") are from hot functions.\n"; - - for (size_t I = 0; I < ColumnTitle.size(); ++I) { - FOS.PadToColumn(ColumnOffset[I]); - FOS << ColumnTitle[I]; - } - FOS << "\n"; - - for (const HotFuncInfo &R : PrintValues) { - FOS.PadToColumn(ColumnOffset[0]); - FOS << R.TotalCount << " (" << format("%.2f%%", R.TotalCountPercent) << ")"; - FOS.PadToColumn(ColumnOffset[1]); - FOS << R.MaxCount; - FOS.PadToColumn(ColumnOffset[2]); - FOS << R.EntryCount; - FOS.PadToColumn(ColumnOffset[3]); - FOS << R.FuncName << "\n"; - } -} - -static int -showHotFunctionList(const StringMap &Profiles, - ProfileSummary &PS, raw_fd_ostream &OS) { - using namespace sampleprof; - - const uint32_t HotFuncCutoff = 990000; - auto &SummaryVector = PS.getDetailedSummary(); - uint64_t MinCountThreshold = 0; - for (const ProfileSummaryEntry &SummaryEntry : SummaryVector) { - if (SummaryEntry.Cutoff == HotFuncCutoff) { - MinCountThreshold = SummaryEntry.MinCount; - break; - } - } - - // Traverse all functions in the profile and keep only hot functions. - // The following loop also calculates the sum of total samples of all - // functions. - std::multimap, - std::greater> - HotFunc; - uint64_t ProfileTotalSample = 0; - uint64_t HotFuncSample = 0; - uint64_t HotFuncCount = 0; - - for (const auto &I : Profiles) { - FuncSampleStats FuncStats; - const FunctionSamples &FuncProf = I.second; - ProfileTotalSample += FuncProf.getTotalSamples(); - getFuncSampleStats(FuncProf, FuncStats, MinCountThreshold); - - if (isFunctionHot(FuncStats, MinCountThreshold)) { - HotFunc.emplace(FuncProf.getTotalSamples(), - std::make_pair(&(I.second), FuncStats.MaxSample)); - HotFuncSample += FuncProf.getTotalSamples(); - ++HotFuncCount; - } - } - - std::vector ColumnTitle{"Total sample (%)", "Max sample", - "Entry sample", "Function name"}; - std::vector ColumnOffset{0, 24, 42, 58}; - std::string Metric = - std::string("max sample >= ") + std::to_string(MinCountThreshold); - std::vector PrintValues; - for (const auto &FuncPair : HotFunc) { - const FunctionSamples &Func = *FuncPair.second.first; - double TotalSamplePercent = - (ProfileTotalSample > 0) - ? (Func.getTotalSamples() * 100.0) / ProfileTotalSample - : 0; - PrintValues.emplace_back( - HotFuncInfo(Func.getName(), Func.getTotalSamples(), TotalSamplePercent, - FuncPair.second.second, Func.getEntrySamples())); - } - dumpHotFunctionList(ColumnTitle, ColumnOffset, PrintValues, HotFuncCount, - Profiles.size(), HotFuncSample, ProfileTotalSample, - Metric, OS); - - return 0; -} - -static int showSampleProfile(const std::string &Filename, bool ShowCounts, - bool ShowAllFunctions, bool ShowDetailedSummary, - const std::string &ShowFunction, - bool ShowProfileSymbolList, - bool ShowSectionInfoOnly, bool ShowHotFuncList, - raw_fd_ostream &OS) { - using namespace sampleprof; - LLVMContext Context; - auto ReaderOrErr = SampleProfileReader::create(Filename, Context); - if (std::error_code EC = ReaderOrErr.getError()) - exitWithErrorCode(EC, Filename); - - auto Reader = std::move(ReaderOrErr.get()); - - if (ShowSectionInfoOnly) { - showSectionInfo(Reader.get(), OS); - return 0; - } - - if (std::error_code EC = Reader->read()) - exitWithErrorCode(EC, Filename); - - if (ShowAllFunctions || ShowFunction.empty()) - Reader->dump(OS); - else - Reader->dumpFunctionProfile(ShowFunction, OS); - - if (ShowProfileSymbolList) { - std::unique_ptr ReaderList = - Reader->getProfileSymbolList(); - ReaderList->dump(OS); - } - - if (ShowDetailedSummary) { - auto &PS = Reader->getSummary(); - PS.printSummary(OS); - PS.printDetailedSummary(OS); - } - - if (ShowHotFuncList) - showHotFunctionList(Reader->getProfiles(), Reader->getSummary(), OS); - - return 0; -} - -static int show_main(int argc, const char *argv[]) { - cl::opt Filename(cl::Positional, cl::Required, - cl::desc("")); - - cl::opt ShowCounts("counts", cl::init(false), - cl::desc("Show counter values for shown functions")); - cl::opt TextFormat( - "text", cl::init(false), - cl::desc("Show instr profile data in text dump format")); - cl::opt ShowIndirectCallTargets( - "ic-targets", cl::init(false), - cl::desc("Show indirect call site target values for shown functions")); - cl::opt ShowMemOPSizes( - "memop-sizes", cl::init(false), - cl::desc("Show the profiled sizes of the memory intrinsic calls " - "for shown functions")); - cl::opt ShowDetailedSummary("detailed-summary", cl::init(false), - cl::desc("Show detailed profile summary")); - cl::list DetailedSummaryCutoffs( - cl::CommaSeparated, "detailed-summary-cutoffs", - cl::desc( - "Cutoff percentages (times 10000) for generating detailed summary"), - cl::value_desc("800000,901000,999999")); - cl::opt ShowHotFuncList( - "hot-func-list", cl::init(false), - cl::desc("Show profile summary of a list of hot functions")); - cl::opt ShowAllFunctions("all-functions", cl::init(false), - cl::desc("Details for every function")); - cl::opt ShowCS("showcs", cl::init(false), - cl::desc("Show context sensitive counts")); - cl::opt ShowFunction("function", - cl::desc("Details for matching functions")); - - cl::opt OutputFilename("output", cl::value_desc("output"), - cl::init("-"), cl::desc("Output file")); - cl::alias OutputFilenameA("o", cl::desc("Alias for --output"), - cl::aliasopt(OutputFilename)); - cl::opt ProfileKind( - cl::desc("Profile kind:"), cl::init(instr), - cl::values(clEnumVal(instr, "Instrumentation profile (default)"), - clEnumVal(sample, "Sample profile"))); - cl::opt TopNFunctions( - "topn", cl::init(0), - cl::desc("Show the list of functions with the largest internal counts")); - cl::opt ValueCutoff( - "value-cutoff", cl::init(0), - cl::desc("Set the count value cutoff. Functions with the maximum count " - "less than this value will not be printed out. (Default is 0)")); - cl::opt OnlyListBelow( - "list-below-cutoff", cl::init(false), - cl::desc("Only output names of functions whose max count values are " - "below the cutoff value")); - cl::opt ShowProfileSymbolList( - "show-prof-sym-list", cl::init(false), - cl::desc("Show profile symbol list if it exists in the profile. ")); - cl::opt ShowSectionInfoOnly( - "show-sec-info-only", cl::init(false), - cl::desc("Show the information of each section in the sample profile. " - "The flag is only usable when the sample profile is in " - "extbinary format")); - - cl::ParseCommandLineOptions(argc, argv, "LLVM profile data summary\n"); - - if (OutputFilename.empty()) - OutputFilename = "-"; - - if (Filename == OutputFilename) { - errs() << sys::path::filename(argv[0]) - << ": Input file name cannot be the same as the output file name!\n"; - return 1; - } - - std::error_code EC; - raw_fd_ostream OS(OutputFilename.data(), EC, sys::fs::OF_Text); - if (EC) - exitWithErrorCode(EC, OutputFilename); - - if (ShowAllFunctions && !ShowFunction.empty()) - WithColor::warning() << "-function argument ignored: showing all functions\n"; - - if (ProfileKind == instr) - return showInstrProfile(Filename, ShowCounts, TopNFunctions, - ShowIndirectCallTargets, ShowMemOPSizes, - ShowDetailedSummary, DetailedSummaryCutoffs, - ShowAllFunctions, ShowCS, ValueCutoff, - OnlyListBelow, ShowFunction, TextFormat, OS); - else - return showSampleProfile(Filename, ShowCounts, ShowAllFunctions, - ShowDetailedSummary, ShowFunction, - ShowProfileSymbolList, ShowSectionInfoOnly, - ShowHotFuncList, OS); -} - -int main(int argc, const char *argv[]) { - InitLLVM X(argc, argv); - - StringRef ProgName(sys::path::filename(argv[0])); - if (argc > 1) { - int (*func)(int, const char *[]) = nullptr; - - if (strcmp(argv[1], "merge") == 0) - func = merge_main; - else if (strcmp(argv[1], "show") == 0) - func = show_main; - else if (strcmp(argv[1], "overlap") == 0) - func = overlap_main; - - if (func) { - std::string Invocation(ProgName.str() + " " + argv[1]); - argv[1] = Invocation.c_str(); - return func(argc - 1, argv + 1); - } - - if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "-help") == 0 || - strcmp(argv[1], "--help") == 0) { - - errs() << "OVERVIEW: LLVM profile data tools\n\n" - << "USAGE: " << ProgName << " [args...]\n" - << "USAGE: " << ProgName << " -help\n\n" - << "See each individual command --help for more details.\n" - << "Available commands: merge, show, overlap\n"; - return 0; - } - } - - if (argc < 2) - errs() << ProgName << ": No command specified!\n"; - else - errs() << ProgName << ": Unknown command!\n"; - - errs() << "USAGE: " << ProgName << " [args...]\n"; - return 1; -} diff --git a/tools/ldc-profdata/llvm-profdata-13.0.cpp b/tools/ldc-profdata/llvm-profdata-13.0.cpp deleted file mode 100644 index 66d70120ac9..00000000000 --- a/tools/ldc-profdata/llvm-profdata-13.0.cpp +++ /dev/null @@ -1,2580 +0,0 @@ -//===- llvm-profdata.cpp - LLVM profile data tool -------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// llvm-profdata merges .profdata files. -// -//===----------------------------------------------------------------------===// - -#include "llvm/ADT/SmallSet.h" -#include "llvm/ADT/SmallVector.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/IR/LLVMContext.h" -#include "llvm/ProfileData/InstrProfReader.h" -#include "llvm/ProfileData/InstrProfWriter.h" -#include "llvm/ProfileData/ProfileCommon.h" -#include "llvm/ProfileData/SampleProfReader.h" -#include "llvm/ProfileData/SampleProfWriter.h" -#include "llvm/Support/CommandLine.h" -#include "llvm/Support/Discriminator.h" -#include "llvm/Support/Errc.h" -#include "llvm/Support/FileSystem.h" -#include "llvm/Support/Format.h" -#include "llvm/Support/FormattedStream.h" -#include "llvm/Support/InitLLVM.h" -#include "llvm/Support/MemoryBuffer.h" -#include "llvm/Support/Path.h" -#include "llvm/Support/ThreadPool.h" -#include "llvm/Support/Threading.h" -#include "llvm/Support/WithColor.h" -#include "llvm/Support/raw_ostream.h" -#include - -using namespace llvm; - -enum ProfileFormat { - PF_None = 0, - PF_Text, - PF_Compact_Binary, - PF_Ext_Binary, - PF_GCC, - PF_Binary -}; - -static void warn(Twine Message, std::string Whence = "", - std::string Hint = "") { - WithColor::warning(); - if (!Whence.empty()) - errs() << Whence << ": "; - errs() << Message << "\n"; - if (!Hint.empty()) - WithColor::note() << Hint << "\n"; -} - -static void warn(Error E, StringRef Whence = "") { - if (E.isA()) { - handleAllErrors(std::move(E), [&](const InstrProfError &IPE) { - warn(IPE.message(), std::string(Whence), std::string("")); - }); - } -} - -static void exitWithError(Twine Message, std::string Whence = "", - std::string Hint = "") { - WithColor::error(); - if (!Whence.empty()) - errs() << Whence << ": "; - errs() << Message << "\n"; - if (!Hint.empty()) - WithColor::note() << Hint << "\n"; - ::exit(1); -} - -static void exitWithError(Error E, StringRef Whence = "") { - if (E.isA()) { - handleAllErrors(std::move(E), [&](const InstrProfError &IPE) { - instrprof_error instrError = IPE.get(); - StringRef Hint = ""; - if (instrError == instrprof_error::unrecognized_format) { - // Hint for common error of forgetting --sample for sample profiles. - Hint = "Perhaps you forgot to use the --sample option?"; - } - exitWithError(IPE.message(), std::string(Whence), std::string(Hint)); - }); - } - - exitWithError(toString(std::move(E)), std::string(Whence)); -} - -static void exitWithErrorCode(std::error_code EC, StringRef Whence = "") { - exitWithError(EC.message(), std::string(Whence)); -} - -namespace { -enum ProfileKinds { instr, sample }; -enum FailureMode { failIfAnyAreInvalid, failIfAllAreInvalid }; -} - -static void warnOrExitGivenError(FailureMode FailMode, std::error_code EC, - StringRef Whence = "") { - if (FailMode == failIfAnyAreInvalid) - exitWithErrorCode(EC, Whence); - else - warn(EC.message(), std::string(Whence)); -} - -static void handleMergeWriterError(Error E, StringRef WhenceFile = "", - StringRef WhenceFunction = "", - bool ShowHint = true) { - if (!WhenceFile.empty()) - errs() << WhenceFile << ": "; - if (!WhenceFunction.empty()) - errs() << WhenceFunction << ": "; - - auto IPE = instrprof_error::success; - E = handleErrors(std::move(E), - [&IPE](std::unique_ptr E) -> Error { - IPE = E->get(); - return Error(std::move(E)); - }); - errs() << toString(std::move(E)) << "\n"; - - if (ShowHint) { - StringRef Hint = ""; - if (IPE != instrprof_error::success) { - switch (IPE) { - case instrprof_error::hash_mismatch: - case instrprof_error::count_mismatch: - case instrprof_error::value_site_count_mismatch: - Hint = "Make sure that all profile data to be merged is generated " - "from the same binary."; - break; - default: - break; - } - } - - if (!Hint.empty()) - errs() << Hint << "\n"; - } -} - -namespace { -/// A remapper from original symbol names to new symbol names based on a file -/// containing a list of mappings from old name to new name. -class SymbolRemapper { - std::unique_ptr File; - DenseMap RemappingTable; - -public: - /// Build a SymbolRemapper from a file containing a list of old/new symbols. - static std::unique_ptr create(StringRef InputFile) { - auto BufOrError = MemoryBuffer::getFileOrSTDIN(InputFile); - if (!BufOrError) - exitWithErrorCode(BufOrError.getError(), InputFile); - - auto Remapper = std::make_unique(); - Remapper->File = std::move(BufOrError.get()); - - for (line_iterator LineIt(*Remapper->File, /*SkipBlanks=*/true, '#'); - !LineIt.is_at_eof(); ++LineIt) { - std::pair Parts = LineIt->split(' '); - if (Parts.first.empty() || Parts.second.empty() || - Parts.second.count(' ')) { - exitWithError("unexpected line in remapping file", - (InputFile + ":" + Twine(LineIt.line_number())).str(), - "expected 'old_symbol new_symbol'"); - } - Remapper->RemappingTable.insert(Parts); - } - return Remapper; - } - - /// Attempt to map the given old symbol into a new symbol. - /// - /// \return The new symbol, or \p Name if no such symbol was found. - StringRef operator()(StringRef Name) { - StringRef New = RemappingTable.lookup(Name); - return New.empty() ? Name : New; - } -}; -} - -struct WeightedFile { - std::string Filename; - uint64_t Weight; -}; -typedef SmallVector WeightedFileVector; - -/// Keep track of merged data and reported errors. -struct WriterContext { - std::mutex Lock; - InstrProfWriter Writer; - std::vector> Errors; - std::mutex &ErrLock; - SmallSet &WriterErrorCodes; - - WriterContext(bool IsSparse, std::mutex &ErrLock, - SmallSet &WriterErrorCodes) - : Lock(), Writer(IsSparse), Errors(), ErrLock(ErrLock), - WriterErrorCodes(WriterErrorCodes) {} -}; - -/// Computer the overlap b/w profile BaseFilename and TestFileName, -/// and store the program level result to Overlap. -static void overlapInput(const std::string &BaseFilename, - const std::string &TestFilename, WriterContext *WC, - OverlapStats &Overlap, - const OverlapFuncFilters &FuncFilter, - raw_fd_ostream &OS, bool IsCS) { - auto ReaderOrErr = InstrProfReader::create(TestFilename); - if (Error E = ReaderOrErr.takeError()) { - // Skip the empty profiles by returning sliently. - instrprof_error IPE = InstrProfError::take(std::move(E)); - if (IPE != instrprof_error::empty_raw_profile) - WC->Errors.emplace_back(make_error(IPE), TestFilename); - return; - } - - auto Reader = std::move(ReaderOrErr.get()); - for (auto &I : *Reader) { - OverlapStats FuncOverlap(OverlapStats::FunctionLevel); - FuncOverlap.setFuncInfo(I.Name, I.Hash); - - WC->Writer.overlapRecord(std::move(I), Overlap, FuncOverlap, FuncFilter); - FuncOverlap.dump(OS); - } -} - -/// Load an input into a writer context. -static void loadInput(const WeightedFile &Input, SymbolRemapper *Remapper, - WriterContext *WC) { - std::unique_lock CtxGuard{WC->Lock}; - - // Copy the filename, because llvm::ThreadPool copied the input "const - // WeightedFile &" by value, making a reference to the filename within it - // invalid outside of this packaged task. - std::string Filename = Input.Filename; - - auto ReaderOrErr = InstrProfReader::create(Input.Filename); - if (Error E = ReaderOrErr.takeError()) { - // Skip the empty profiles by returning sliently. - instrprof_error IPE = InstrProfError::take(std::move(E)); - if (IPE != instrprof_error::empty_raw_profile) - WC->Errors.emplace_back(make_error(IPE), Filename); - return; - } - - auto Reader = std::move(ReaderOrErr.get()); - bool IsIRProfile = Reader->isIRLevelProfile(); - bool HasCSIRProfile = Reader->hasCSIRLevelProfile(); - if (Error E = WC->Writer.setIsIRLevelProfile(IsIRProfile, HasCSIRProfile)) { - consumeError(std::move(E)); - WC->Errors.emplace_back( - make_error( - "Merge IR generated profile with Clang generated profile.", - std::error_code()), - Filename); - return; - } - WC->Writer.setInstrEntryBBEnabled(Reader->instrEntryBBEnabled()); - - for (auto &I : *Reader) { - if (Remapper) - I.Name = (*Remapper)(I.Name); - const StringRef FuncName = I.Name; - bool Reported = false; - WC->Writer.addRecord(std::move(I), Input.Weight, [&](Error E) { - if (Reported) { - consumeError(std::move(E)); - return; - } - Reported = true; - // Only show hint the first time an error occurs. - instrprof_error IPE = InstrProfError::take(std::move(E)); - std::unique_lock ErrGuard{WC->ErrLock}; - bool firstTime = WC->WriterErrorCodes.insert(IPE).second; - handleMergeWriterError(make_error(IPE), Input.Filename, - FuncName, firstTime); - }); - } - if (Reader->hasError()) - if (Error E = Reader->getError()) - WC->Errors.emplace_back(std::move(E), Filename); -} - -/// Merge the \p Src writer context into \p Dst. -static void mergeWriterContexts(WriterContext *Dst, WriterContext *Src) { - for (auto &ErrorPair : Src->Errors) - Dst->Errors.push_back(std::move(ErrorPair)); - Src->Errors.clear(); - - Dst->Writer.mergeRecordsFromWriter(std::move(Src->Writer), [&](Error E) { - instrprof_error IPE = InstrProfError::take(std::move(E)); - std::unique_lock ErrGuard{Dst->ErrLock}; - bool firstTime = Dst->WriterErrorCodes.insert(IPE).second; - if (firstTime) - warn(toString(make_error(IPE))); - }); -} - -static void writeInstrProfile(StringRef OutputFilename, - ProfileFormat OutputFormat, - InstrProfWriter &Writer) { - std::error_code EC; - raw_fd_ostream Output(OutputFilename.data(), EC, - OutputFormat == PF_Text ? sys::fs::OF_TextWithCRLF - : sys::fs::OF_None); - if (EC) - exitWithErrorCode(EC, OutputFilename); - - if (OutputFormat == PF_Text) { - if (Error E = Writer.writeText(Output)) - warn(std::move(E)); - } else { - if (Output.is_displayed()) - exitWithError("cannot write a non-text format profile to the terminal"); - if (Error E = Writer.write(Output)) - warn(std::move(E)); - } -} - -static void mergeInstrProfile(const WeightedFileVector &Inputs, - SymbolRemapper *Remapper, - StringRef OutputFilename, - ProfileFormat OutputFormat, bool OutputSparse, - unsigned NumThreads, FailureMode FailMode) { - if (OutputFormat != PF_Binary && OutputFormat != PF_Compact_Binary && - OutputFormat != PF_Ext_Binary && OutputFormat != PF_Text) - exitWithError("unknown format is specified"); - - std::mutex ErrorLock; - SmallSet WriterErrorCodes; - - // If NumThreads is not specified, auto-detect a good default. - if (NumThreads == 0) - NumThreads = std::min(hardware_concurrency().compute_thread_count(), - unsigned((Inputs.size() + 1) / 2)); - // FIXME: There's a bug here, where setting NumThreads = Inputs.size() fails - // the merge_empty_profile.test because the InstrProfWriter.ProfileKind isn't - // merged, thus the emitted file ends up with a PF_Unknown kind. - - // Initialize the writer contexts. - SmallVector, 4> Contexts; - for (unsigned I = 0; I < NumThreads; ++I) - Contexts.emplace_back(std::make_unique( - OutputSparse, ErrorLock, WriterErrorCodes)); - - if (NumThreads == 1) { - for (const auto &Input : Inputs) - loadInput(Input, Remapper, Contexts[0].get()); - } else { - ThreadPool Pool(hardware_concurrency(NumThreads)); - - // Load the inputs in parallel (N/NumThreads serial steps). - unsigned Ctx = 0; - for (const auto &Input : Inputs) { - Pool.async(loadInput, Input, Remapper, Contexts[Ctx].get()); - Ctx = (Ctx + 1) % NumThreads; - } - Pool.wait(); - - // Merge the writer contexts together (~ lg(NumThreads) serial steps). - unsigned Mid = Contexts.size() / 2; - unsigned End = Contexts.size(); - assert(Mid > 0 && "Expected more than one context"); - do { - for (unsigned I = 0; I < Mid; ++I) - Pool.async(mergeWriterContexts, Contexts[I].get(), - Contexts[I + Mid].get()); - Pool.wait(); - if (End & 1) { - Pool.async(mergeWriterContexts, Contexts[0].get(), - Contexts[End - 1].get()); - Pool.wait(); - } - End = Mid; - Mid /= 2; - } while (Mid > 0); - } - - // Handle deferred errors encountered during merging. If the number of errors - // is equal to the number of inputs the merge failed. - unsigned NumErrors = 0; - for (std::unique_ptr &WC : Contexts) { - for (auto &ErrorPair : WC->Errors) { - ++NumErrors; - warn(toString(std::move(ErrorPair.first)), ErrorPair.second); - } - } - if (NumErrors == Inputs.size() || - (NumErrors > 0 && FailMode == failIfAnyAreInvalid)) - exitWithError("no profile can be merged"); - - writeInstrProfile(OutputFilename, OutputFormat, Contexts[0]->Writer); -} - -/// The profile entry for a function in instrumentation profile. -struct InstrProfileEntry { - uint64_t MaxCount = 0; - float ZeroCounterRatio = 0.0; - InstrProfRecord *ProfRecord; - InstrProfileEntry(InstrProfRecord *Record); - InstrProfileEntry() = default; -}; - -InstrProfileEntry::InstrProfileEntry(InstrProfRecord *Record) { - ProfRecord = Record; - uint64_t CntNum = Record->Counts.size(); - uint64_t ZeroCntNum = 0; - for (size_t I = 0; I < CntNum; ++I) { - MaxCount = std::max(MaxCount, Record->Counts[I]); - ZeroCntNum += !Record->Counts[I]; - } - ZeroCounterRatio = (float)ZeroCntNum / CntNum; -} - -/// Either set all the counters in the instr profile entry \p IFE to -1 -/// in order to drop the profile or scale up the counters in \p IFP to -/// be above hot threshold. We use the ratio of zero counters in the -/// profile of a function to decide the profile is helpful or harmful -/// for performance, and to choose whether to scale up or drop it. -static void updateInstrProfileEntry(InstrProfileEntry &IFE, - uint64_t HotInstrThreshold, - float ZeroCounterThreshold) { - InstrProfRecord *ProfRecord = IFE.ProfRecord; - if (!IFE.MaxCount || IFE.ZeroCounterRatio > ZeroCounterThreshold) { - // If all or most of the counters of the function are zero, the - // profile is unaccountable and shuld be dropped. Reset all the - // counters to be -1 and PGO profile-use will drop the profile. - // All counters being -1 also implies that the function is hot so - // PGO profile-use will also set the entry count metadata to be - // above hot threshold. - for (size_t I = 0; I < ProfRecord->Counts.size(); ++I) - ProfRecord->Counts[I] = -1; - return; - } - - // Scale up the MaxCount to be multiple times above hot threshold. - const unsigned MultiplyFactor = 3; - uint64_t Numerator = HotInstrThreshold * MultiplyFactor; - uint64_t Denominator = IFE.MaxCount; - ProfRecord->scale(Numerator, Denominator, [&](instrprof_error E) { - warn(toString(make_error(E))); - }); -} - -const uint64_t ColdPercentileIdx = 15; -const uint64_t HotPercentileIdx = 11; - -using sampleprof::FSDiscriminatorPass; - -// Internal options to set FSDiscriminatorPass. Used in merge and show -// commands. -static cl::opt FSDiscriminatorPassOption( - "fs-discriminator-pass", cl::init(PassLast), cl::Hidden, - cl::desc("Zero out the discriminator bits for the FS discrimiantor " - "pass beyond this value. The enum values are defined in " - "Support/Discriminator.h"), - cl::values(clEnumVal(Base, "Use base discriminators only"), - clEnumVal(Pass1, "Use base and pass 1 discriminators"), - clEnumVal(Pass2, "Use base and pass 1-2 discriminators"), - clEnumVal(Pass3, "Use base and pass 1-3 discriminators"), - clEnumVal(PassLast, "Use all discriminator bits (default)"))); - -static unsigned getDiscriminatorMask() { - return getN1Bits(getFSPassBitEnd(FSDiscriminatorPassOption.getValue())); -} - -/// Adjust the instr profile in \p WC based on the sample profile in -/// \p Reader. -static void -adjustInstrProfile(std::unique_ptr &WC, - std::unique_ptr &Reader, - unsigned SupplMinSizeThreshold, float ZeroCounterThreshold, - unsigned InstrProfColdThreshold) { - // Function to its entry in instr profile. - StringMap InstrProfileMap; - InstrProfSummaryBuilder IPBuilder(ProfileSummaryBuilder::DefaultCutoffs); - for (auto &PD : WC->Writer.getProfileData()) { - // Populate IPBuilder. - for (const auto &PDV : PD.getValue()) { - InstrProfRecord Record = PDV.second; - IPBuilder.addRecord(Record); - } - - // If a function has multiple entries in instr profile, skip it. - if (PD.getValue().size() != 1) - continue; - - // Initialize InstrProfileMap. - InstrProfRecord *R = &PD.getValue().begin()->second; - InstrProfileMap[PD.getKey()] = InstrProfileEntry(R); - } - - ProfileSummary InstrPS = *IPBuilder.getSummary(); - ProfileSummary SamplePS = Reader->getSummary(); - - // Compute cold thresholds for instr profile and sample profile. - uint64_t ColdSampleThreshold = - ProfileSummaryBuilder::getEntryForPercentile( - SamplePS.getDetailedSummary(), - ProfileSummaryBuilder::DefaultCutoffs[ColdPercentileIdx]) - .MinCount; - uint64_t HotInstrThreshold = - ProfileSummaryBuilder::getEntryForPercentile( - InstrPS.getDetailedSummary(), - ProfileSummaryBuilder::DefaultCutoffs[HotPercentileIdx]) - .MinCount; - uint64_t ColdInstrThreshold = - InstrProfColdThreshold - ? InstrProfColdThreshold - : ProfileSummaryBuilder::getEntryForPercentile( - InstrPS.getDetailedSummary(), - ProfileSummaryBuilder::DefaultCutoffs[ColdPercentileIdx]) - .MinCount; - - // Find hot/warm functions in sample profile which is cold in instr profile - // and adjust the profiles of those functions in the instr profile. - for (const auto &PD : Reader->getProfiles()) { - StringRef FName = PD.getKey(); - const sampleprof::FunctionSamples &FS = PD.getValue(); - auto It = InstrProfileMap.find(FName); - if (FS.getHeadSamples() > ColdSampleThreshold && - It != InstrProfileMap.end() && - It->second.MaxCount <= ColdInstrThreshold && - FS.getBodySamples().size() >= SupplMinSizeThreshold) { - updateInstrProfileEntry(It->second, HotInstrThreshold, - ZeroCounterThreshold); - } - } -} - -/// The main function to supplement instr profile with sample profile. -/// \Inputs contains the instr profile. \p SampleFilename specifies the -/// sample profile. \p OutputFilename specifies the output profile name. -/// \p OutputFormat specifies the output profile format. \p OutputSparse -/// specifies whether to generate sparse profile. \p SupplMinSizeThreshold -/// specifies the minimal size for the functions whose profile will be -/// adjusted. \p ZeroCounterThreshold is the threshold to check whether -/// a function contains too many zero counters and whether its profile -/// should be dropped. \p InstrProfColdThreshold is the user specified -/// cold threshold which will override the cold threshold got from the -/// instr profile summary. -static void supplementInstrProfile( - const WeightedFileVector &Inputs, StringRef SampleFilename, - StringRef OutputFilename, ProfileFormat OutputFormat, bool OutputSparse, - unsigned SupplMinSizeThreshold, float ZeroCounterThreshold, - unsigned InstrProfColdThreshold) { - if (OutputFilename.compare("-") == 0) - exitWithError("cannot write indexed profdata format to stdout"); - if (Inputs.size() != 1) - exitWithError("expect one input to be an instr profile"); - if (Inputs[0].Weight != 1) - exitWithError("expect instr profile doesn't have weight"); - - StringRef InstrFilename = Inputs[0].Filename; - - // Read sample profile. - LLVMContext Context; - auto ReaderOrErr = sampleprof::SampleProfileReader::create( - SampleFilename.str(), Context, FSDiscriminatorPassOption); - if (std::error_code EC = ReaderOrErr.getError()) - exitWithErrorCode(EC, SampleFilename); - auto Reader = std::move(ReaderOrErr.get()); - if (std::error_code EC = Reader->read()) - exitWithErrorCode(EC, SampleFilename); - - // Read instr profile. - std::mutex ErrorLock; - SmallSet WriterErrorCodes; - auto WC = std::make_unique(OutputSparse, ErrorLock, - WriterErrorCodes); - loadInput(Inputs[0], nullptr, WC.get()); - if (WC->Errors.size() > 0) - exitWithError(std::move(WC->Errors[0].first), InstrFilename); - - adjustInstrProfile(WC, Reader, SupplMinSizeThreshold, ZeroCounterThreshold, - InstrProfColdThreshold); - writeInstrProfile(OutputFilename, OutputFormat, WC->Writer); -} - -/// Make a copy of the given function samples with all symbol names remapped -/// by the provided symbol remapper. -static sampleprof::FunctionSamples -remapSamples(const sampleprof::FunctionSamples &Samples, - SymbolRemapper &Remapper, sampleprof_error &Error) { - sampleprof::FunctionSamples Result; - Result.setName(Remapper(Samples.getName())); - Result.addTotalSamples(Samples.getTotalSamples()); - Result.addHeadSamples(Samples.getHeadSamples()); - for (const auto &BodySample : Samples.getBodySamples()) { - uint32_t MaskedDiscriminator = - BodySample.first.Discriminator & getDiscriminatorMask(); - Result.addBodySamples(BodySample.first.LineOffset, MaskedDiscriminator, - BodySample.second.getSamples()); - for (const auto &Target : BodySample.second.getCallTargets()) { - Result.addCalledTargetSamples(BodySample.first.LineOffset, - MaskedDiscriminator, - Remapper(Target.first()), Target.second); - } - } - for (const auto &CallsiteSamples : Samples.getCallsiteSamples()) { - sampleprof::FunctionSamplesMap &Target = - Result.functionSamplesAt(CallsiteSamples.first); - for (const auto &Callsite : CallsiteSamples.second) { - sampleprof::FunctionSamples Remapped = - remapSamples(Callsite.second, Remapper, Error); - MergeResult(Error, - Target[std::string(Remapped.getName())].merge(Remapped)); - } - } - return Result; -} - -static sampleprof::SampleProfileFormat FormatMap[] = { - sampleprof::SPF_None, - sampleprof::SPF_Text, - sampleprof::SPF_Compact_Binary, - sampleprof::SPF_Ext_Binary, - sampleprof::SPF_GCC, - sampleprof::SPF_Binary}; - -static std::unique_ptr -getInputFileBuf(const StringRef &InputFile) { - if (InputFile == "") - return {}; - - auto BufOrError = MemoryBuffer::getFileOrSTDIN(InputFile); - if (!BufOrError) - exitWithErrorCode(BufOrError.getError(), InputFile); - - return std::move(*BufOrError); -} - -static void populateProfileSymbolList(MemoryBuffer *Buffer, - sampleprof::ProfileSymbolList &PSL) { - if (!Buffer) - return; - - SmallVector SymbolVec; - StringRef Data = Buffer->getBuffer(); - Data.split(SymbolVec, '\n', /*MaxSplit=*/-1, /*KeepEmpty=*/false); - - for (StringRef symbol : SymbolVec) - PSL.add(symbol); -} - -static void handleExtBinaryWriter(sampleprof::SampleProfileWriter &Writer, - ProfileFormat OutputFormat, - MemoryBuffer *Buffer, - sampleprof::ProfileSymbolList &WriterList, - bool CompressAllSections, bool UseMD5, - bool GenPartialProfile) { - populateProfileSymbolList(Buffer, WriterList); - if (WriterList.size() > 0 && OutputFormat != PF_Ext_Binary) - warn("Profile Symbol list is not empty but the output format is not " - "ExtBinary format. The list will be lost in the output. "); - - Writer.setProfileSymbolList(&WriterList); - - if (CompressAllSections) { - if (OutputFormat != PF_Ext_Binary) - warn("-compress-all-section is ignored. Specify -extbinary to enable it"); - else - Writer.setToCompressAllSections(); - } - if (UseMD5) { - if (OutputFormat != PF_Ext_Binary) - warn("-use-md5 is ignored. Specify -extbinary to enable it"); - else - Writer.setUseMD5(); - } - if (GenPartialProfile) { - if (OutputFormat != PF_Ext_Binary) - warn("-gen-partial-profile is ignored. Specify -extbinary to enable it"); - else - Writer.setPartialProfile(); - } -} - -static void -mergeSampleProfile(const WeightedFileVector &Inputs, SymbolRemapper *Remapper, - StringRef OutputFilename, ProfileFormat OutputFormat, - StringRef ProfileSymbolListFile, bool CompressAllSections, - bool UseMD5, bool GenPartialProfile, - bool SampleMergeColdContext, bool SampleTrimColdContext, - bool SampleColdContextFrameDepth, FailureMode FailMode) { - using namespace sampleprof; - StringMap ProfileMap; - SmallVector, 5> Readers; - LLVMContext Context; - sampleprof::ProfileSymbolList WriterList; - Optional ProfileIsProbeBased; - Optional ProfileIsCS; - for (const auto &Input : Inputs) { - auto ReaderOrErr = SampleProfileReader::create(Input.Filename, Context, - FSDiscriminatorPassOption); - if (std::error_code EC = ReaderOrErr.getError()) { - warnOrExitGivenError(FailMode, EC, Input.Filename); - continue; - } - - // We need to keep the readers around until after all the files are - // read so that we do not lose the function names stored in each - // reader's memory. The function names are needed to write out the - // merged profile map. - Readers.push_back(std::move(ReaderOrErr.get())); - const auto Reader = Readers.back().get(); - if (std::error_code EC = Reader->read()) { - warnOrExitGivenError(FailMode, EC, Input.Filename); - Readers.pop_back(); - continue; - } - - StringMap &Profiles = Reader->getProfiles(); - if (ProfileIsProbeBased.hasValue() && - ProfileIsProbeBased != FunctionSamples::ProfileIsProbeBased) - exitWithError( - "cannot merge probe-based profile with non-probe-based profile"); - ProfileIsProbeBased = FunctionSamples::ProfileIsProbeBased; - if (ProfileIsCS.hasValue() && ProfileIsCS != FunctionSamples::ProfileIsCS) - exitWithError("cannot merge CS profile with non-CS profile"); - ProfileIsCS = FunctionSamples::ProfileIsCS; - for (StringMap::iterator I = Profiles.begin(), - E = Profiles.end(); - I != E; ++I) { - sampleprof_error Result = sampleprof_error::success; - FunctionSamples Remapped = - Remapper ? remapSamples(I->second, *Remapper, Result) - : FunctionSamples(); - FunctionSamples &Samples = Remapper ? Remapped : I->second; - StringRef FName = Samples.getNameWithContext(); - MergeResult(Result, ProfileMap[FName].merge(Samples, Input.Weight)); - if (Result != sampleprof_error::success) { - std::error_code EC = make_error_code(Result); - handleMergeWriterError(errorCodeToError(EC), Input.Filename, FName); - } - } - - std::unique_ptr ReaderList = - Reader->getProfileSymbolList(); - if (ReaderList) - WriterList.merge(*ReaderList); - } - - if (ProfileIsCS && (SampleMergeColdContext || SampleTrimColdContext)) { - // Use threshold calculated from profile summary unless specified. - SampleProfileSummaryBuilder Builder(ProfileSummaryBuilder::DefaultCutoffs); - auto Summary = Builder.computeSummaryForProfiles(ProfileMap); - uint64_t SampleProfColdThreshold = - ProfileSummaryBuilder::getColdCountThreshold( - (Summary->getDetailedSummary())); - - // Trim and merge cold context profile using cold threshold above; - SampleContextTrimmer(ProfileMap) - .trimAndMergeColdContextProfiles( - SampleProfColdThreshold, SampleTrimColdContext, - SampleMergeColdContext, SampleColdContextFrameDepth); - } - - auto WriterOrErr = - SampleProfileWriter::create(OutputFilename, FormatMap[OutputFormat]); - if (std::error_code EC = WriterOrErr.getError()) - exitWithErrorCode(EC, OutputFilename); - - auto Writer = std::move(WriterOrErr.get()); - // WriterList will have StringRef refering to string in Buffer. - // Make sure Buffer lives as long as WriterList. - auto Buffer = getInputFileBuf(ProfileSymbolListFile); - handleExtBinaryWriter(*Writer, OutputFormat, Buffer.get(), WriterList, - CompressAllSections, UseMD5, GenPartialProfile); - if (std::error_code EC = Writer->write(ProfileMap)) - exitWithErrorCode(std::move(EC)); -} - -static WeightedFile parseWeightedFile(const StringRef &WeightedFilename) { - StringRef WeightStr, FileName; - std::tie(WeightStr, FileName) = WeightedFilename.split(','); - - uint64_t Weight; - if (WeightStr.getAsInteger(10, Weight) || Weight < 1) - exitWithError("input weight must be a positive integer"); - - return {std::string(FileName), Weight}; -} - -static void addWeightedInput(WeightedFileVector &WNI, const WeightedFile &WF) { - StringRef Filename = WF.Filename; - uint64_t Weight = WF.Weight; - - // If it's STDIN just pass it on. - if (Filename == "-") { - WNI.push_back({std::string(Filename), Weight}); - return; - } - - llvm::sys::fs::file_status Status; - llvm::sys::fs::status(Filename, Status); - if (!llvm::sys::fs::exists(Status)) - exitWithErrorCode(make_error_code(errc::no_such_file_or_directory), - Filename); - // If it's a source file, collect it. - if (llvm::sys::fs::is_regular_file(Status)) { - WNI.push_back({std::string(Filename), Weight}); - return; - } - - if (llvm::sys::fs::is_directory(Status)) { - std::error_code EC; - for (llvm::sys::fs::recursive_directory_iterator F(Filename, EC), E; - F != E && !EC; F.increment(EC)) { - if (llvm::sys::fs::is_regular_file(F->path())) { - addWeightedInput(WNI, {F->path(), Weight}); - } - } - if (EC) - exitWithErrorCode(EC, Filename); - } -} - -static void parseInputFilenamesFile(MemoryBuffer *Buffer, - WeightedFileVector &WFV) { - if (!Buffer) - return; - - SmallVector Entries; - StringRef Data = Buffer->getBuffer(); - Data.split(Entries, '\n', /*MaxSplit=*/-1, /*KeepEmpty=*/false); - for (const StringRef &FileWeightEntry : Entries) { - StringRef SanitizedEntry = FileWeightEntry.trim(" \t\v\f\r"); - // Skip comments. - if (SanitizedEntry.startswith("#")) - continue; - // If there's no comma, it's an unweighted profile. - else if (SanitizedEntry.find(',') == StringRef::npos) - addWeightedInput(WFV, {std::string(SanitizedEntry), 1}); - else - addWeightedInput(WFV, parseWeightedFile(SanitizedEntry)); - } -} - -static int merge_main(int argc, const char *argv[]) { - cl::list InputFilenames(cl::Positional, - cl::desc("")); - cl::list WeightedInputFilenames("weighted-input", - cl::desc(",")); - cl::opt InputFilenamesFile( - "input-files", cl::init(""), - cl::desc("Path to file containing newline-separated " - "[,] entries")); - cl::alias InputFilenamesFileA("f", cl::desc("Alias for --input-files"), - cl::aliasopt(InputFilenamesFile)); - cl::opt DumpInputFileList( - "dump-input-file-list", cl::init(false), cl::Hidden, - cl::desc("Dump the list of input files and their weights, then exit")); - cl::opt RemappingFile("remapping-file", cl::value_desc("file"), - cl::desc("Symbol remapping file")); - cl::alias RemappingFileA("r", cl::desc("Alias for --remapping-file"), - cl::aliasopt(RemappingFile)); - cl::opt OutputFilename("output", cl::value_desc("output"), - cl::init("-"), cl::desc("Output file")); - cl::alias OutputFilenameA("o", cl::desc("Alias for --output"), - cl::aliasopt(OutputFilename)); - cl::opt ProfileKind( - cl::desc("Profile kind:"), cl::init(instr), - cl::values(clEnumVal(instr, "Instrumentation profile (default)"), - clEnumVal(sample, "Sample profile"))); - cl::opt OutputFormat( - cl::desc("Format of output profile"), cl::init(PF_Binary), - cl::values( - clEnumValN(PF_Binary, "binary", "Binary encoding (default)"), - clEnumValN(PF_Compact_Binary, "compbinary", - "Compact binary encoding"), - clEnumValN(PF_Ext_Binary, "extbinary", "Extensible binary encoding"), - clEnumValN(PF_Text, "text", "Text encoding"), - clEnumValN(PF_GCC, "gcc", - "GCC encoding (only meaningful for -sample)"))); - cl::opt FailureMode( - "failure-mode", cl::init(failIfAnyAreInvalid), cl::desc("Failure mode:"), - cl::values(clEnumValN(failIfAnyAreInvalid, "any", - "Fail if any profile is invalid."), - clEnumValN(failIfAllAreInvalid, "all", - "Fail only if all profiles are invalid."))); - cl::opt OutputSparse("sparse", cl::init(false), - cl::desc("Generate a sparse profile (only meaningful for -instr)")); - cl::opt NumThreads( - "num-threads", cl::init(0), - cl::desc("Number of merge threads to use (default: autodetect)")); - cl::alias NumThreadsA("j", cl::desc("Alias for --num-threads"), - cl::aliasopt(NumThreads)); - cl::opt ProfileSymbolListFile( - "prof-sym-list", cl::init(""), - cl::desc("Path to file containing the list of function symbols " - "used to populate profile symbol list")); - cl::opt CompressAllSections( - "compress-all-sections", cl::init(false), cl::Hidden, - cl::desc("Compress all sections when writing the profile (only " - "meaningful for -extbinary)")); - cl::opt UseMD5( - "use-md5", cl::init(false), cl::Hidden, - cl::desc("Choose to use MD5 to represent string in name table (only " - "meaningful for -extbinary)")); - cl::opt SampleMergeColdContext( - "sample-merge-cold-context", cl::init(false), cl::Hidden, - cl::desc( - "Merge context sample profiles whose count is below cold threshold")); - cl::opt SampleTrimColdContext( - "sample-trim-cold-context", cl::init(false), cl::Hidden, - cl::desc( - "Trim context sample profiles whose count is below cold threshold")); - cl::opt SampleColdContextFrameDepth( - "sample-frame-depth-for-cold-context", cl::init(1), cl::ZeroOrMore, - cl::desc("Keep the last K frames while merging cold profile. 1 means the " - "context-less base profile")); - cl::opt GenPartialProfile( - "gen-partial-profile", cl::init(false), cl::Hidden, - cl::desc("Generate a partial profile (only meaningful for -extbinary)")); - cl::opt SupplInstrWithSample( - "supplement-instr-with-sample", cl::init(""), cl::Hidden, - cl::desc("Supplement an instr profile with sample profile, to correct " - "the profile unrepresentativeness issue. The sample " - "profile is the input of the flag. Output will be in instr " - "format (The flag only works with -instr)")); - cl::opt ZeroCounterThreshold( - "zero-counter-threshold", cl::init(0.7), cl::Hidden, - cl::desc("For the function which is cold in instr profile but hot in " - "sample profile, if the ratio of the number of zero counters " - "divided by the the total number of counters is above the " - "threshold, the profile of the function will be regarded as " - "being harmful for performance and will be dropped.")); - cl::opt SupplMinSizeThreshold( - "suppl-min-size-threshold", cl::init(10), cl::Hidden, - cl::desc("If the size of a function is smaller than the threshold, " - "assume it can be inlined by PGO early inliner and it won't " - "be adjusted based on sample profile.")); - cl::opt InstrProfColdThreshold( - "instr-prof-cold-threshold", cl::init(0), cl::Hidden, - cl::desc("User specified cold threshold for instr profile which will " - "override the cold threshold got from profile summary.")); - - cl::ParseCommandLineOptions(argc, argv, "LLVM profile data merger\n"); - - WeightedFileVector WeightedInputs; - for (StringRef Filename : InputFilenames) - addWeightedInput(WeightedInputs, {std::string(Filename), 1}); - for (StringRef WeightedFilename : WeightedInputFilenames) - addWeightedInput(WeightedInputs, parseWeightedFile(WeightedFilename)); - - // Make sure that the file buffer stays alive for the duration of the - // weighted input vector's lifetime. - auto Buffer = getInputFileBuf(InputFilenamesFile); - parseInputFilenamesFile(Buffer.get(), WeightedInputs); - - if (WeightedInputs.empty()) - exitWithError("no input files specified. See " + - sys::path::filename(argv[0]) + " -help"); - - if (DumpInputFileList) { - for (auto &WF : WeightedInputs) - outs() << WF.Weight << "," << WF.Filename << "\n"; - return 0; - } - - std::unique_ptr Remapper; - if (!RemappingFile.empty()) - Remapper = SymbolRemapper::create(RemappingFile); - - if (!SupplInstrWithSample.empty()) { - if (ProfileKind != instr) - exitWithError( - "-supplement-instr-with-sample can only work with -instr. "); - - supplementInstrProfile(WeightedInputs, SupplInstrWithSample, OutputFilename, - OutputFormat, OutputSparse, SupplMinSizeThreshold, - ZeroCounterThreshold, InstrProfColdThreshold); - return 0; - } - - if (ProfileKind == instr) - mergeInstrProfile(WeightedInputs, Remapper.get(), OutputFilename, - OutputFormat, OutputSparse, NumThreads, FailureMode); - else - mergeSampleProfile(WeightedInputs, Remapper.get(), OutputFilename, - OutputFormat, ProfileSymbolListFile, CompressAllSections, - UseMD5, GenPartialProfile, SampleMergeColdContext, - SampleTrimColdContext, SampleColdContextFrameDepth, - FailureMode); - - return 0; -} - -/// Computer the overlap b/w profile BaseFilename and profile TestFilename. -static void overlapInstrProfile(const std::string &BaseFilename, - const std::string &TestFilename, - const OverlapFuncFilters &FuncFilter, - raw_fd_ostream &OS, bool IsCS) { - std::mutex ErrorLock; - SmallSet WriterErrorCodes; - WriterContext Context(false, ErrorLock, WriterErrorCodes); - WeightedFile WeightedInput{BaseFilename, 1}; - OverlapStats Overlap; - Error E = Overlap.accumulateCounts(BaseFilename, TestFilename, IsCS); - if (E) - exitWithError(std::move(E), "error in getting profile count sums"); - if (Overlap.Base.CountSum < 1.0f) { - OS << "Sum of edge counts for profile " << BaseFilename << " is 0.\n"; - exit(0); - } - if (Overlap.Test.CountSum < 1.0f) { - OS << "Sum of edge counts for profile " << TestFilename << " is 0.\n"; - exit(0); - } - loadInput(WeightedInput, nullptr, &Context); - overlapInput(BaseFilename, TestFilename, &Context, Overlap, FuncFilter, OS, - IsCS); - Overlap.dump(OS); -} - -namespace { -struct SampleOverlapStats { - StringRef BaseName; - StringRef TestName; - // Number of overlap units - uint64_t OverlapCount; - // Total samples of overlap units - uint64_t OverlapSample; - // Number of and total samples of units that only present in base or test - // profile - uint64_t BaseUniqueCount; - uint64_t BaseUniqueSample; - uint64_t TestUniqueCount; - uint64_t TestUniqueSample; - // Number of units and total samples in base or test profile - uint64_t BaseCount; - uint64_t BaseSample; - uint64_t TestCount; - uint64_t TestSample; - // Number of and total samples of units that present in at least one profile - uint64_t UnionCount; - uint64_t UnionSample; - // Weighted similarity - double Similarity; - // For SampleOverlapStats instances representing functions, weights of the - // function in base and test profiles - double BaseWeight; - double TestWeight; - - SampleOverlapStats() - : OverlapCount(0), OverlapSample(0), BaseUniqueCount(0), - BaseUniqueSample(0), TestUniqueCount(0), TestUniqueSample(0), - BaseCount(0), BaseSample(0), TestCount(0), TestSample(0), UnionCount(0), - UnionSample(0), Similarity(0.0), BaseWeight(0.0), TestWeight(0.0) {} -}; -} // end anonymous namespace - -namespace { -struct FuncSampleStats { - uint64_t SampleSum; - uint64_t MaxSample; - uint64_t HotBlockCount; - FuncSampleStats() : SampleSum(0), MaxSample(0), HotBlockCount(0) {} - FuncSampleStats(uint64_t SampleSum, uint64_t MaxSample, - uint64_t HotBlockCount) - : SampleSum(SampleSum), MaxSample(MaxSample), - HotBlockCount(HotBlockCount) {} -}; -} // end anonymous namespace - -namespace { -enum MatchStatus { MS_Match, MS_FirstUnique, MS_SecondUnique, MS_None }; - -// Class for updating merging steps for two sorted maps. The class should be -// instantiated with a map iterator type. -template class MatchStep { -public: - MatchStep() = delete; - - MatchStep(T FirstIter, T FirstEnd, T SecondIter, T SecondEnd) - : FirstIter(FirstIter), FirstEnd(FirstEnd), SecondIter(SecondIter), - SecondEnd(SecondEnd), Status(MS_None) {} - - bool areBothFinished() const { - return (FirstIter == FirstEnd && SecondIter == SecondEnd); - } - - bool isFirstFinished() const { return FirstIter == FirstEnd; } - - bool isSecondFinished() const { return SecondIter == SecondEnd; } - - /// Advance one step based on the previous match status unless the previous - /// status is MS_None. Then update Status based on the comparison between two - /// container iterators at the current step. If the previous status is - /// MS_None, it means two iterators are at the beginning and no comparison has - /// been made, so we simply update Status without advancing the iterators. - void updateOneStep(); - - T getFirstIter() const { return FirstIter; } - - T getSecondIter() const { return SecondIter; } - - MatchStatus getMatchStatus() const { return Status; } - -private: - // Current iterator and end iterator of the first container. - T FirstIter; - T FirstEnd; - // Current iterator and end iterator of the second container. - T SecondIter; - T SecondEnd; - // Match status of the current step. - MatchStatus Status; -}; -} // end anonymous namespace - -template void MatchStep::updateOneStep() { - switch (Status) { - case MS_Match: - ++FirstIter; - ++SecondIter; - break; - case MS_FirstUnique: - ++FirstIter; - break; - case MS_SecondUnique: - ++SecondIter; - break; - case MS_None: - break; - } - - // Update Status according to iterators at the current step. - if (areBothFinished()) - return; - if (FirstIter != FirstEnd && - (SecondIter == SecondEnd || FirstIter->first < SecondIter->first)) - Status = MS_FirstUnique; - else if (SecondIter != SecondEnd && - (FirstIter == FirstEnd || SecondIter->first < FirstIter->first)) - Status = MS_SecondUnique; - else - Status = MS_Match; -} - -// Return the sum of line/block samples, the max line/block sample, and the -// number of line/block samples above the given threshold in a function -// including its inlinees. -static void getFuncSampleStats(const sampleprof::FunctionSamples &Func, - FuncSampleStats &FuncStats, - uint64_t HotThreshold) { - for (const auto &L : Func.getBodySamples()) { - uint64_t Sample = L.second.getSamples(); - FuncStats.SampleSum += Sample; - FuncStats.MaxSample = std::max(FuncStats.MaxSample, Sample); - if (Sample >= HotThreshold) - ++FuncStats.HotBlockCount; - } - - for (const auto &C : Func.getCallsiteSamples()) { - for (const auto &F : C.second) - getFuncSampleStats(F.second, FuncStats, HotThreshold); - } -} - -/// Predicate that determines if a function is hot with a given threshold. We -/// keep it separate from its callsites for possible extension in the future. -static bool isFunctionHot(const FuncSampleStats &FuncStats, - uint64_t HotThreshold) { - // We intentionally compare the maximum sample count in a function with the - // HotThreshold to get an approximate determination on hot functions. - return (FuncStats.MaxSample >= HotThreshold); -} - -namespace { -class SampleOverlapAggregator { -public: - SampleOverlapAggregator(const std::string &BaseFilename, - const std::string &TestFilename, - double LowSimilarityThreshold, double Epsilon, - const OverlapFuncFilters &FuncFilter) - : BaseFilename(BaseFilename), TestFilename(TestFilename), - LowSimilarityThreshold(LowSimilarityThreshold), Epsilon(Epsilon), - FuncFilter(FuncFilter) {} - - /// Detect 0-sample input profile and report to output stream. This interface - /// should be called after loadProfiles(). - bool detectZeroSampleProfile(raw_fd_ostream &OS) const; - - /// Write out function-level similarity statistics for functions specified by - /// options --function, --value-cutoff, and --similarity-cutoff. - void dumpFuncSimilarity(raw_fd_ostream &OS) const; - - /// Write out program-level similarity and overlap statistics. - void dumpProgramSummary(raw_fd_ostream &OS) const; - - /// Write out hot-function and hot-block statistics for base_profile, - /// test_profile, and their overlap. For both cases, the overlap HO is - /// calculated as follows: - /// Given the number of functions (or blocks) that are hot in both profiles - /// HCommon and the number of functions (or blocks) that are hot in at - /// least one profile HUnion, HO = HCommon / HUnion. - void dumpHotFuncAndBlockOverlap(raw_fd_ostream &OS) const; - - /// This function tries matching functions in base and test profiles. For each - /// pair of matched functions, it aggregates the function-level - /// similarity into a profile-level similarity. It also dump function-level - /// similarity information of functions specified by --function, - /// --value-cutoff, and --similarity-cutoff options. The program-level - /// similarity PS is computed as follows: - /// Given function-level similarity FS(A) for all function A, the - /// weight of function A in base profile WB(A), and the weight of function - /// A in test profile WT(A), compute PS(base_profile, test_profile) = - /// sum_A(FS(A) * avg(WB(A), WT(A))) ranging in [0.0f to 1.0f] with 0.0 - /// meaning no-overlap. - void computeSampleProfileOverlap(raw_fd_ostream &OS); - - /// Initialize ProfOverlap with the sum of samples in base and test - /// profiles. This function also computes and keeps the sum of samples and - /// max sample counts of each function in BaseStats and TestStats for later - /// use to avoid re-computations. - void initializeSampleProfileOverlap(); - - /// Load profiles specified by BaseFilename and TestFilename. - std::error_code loadProfiles(); - -private: - SampleOverlapStats ProfOverlap; - SampleOverlapStats HotFuncOverlap; - SampleOverlapStats HotBlockOverlap; - std::string BaseFilename; - std::string TestFilename; - std::unique_ptr BaseReader; - std::unique_ptr TestReader; - // BaseStats and TestStats hold FuncSampleStats for each function, with - // function name as the key. - StringMap BaseStats; - StringMap TestStats; - // Low similarity threshold in floating point number - double LowSimilarityThreshold; - // Block samples above BaseHotThreshold or TestHotThreshold are considered hot - // for tracking hot blocks. - uint64_t BaseHotThreshold; - uint64_t TestHotThreshold; - // A small threshold used to round the results of floating point accumulations - // to resolve imprecision. - const double Epsilon; - std::multimap> - FuncSimilarityDump; - // FuncFilter carries specifications in options --value-cutoff and - // --function. - OverlapFuncFilters FuncFilter; - // Column offsets for printing the function-level details table. - static const unsigned int TestWeightCol = 15; - static const unsigned int SimilarityCol = 30; - static const unsigned int OverlapCol = 43; - static const unsigned int BaseUniqueCol = 53; - static const unsigned int TestUniqueCol = 67; - static const unsigned int BaseSampleCol = 81; - static const unsigned int TestSampleCol = 96; - static const unsigned int FuncNameCol = 111; - - /// Return a similarity of two line/block sample counters in the same - /// function in base and test profiles. The line/block-similarity BS(i) is - /// computed as follows: - /// For an offsets i, given the sample count at i in base profile BB(i), - /// the sample count at i in test profile BT(i), the sum of sample counts - /// in this function in base profile SB, and the sum of sample counts in - /// this function in test profile ST, compute BS(i) = 1.0 - fabs(BB(i)/SB - - /// BT(i)/ST), ranging in [0.0f to 1.0f] with 0.0 meaning no-overlap. - double computeBlockSimilarity(uint64_t BaseSample, uint64_t TestSample, - const SampleOverlapStats &FuncOverlap) const; - - void updateHotBlockOverlap(uint64_t BaseSample, uint64_t TestSample, - uint64_t HotBlockCount); - - void getHotFunctions(const StringMap &ProfStats, - StringMap &HotFunc, - uint64_t HotThreshold) const; - - void computeHotFuncOverlap(); - - /// This function updates statistics in FuncOverlap, HotBlockOverlap, and - /// Difference for two sample units in a matched function according to the - /// given match status. - void updateOverlapStatsForFunction(uint64_t BaseSample, uint64_t TestSample, - uint64_t HotBlockCount, - SampleOverlapStats &FuncOverlap, - double &Difference, MatchStatus Status); - - /// This function updates statistics in FuncOverlap, HotBlockOverlap, and - /// Difference for unmatched callees that only present in one profile in a - /// matched caller function. - void updateForUnmatchedCallee(const sampleprof::FunctionSamples &Func, - SampleOverlapStats &FuncOverlap, - double &Difference, MatchStatus Status); - - /// This function updates sample overlap statistics of an overlap function in - /// base and test profile. It also calculates a function-internal similarity - /// FIS as follows: - /// For offsets i that have samples in at least one profile in this - /// function A, given BS(i) returned by computeBlockSimilarity(), compute - /// FIS(A) = (2.0 - sum_i(1.0 - BS(i))) / 2, ranging in [0.0f to 1.0f] with - /// 0.0 meaning no overlap. - double computeSampleFunctionInternalOverlap( - const sampleprof::FunctionSamples &BaseFunc, - const sampleprof::FunctionSamples &TestFunc, - SampleOverlapStats &FuncOverlap); - - /// Function-level similarity (FS) is a weighted value over function internal - /// similarity (FIS). This function computes a function's FS from its FIS by - /// applying the weight. - double weightForFuncSimilarity(double FuncSimilarity, uint64_t BaseFuncSample, - uint64_t TestFuncSample) const; - - /// The function-level similarity FS(A) for a function A is computed as - /// follows: - /// Compute a function-internal similarity FIS(A) by - /// computeSampleFunctionInternalOverlap(). Then, with the weight of - /// function A in base profile WB(A), and the weight of function A in test - /// profile WT(A), compute FS(A) = FIS(A) * (1.0 - fabs(WB(A) - WT(A))) - /// ranging in [0.0f to 1.0f] with 0.0 meaning no overlap. - double - computeSampleFunctionOverlap(const sampleprof::FunctionSamples *BaseFunc, - const sampleprof::FunctionSamples *TestFunc, - SampleOverlapStats *FuncOverlap, - uint64_t BaseFuncSample, - uint64_t TestFuncSample); - - /// Profile-level similarity (PS) is a weighted aggregate over function-level - /// similarities (FS). This method weights the FS value by the function - /// weights in the base and test profiles for the aggregation. - double weightByImportance(double FuncSimilarity, uint64_t BaseFuncSample, - uint64_t TestFuncSample) const; -}; -} // end anonymous namespace - -bool SampleOverlapAggregator::detectZeroSampleProfile( - raw_fd_ostream &OS) const { - bool HaveZeroSample = false; - if (ProfOverlap.BaseSample == 0) { - OS << "Sum of sample counts for profile " << BaseFilename << " is 0.\n"; - HaveZeroSample = true; - } - if (ProfOverlap.TestSample == 0) { - OS << "Sum of sample counts for profile " << TestFilename << " is 0.\n"; - HaveZeroSample = true; - } - return HaveZeroSample; -} - -double SampleOverlapAggregator::computeBlockSimilarity( - uint64_t BaseSample, uint64_t TestSample, - const SampleOverlapStats &FuncOverlap) const { - double BaseFrac = 0.0; - double TestFrac = 0.0; - if (FuncOverlap.BaseSample > 0) - BaseFrac = static_cast(BaseSample) / FuncOverlap.BaseSample; - if (FuncOverlap.TestSample > 0) - TestFrac = static_cast(TestSample) / FuncOverlap.TestSample; - return 1.0 - std::fabs(BaseFrac - TestFrac); -} - -void SampleOverlapAggregator::updateHotBlockOverlap(uint64_t BaseSample, - uint64_t TestSample, - uint64_t HotBlockCount) { - bool IsBaseHot = (BaseSample >= BaseHotThreshold); - bool IsTestHot = (TestSample >= TestHotThreshold); - if (!IsBaseHot && !IsTestHot) - return; - - HotBlockOverlap.UnionCount += HotBlockCount; - if (IsBaseHot) - HotBlockOverlap.BaseCount += HotBlockCount; - if (IsTestHot) - HotBlockOverlap.TestCount += HotBlockCount; - if (IsBaseHot && IsTestHot) - HotBlockOverlap.OverlapCount += HotBlockCount; -} - -void SampleOverlapAggregator::getHotFunctions( - const StringMap &ProfStats, - StringMap &HotFunc, uint64_t HotThreshold) const { - for (const auto &F : ProfStats) { - if (isFunctionHot(F.second, HotThreshold)) - HotFunc.try_emplace(F.first(), F.second); - } -} - -void SampleOverlapAggregator::computeHotFuncOverlap() { - StringMap BaseHotFunc; - getHotFunctions(BaseStats, BaseHotFunc, BaseHotThreshold); - HotFuncOverlap.BaseCount = BaseHotFunc.size(); - - StringMap TestHotFunc; - getHotFunctions(TestStats, TestHotFunc, TestHotThreshold); - HotFuncOverlap.TestCount = TestHotFunc.size(); - HotFuncOverlap.UnionCount = HotFuncOverlap.TestCount; - - for (const auto &F : BaseHotFunc) { - if (TestHotFunc.count(F.first())) - ++HotFuncOverlap.OverlapCount; - else - ++HotFuncOverlap.UnionCount; - } -} - -void SampleOverlapAggregator::updateOverlapStatsForFunction( - uint64_t BaseSample, uint64_t TestSample, uint64_t HotBlockCount, - SampleOverlapStats &FuncOverlap, double &Difference, MatchStatus Status) { - assert(Status != MS_None && - "Match status should be updated before updating overlap statistics"); - if (Status == MS_FirstUnique) { - TestSample = 0; - FuncOverlap.BaseUniqueSample += BaseSample; - } else if (Status == MS_SecondUnique) { - BaseSample = 0; - FuncOverlap.TestUniqueSample += TestSample; - } else { - ++FuncOverlap.OverlapCount; - } - - FuncOverlap.UnionSample += std::max(BaseSample, TestSample); - FuncOverlap.OverlapSample += std::min(BaseSample, TestSample); - Difference += - 1.0 - computeBlockSimilarity(BaseSample, TestSample, FuncOverlap); - updateHotBlockOverlap(BaseSample, TestSample, HotBlockCount); -} - -void SampleOverlapAggregator::updateForUnmatchedCallee( - const sampleprof::FunctionSamples &Func, SampleOverlapStats &FuncOverlap, - double &Difference, MatchStatus Status) { - assert((Status == MS_FirstUnique || Status == MS_SecondUnique) && - "Status must be either of the two unmatched cases"); - FuncSampleStats FuncStats; - if (Status == MS_FirstUnique) { - getFuncSampleStats(Func, FuncStats, BaseHotThreshold); - updateOverlapStatsForFunction(FuncStats.SampleSum, 0, - FuncStats.HotBlockCount, FuncOverlap, - Difference, Status); - } else { - getFuncSampleStats(Func, FuncStats, TestHotThreshold); - updateOverlapStatsForFunction(0, FuncStats.SampleSum, - FuncStats.HotBlockCount, FuncOverlap, - Difference, Status); - } -} - -double SampleOverlapAggregator::computeSampleFunctionInternalOverlap( - const sampleprof::FunctionSamples &BaseFunc, - const sampleprof::FunctionSamples &TestFunc, - SampleOverlapStats &FuncOverlap) { - - using namespace sampleprof; - - double Difference = 0; - - // Accumulate Difference for regular line/block samples in the function. - // We match them through sort-merge join algorithm because - // FunctionSamples::getBodySamples() returns a map of sample counters ordered - // by their offsets. - MatchStep BlockIterStep( - BaseFunc.getBodySamples().cbegin(), BaseFunc.getBodySamples().cend(), - TestFunc.getBodySamples().cbegin(), TestFunc.getBodySamples().cend()); - BlockIterStep.updateOneStep(); - while (!BlockIterStep.areBothFinished()) { - uint64_t BaseSample = - BlockIterStep.isFirstFinished() - ? 0 - : BlockIterStep.getFirstIter()->second.getSamples(); - uint64_t TestSample = - BlockIterStep.isSecondFinished() - ? 0 - : BlockIterStep.getSecondIter()->second.getSamples(); - updateOverlapStatsForFunction(BaseSample, TestSample, 1, FuncOverlap, - Difference, BlockIterStep.getMatchStatus()); - - BlockIterStep.updateOneStep(); - } - - // Accumulate Difference for callsite lines in the function. We match - // them through sort-merge algorithm because - // FunctionSamples::getCallsiteSamples() returns a map of callsite records - // ordered by their offsets. - MatchStep CallsiteIterStep( - BaseFunc.getCallsiteSamples().cbegin(), - BaseFunc.getCallsiteSamples().cend(), - TestFunc.getCallsiteSamples().cbegin(), - TestFunc.getCallsiteSamples().cend()); - CallsiteIterStep.updateOneStep(); - while (!CallsiteIterStep.areBothFinished()) { - MatchStatus CallsiteStepStatus = CallsiteIterStep.getMatchStatus(); - assert(CallsiteStepStatus != MS_None && - "Match status should be updated before entering loop body"); - - if (CallsiteStepStatus != MS_Match) { - auto Callsite = (CallsiteStepStatus == MS_FirstUnique) - ? CallsiteIterStep.getFirstIter() - : CallsiteIterStep.getSecondIter(); - for (const auto &F : Callsite->second) - updateForUnmatchedCallee(F.second, FuncOverlap, Difference, - CallsiteStepStatus); - } else { - // There may be multiple inlinees at the same offset, so we need to try - // matching all of them. This match is implemented through sort-merge - // algorithm because callsite records at the same offset are ordered by - // function names. - MatchStep CalleeIterStep( - CallsiteIterStep.getFirstIter()->second.cbegin(), - CallsiteIterStep.getFirstIter()->second.cend(), - CallsiteIterStep.getSecondIter()->second.cbegin(), - CallsiteIterStep.getSecondIter()->second.cend()); - CalleeIterStep.updateOneStep(); - while (!CalleeIterStep.areBothFinished()) { - MatchStatus CalleeStepStatus = CalleeIterStep.getMatchStatus(); - if (CalleeStepStatus != MS_Match) { - auto Callee = (CalleeStepStatus == MS_FirstUnique) - ? CalleeIterStep.getFirstIter() - : CalleeIterStep.getSecondIter(); - updateForUnmatchedCallee(Callee->second, FuncOverlap, Difference, - CalleeStepStatus); - } else { - // An inlined function can contain other inlinees inside, so compute - // the Difference recursively. - Difference += 2.0 - 2 * computeSampleFunctionInternalOverlap( - CalleeIterStep.getFirstIter()->second, - CalleeIterStep.getSecondIter()->second, - FuncOverlap); - } - CalleeIterStep.updateOneStep(); - } - } - CallsiteIterStep.updateOneStep(); - } - - // Difference reflects the total differences of line/block samples in this - // function and ranges in [0.0f to 2.0f]. Take (2.0 - Difference) / 2 to - // reflect the similarity between function profiles in [0.0f to 1.0f]. - return (2.0 - Difference) / 2; -} - -double SampleOverlapAggregator::weightForFuncSimilarity( - double FuncInternalSimilarity, uint64_t BaseFuncSample, - uint64_t TestFuncSample) const { - // Compute the weight as the distance between the function weights in two - // profiles. - double BaseFrac = 0.0; - double TestFrac = 0.0; - assert(ProfOverlap.BaseSample > 0 && - "Total samples in base profile should be greater than 0"); - BaseFrac = static_cast(BaseFuncSample) / ProfOverlap.BaseSample; - assert(ProfOverlap.TestSample > 0 && - "Total samples in test profile should be greater than 0"); - TestFrac = static_cast(TestFuncSample) / ProfOverlap.TestSample; - double WeightDistance = std::fabs(BaseFrac - TestFrac); - - // Take WeightDistance into the similarity. - return FuncInternalSimilarity * (1 - WeightDistance); -} - -double -SampleOverlapAggregator::weightByImportance(double FuncSimilarity, - uint64_t BaseFuncSample, - uint64_t TestFuncSample) const { - - double BaseFrac = 0.0; - double TestFrac = 0.0; - assert(ProfOverlap.BaseSample > 0 && - "Total samples in base profile should be greater than 0"); - BaseFrac = static_cast(BaseFuncSample) / ProfOverlap.BaseSample / 2.0; - assert(ProfOverlap.TestSample > 0 && - "Total samples in test profile should be greater than 0"); - TestFrac = static_cast(TestFuncSample) / ProfOverlap.TestSample / 2.0; - return FuncSimilarity * (BaseFrac + TestFrac); -} - -double SampleOverlapAggregator::computeSampleFunctionOverlap( - const sampleprof::FunctionSamples *BaseFunc, - const sampleprof::FunctionSamples *TestFunc, - SampleOverlapStats *FuncOverlap, uint64_t BaseFuncSample, - uint64_t TestFuncSample) { - // Default function internal similarity before weighted, meaning two functions - // has no overlap. - const double DefaultFuncInternalSimilarity = 0; - double FuncSimilarity; - double FuncInternalSimilarity; - - // If BaseFunc or TestFunc is nullptr, it means the functions do not overlap. - // In this case, we use DefaultFuncInternalSimilarity as the function internal - // similarity. - if (!BaseFunc || !TestFunc) { - FuncInternalSimilarity = DefaultFuncInternalSimilarity; - } else { - assert(FuncOverlap != nullptr && - "FuncOverlap should be provided in this case"); - FuncInternalSimilarity = computeSampleFunctionInternalOverlap( - *BaseFunc, *TestFunc, *FuncOverlap); - // Now, FuncInternalSimilarity may be a little less than 0 due to - // imprecision of floating point accumulations. Make it zero if the - // difference is below Epsilon. - FuncInternalSimilarity = (std::fabs(FuncInternalSimilarity - 0) < Epsilon) - ? 0 - : FuncInternalSimilarity; - } - FuncSimilarity = weightForFuncSimilarity(FuncInternalSimilarity, - BaseFuncSample, TestFuncSample); - return FuncSimilarity; -} - -void SampleOverlapAggregator::computeSampleProfileOverlap(raw_fd_ostream &OS) { - using namespace sampleprof; - - StringMap BaseFuncProf; - const auto &BaseProfiles = BaseReader->getProfiles(); - for (const auto &BaseFunc : BaseProfiles) { - BaseFuncProf.try_emplace(BaseFunc.second.getNameWithContext(), - &(BaseFunc.second)); - } - ProfOverlap.UnionCount = BaseFuncProf.size(); - - const auto &TestProfiles = TestReader->getProfiles(); - for (const auto &TestFunc : TestProfiles) { - SampleOverlapStats FuncOverlap; - FuncOverlap.TestName = TestFunc.second.getNameWithContext(); - assert(TestStats.count(FuncOverlap.TestName) && - "TestStats should have records for all functions in test profile " - "except inlinees"); - FuncOverlap.TestSample = TestStats[FuncOverlap.TestName].SampleSum; - - const auto Match = BaseFuncProf.find(FuncOverlap.TestName); - if (Match == BaseFuncProf.end()) { - const FuncSampleStats &FuncStats = TestStats[FuncOverlap.TestName]; - ++ProfOverlap.TestUniqueCount; - ProfOverlap.TestUniqueSample += FuncStats.SampleSum; - FuncOverlap.TestUniqueSample = FuncStats.SampleSum; - - updateHotBlockOverlap(0, FuncStats.SampleSum, FuncStats.HotBlockCount); - - double FuncSimilarity = computeSampleFunctionOverlap( - nullptr, nullptr, nullptr, 0, FuncStats.SampleSum); - ProfOverlap.Similarity += - weightByImportance(FuncSimilarity, 0, FuncStats.SampleSum); - - ++ProfOverlap.UnionCount; - ProfOverlap.UnionSample += FuncStats.SampleSum; - } else { - ++ProfOverlap.OverlapCount; - - // Two functions match with each other. Compute function-level overlap and - // aggregate them into profile-level overlap. - FuncOverlap.BaseName = Match->second->getNameWithContext(); - assert(BaseStats.count(FuncOverlap.BaseName) && - "BaseStats should have records for all functions in base profile " - "except inlinees"); - FuncOverlap.BaseSample = BaseStats[FuncOverlap.BaseName].SampleSum; - - FuncOverlap.Similarity = computeSampleFunctionOverlap( - Match->second, &TestFunc.second, &FuncOverlap, FuncOverlap.BaseSample, - FuncOverlap.TestSample); - ProfOverlap.Similarity += - weightByImportance(FuncOverlap.Similarity, FuncOverlap.BaseSample, - FuncOverlap.TestSample); - ProfOverlap.OverlapSample += FuncOverlap.OverlapSample; - ProfOverlap.UnionSample += FuncOverlap.UnionSample; - - // Accumulate the percentage of base unique and test unique samples into - // ProfOverlap. - ProfOverlap.BaseUniqueSample += FuncOverlap.BaseUniqueSample; - ProfOverlap.TestUniqueSample += FuncOverlap.TestUniqueSample; - - // Remove matched base functions for later reporting functions not found - // in test profile. - BaseFuncProf.erase(Match); - } - - // Print function-level similarity information if specified by options. - assert(TestStats.count(FuncOverlap.TestName) && - "TestStats should have records for all functions in test profile " - "except inlinees"); - if (TestStats[FuncOverlap.TestName].MaxSample >= FuncFilter.ValueCutoff || - (Match != BaseFuncProf.end() && - FuncOverlap.Similarity < LowSimilarityThreshold) || - (Match != BaseFuncProf.end() && !FuncFilter.NameFilter.empty() && - FuncOverlap.BaseName.find(FuncFilter.NameFilter) != - FuncOverlap.BaseName.npos)) { - assert(ProfOverlap.BaseSample > 0 && - "Total samples in base profile should be greater than 0"); - FuncOverlap.BaseWeight = - static_cast(FuncOverlap.BaseSample) / ProfOverlap.BaseSample; - assert(ProfOverlap.TestSample > 0 && - "Total samples in test profile should be greater than 0"); - FuncOverlap.TestWeight = - static_cast(FuncOverlap.TestSample) / ProfOverlap.TestSample; - FuncSimilarityDump.emplace(FuncOverlap.BaseWeight, FuncOverlap); - } - } - - // Traverse through functions in base profile but not in test profile. - for (const auto &F : BaseFuncProf) { - assert(BaseStats.count(F.second->getNameWithContext()) && - "BaseStats should have records for all functions in base profile " - "except inlinees"); - const FuncSampleStats &FuncStats = - BaseStats[F.second->getNameWithContext()]; - ++ProfOverlap.BaseUniqueCount; - ProfOverlap.BaseUniqueSample += FuncStats.SampleSum; - - updateHotBlockOverlap(FuncStats.SampleSum, 0, FuncStats.HotBlockCount); - - double FuncSimilarity = computeSampleFunctionOverlap( - nullptr, nullptr, nullptr, FuncStats.SampleSum, 0); - ProfOverlap.Similarity += - weightByImportance(FuncSimilarity, FuncStats.SampleSum, 0); - - ProfOverlap.UnionSample += FuncStats.SampleSum; - } - - // Now, ProfSimilarity may be a little greater than 1 due to imprecision - // of floating point accumulations. Make it 1.0 if the difference is below - // Epsilon. - ProfOverlap.Similarity = (std::fabs(ProfOverlap.Similarity - 1) < Epsilon) - ? 1 - : ProfOverlap.Similarity; - - computeHotFuncOverlap(); -} - -void SampleOverlapAggregator::initializeSampleProfileOverlap() { - const auto &BaseProf = BaseReader->getProfiles(); - for (const auto &I : BaseProf) { - ++ProfOverlap.BaseCount; - FuncSampleStats FuncStats; - getFuncSampleStats(I.second, FuncStats, BaseHotThreshold); - ProfOverlap.BaseSample += FuncStats.SampleSum; - BaseStats.try_emplace(I.second.getNameWithContext(), FuncStats); - } - - const auto &TestProf = TestReader->getProfiles(); - for (const auto &I : TestProf) { - ++ProfOverlap.TestCount; - FuncSampleStats FuncStats; - getFuncSampleStats(I.second, FuncStats, TestHotThreshold); - ProfOverlap.TestSample += FuncStats.SampleSum; - TestStats.try_emplace(I.second.getNameWithContext(), FuncStats); - } - - ProfOverlap.BaseName = StringRef(BaseFilename); - ProfOverlap.TestName = StringRef(TestFilename); -} - -void SampleOverlapAggregator::dumpFuncSimilarity(raw_fd_ostream &OS) const { - using namespace sampleprof; - - if (FuncSimilarityDump.empty()) - return; - - formatted_raw_ostream FOS(OS); - FOS << "Function-level details:\n"; - FOS << "Base weight"; - FOS.PadToColumn(TestWeightCol); - FOS << "Test weight"; - FOS.PadToColumn(SimilarityCol); - FOS << "Similarity"; - FOS.PadToColumn(OverlapCol); - FOS << "Overlap"; - FOS.PadToColumn(BaseUniqueCol); - FOS << "Base unique"; - FOS.PadToColumn(TestUniqueCol); - FOS << "Test unique"; - FOS.PadToColumn(BaseSampleCol); - FOS << "Base samples"; - FOS.PadToColumn(TestSampleCol); - FOS << "Test samples"; - FOS.PadToColumn(FuncNameCol); - FOS << "Function name\n"; - for (const auto &F : FuncSimilarityDump) { - double OverlapPercent = - F.second.UnionSample > 0 - ? static_cast(F.second.OverlapSample) / F.second.UnionSample - : 0; - double BaseUniquePercent = - F.second.BaseSample > 0 - ? static_cast(F.second.BaseUniqueSample) / - F.second.BaseSample - : 0; - double TestUniquePercent = - F.second.TestSample > 0 - ? static_cast(F.second.TestUniqueSample) / - F.second.TestSample - : 0; - - FOS << format("%.2f%%", F.second.BaseWeight * 100); - FOS.PadToColumn(TestWeightCol); - FOS << format("%.2f%%", F.second.TestWeight * 100); - FOS.PadToColumn(SimilarityCol); - FOS << format("%.2f%%", F.second.Similarity * 100); - FOS.PadToColumn(OverlapCol); - FOS << format("%.2f%%", OverlapPercent * 100); - FOS.PadToColumn(BaseUniqueCol); - FOS << format("%.2f%%", BaseUniquePercent * 100); - FOS.PadToColumn(TestUniqueCol); - FOS << format("%.2f%%", TestUniquePercent * 100); - FOS.PadToColumn(BaseSampleCol); - FOS << F.second.BaseSample; - FOS.PadToColumn(TestSampleCol); - FOS << F.second.TestSample; - FOS.PadToColumn(FuncNameCol); - FOS << F.second.TestName << "\n"; - } -} - -void SampleOverlapAggregator::dumpProgramSummary(raw_fd_ostream &OS) const { - OS << "Profile overlap infomation for base_profile: " << ProfOverlap.BaseName - << " and test_profile: " << ProfOverlap.TestName << "\nProgram level:\n"; - - OS << " Whole program profile similarity: " - << format("%.3f%%", ProfOverlap.Similarity * 100) << "\n"; - - assert(ProfOverlap.UnionSample > 0 && - "Total samples in two profile should be greater than 0"); - double OverlapPercent = - static_cast(ProfOverlap.OverlapSample) / ProfOverlap.UnionSample; - assert(ProfOverlap.BaseSample > 0 && - "Total samples in base profile should be greater than 0"); - double BaseUniquePercent = static_cast(ProfOverlap.BaseUniqueSample) / - ProfOverlap.BaseSample; - assert(ProfOverlap.TestSample > 0 && - "Total samples in test profile should be greater than 0"); - double TestUniquePercent = static_cast(ProfOverlap.TestUniqueSample) / - ProfOverlap.TestSample; - - OS << " Whole program sample overlap: " - << format("%.3f%%", OverlapPercent * 100) << "\n"; - OS << " percentage of samples unique in base profile: " - << format("%.3f%%", BaseUniquePercent * 100) << "\n"; - OS << " percentage of samples unique in test profile: " - << format("%.3f%%", TestUniquePercent * 100) << "\n"; - OS << " total samples in base profile: " << ProfOverlap.BaseSample << "\n" - << " total samples in test profile: " << ProfOverlap.TestSample << "\n"; - - assert(ProfOverlap.UnionCount > 0 && - "There should be at least one function in two input profiles"); - double FuncOverlapPercent = - static_cast(ProfOverlap.OverlapCount) / ProfOverlap.UnionCount; - OS << " Function overlap: " << format("%.3f%%", FuncOverlapPercent * 100) - << "\n"; - OS << " overlap functions: " << ProfOverlap.OverlapCount << "\n"; - OS << " functions unique in base profile: " << ProfOverlap.BaseUniqueCount - << "\n"; - OS << " functions unique in test profile: " << ProfOverlap.TestUniqueCount - << "\n"; -} - -void SampleOverlapAggregator::dumpHotFuncAndBlockOverlap( - raw_fd_ostream &OS) const { - assert(HotFuncOverlap.UnionCount > 0 && - "There should be at least one hot function in two input profiles"); - OS << " Hot-function overlap: " - << format("%.3f%%", static_cast(HotFuncOverlap.OverlapCount) / - HotFuncOverlap.UnionCount * 100) - << "\n"; - OS << " overlap hot functions: " << HotFuncOverlap.OverlapCount << "\n"; - OS << " hot functions unique in base profile: " - << HotFuncOverlap.BaseCount - HotFuncOverlap.OverlapCount << "\n"; - OS << " hot functions unique in test profile: " - << HotFuncOverlap.TestCount - HotFuncOverlap.OverlapCount << "\n"; - - assert(HotBlockOverlap.UnionCount > 0 && - "There should be at least one hot block in two input profiles"); - OS << " Hot-block overlap: " - << format("%.3f%%", static_cast(HotBlockOverlap.OverlapCount) / - HotBlockOverlap.UnionCount * 100) - << "\n"; - OS << " overlap hot blocks: " << HotBlockOverlap.OverlapCount << "\n"; - OS << " hot blocks unique in base profile: " - << HotBlockOverlap.BaseCount - HotBlockOverlap.OverlapCount << "\n"; - OS << " hot blocks unique in test profile: " - << HotBlockOverlap.TestCount - HotBlockOverlap.OverlapCount << "\n"; -} - -std::error_code SampleOverlapAggregator::loadProfiles() { - using namespace sampleprof; - - LLVMContext Context; - auto BaseReaderOrErr = SampleProfileReader::create(BaseFilename, Context, - FSDiscriminatorPassOption); - if (std::error_code EC = BaseReaderOrErr.getError()) - exitWithErrorCode(EC, BaseFilename); - - auto TestReaderOrErr = SampleProfileReader::create(TestFilename, Context, - FSDiscriminatorPassOption); - if (std::error_code EC = TestReaderOrErr.getError()) - exitWithErrorCode(EC, TestFilename); - - BaseReader = std::move(BaseReaderOrErr.get()); - TestReader = std::move(TestReaderOrErr.get()); - - if (std::error_code EC = BaseReader->read()) - exitWithErrorCode(EC, BaseFilename); - if (std::error_code EC = TestReader->read()) - exitWithErrorCode(EC, TestFilename); - if (BaseReader->profileIsProbeBased() != TestReader->profileIsProbeBased()) - exitWithError( - "cannot compare probe-based profile with non-probe-based profile"); - if (BaseReader->profileIsCS() != TestReader->profileIsCS()) - exitWithError("cannot compare CS profile with non-CS profile"); - - // Load BaseHotThreshold and TestHotThreshold as 99-percentile threshold in - // profile summary. - const uint64_t HotCutoff = 990000; - ProfileSummary &BasePS = BaseReader->getSummary(); - for (const auto &SummaryEntry : BasePS.getDetailedSummary()) { - if (SummaryEntry.Cutoff == HotCutoff) { - BaseHotThreshold = SummaryEntry.MinCount; - break; - } - } - - ProfileSummary &TestPS = TestReader->getSummary(); - for (const auto &SummaryEntry : TestPS.getDetailedSummary()) { - if (SummaryEntry.Cutoff == HotCutoff) { - TestHotThreshold = SummaryEntry.MinCount; - break; - } - } - return std::error_code(); -} - -void overlapSampleProfile(const std::string &BaseFilename, - const std::string &TestFilename, - const OverlapFuncFilters &FuncFilter, - uint64_t SimilarityCutoff, raw_fd_ostream &OS) { - using namespace sampleprof; - - // We use 0.000005 to initialize OverlapAggr.Epsilon because the final metrics - // report 2--3 places after decimal point in percentage numbers. - SampleOverlapAggregator OverlapAggr( - BaseFilename, TestFilename, - static_cast(SimilarityCutoff) / 1000000, 0.000005, FuncFilter); - if (std::error_code EC = OverlapAggr.loadProfiles()) - exitWithErrorCode(EC); - - OverlapAggr.initializeSampleProfileOverlap(); - if (OverlapAggr.detectZeroSampleProfile(OS)) - return; - - OverlapAggr.computeSampleProfileOverlap(OS); - - OverlapAggr.dumpProgramSummary(OS); - OverlapAggr.dumpHotFuncAndBlockOverlap(OS); - OverlapAggr.dumpFuncSimilarity(OS); -} - -static int overlap_main(int argc, const char *argv[]) { - cl::opt BaseFilename(cl::Positional, cl::Required, - cl::desc("")); - cl::opt TestFilename(cl::Positional, cl::Required, - cl::desc("")); - cl::opt Output("output", cl::value_desc("output"), cl::init("-"), - cl::desc("Output file")); - cl::alias OutputA("o", cl::desc("Alias for --output"), cl::aliasopt(Output)); - cl::opt IsCS( - "cs", cl::init(false), - cl::desc("For context sensitive PGO counts. Does not work with CSSPGO.")); - cl::opt ValueCutoff( - "value-cutoff", cl::init(-1), - cl::desc( - "Function level overlap information for every function (with calling " - "context for csspgo) in test " - "profile with max count value greater then the parameter value")); - cl::opt FuncNameFilter( - "function", - cl::desc("Function level overlap information for matching functions. For " - "CSSPGO this takes a a function name with calling context")); - cl::opt SimilarityCutoff( - "similarity-cutoff", cl::init(0), - cl::desc("For sample profiles, list function names (with calling context " - "for csspgo) for overlapped functions " - "with similarities below the cutoff (percentage times 10000).")); - cl::opt ProfileKind( - cl::desc("Profile kind:"), cl::init(instr), - cl::values(clEnumVal(instr, "Instrumentation profile (default)"), - clEnumVal(sample, "Sample profile"))); - cl::ParseCommandLineOptions(argc, argv, "LLVM profile data overlap tool\n"); - - std::error_code EC; - raw_fd_ostream OS(Output.data(), EC, sys::fs::OF_TextWithCRLF); - if (EC) - exitWithErrorCode(EC, Output); - - if (ProfileKind == instr) - overlapInstrProfile(BaseFilename, TestFilename, - OverlapFuncFilters{ValueCutoff, FuncNameFilter}, OS, - IsCS); - else - overlapSampleProfile(BaseFilename, TestFilename, - OverlapFuncFilters{ValueCutoff, FuncNameFilter}, - SimilarityCutoff, OS); - - return 0; -} - -namespace { -struct ValueSitesStats { - ValueSitesStats() - : TotalNumValueSites(0), TotalNumValueSitesWithValueProfile(0), - TotalNumValues(0) {} - uint64_t TotalNumValueSites; - uint64_t TotalNumValueSitesWithValueProfile; - uint64_t TotalNumValues; - std::vector ValueSitesHistogram; -}; -} // namespace - -static void traverseAllValueSites(const InstrProfRecord &Func, uint32_t VK, - ValueSitesStats &Stats, raw_fd_ostream &OS, - InstrProfSymtab *Symtab) { - uint32_t NS = Func.getNumValueSites(VK); - Stats.TotalNumValueSites += NS; - for (size_t I = 0; I < NS; ++I) { - uint32_t NV = Func.getNumValueDataForSite(VK, I); - std::unique_ptr VD = Func.getValueForSite(VK, I); - Stats.TotalNumValues += NV; - if (NV) { - Stats.TotalNumValueSitesWithValueProfile++; - if (NV > Stats.ValueSitesHistogram.size()) - Stats.ValueSitesHistogram.resize(NV, 0); - Stats.ValueSitesHistogram[NV - 1]++; - } - - uint64_t SiteSum = 0; - for (uint32_t V = 0; V < NV; V++) - SiteSum += VD[V].Count; - if (SiteSum == 0) - SiteSum = 1; - - for (uint32_t V = 0; V < NV; V++) { - OS << "\t[ " << format("%2u", I) << ", "; - if (Symtab == nullptr) - OS << format("%4" PRIu64, VD[V].Value); - else - OS << Symtab->getFuncName(VD[V].Value); - OS << ", " << format("%10" PRId64, VD[V].Count) << " ] (" - << format("%.2f%%", (VD[V].Count * 100.0 / SiteSum)) << ")\n"; - } - } -} - -static void showValueSitesStats(raw_fd_ostream &OS, uint32_t VK, - ValueSitesStats &Stats) { - OS << " Total number of sites: " << Stats.TotalNumValueSites << "\n"; - OS << " Total number of sites with values: " - << Stats.TotalNumValueSitesWithValueProfile << "\n"; - OS << " Total number of profiled values: " << Stats.TotalNumValues << "\n"; - - OS << " Value sites histogram:\n\tNumTargets, SiteCount\n"; - for (unsigned I = 0; I < Stats.ValueSitesHistogram.size(); I++) { - if (Stats.ValueSitesHistogram[I] > 0) - OS << "\t" << I + 1 << ", " << Stats.ValueSitesHistogram[I] << "\n"; - } -} - -static int showInstrProfile(const std::string &Filename, bool ShowCounts, - uint32_t TopN, bool ShowIndirectCallTargets, - bool ShowMemOPSizes, bool ShowDetailedSummary, - std::vector DetailedSummaryCutoffs, - bool ShowAllFunctions, bool ShowCS, - uint64_t ValueCutoff, bool OnlyListBelow, - const std::string &ShowFunction, bool TextFormat, - bool ShowBinaryIds, raw_fd_ostream &OS) { - auto ReaderOrErr = InstrProfReader::create(Filename); - std::vector Cutoffs = std::move(DetailedSummaryCutoffs); - if (ShowDetailedSummary && Cutoffs.empty()) { - Cutoffs = {800000, 900000, 950000, 990000, 999000, 999900, 999990}; - } - InstrProfSummaryBuilder Builder(std::move(Cutoffs)); - if (Error E = ReaderOrErr.takeError()) - exitWithError(std::move(E), Filename); - - auto Reader = std::move(ReaderOrErr.get()); - bool IsIRInstr = Reader->isIRLevelProfile(); - size_t ShownFunctions = 0; - size_t BelowCutoffFunctions = 0; - int NumVPKind = IPVK_Last - IPVK_First + 1; - std::vector VPStats(NumVPKind); - - auto MinCmp = [](const std::pair &v1, - const std::pair &v2) { - return v1.second > v2.second; - }; - - std::priority_queue, - std::vector>, - decltype(MinCmp)> - HottestFuncs(MinCmp); - - if (!TextFormat && OnlyListBelow) { - OS << "The list of functions with the maximum counter less than " - << ValueCutoff << ":\n"; - } - - // Add marker so that IR-level instrumentation round-trips properly. - if (TextFormat && IsIRInstr) - OS << ":ir\n"; - - for (const auto &Func : *Reader) { - if (Reader->isIRLevelProfile()) { - bool FuncIsCS = NamedInstrProfRecord::hasCSFlagInHash(Func.Hash); - if (FuncIsCS != ShowCS) - continue; - } - bool Show = - ShowAllFunctions || (!ShowFunction.empty() && - Func.Name.find(ShowFunction) != Func.Name.npos); - - bool doTextFormatDump = (Show && TextFormat); - - if (doTextFormatDump) { - InstrProfSymtab &Symtab = Reader->getSymtab(); - InstrProfWriter::writeRecordInText(Func.Name, Func.Hash, Func, Symtab, - OS); - continue; - } - - assert(Func.Counts.size() > 0 && "function missing entry counter"); - Builder.addRecord(Func); - - uint64_t FuncMax = 0; - uint64_t FuncSum = 0; - for (size_t I = 0, E = Func.Counts.size(); I < E; ++I) { - if (Func.Counts[I] == (uint64_t)-1) - continue; - FuncMax = std::max(FuncMax, Func.Counts[I]); - FuncSum += Func.Counts[I]; - } - - if (FuncMax < ValueCutoff) { - ++BelowCutoffFunctions; - if (OnlyListBelow) { - OS << " " << Func.Name << ": (Max = " << FuncMax - << " Sum = " << FuncSum << ")\n"; - } - continue; - } else if (OnlyListBelow) - continue; - - if (TopN) { - if (HottestFuncs.size() == TopN) { - if (HottestFuncs.top().second < FuncMax) { - HottestFuncs.pop(); - HottestFuncs.emplace(std::make_pair(std::string(Func.Name), FuncMax)); - } - } else - HottestFuncs.emplace(std::make_pair(std::string(Func.Name), FuncMax)); - } - - if (Show) { - if (!ShownFunctions) - OS << "Counters:\n"; - - ++ShownFunctions; - - OS << " " << Func.Name << ":\n" - << " Hash: " << format("0x%016" PRIx64, Func.Hash) << "\n" - << " Counters: " << Func.Counts.size() << "\n"; - if (!IsIRInstr) - OS << " Function count: " << Func.Counts[0] << "\n"; - - if (ShowIndirectCallTargets) - OS << " Indirect Call Site Count: " - << Func.getNumValueSites(IPVK_IndirectCallTarget) << "\n"; - - uint32_t NumMemOPCalls = Func.getNumValueSites(IPVK_MemOPSize); - if (ShowMemOPSizes && NumMemOPCalls > 0) - OS << " Number of Memory Intrinsics Calls: " << NumMemOPCalls - << "\n"; - - if (ShowCounts) { - OS << " Block counts: ["; - size_t Start = (IsIRInstr ? 0 : 1); - for (size_t I = Start, E = Func.Counts.size(); I < E; ++I) { - OS << (I == Start ? "" : ", ") << Func.Counts[I]; - } - OS << "]\n"; - } - - if (ShowIndirectCallTargets) { - OS << " Indirect Target Results:\n"; - traverseAllValueSites(Func, IPVK_IndirectCallTarget, - VPStats[IPVK_IndirectCallTarget], OS, - &(Reader->getSymtab())); - } - - if (ShowMemOPSizes && NumMemOPCalls > 0) { - OS << " Memory Intrinsic Size Results:\n"; - traverseAllValueSites(Func, IPVK_MemOPSize, VPStats[IPVK_MemOPSize], OS, - nullptr); - } - } - } - if (Reader->hasError()) - exitWithError(Reader->getError(), Filename); - - if (TextFormat) - return 0; - std::unique_ptr PS(Builder.getSummary()); - bool IsIR = Reader->isIRLevelProfile(); - OS << "Instrumentation level: " << (IsIR ? "IR" : "Front-end"); - if (IsIR) - OS << " entry_first = " << Reader->instrEntryBBEnabled(); - OS << "\n"; - if (ShowAllFunctions || !ShowFunction.empty()) - OS << "Functions shown: " << ShownFunctions << "\n"; - OS << "Total functions: " << PS->getNumFunctions() << "\n"; - if (ValueCutoff > 0) { - OS << "Number of functions with maximum count (< " << ValueCutoff - << "): " << BelowCutoffFunctions << "\n"; - OS << "Number of functions with maximum count (>= " << ValueCutoff - << "): " << PS->getNumFunctions() - BelowCutoffFunctions << "\n"; - } - OS << "Maximum function count: " << PS->getMaxFunctionCount() << "\n"; - OS << "Maximum internal block count: " << PS->getMaxInternalCount() << "\n"; - - if (TopN) { - std::vector> SortedHottestFuncs; - while (!HottestFuncs.empty()) { - SortedHottestFuncs.emplace_back(HottestFuncs.top()); - HottestFuncs.pop(); - } - OS << "Top " << TopN - << " functions with the largest internal block counts: \n"; - for (auto &hotfunc : llvm::reverse(SortedHottestFuncs)) - OS << " " << hotfunc.first << ", max count = " << hotfunc.second << "\n"; - } - - if (ShownFunctions && ShowIndirectCallTargets) { - OS << "Statistics for indirect call sites profile:\n"; - showValueSitesStats(OS, IPVK_IndirectCallTarget, - VPStats[IPVK_IndirectCallTarget]); - } - - if (ShownFunctions && ShowMemOPSizes) { - OS << "Statistics for memory intrinsic calls sizes profile:\n"; - showValueSitesStats(OS, IPVK_MemOPSize, VPStats[IPVK_MemOPSize]); - } - - if (ShowDetailedSummary) { - OS << "Total number of blocks: " << PS->getNumCounts() << "\n"; - OS << "Total count: " << PS->getTotalCount() << "\n"; - PS->printDetailedSummary(OS); - } - - if (ShowBinaryIds) - if (Error E = Reader->printBinaryIds(OS)) - exitWithError(std::move(E), Filename); - - return 0; -} - -static void showSectionInfo(sampleprof::SampleProfileReader *Reader, - raw_fd_ostream &OS) { - if (!Reader->dumpSectionInfo(OS)) { - WithColor::warning() << "-show-sec-info-only is only supported for " - << "sample profile in extbinary format and is " - << "ignored for other formats.\n"; - return; - } -} - -namespace { -struct HotFuncInfo { - StringRef FuncName; - uint64_t TotalCount; - double TotalCountPercent; - uint64_t MaxCount; - uint64_t EntryCount; - - HotFuncInfo() - : FuncName(), TotalCount(0), TotalCountPercent(0.0f), MaxCount(0), - EntryCount(0) {} - - HotFuncInfo(StringRef FN, uint64_t TS, double TSP, uint64_t MS, uint64_t ES) - : FuncName(FN), TotalCount(TS), TotalCountPercent(TSP), MaxCount(MS), - EntryCount(ES) {} -}; -} // namespace - -// Print out detailed information about hot functions in PrintValues vector. -// Users specify titles and offset of every columns through ColumnTitle and -// ColumnOffset. The size of ColumnTitle and ColumnOffset need to be the same -// and at least 4. Besides, users can optionally give a HotFuncMetric string to -// print out or let it be an empty string. -static void dumpHotFunctionList(const std::vector &ColumnTitle, - const std::vector &ColumnOffset, - const std::vector &PrintValues, - uint64_t HotFuncCount, uint64_t TotalFuncCount, - uint64_t HotProfCount, uint64_t TotalProfCount, - const std::string &HotFuncMetric, - raw_fd_ostream &OS) { - assert(ColumnOffset.size() == ColumnTitle.size() && - "ColumnOffset and ColumnTitle should have the same size"); - assert(ColumnTitle.size() >= 4 && - "ColumnTitle should have at least 4 elements"); - assert(TotalFuncCount > 0 && - "There should be at least one function in the profile"); - double TotalProfPercent = 0; - if (TotalProfCount > 0) - TotalProfPercent = static_cast(HotProfCount) / TotalProfCount * 100; - - formatted_raw_ostream FOS(OS); - FOS << HotFuncCount << " out of " << TotalFuncCount - << " functions with profile (" - << format("%.2f%%", - (static_cast(HotFuncCount) / TotalFuncCount * 100)) - << ") are considered hot functions"; - if (!HotFuncMetric.empty()) - FOS << " (" << HotFuncMetric << ")"; - FOS << ".\n"; - FOS << HotProfCount << " out of " << TotalProfCount << " profile counts (" - << format("%.2f%%", TotalProfPercent) << ") are from hot functions.\n"; - - for (size_t I = 0; I < ColumnTitle.size(); ++I) { - FOS.PadToColumn(ColumnOffset[I]); - FOS << ColumnTitle[I]; - } - FOS << "\n"; - - for (const HotFuncInfo &R : PrintValues) { - FOS.PadToColumn(ColumnOffset[0]); - FOS << R.TotalCount << " (" << format("%.2f%%", R.TotalCountPercent) << ")"; - FOS.PadToColumn(ColumnOffset[1]); - FOS << R.MaxCount; - FOS.PadToColumn(ColumnOffset[2]); - FOS << R.EntryCount; - FOS.PadToColumn(ColumnOffset[3]); - FOS << R.FuncName << "\n"; - } -} - -static int -showHotFunctionList(const StringMap &Profiles, - ProfileSummary &PS, raw_fd_ostream &OS) { - using namespace sampleprof; - - const uint32_t HotFuncCutoff = 990000; - auto &SummaryVector = PS.getDetailedSummary(); - uint64_t MinCountThreshold = 0; - for (const ProfileSummaryEntry &SummaryEntry : SummaryVector) { - if (SummaryEntry.Cutoff == HotFuncCutoff) { - MinCountThreshold = SummaryEntry.MinCount; - break; - } - } - - // Traverse all functions in the profile and keep only hot functions. - // The following loop also calculates the sum of total samples of all - // functions. - std::multimap, - std::greater> - HotFunc; - uint64_t ProfileTotalSample = 0; - uint64_t HotFuncSample = 0; - uint64_t HotFuncCount = 0; - - for (const auto &I : Profiles) { - FuncSampleStats FuncStats; - const FunctionSamples &FuncProf = I.second; - ProfileTotalSample += FuncProf.getTotalSamples(); - getFuncSampleStats(FuncProf, FuncStats, MinCountThreshold); - - if (isFunctionHot(FuncStats, MinCountThreshold)) { - HotFunc.emplace(FuncProf.getTotalSamples(), - std::make_pair(&(I.second), FuncStats.MaxSample)); - HotFuncSample += FuncProf.getTotalSamples(); - ++HotFuncCount; - } - } - - std::vector ColumnTitle{"Total sample (%)", "Max sample", - "Entry sample", "Function name"}; - std::vector ColumnOffset{0, 24, 42, 58}; - std::string Metric = - std::string("max sample >= ") + std::to_string(MinCountThreshold); - std::vector PrintValues; - for (const auto &FuncPair : HotFunc) { - const FunctionSamples &Func = *FuncPair.second.first; - double TotalSamplePercent = - (ProfileTotalSample > 0) - ? (Func.getTotalSamples() * 100.0) / ProfileTotalSample - : 0; - PrintValues.emplace_back(HotFuncInfo( - Func.getNameWithContext(), Func.getTotalSamples(), TotalSamplePercent, - FuncPair.second.second, Func.getEntrySamples())); - } - dumpHotFunctionList(ColumnTitle, ColumnOffset, PrintValues, HotFuncCount, - Profiles.size(), HotFuncSample, ProfileTotalSample, - Metric, OS); - - return 0; -} - -static int showSampleProfile(const std::string &Filename, bool ShowCounts, - bool ShowAllFunctions, bool ShowDetailedSummary, - const std::string &ShowFunction, - bool ShowProfileSymbolList, - bool ShowSectionInfoOnly, bool ShowHotFuncList, - raw_fd_ostream &OS) { - using namespace sampleprof; - LLVMContext Context; - auto ReaderOrErr = - SampleProfileReader::create(Filename, Context, FSDiscriminatorPassOption); - if (std::error_code EC = ReaderOrErr.getError()) - exitWithErrorCode(EC, Filename); - - auto Reader = std::move(ReaderOrErr.get()); - if (ShowSectionInfoOnly) { - showSectionInfo(Reader.get(), OS); - return 0; - } - - if (std::error_code EC = Reader->read()) - exitWithErrorCode(EC, Filename); - - if (ShowAllFunctions || ShowFunction.empty()) - Reader->dump(OS); - else - Reader->dumpFunctionProfile(ShowFunction, OS); - - if (ShowProfileSymbolList) { - std::unique_ptr ReaderList = - Reader->getProfileSymbolList(); - ReaderList->dump(OS); - } - - if (ShowDetailedSummary) { - auto &PS = Reader->getSummary(); - PS.printSummary(OS); - PS.printDetailedSummary(OS); - } - - if (ShowHotFuncList) - showHotFunctionList(Reader->getProfiles(), Reader->getSummary(), OS); - - return 0; -} - -static int show_main(int argc, const char *argv[]) { - cl::opt Filename(cl::Positional, cl::Required, - cl::desc("")); - - cl::opt ShowCounts("counts", cl::init(false), - cl::desc("Show counter values for shown functions")); - cl::opt TextFormat( - "text", cl::init(false), - cl::desc("Show instr profile data in text dump format")); - cl::opt ShowIndirectCallTargets( - "ic-targets", cl::init(false), - cl::desc("Show indirect call site target values for shown functions")); - cl::opt ShowMemOPSizes( - "memop-sizes", cl::init(false), - cl::desc("Show the profiled sizes of the memory intrinsic calls " - "for shown functions")); - cl::opt ShowDetailedSummary("detailed-summary", cl::init(false), - cl::desc("Show detailed profile summary")); - cl::list DetailedSummaryCutoffs( - cl::CommaSeparated, "detailed-summary-cutoffs", - cl::desc( - "Cutoff percentages (times 10000) for generating detailed summary"), - cl::value_desc("800000,901000,999999")); - cl::opt ShowHotFuncList( - "hot-func-list", cl::init(false), - cl::desc("Show profile summary of a list of hot functions")); - cl::opt ShowAllFunctions("all-functions", cl::init(false), - cl::desc("Details for every function")); - cl::opt ShowCS("showcs", cl::init(false), - cl::desc("Show context sensitive counts")); - cl::opt ShowFunction("function", - cl::desc("Details for matching functions")); - - cl::opt OutputFilename("output", cl::value_desc("output"), - cl::init("-"), cl::desc("Output file")); - cl::alias OutputFilenameA("o", cl::desc("Alias for --output"), - cl::aliasopt(OutputFilename)); - cl::opt ProfileKind( - cl::desc("Profile kind:"), cl::init(instr), - cl::values(clEnumVal(instr, "Instrumentation profile (default)"), - clEnumVal(sample, "Sample profile"))); - cl::opt TopNFunctions( - "topn", cl::init(0), - cl::desc("Show the list of functions with the largest internal counts")); - cl::opt ValueCutoff( - "value-cutoff", cl::init(0), - cl::desc("Set the count value cutoff. Functions with the maximum count " - "less than this value will not be printed out. (Default is 0)")); - cl::opt OnlyListBelow( - "list-below-cutoff", cl::init(false), - cl::desc("Only output names of functions whose max count values are " - "below the cutoff value")); - cl::opt ShowProfileSymbolList( - "show-prof-sym-list", cl::init(false), - cl::desc("Show profile symbol list if it exists in the profile. ")); - cl::opt ShowSectionInfoOnly( - "show-sec-info-only", cl::init(false), - cl::desc("Show the information of each section in the sample profile. " - "The flag is only usable when the sample profile is in " - "extbinary format")); - cl::opt ShowBinaryIds("binary-ids", cl::init(false), - cl::desc("Show binary ids in the profile. ")); - - cl::ParseCommandLineOptions(argc, argv, "LLVM profile data summary\n"); - - if (Filename == OutputFilename) { - errs() << sys::path::filename(argv[0]) - << ": Input file name cannot be the same as the output file name!\n"; - return 1; - } - - std::error_code EC; - raw_fd_ostream OS(OutputFilename.data(), EC, sys::fs::OF_TextWithCRLF); - if (EC) - exitWithErrorCode(EC, OutputFilename); - - if (ShowAllFunctions && !ShowFunction.empty()) - WithColor::warning() << "-function argument ignored: showing all functions\n"; - - if (ProfileKind == instr) - return showInstrProfile( - Filename, ShowCounts, TopNFunctions, ShowIndirectCallTargets, - ShowMemOPSizes, ShowDetailedSummary, DetailedSummaryCutoffs, - ShowAllFunctions, ShowCS, ValueCutoff, OnlyListBelow, ShowFunction, - TextFormat, ShowBinaryIds, OS); - else - return showSampleProfile(Filename, ShowCounts, ShowAllFunctions, - ShowDetailedSummary, ShowFunction, - ShowProfileSymbolList, ShowSectionInfoOnly, - ShowHotFuncList, OS); -} - -int main(int argc, const char *argv[]) { - InitLLVM X(argc, argv); - - StringRef ProgName(sys::path::filename(argv[0])); - if (argc > 1) { - int (*func)(int, const char *[]) = nullptr; - - if (strcmp(argv[1], "merge") == 0) - func = merge_main; - else if (strcmp(argv[1], "show") == 0) - func = show_main; - else if (strcmp(argv[1], "overlap") == 0) - func = overlap_main; - - if (func) { - std::string Invocation(ProgName.str() + " " + argv[1]); - argv[1] = Invocation.c_str(); - return func(argc - 1, argv + 1); - } - - if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "-help") == 0 || - strcmp(argv[1], "--help") == 0) { - - errs() << "OVERVIEW: LLVM profile data tools\n\n" - << "USAGE: " << ProgName << " [args...]\n" - << "USAGE: " << ProgName << " -help\n\n" - << "See each individual command --help for more details.\n" - << "Available commands: merge, show, overlap\n"; - return 0; - } - } - - if (argc < 2) - errs() << ProgName << ": No command specified!\n"; - else - errs() << ProgName << ": Unknown command!\n"; - - errs() << "USAGE: " << ProgName << " [args...]\n"; - return 1; -} diff --git a/tools/ldc-profdata/llvm-profdata-14.0.cpp b/tools/ldc-profdata/llvm-profdata-14.0.cpp deleted file mode 100644 index 6000460d3c2..00000000000 --- a/tools/ldc-profdata/llvm-profdata-14.0.cpp +++ /dev/null @@ -1,2669 +0,0 @@ -//===- llvm-profdata.cpp - LLVM profile data tool -------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// llvm-profdata merges .profdata files. -// -//===----------------------------------------------------------------------===// - -#include "llvm/ADT/SmallSet.h" -#include "llvm/ADT/SmallVector.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/DebugInfo/DWARF/DWARFContext.h" -#include "llvm/IR/LLVMContext.h" -#include "llvm/Object/Binary.h" -#include "llvm/ProfileData/InstrProfCorrelator.h" -#include "llvm/ProfileData/InstrProfReader.h" -#include "llvm/ProfileData/InstrProfWriter.h" -#include "llvm/ProfileData/ProfileCommon.h" -#include "llvm/ProfileData/RawMemProfReader.h" -#include "llvm/ProfileData/SampleProfReader.h" -#include "llvm/ProfileData/SampleProfWriter.h" -#include "llvm/Support/CommandLine.h" -#include "llvm/Support/Discriminator.h" -#include "llvm/Support/Errc.h" -#include "llvm/Support/FileSystem.h" -#include "llvm/Support/Format.h" -#include "llvm/Support/FormattedStream.h" -#include "llvm/Support/InitLLVM.h" -#include "llvm/Support/MemoryBuffer.h" -#include "llvm/Support/Path.h" -#include "llvm/Support/ThreadPool.h" -#include "llvm/Support/Threading.h" -#include "llvm/Support/WithColor.h" -#include "llvm/Support/raw_ostream.h" -#include - -using namespace llvm; - -enum ProfileFormat { - PF_None = 0, - PF_Text, - PF_Compact_Binary, - PF_Ext_Binary, - PF_GCC, - PF_Binary -}; - -static void warn(Twine Message, std::string Whence = "", - std::string Hint = "") { - WithColor::warning(); - if (!Whence.empty()) - errs() << Whence << ": "; - errs() << Message << "\n"; - if (!Hint.empty()) - WithColor::note() << Hint << "\n"; -} - -static void warn(Error E, StringRef Whence = "") { - if (E.isA()) { - handleAllErrors(std::move(E), [&](const InstrProfError &IPE) { - warn(IPE.message(), std::string(Whence), std::string("")); - }); - } -} - -static void exitWithError(Twine Message, std::string Whence = "", - std::string Hint = "") { - WithColor::error(); - if (!Whence.empty()) - errs() << Whence << ": "; - errs() << Message << "\n"; - if (!Hint.empty()) - WithColor::note() << Hint << "\n"; - ::exit(1); -} - -static void exitWithError(Error E, StringRef Whence = "") { - if (E.isA()) { - handleAllErrors(std::move(E), [&](const InstrProfError &IPE) { - instrprof_error instrError = IPE.get(); - StringRef Hint = ""; - if (instrError == instrprof_error::unrecognized_format) { - // Hint in case user missed specifying the profile type. - Hint = "Perhaps you forgot to use the --sample or --memory option?"; - } - exitWithError(IPE.message(), std::string(Whence), std::string(Hint)); - }); - } - - exitWithError(toString(std::move(E)), std::string(Whence)); -} - -static void exitWithErrorCode(std::error_code EC, StringRef Whence = "") { - exitWithError(EC.message(), std::string(Whence)); -} - -namespace { -enum ProfileKinds { instr, sample, memory }; -enum FailureMode { failIfAnyAreInvalid, failIfAllAreInvalid }; -} - -static void warnOrExitGivenError(FailureMode FailMode, std::error_code EC, - StringRef Whence = "") { - if (FailMode == failIfAnyAreInvalid) - exitWithErrorCode(EC, Whence); - else - warn(EC.message(), std::string(Whence)); -} - -static void handleMergeWriterError(Error E, StringRef WhenceFile = "", - StringRef WhenceFunction = "", - bool ShowHint = true) { - if (!WhenceFile.empty()) - errs() << WhenceFile << ": "; - if (!WhenceFunction.empty()) - errs() << WhenceFunction << ": "; - - auto IPE = instrprof_error::success; - E = handleErrors(std::move(E), - [&IPE](std::unique_ptr E) -> Error { - IPE = E->get(); - return Error(std::move(E)); - }); - errs() << toString(std::move(E)) << "\n"; - - if (ShowHint) { - StringRef Hint = ""; - if (IPE != instrprof_error::success) { - switch (IPE) { - case instrprof_error::hash_mismatch: - case instrprof_error::count_mismatch: - case instrprof_error::value_site_count_mismatch: - Hint = "Make sure that all profile data to be merged is generated " - "from the same binary."; - break; - default: - break; - } - } - - if (!Hint.empty()) - errs() << Hint << "\n"; - } -} - -namespace { -/// A remapper from original symbol names to new symbol names based on a file -/// containing a list of mappings from old name to new name. -class SymbolRemapper { - std::unique_ptr File; - DenseMap RemappingTable; - -public: - /// Build a SymbolRemapper from a file containing a list of old/new symbols. - static std::unique_ptr create(StringRef InputFile) { - auto BufOrError = MemoryBuffer::getFileOrSTDIN(InputFile); - if (!BufOrError) - exitWithErrorCode(BufOrError.getError(), InputFile); - - auto Remapper = std::make_unique(); - Remapper->File = std::move(BufOrError.get()); - - for (line_iterator LineIt(*Remapper->File, /*SkipBlanks=*/true, '#'); - !LineIt.is_at_eof(); ++LineIt) { - std::pair Parts = LineIt->split(' '); - if (Parts.first.empty() || Parts.second.empty() || - Parts.second.count(' ')) { - exitWithError("unexpected line in remapping file", - (InputFile + ":" + Twine(LineIt.line_number())).str(), - "expected 'old_symbol new_symbol'"); - } - Remapper->RemappingTable.insert(Parts); - } - return Remapper; - } - - /// Attempt to map the given old symbol into a new symbol. - /// - /// \return The new symbol, or \p Name if no such symbol was found. - StringRef operator()(StringRef Name) { - StringRef New = RemappingTable.lookup(Name); - return New.empty() ? Name : New; - } -}; -} - -struct WeightedFile { - std::string Filename; - uint64_t Weight; -}; -typedef SmallVector WeightedFileVector; - -/// Keep track of merged data and reported errors. -struct WriterContext { - std::mutex Lock; - InstrProfWriter Writer; - std::vector> Errors; - std::mutex &ErrLock; - SmallSet &WriterErrorCodes; - - WriterContext(bool IsSparse, std::mutex &ErrLock, - SmallSet &WriterErrorCodes) - : Writer(IsSparse), ErrLock(ErrLock), WriterErrorCodes(WriterErrorCodes) { - } -}; - -/// Computer the overlap b/w profile BaseFilename and TestFileName, -/// and store the program level result to Overlap. -static void overlapInput(const std::string &BaseFilename, - const std::string &TestFilename, WriterContext *WC, - OverlapStats &Overlap, - const OverlapFuncFilters &FuncFilter, - raw_fd_ostream &OS, bool IsCS) { - auto ReaderOrErr = InstrProfReader::create(TestFilename); - if (Error E = ReaderOrErr.takeError()) { - // Skip the empty profiles by returning sliently. - instrprof_error IPE = InstrProfError::take(std::move(E)); - if (IPE != instrprof_error::empty_raw_profile) - WC->Errors.emplace_back(make_error(IPE), TestFilename); - return; - } - - auto Reader = std::move(ReaderOrErr.get()); - for (auto &I : *Reader) { - OverlapStats FuncOverlap(OverlapStats::FunctionLevel); - FuncOverlap.setFuncInfo(I.Name, I.Hash); - - WC->Writer.overlapRecord(std::move(I), Overlap, FuncOverlap, FuncFilter); - FuncOverlap.dump(OS); - } -} - -/// Load an input into a writer context. -static void loadInput(const WeightedFile &Input, SymbolRemapper *Remapper, - const InstrProfCorrelator *Correlator, - WriterContext *WC) { - std::unique_lock CtxGuard{WC->Lock}; - - // Copy the filename, because llvm::ThreadPool copied the input "const - // WeightedFile &" by value, making a reference to the filename within it - // invalid outside of this packaged task. - std::string Filename = Input.Filename; - - auto ReaderOrErr = InstrProfReader::create(Input.Filename, Correlator); - if (Error E = ReaderOrErr.takeError()) { - // Skip the empty profiles by returning sliently. - instrprof_error IPE = InstrProfError::take(std::move(E)); - if (IPE != instrprof_error::empty_raw_profile) - WC->Errors.emplace_back(make_error(IPE), Filename); - return; - } - - auto Reader = std::move(ReaderOrErr.get()); - if (Error E = WC->Writer.mergeProfileKind(Reader->getProfileKind())) { - consumeError(std::move(E)); - WC->Errors.emplace_back( - make_error( - "Merge IR generated profile with Clang generated profile.", - std::error_code()), - Filename); - return; - } - - for (auto &I : *Reader) { - if (Remapper) - I.Name = (*Remapper)(I.Name); - const StringRef FuncName = I.Name; - bool Reported = false; - WC->Writer.addRecord(std::move(I), Input.Weight, [&](Error E) { - if (Reported) { - consumeError(std::move(E)); - return; - } - Reported = true; - // Only show hint the first time an error occurs. - instrprof_error IPE = InstrProfError::take(std::move(E)); - std::unique_lock ErrGuard{WC->ErrLock}; - bool firstTime = WC->WriterErrorCodes.insert(IPE).second; - handleMergeWriterError(make_error(IPE), Input.Filename, - FuncName, firstTime); - }); - } - if (Reader->hasError()) - if (Error E = Reader->getError()) - WC->Errors.emplace_back(std::move(E), Filename); -} - -/// Merge the \p Src writer context into \p Dst. -static void mergeWriterContexts(WriterContext *Dst, WriterContext *Src) { - for (auto &ErrorPair : Src->Errors) - Dst->Errors.push_back(std::move(ErrorPair)); - Src->Errors.clear(); - - Dst->Writer.mergeRecordsFromWriter(std::move(Src->Writer), [&](Error E) { - instrprof_error IPE = InstrProfError::take(std::move(E)); - std::unique_lock ErrGuard{Dst->ErrLock}; - bool firstTime = Dst->WriterErrorCodes.insert(IPE).second; - if (firstTime) - warn(toString(make_error(IPE))); - }); -} - -static void writeInstrProfile(StringRef OutputFilename, - ProfileFormat OutputFormat, - InstrProfWriter &Writer) { - std::error_code EC; - raw_fd_ostream Output(OutputFilename.data(), EC, - OutputFormat == PF_Text ? sys::fs::OF_TextWithCRLF - : sys::fs::OF_None); - if (EC) - exitWithErrorCode(EC, OutputFilename); - - if (OutputFormat == PF_Text) { - if (Error E = Writer.writeText(Output)) - warn(std::move(E)); - } else { - if (Output.is_displayed()) - exitWithError("cannot write a non-text format profile to the terminal"); - if (Error E = Writer.write(Output)) - warn(std::move(E)); - } -} - -static void mergeInstrProfile(const WeightedFileVector &Inputs, - StringRef DebugInfoFilename, - SymbolRemapper *Remapper, - StringRef OutputFilename, - ProfileFormat OutputFormat, bool OutputSparse, - unsigned NumThreads, FailureMode FailMode) { - if (OutputFormat != PF_Binary && OutputFormat != PF_Compact_Binary && - OutputFormat != PF_Ext_Binary && OutputFormat != PF_Text) - exitWithError("unknown format is specified"); - - std::unique_ptr Correlator; - if (!DebugInfoFilename.empty()) { - if (auto Err = - InstrProfCorrelator::get(DebugInfoFilename).moveInto(Correlator)) - exitWithError(std::move(Err), DebugInfoFilename); - if (auto Err = Correlator->correlateProfileData()) - exitWithError(std::move(Err), DebugInfoFilename); - } - - std::mutex ErrorLock; - SmallSet WriterErrorCodes; - - // If NumThreads is not specified, auto-detect a good default. - if (NumThreads == 0) - NumThreads = std::min(hardware_concurrency().compute_thread_count(), - unsigned((Inputs.size() + 1) / 2)); - // FIXME: There's a bug here, where setting NumThreads = Inputs.size() fails - // the merge_empty_profile.test because the InstrProfWriter.ProfileKind isn't - // merged, thus the emitted file ends up with a PF_Unknown kind. - - // Initialize the writer contexts. - SmallVector, 4> Contexts; - for (unsigned I = 0; I < NumThreads; ++I) - Contexts.emplace_back(std::make_unique( - OutputSparse, ErrorLock, WriterErrorCodes)); - - if (NumThreads == 1) { - for (const auto &Input : Inputs) - loadInput(Input, Remapper, Correlator.get(), Contexts[0].get()); - } else { - ThreadPool Pool(hardware_concurrency(NumThreads)); - - // Load the inputs in parallel (N/NumThreads serial steps). - unsigned Ctx = 0; - for (const auto &Input : Inputs) { - Pool.async(loadInput, Input, Remapper, Correlator.get(), - Contexts[Ctx].get()); - Ctx = (Ctx + 1) % NumThreads; - } - Pool.wait(); - - // Merge the writer contexts together (~ lg(NumThreads) serial steps). - unsigned Mid = Contexts.size() / 2; - unsigned End = Contexts.size(); - assert(Mid > 0 && "Expected more than one context"); - do { - for (unsigned I = 0; I < Mid; ++I) - Pool.async(mergeWriterContexts, Contexts[I].get(), - Contexts[I + Mid].get()); - Pool.wait(); - if (End & 1) { - Pool.async(mergeWriterContexts, Contexts[0].get(), - Contexts[End - 1].get()); - Pool.wait(); - } - End = Mid; - Mid /= 2; - } while (Mid > 0); - } - - // Handle deferred errors encountered during merging. If the number of errors - // is equal to the number of inputs the merge failed. - unsigned NumErrors = 0; - for (std::unique_ptr &WC : Contexts) { - for (auto &ErrorPair : WC->Errors) { - ++NumErrors; - warn(toString(std::move(ErrorPair.first)), ErrorPair.second); - } - } - if (NumErrors == Inputs.size() || - (NumErrors > 0 && FailMode == failIfAnyAreInvalid)) - exitWithError("no profile can be merged"); - - writeInstrProfile(OutputFilename, OutputFormat, Contexts[0]->Writer); -} - -/// The profile entry for a function in instrumentation profile. -struct InstrProfileEntry { - uint64_t MaxCount = 0; - float ZeroCounterRatio = 0.0; - InstrProfRecord *ProfRecord; - InstrProfileEntry(InstrProfRecord *Record); - InstrProfileEntry() = default; -}; - -InstrProfileEntry::InstrProfileEntry(InstrProfRecord *Record) { - ProfRecord = Record; - uint64_t CntNum = Record->Counts.size(); - uint64_t ZeroCntNum = 0; - for (size_t I = 0; I < CntNum; ++I) { - MaxCount = std::max(MaxCount, Record->Counts[I]); - ZeroCntNum += !Record->Counts[I]; - } - ZeroCounterRatio = (float)ZeroCntNum / CntNum; -} - -/// Either set all the counters in the instr profile entry \p IFE to -1 -/// in order to drop the profile or scale up the counters in \p IFP to -/// be above hot threshold. We use the ratio of zero counters in the -/// profile of a function to decide the profile is helpful or harmful -/// for performance, and to choose whether to scale up or drop it. -static void updateInstrProfileEntry(InstrProfileEntry &IFE, - uint64_t HotInstrThreshold, - float ZeroCounterThreshold) { - InstrProfRecord *ProfRecord = IFE.ProfRecord; - if (!IFE.MaxCount || IFE.ZeroCounterRatio > ZeroCounterThreshold) { - // If all or most of the counters of the function are zero, the - // profile is unaccountable and shuld be dropped. Reset all the - // counters to be -1 and PGO profile-use will drop the profile. - // All counters being -1 also implies that the function is hot so - // PGO profile-use will also set the entry count metadata to be - // above hot threshold. - for (size_t I = 0; I < ProfRecord->Counts.size(); ++I) - ProfRecord->Counts[I] = -1; - return; - } - - // Scale up the MaxCount to be multiple times above hot threshold. - const unsigned MultiplyFactor = 3; - uint64_t Numerator = HotInstrThreshold * MultiplyFactor; - uint64_t Denominator = IFE.MaxCount; - ProfRecord->scale(Numerator, Denominator, [&](instrprof_error E) { - warn(toString(make_error(E))); - }); -} - -const uint64_t ColdPercentileIdx = 15; -const uint64_t HotPercentileIdx = 11; - -using sampleprof::FSDiscriminatorPass; - -// Internal options to set FSDiscriminatorPass. Used in merge and show -// commands. -static cl::opt FSDiscriminatorPassOption( - "fs-discriminator-pass", cl::init(PassLast), cl::Hidden, - cl::desc("Zero out the discriminator bits for the FS discrimiantor " - "pass beyond this value. The enum values are defined in " - "Support/Discriminator.h"), - cl::values(clEnumVal(Base, "Use base discriminators only"), - clEnumVal(Pass1, "Use base and pass 1 discriminators"), - clEnumVal(Pass2, "Use base and pass 1-2 discriminators"), - clEnumVal(Pass3, "Use base and pass 1-3 discriminators"), - clEnumVal(PassLast, "Use all discriminator bits (default)"))); - -static unsigned getDiscriminatorMask() { - return getN1Bits(getFSPassBitEnd(FSDiscriminatorPassOption.getValue())); -} - -/// Adjust the instr profile in \p WC based on the sample profile in -/// \p Reader. -static void -adjustInstrProfile(std::unique_ptr &WC, - std::unique_ptr &Reader, - unsigned SupplMinSizeThreshold, float ZeroCounterThreshold, - unsigned InstrProfColdThreshold) { - // Function to its entry in instr profile. - StringMap InstrProfileMap; - InstrProfSummaryBuilder IPBuilder(ProfileSummaryBuilder::DefaultCutoffs); - for (auto &PD : WC->Writer.getProfileData()) { - // Populate IPBuilder. - for (const auto &PDV : PD.getValue()) { - InstrProfRecord Record = PDV.second; - IPBuilder.addRecord(Record); - } - - // If a function has multiple entries in instr profile, skip it. - if (PD.getValue().size() != 1) - continue; - - // Initialize InstrProfileMap. - InstrProfRecord *R = &PD.getValue().begin()->second; - InstrProfileMap[PD.getKey()] = InstrProfileEntry(R); - } - - ProfileSummary InstrPS = *IPBuilder.getSummary(); - ProfileSummary SamplePS = Reader->getSummary(); - - // Compute cold thresholds for instr profile and sample profile. - uint64_t ColdSampleThreshold = - ProfileSummaryBuilder::getEntryForPercentile( - SamplePS.getDetailedSummary(), - ProfileSummaryBuilder::DefaultCutoffs[ColdPercentileIdx]) - .MinCount; - uint64_t HotInstrThreshold = - ProfileSummaryBuilder::getEntryForPercentile( - InstrPS.getDetailedSummary(), - ProfileSummaryBuilder::DefaultCutoffs[HotPercentileIdx]) - .MinCount; - uint64_t ColdInstrThreshold = - InstrProfColdThreshold - ? InstrProfColdThreshold - : ProfileSummaryBuilder::getEntryForPercentile( - InstrPS.getDetailedSummary(), - ProfileSummaryBuilder::DefaultCutoffs[ColdPercentileIdx]) - .MinCount; - - // Find hot/warm functions in sample profile which is cold in instr profile - // and adjust the profiles of those functions in the instr profile. - for (const auto &PD : Reader->getProfiles()) { - auto &FContext = PD.first; - const sampleprof::FunctionSamples &FS = PD.second; - auto It = InstrProfileMap.find(FContext.toString()); - if (FS.getHeadSamples() > ColdSampleThreshold && - It != InstrProfileMap.end() && - It->second.MaxCount <= ColdInstrThreshold && - FS.getBodySamples().size() >= SupplMinSizeThreshold) { - updateInstrProfileEntry(It->second, HotInstrThreshold, - ZeroCounterThreshold); - } - } -} - -/// The main function to supplement instr profile with sample profile. -/// \Inputs contains the instr profile. \p SampleFilename specifies the -/// sample profile. \p OutputFilename specifies the output profile name. -/// \p OutputFormat specifies the output profile format. \p OutputSparse -/// specifies whether to generate sparse profile. \p SupplMinSizeThreshold -/// specifies the minimal size for the functions whose profile will be -/// adjusted. \p ZeroCounterThreshold is the threshold to check whether -/// a function contains too many zero counters and whether its profile -/// should be dropped. \p InstrProfColdThreshold is the user specified -/// cold threshold which will override the cold threshold got from the -/// instr profile summary. -static void supplementInstrProfile( - const WeightedFileVector &Inputs, StringRef SampleFilename, - StringRef OutputFilename, ProfileFormat OutputFormat, bool OutputSparse, - unsigned SupplMinSizeThreshold, float ZeroCounterThreshold, - unsigned InstrProfColdThreshold) { - if (OutputFilename.compare("-") == 0) - exitWithError("cannot write indexed profdata format to stdout"); - if (Inputs.size() != 1) - exitWithError("expect one input to be an instr profile"); - if (Inputs[0].Weight != 1) - exitWithError("expect instr profile doesn't have weight"); - - StringRef InstrFilename = Inputs[0].Filename; - - // Read sample profile. - LLVMContext Context; - auto ReaderOrErr = sampleprof::SampleProfileReader::create( - SampleFilename.str(), Context, FSDiscriminatorPassOption); - if (std::error_code EC = ReaderOrErr.getError()) - exitWithErrorCode(EC, SampleFilename); - auto Reader = std::move(ReaderOrErr.get()); - if (std::error_code EC = Reader->read()) - exitWithErrorCode(EC, SampleFilename); - - // Read instr profile. - std::mutex ErrorLock; - SmallSet WriterErrorCodes; - auto WC = std::make_unique(OutputSparse, ErrorLock, - WriterErrorCodes); - loadInput(Inputs[0], nullptr, nullptr, WC.get()); - if (WC->Errors.size() > 0) - exitWithError(std::move(WC->Errors[0].first), InstrFilename); - - adjustInstrProfile(WC, Reader, SupplMinSizeThreshold, ZeroCounterThreshold, - InstrProfColdThreshold); - writeInstrProfile(OutputFilename, OutputFormat, WC->Writer); -} - -/// Make a copy of the given function samples with all symbol names remapped -/// by the provided symbol remapper. -static sampleprof::FunctionSamples -remapSamples(const sampleprof::FunctionSamples &Samples, - SymbolRemapper &Remapper, sampleprof_error &Error) { - sampleprof::FunctionSamples Result; - Result.setName(Remapper(Samples.getName())); - Result.addTotalSamples(Samples.getTotalSamples()); - Result.addHeadSamples(Samples.getHeadSamples()); - for (const auto &BodySample : Samples.getBodySamples()) { - uint32_t MaskedDiscriminator = - BodySample.first.Discriminator & getDiscriminatorMask(); - Result.addBodySamples(BodySample.first.LineOffset, MaskedDiscriminator, - BodySample.second.getSamples()); - for (const auto &Target : BodySample.second.getCallTargets()) { - Result.addCalledTargetSamples(BodySample.first.LineOffset, - MaskedDiscriminator, - Remapper(Target.first()), Target.second); - } - } - for (const auto &CallsiteSamples : Samples.getCallsiteSamples()) { - sampleprof::FunctionSamplesMap &Target = - Result.functionSamplesAt(CallsiteSamples.first); - for (const auto &Callsite : CallsiteSamples.second) { - sampleprof::FunctionSamples Remapped = - remapSamples(Callsite.second, Remapper, Error); - MergeResult(Error, - Target[std::string(Remapped.getName())].merge(Remapped)); - } - } - return Result; -} - -static sampleprof::SampleProfileFormat FormatMap[] = { - sampleprof::SPF_None, - sampleprof::SPF_Text, - sampleprof::SPF_Compact_Binary, - sampleprof::SPF_Ext_Binary, - sampleprof::SPF_GCC, - sampleprof::SPF_Binary}; - -static std::unique_ptr -getInputFileBuf(const StringRef &InputFile) { - if (InputFile == "") - return {}; - - auto BufOrError = MemoryBuffer::getFileOrSTDIN(InputFile); - if (!BufOrError) - exitWithErrorCode(BufOrError.getError(), InputFile); - - return std::move(*BufOrError); -} - -static void populateProfileSymbolList(MemoryBuffer *Buffer, - sampleprof::ProfileSymbolList &PSL) { - if (!Buffer) - return; - - SmallVector SymbolVec; - StringRef Data = Buffer->getBuffer(); - Data.split(SymbolVec, '\n', /*MaxSplit=*/-1, /*KeepEmpty=*/false); - - for (StringRef SymbolStr : SymbolVec) - PSL.add(SymbolStr.trim()); -} - -static void handleExtBinaryWriter(sampleprof::SampleProfileWriter &Writer, - ProfileFormat OutputFormat, - MemoryBuffer *Buffer, - sampleprof::ProfileSymbolList &WriterList, - bool CompressAllSections, bool UseMD5, - bool GenPartialProfile) { - populateProfileSymbolList(Buffer, WriterList); - if (WriterList.size() > 0 && OutputFormat != PF_Ext_Binary) - warn("Profile Symbol list is not empty but the output format is not " - "ExtBinary format. The list will be lost in the output. "); - - Writer.setProfileSymbolList(&WriterList); - - if (CompressAllSections) { - if (OutputFormat != PF_Ext_Binary) - warn("-compress-all-section is ignored. Specify -extbinary to enable it"); - else - Writer.setToCompressAllSections(); - } - if (UseMD5) { - if (OutputFormat != PF_Ext_Binary) - warn("-use-md5 is ignored. Specify -extbinary to enable it"); - else - Writer.setUseMD5(); - } - if (GenPartialProfile) { - if (OutputFormat != PF_Ext_Binary) - warn("-gen-partial-profile is ignored. Specify -extbinary to enable it"); - else - Writer.setPartialProfile(); - } -} - -static void -mergeSampleProfile(const WeightedFileVector &Inputs, SymbolRemapper *Remapper, - StringRef OutputFilename, ProfileFormat OutputFormat, - StringRef ProfileSymbolListFile, bool CompressAllSections, - bool UseMD5, bool GenPartialProfile, bool GenCSNestedProfile, - bool SampleMergeColdContext, bool SampleTrimColdContext, - bool SampleColdContextFrameDepth, FailureMode FailMode) { - using namespace sampleprof; - SampleProfileMap ProfileMap; - SmallVector, 5> Readers; - LLVMContext Context; - sampleprof::ProfileSymbolList WriterList; - Optional ProfileIsProbeBased; - Optional ProfileIsCSFlat; - for (const auto &Input : Inputs) { - auto ReaderOrErr = SampleProfileReader::create(Input.Filename, Context, - FSDiscriminatorPassOption); - if (std::error_code EC = ReaderOrErr.getError()) { - warnOrExitGivenError(FailMode, EC, Input.Filename); - continue; - } - - // We need to keep the readers around until after all the files are - // read so that we do not lose the function names stored in each - // reader's memory. The function names are needed to write out the - // merged profile map. - Readers.push_back(std::move(ReaderOrErr.get())); - const auto Reader = Readers.back().get(); - if (std::error_code EC = Reader->read()) { - warnOrExitGivenError(FailMode, EC, Input.Filename); - Readers.pop_back(); - continue; - } - - SampleProfileMap &Profiles = Reader->getProfiles(); - if (ProfileIsProbeBased.hasValue() && - ProfileIsProbeBased != FunctionSamples::ProfileIsProbeBased) - exitWithError( - "cannot merge probe-based profile with non-probe-based profile"); - ProfileIsProbeBased = FunctionSamples::ProfileIsProbeBased; - if (ProfileIsCSFlat.hasValue() && - ProfileIsCSFlat != FunctionSamples::ProfileIsCSFlat) - exitWithError("cannot merge CS profile with non-CS profile"); - ProfileIsCSFlat = FunctionSamples::ProfileIsCSFlat; - for (SampleProfileMap::iterator I = Profiles.begin(), E = Profiles.end(); - I != E; ++I) { - sampleprof_error Result = sampleprof_error::success; - FunctionSamples Remapped = - Remapper ? remapSamples(I->second, *Remapper, Result) - : FunctionSamples(); - FunctionSamples &Samples = Remapper ? Remapped : I->second; - SampleContext FContext = Samples.getContext(); - MergeResult(Result, ProfileMap[FContext].merge(Samples, Input.Weight)); - if (Result != sampleprof_error::success) { - std::error_code EC = make_error_code(Result); - handleMergeWriterError(errorCodeToError(EC), Input.Filename, - FContext.toString()); - } - } - - std::unique_ptr ReaderList = - Reader->getProfileSymbolList(); - if (ReaderList) - WriterList.merge(*ReaderList); - } - - if (ProfileIsCSFlat && (SampleMergeColdContext || SampleTrimColdContext)) { - // Use threshold calculated from profile summary unless specified. - SampleProfileSummaryBuilder Builder(ProfileSummaryBuilder::DefaultCutoffs); - auto Summary = Builder.computeSummaryForProfiles(ProfileMap); - uint64_t SampleProfColdThreshold = - ProfileSummaryBuilder::getColdCountThreshold( - (Summary->getDetailedSummary())); - - // Trim and merge cold context profile using cold threshold above; - SampleContextTrimmer(ProfileMap) - .trimAndMergeColdContextProfiles( - SampleProfColdThreshold, SampleTrimColdContext, - SampleMergeColdContext, SampleColdContextFrameDepth, false); - } - - if (ProfileIsCSFlat && GenCSNestedProfile) { - CSProfileConverter CSConverter(ProfileMap); - CSConverter.convertProfiles(); - ProfileIsCSFlat = FunctionSamples::ProfileIsCSFlat = false; - } - - auto WriterOrErr = - SampleProfileWriter::create(OutputFilename, FormatMap[OutputFormat]); - if (std::error_code EC = WriterOrErr.getError()) - exitWithErrorCode(EC, OutputFilename); - - auto Writer = std::move(WriterOrErr.get()); - // WriterList will have StringRef refering to string in Buffer. - // Make sure Buffer lives as long as WriterList. - auto Buffer = getInputFileBuf(ProfileSymbolListFile); - handleExtBinaryWriter(*Writer, OutputFormat, Buffer.get(), WriterList, - CompressAllSections, UseMD5, GenPartialProfile); - if (std::error_code EC = Writer->write(ProfileMap)) - exitWithErrorCode(std::move(EC)); -} - -static WeightedFile parseWeightedFile(const StringRef &WeightedFilename) { - StringRef WeightStr, FileName; - std::tie(WeightStr, FileName) = WeightedFilename.split(','); - - uint64_t Weight; - if (WeightStr.getAsInteger(10, Weight) || Weight < 1) - exitWithError("input weight must be a positive integer"); - - return {std::string(FileName), Weight}; -} - -static void addWeightedInput(WeightedFileVector &WNI, const WeightedFile &WF) { - StringRef Filename = WF.Filename; - uint64_t Weight = WF.Weight; - - // If it's STDIN just pass it on. - if (Filename == "-") { - WNI.push_back({std::string(Filename), Weight}); - return; - } - - llvm::sys::fs::file_status Status; - llvm::sys::fs::status(Filename, Status); - if (!llvm::sys::fs::exists(Status)) - exitWithErrorCode(make_error_code(errc::no_such_file_or_directory), - Filename); - // If it's a source file, collect it. - if (llvm::sys::fs::is_regular_file(Status)) { - WNI.push_back({std::string(Filename), Weight}); - return; - } - - if (llvm::sys::fs::is_directory(Status)) { - std::error_code EC; - for (llvm::sys::fs::recursive_directory_iterator F(Filename, EC), E; - F != E && !EC; F.increment(EC)) { - if (llvm::sys::fs::is_regular_file(F->path())) { - addWeightedInput(WNI, {F->path(), Weight}); - } - } - if (EC) - exitWithErrorCode(EC, Filename); - } -} - -static void parseInputFilenamesFile(MemoryBuffer *Buffer, - WeightedFileVector &WFV) { - if (!Buffer) - return; - - SmallVector Entries; - StringRef Data = Buffer->getBuffer(); - Data.split(Entries, '\n', /*MaxSplit=*/-1, /*KeepEmpty=*/false); - for (const StringRef &FileWeightEntry : Entries) { - StringRef SanitizedEntry = FileWeightEntry.trim(" \t\v\f\r"); - // Skip comments. - if (SanitizedEntry.startswith("#")) - continue; - // If there's no comma, it's an unweighted profile. - else if (!SanitizedEntry.contains(',')) - addWeightedInput(WFV, {std::string(SanitizedEntry), 1}); - else - addWeightedInput(WFV, parseWeightedFile(SanitizedEntry)); - } -} - -static int merge_main(int argc, const char *argv[]) { - cl::list InputFilenames(cl::Positional, - cl::desc("")); - cl::list WeightedInputFilenames("weighted-input", - cl::desc(",")); - cl::opt InputFilenamesFile( - "input-files", cl::init(""), - cl::desc("Path to file containing newline-separated " - "[,] entries")); - cl::alias InputFilenamesFileA("f", cl::desc("Alias for --input-files"), - cl::aliasopt(InputFilenamesFile)); - cl::opt DumpInputFileList( - "dump-input-file-list", cl::init(false), cl::Hidden, - cl::desc("Dump the list of input files and their weights, then exit")); - cl::opt RemappingFile("remapping-file", cl::value_desc("file"), - cl::desc("Symbol remapping file")); - cl::alias RemappingFileA("r", cl::desc("Alias for --remapping-file"), - cl::aliasopt(RemappingFile)); - cl::opt OutputFilename("output", cl::value_desc("output"), - cl::init("-"), cl::desc("Output file")); - cl::alias OutputFilenameA("o", cl::desc("Alias for --output"), - cl::aliasopt(OutputFilename)); - cl::opt ProfileKind( - cl::desc("Profile kind:"), cl::init(instr), - cl::values(clEnumVal(instr, "Instrumentation profile (default)"), - clEnumVal(sample, "Sample profile"))); - cl::opt OutputFormat( - cl::desc("Format of output profile"), cl::init(PF_Binary), - cl::values( - clEnumValN(PF_Binary, "binary", "Binary encoding (default)"), - clEnumValN(PF_Compact_Binary, "compbinary", - "Compact binary encoding"), - clEnumValN(PF_Ext_Binary, "extbinary", "Extensible binary encoding"), - clEnumValN(PF_Text, "text", "Text encoding"), - clEnumValN(PF_GCC, "gcc", - "GCC encoding (only meaningful for -sample)"))); - cl::opt FailureMode( - "failure-mode", cl::init(failIfAnyAreInvalid), cl::desc("Failure mode:"), - cl::values(clEnumValN(failIfAnyAreInvalid, "any", - "Fail if any profile is invalid."), - clEnumValN(failIfAllAreInvalid, "all", - "Fail only if all profiles are invalid."))); - cl::opt OutputSparse("sparse", cl::init(false), - cl::desc("Generate a sparse profile (only meaningful for -instr)")); - cl::opt NumThreads( - "num-threads", cl::init(0), - cl::desc("Number of merge threads to use (default: autodetect)")); - cl::alias NumThreadsA("j", cl::desc("Alias for --num-threads"), - cl::aliasopt(NumThreads)); - cl::opt ProfileSymbolListFile( - "prof-sym-list", cl::init(""), - cl::desc("Path to file containing the list of function symbols " - "used to populate profile symbol list")); - cl::opt CompressAllSections( - "compress-all-sections", cl::init(false), cl::Hidden, - cl::desc("Compress all sections when writing the profile (only " - "meaningful for -extbinary)")); - cl::opt UseMD5( - "use-md5", cl::init(false), cl::Hidden, - cl::desc("Choose to use MD5 to represent string in name table (only " - "meaningful for -extbinary)")); - cl::opt SampleMergeColdContext( - "sample-merge-cold-context", cl::init(false), cl::Hidden, - cl::desc( - "Merge context sample profiles whose count is below cold threshold")); - cl::opt SampleTrimColdContext( - "sample-trim-cold-context", cl::init(false), cl::Hidden, - cl::desc( - "Trim context sample profiles whose count is below cold threshold")); - cl::opt SampleColdContextFrameDepth( - "sample-frame-depth-for-cold-context", cl::init(1), cl::ZeroOrMore, - cl::desc("Keep the last K frames while merging cold profile. 1 means the " - "context-less base profile")); - cl::opt GenPartialProfile( - "gen-partial-profile", cl::init(false), cl::Hidden, - cl::desc("Generate a partial profile (only meaningful for -extbinary)")); - cl::opt SupplInstrWithSample( - "supplement-instr-with-sample", cl::init(""), cl::Hidden, - cl::desc("Supplement an instr profile with sample profile, to correct " - "the profile unrepresentativeness issue. The sample " - "profile is the input of the flag. Output will be in instr " - "format (The flag only works with -instr)")); - cl::opt ZeroCounterThreshold( - "zero-counter-threshold", cl::init(0.7), cl::Hidden, - cl::desc("For the function which is cold in instr profile but hot in " - "sample profile, if the ratio of the number of zero counters " - "divided by the the total number of counters is above the " - "threshold, the profile of the function will be regarded as " - "being harmful for performance and will be dropped.")); - cl::opt SupplMinSizeThreshold( - "suppl-min-size-threshold", cl::init(10), cl::Hidden, - cl::desc("If the size of a function is smaller than the threshold, " - "assume it can be inlined by PGO early inliner and it won't " - "be adjusted based on sample profile.")); - cl::opt InstrProfColdThreshold( - "instr-prof-cold-threshold", cl::init(0), cl::Hidden, - cl::desc("User specified cold threshold for instr profile which will " - "override the cold threshold got from profile summary. ")); - cl::opt GenCSNestedProfile( - "gen-cs-nested-profile", cl::Hidden, cl::init(false), - cl::desc("Generate nested function profiles for CSSPGO")); - cl::opt DebugInfoFilename( - "debug-info", cl::init(""), - cl::desc("Use the provided debug info to correlate the raw profile.")); - - cl::ParseCommandLineOptions(argc, argv, "LLVM profile data merger\n"); - - WeightedFileVector WeightedInputs; - for (StringRef Filename : InputFilenames) - addWeightedInput(WeightedInputs, {std::string(Filename), 1}); - for (StringRef WeightedFilename : WeightedInputFilenames) - addWeightedInput(WeightedInputs, parseWeightedFile(WeightedFilename)); - - // Make sure that the file buffer stays alive for the duration of the - // weighted input vector's lifetime. - auto Buffer = getInputFileBuf(InputFilenamesFile); - parseInputFilenamesFile(Buffer.get(), WeightedInputs); - - if (WeightedInputs.empty()) - exitWithError("no input files specified. See " + - sys::path::filename(argv[0]) + " -help"); - - if (DumpInputFileList) { - for (auto &WF : WeightedInputs) - outs() << WF.Weight << "," << WF.Filename << "\n"; - return 0; - } - - std::unique_ptr Remapper; - if (!RemappingFile.empty()) - Remapper = SymbolRemapper::create(RemappingFile); - - if (!SupplInstrWithSample.empty()) { - if (ProfileKind != instr) - exitWithError( - "-supplement-instr-with-sample can only work with -instr. "); - - supplementInstrProfile(WeightedInputs, SupplInstrWithSample, OutputFilename, - OutputFormat, OutputSparse, SupplMinSizeThreshold, - ZeroCounterThreshold, InstrProfColdThreshold); - return 0; - } - - if (ProfileKind == instr) - mergeInstrProfile(WeightedInputs, DebugInfoFilename, Remapper.get(), - OutputFilename, OutputFormat, OutputSparse, NumThreads, - FailureMode); - else - mergeSampleProfile(WeightedInputs, Remapper.get(), OutputFilename, - OutputFormat, ProfileSymbolListFile, CompressAllSections, - UseMD5, GenPartialProfile, GenCSNestedProfile, - SampleMergeColdContext, SampleTrimColdContext, - SampleColdContextFrameDepth, FailureMode); - return 0; -} - -/// Computer the overlap b/w profile BaseFilename and profile TestFilename. -static void overlapInstrProfile(const std::string &BaseFilename, - const std::string &TestFilename, - const OverlapFuncFilters &FuncFilter, - raw_fd_ostream &OS, bool IsCS) { - std::mutex ErrorLock; - SmallSet WriterErrorCodes; - WriterContext Context(false, ErrorLock, WriterErrorCodes); - WeightedFile WeightedInput{BaseFilename, 1}; - OverlapStats Overlap; - Error E = Overlap.accumulateCounts(BaseFilename, TestFilename, IsCS); - if (E) - exitWithError(std::move(E), "error in getting profile count sums"); - if (Overlap.Base.CountSum < 1.0f) { - OS << "Sum of edge counts for profile " << BaseFilename << " is 0.\n"; - exit(0); - } - if (Overlap.Test.CountSum < 1.0f) { - OS << "Sum of edge counts for profile " << TestFilename << " is 0.\n"; - exit(0); - } - loadInput(WeightedInput, nullptr, nullptr, &Context); - overlapInput(BaseFilename, TestFilename, &Context, Overlap, FuncFilter, OS, - IsCS); - Overlap.dump(OS); -} - -namespace { -struct SampleOverlapStats { - SampleContext BaseName; - SampleContext TestName; - // Number of overlap units - uint64_t OverlapCount; - // Total samples of overlap units - uint64_t OverlapSample; - // Number of and total samples of units that only present in base or test - // profile - uint64_t BaseUniqueCount; - uint64_t BaseUniqueSample; - uint64_t TestUniqueCount; - uint64_t TestUniqueSample; - // Number of units and total samples in base or test profile - uint64_t BaseCount; - uint64_t BaseSample; - uint64_t TestCount; - uint64_t TestSample; - // Number of and total samples of units that present in at least one profile - uint64_t UnionCount; - uint64_t UnionSample; - // Weighted similarity - double Similarity; - // For SampleOverlapStats instances representing functions, weights of the - // function in base and test profiles - double BaseWeight; - double TestWeight; - - SampleOverlapStats() - : OverlapCount(0), OverlapSample(0), BaseUniqueCount(0), - BaseUniqueSample(0), TestUniqueCount(0), TestUniqueSample(0), - BaseCount(0), BaseSample(0), TestCount(0), TestSample(0), UnionCount(0), - UnionSample(0), Similarity(0.0), BaseWeight(0.0), TestWeight(0.0) {} -}; -} // end anonymous namespace - -namespace { -struct FuncSampleStats { - uint64_t SampleSum; - uint64_t MaxSample; - uint64_t HotBlockCount; - FuncSampleStats() : SampleSum(0), MaxSample(0), HotBlockCount(0) {} - FuncSampleStats(uint64_t SampleSum, uint64_t MaxSample, - uint64_t HotBlockCount) - : SampleSum(SampleSum), MaxSample(MaxSample), - HotBlockCount(HotBlockCount) {} -}; -} // end anonymous namespace - -namespace { -enum MatchStatus { MS_Match, MS_FirstUnique, MS_SecondUnique, MS_None }; - -// Class for updating merging steps for two sorted maps. The class should be -// instantiated with a map iterator type. -template class MatchStep { -public: - MatchStep() = delete; - - MatchStep(T FirstIter, T FirstEnd, T SecondIter, T SecondEnd) - : FirstIter(FirstIter), FirstEnd(FirstEnd), SecondIter(SecondIter), - SecondEnd(SecondEnd), Status(MS_None) {} - - bool areBothFinished() const { - return (FirstIter == FirstEnd && SecondIter == SecondEnd); - } - - bool isFirstFinished() const { return FirstIter == FirstEnd; } - - bool isSecondFinished() const { return SecondIter == SecondEnd; } - - /// Advance one step based on the previous match status unless the previous - /// status is MS_None. Then update Status based on the comparison between two - /// container iterators at the current step. If the previous status is - /// MS_None, it means two iterators are at the beginning and no comparison has - /// been made, so we simply update Status without advancing the iterators. - void updateOneStep(); - - T getFirstIter() const { return FirstIter; } - - T getSecondIter() const { return SecondIter; } - - MatchStatus getMatchStatus() const { return Status; } - -private: - // Current iterator and end iterator of the first container. - T FirstIter; - T FirstEnd; - // Current iterator and end iterator of the second container. - T SecondIter; - T SecondEnd; - // Match status of the current step. - MatchStatus Status; -}; -} // end anonymous namespace - -template void MatchStep::updateOneStep() { - switch (Status) { - case MS_Match: - ++FirstIter; - ++SecondIter; - break; - case MS_FirstUnique: - ++FirstIter; - break; - case MS_SecondUnique: - ++SecondIter; - break; - case MS_None: - break; - } - - // Update Status according to iterators at the current step. - if (areBothFinished()) - return; - if (FirstIter != FirstEnd && - (SecondIter == SecondEnd || FirstIter->first < SecondIter->first)) - Status = MS_FirstUnique; - else if (SecondIter != SecondEnd && - (FirstIter == FirstEnd || SecondIter->first < FirstIter->first)) - Status = MS_SecondUnique; - else - Status = MS_Match; -} - -// Return the sum of line/block samples, the max line/block sample, and the -// number of line/block samples above the given threshold in a function -// including its inlinees. -static void getFuncSampleStats(const sampleprof::FunctionSamples &Func, - FuncSampleStats &FuncStats, - uint64_t HotThreshold) { - for (const auto &L : Func.getBodySamples()) { - uint64_t Sample = L.second.getSamples(); - FuncStats.SampleSum += Sample; - FuncStats.MaxSample = std::max(FuncStats.MaxSample, Sample); - if (Sample >= HotThreshold) - ++FuncStats.HotBlockCount; - } - - for (const auto &C : Func.getCallsiteSamples()) { - for (const auto &F : C.second) - getFuncSampleStats(F.second, FuncStats, HotThreshold); - } -} - -/// Predicate that determines if a function is hot with a given threshold. We -/// keep it separate from its callsites for possible extension in the future. -static bool isFunctionHot(const FuncSampleStats &FuncStats, - uint64_t HotThreshold) { - // We intentionally compare the maximum sample count in a function with the - // HotThreshold to get an approximate determination on hot functions. - return (FuncStats.MaxSample >= HotThreshold); -} - -namespace { -class SampleOverlapAggregator { -public: - SampleOverlapAggregator(const std::string &BaseFilename, - const std::string &TestFilename, - double LowSimilarityThreshold, double Epsilon, - const OverlapFuncFilters &FuncFilter) - : BaseFilename(BaseFilename), TestFilename(TestFilename), - LowSimilarityThreshold(LowSimilarityThreshold), Epsilon(Epsilon), - FuncFilter(FuncFilter) {} - - /// Detect 0-sample input profile and report to output stream. This interface - /// should be called after loadProfiles(). - bool detectZeroSampleProfile(raw_fd_ostream &OS) const; - - /// Write out function-level similarity statistics for functions specified by - /// options --function, --value-cutoff, and --similarity-cutoff. - void dumpFuncSimilarity(raw_fd_ostream &OS) const; - - /// Write out program-level similarity and overlap statistics. - void dumpProgramSummary(raw_fd_ostream &OS) const; - - /// Write out hot-function and hot-block statistics for base_profile, - /// test_profile, and their overlap. For both cases, the overlap HO is - /// calculated as follows: - /// Given the number of functions (or blocks) that are hot in both profiles - /// HCommon and the number of functions (or blocks) that are hot in at - /// least one profile HUnion, HO = HCommon / HUnion. - void dumpHotFuncAndBlockOverlap(raw_fd_ostream &OS) const; - - /// This function tries matching functions in base and test profiles. For each - /// pair of matched functions, it aggregates the function-level - /// similarity into a profile-level similarity. It also dump function-level - /// similarity information of functions specified by --function, - /// --value-cutoff, and --similarity-cutoff options. The program-level - /// similarity PS is computed as follows: - /// Given function-level similarity FS(A) for all function A, the - /// weight of function A in base profile WB(A), and the weight of function - /// A in test profile WT(A), compute PS(base_profile, test_profile) = - /// sum_A(FS(A) * avg(WB(A), WT(A))) ranging in [0.0f to 1.0f] with 0.0 - /// meaning no-overlap. - void computeSampleProfileOverlap(raw_fd_ostream &OS); - - /// Initialize ProfOverlap with the sum of samples in base and test - /// profiles. This function also computes and keeps the sum of samples and - /// max sample counts of each function in BaseStats and TestStats for later - /// use to avoid re-computations. - void initializeSampleProfileOverlap(); - - /// Load profiles specified by BaseFilename and TestFilename. - std::error_code loadProfiles(); - - using FuncSampleStatsMap = - std::unordered_map; - -private: - SampleOverlapStats ProfOverlap; - SampleOverlapStats HotFuncOverlap; - SampleOverlapStats HotBlockOverlap; - std::string BaseFilename; - std::string TestFilename; - std::unique_ptr BaseReader; - std::unique_ptr TestReader; - // BaseStats and TestStats hold FuncSampleStats for each function, with - // function name as the key. - FuncSampleStatsMap BaseStats; - FuncSampleStatsMap TestStats; - // Low similarity threshold in floating point number - double LowSimilarityThreshold; - // Block samples above BaseHotThreshold or TestHotThreshold are considered hot - // for tracking hot blocks. - uint64_t BaseHotThreshold; - uint64_t TestHotThreshold; - // A small threshold used to round the results of floating point accumulations - // to resolve imprecision. - const double Epsilon; - std::multimap> - FuncSimilarityDump; - // FuncFilter carries specifications in options --value-cutoff and - // --function. - OverlapFuncFilters FuncFilter; - // Column offsets for printing the function-level details table. - static const unsigned int TestWeightCol = 15; - static const unsigned int SimilarityCol = 30; - static const unsigned int OverlapCol = 43; - static const unsigned int BaseUniqueCol = 53; - static const unsigned int TestUniqueCol = 67; - static const unsigned int BaseSampleCol = 81; - static const unsigned int TestSampleCol = 96; - static const unsigned int FuncNameCol = 111; - - /// Return a similarity of two line/block sample counters in the same - /// function in base and test profiles. The line/block-similarity BS(i) is - /// computed as follows: - /// For an offsets i, given the sample count at i in base profile BB(i), - /// the sample count at i in test profile BT(i), the sum of sample counts - /// in this function in base profile SB, and the sum of sample counts in - /// this function in test profile ST, compute BS(i) = 1.0 - fabs(BB(i)/SB - - /// BT(i)/ST), ranging in [0.0f to 1.0f] with 0.0 meaning no-overlap. - double computeBlockSimilarity(uint64_t BaseSample, uint64_t TestSample, - const SampleOverlapStats &FuncOverlap) const; - - void updateHotBlockOverlap(uint64_t BaseSample, uint64_t TestSample, - uint64_t HotBlockCount); - - void getHotFunctions(const FuncSampleStatsMap &ProfStats, - FuncSampleStatsMap &HotFunc, - uint64_t HotThreshold) const; - - void computeHotFuncOverlap(); - - /// This function updates statistics in FuncOverlap, HotBlockOverlap, and - /// Difference for two sample units in a matched function according to the - /// given match status. - void updateOverlapStatsForFunction(uint64_t BaseSample, uint64_t TestSample, - uint64_t HotBlockCount, - SampleOverlapStats &FuncOverlap, - double &Difference, MatchStatus Status); - - /// This function updates statistics in FuncOverlap, HotBlockOverlap, and - /// Difference for unmatched callees that only present in one profile in a - /// matched caller function. - void updateForUnmatchedCallee(const sampleprof::FunctionSamples &Func, - SampleOverlapStats &FuncOverlap, - double &Difference, MatchStatus Status); - - /// This function updates sample overlap statistics of an overlap function in - /// base and test profile. It also calculates a function-internal similarity - /// FIS as follows: - /// For offsets i that have samples in at least one profile in this - /// function A, given BS(i) returned by computeBlockSimilarity(), compute - /// FIS(A) = (2.0 - sum_i(1.0 - BS(i))) / 2, ranging in [0.0f to 1.0f] with - /// 0.0 meaning no overlap. - double computeSampleFunctionInternalOverlap( - const sampleprof::FunctionSamples &BaseFunc, - const sampleprof::FunctionSamples &TestFunc, - SampleOverlapStats &FuncOverlap); - - /// Function-level similarity (FS) is a weighted value over function internal - /// similarity (FIS). This function computes a function's FS from its FIS by - /// applying the weight. - double weightForFuncSimilarity(double FuncSimilarity, uint64_t BaseFuncSample, - uint64_t TestFuncSample) const; - - /// The function-level similarity FS(A) for a function A is computed as - /// follows: - /// Compute a function-internal similarity FIS(A) by - /// computeSampleFunctionInternalOverlap(). Then, with the weight of - /// function A in base profile WB(A), and the weight of function A in test - /// profile WT(A), compute FS(A) = FIS(A) * (1.0 - fabs(WB(A) - WT(A))) - /// ranging in [0.0f to 1.0f] with 0.0 meaning no overlap. - double - computeSampleFunctionOverlap(const sampleprof::FunctionSamples *BaseFunc, - const sampleprof::FunctionSamples *TestFunc, - SampleOverlapStats *FuncOverlap, - uint64_t BaseFuncSample, - uint64_t TestFuncSample); - - /// Profile-level similarity (PS) is a weighted aggregate over function-level - /// similarities (FS). This method weights the FS value by the function - /// weights in the base and test profiles for the aggregation. - double weightByImportance(double FuncSimilarity, uint64_t BaseFuncSample, - uint64_t TestFuncSample) const; -}; -} // end anonymous namespace - -bool SampleOverlapAggregator::detectZeroSampleProfile( - raw_fd_ostream &OS) const { - bool HaveZeroSample = false; - if (ProfOverlap.BaseSample == 0) { - OS << "Sum of sample counts for profile " << BaseFilename << " is 0.\n"; - HaveZeroSample = true; - } - if (ProfOverlap.TestSample == 0) { - OS << "Sum of sample counts for profile " << TestFilename << " is 0.\n"; - HaveZeroSample = true; - } - return HaveZeroSample; -} - -double SampleOverlapAggregator::computeBlockSimilarity( - uint64_t BaseSample, uint64_t TestSample, - const SampleOverlapStats &FuncOverlap) const { - double BaseFrac = 0.0; - double TestFrac = 0.0; - if (FuncOverlap.BaseSample > 0) - BaseFrac = static_cast(BaseSample) / FuncOverlap.BaseSample; - if (FuncOverlap.TestSample > 0) - TestFrac = static_cast(TestSample) / FuncOverlap.TestSample; - return 1.0 - std::fabs(BaseFrac - TestFrac); -} - -void SampleOverlapAggregator::updateHotBlockOverlap(uint64_t BaseSample, - uint64_t TestSample, - uint64_t HotBlockCount) { - bool IsBaseHot = (BaseSample >= BaseHotThreshold); - bool IsTestHot = (TestSample >= TestHotThreshold); - if (!IsBaseHot && !IsTestHot) - return; - - HotBlockOverlap.UnionCount += HotBlockCount; - if (IsBaseHot) - HotBlockOverlap.BaseCount += HotBlockCount; - if (IsTestHot) - HotBlockOverlap.TestCount += HotBlockCount; - if (IsBaseHot && IsTestHot) - HotBlockOverlap.OverlapCount += HotBlockCount; -} - -void SampleOverlapAggregator::getHotFunctions( - const FuncSampleStatsMap &ProfStats, FuncSampleStatsMap &HotFunc, - uint64_t HotThreshold) const { - for (const auto &F : ProfStats) { - if (isFunctionHot(F.second, HotThreshold)) - HotFunc.emplace(F.first, F.second); - } -} - -void SampleOverlapAggregator::computeHotFuncOverlap() { - FuncSampleStatsMap BaseHotFunc; - getHotFunctions(BaseStats, BaseHotFunc, BaseHotThreshold); - HotFuncOverlap.BaseCount = BaseHotFunc.size(); - - FuncSampleStatsMap TestHotFunc; - getHotFunctions(TestStats, TestHotFunc, TestHotThreshold); - HotFuncOverlap.TestCount = TestHotFunc.size(); - HotFuncOverlap.UnionCount = HotFuncOverlap.TestCount; - - for (const auto &F : BaseHotFunc) { - if (TestHotFunc.count(F.first)) - ++HotFuncOverlap.OverlapCount; - else - ++HotFuncOverlap.UnionCount; - } -} - -void SampleOverlapAggregator::updateOverlapStatsForFunction( - uint64_t BaseSample, uint64_t TestSample, uint64_t HotBlockCount, - SampleOverlapStats &FuncOverlap, double &Difference, MatchStatus Status) { - assert(Status != MS_None && - "Match status should be updated before updating overlap statistics"); - if (Status == MS_FirstUnique) { - TestSample = 0; - FuncOverlap.BaseUniqueSample += BaseSample; - } else if (Status == MS_SecondUnique) { - BaseSample = 0; - FuncOverlap.TestUniqueSample += TestSample; - } else { - ++FuncOverlap.OverlapCount; - } - - FuncOverlap.UnionSample += std::max(BaseSample, TestSample); - FuncOverlap.OverlapSample += std::min(BaseSample, TestSample); - Difference += - 1.0 - computeBlockSimilarity(BaseSample, TestSample, FuncOverlap); - updateHotBlockOverlap(BaseSample, TestSample, HotBlockCount); -} - -void SampleOverlapAggregator::updateForUnmatchedCallee( - const sampleprof::FunctionSamples &Func, SampleOverlapStats &FuncOverlap, - double &Difference, MatchStatus Status) { - assert((Status == MS_FirstUnique || Status == MS_SecondUnique) && - "Status must be either of the two unmatched cases"); - FuncSampleStats FuncStats; - if (Status == MS_FirstUnique) { - getFuncSampleStats(Func, FuncStats, BaseHotThreshold); - updateOverlapStatsForFunction(FuncStats.SampleSum, 0, - FuncStats.HotBlockCount, FuncOverlap, - Difference, Status); - } else { - getFuncSampleStats(Func, FuncStats, TestHotThreshold); - updateOverlapStatsForFunction(0, FuncStats.SampleSum, - FuncStats.HotBlockCount, FuncOverlap, - Difference, Status); - } -} - -double SampleOverlapAggregator::computeSampleFunctionInternalOverlap( - const sampleprof::FunctionSamples &BaseFunc, - const sampleprof::FunctionSamples &TestFunc, - SampleOverlapStats &FuncOverlap) { - - using namespace sampleprof; - - double Difference = 0; - - // Accumulate Difference for regular line/block samples in the function. - // We match them through sort-merge join algorithm because - // FunctionSamples::getBodySamples() returns a map of sample counters ordered - // by their offsets. - MatchStep BlockIterStep( - BaseFunc.getBodySamples().cbegin(), BaseFunc.getBodySamples().cend(), - TestFunc.getBodySamples().cbegin(), TestFunc.getBodySamples().cend()); - BlockIterStep.updateOneStep(); - while (!BlockIterStep.areBothFinished()) { - uint64_t BaseSample = - BlockIterStep.isFirstFinished() - ? 0 - : BlockIterStep.getFirstIter()->second.getSamples(); - uint64_t TestSample = - BlockIterStep.isSecondFinished() - ? 0 - : BlockIterStep.getSecondIter()->second.getSamples(); - updateOverlapStatsForFunction(BaseSample, TestSample, 1, FuncOverlap, - Difference, BlockIterStep.getMatchStatus()); - - BlockIterStep.updateOneStep(); - } - - // Accumulate Difference for callsite lines in the function. We match - // them through sort-merge algorithm because - // FunctionSamples::getCallsiteSamples() returns a map of callsite records - // ordered by their offsets. - MatchStep CallsiteIterStep( - BaseFunc.getCallsiteSamples().cbegin(), - BaseFunc.getCallsiteSamples().cend(), - TestFunc.getCallsiteSamples().cbegin(), - TestFunc.getCallsiteSamples().cend()); - CallsiteIterStep.updateOneStep(); - while (!CallsiteIterStep.areBothFinished()) { - MatchStatus CallsiteStepStatus = CallsiteIterStep.getMatchStatus(); - assert(CallsiteStepStatus != MS_None && - "Match status should be updated before entering loop body"); - - if (CallsiteStepStatus != MS_Match) { - auto Callsite = (CallsiteStepStatus == MS_FirstUnique) - ? CallsiteIterStep.getFirstIter() - : CallsiteIterStep.getSecondIter(); - for (const auto &F : Callsite->second) - updateForUnmatchedCallee(F.second, FuncOverlap, Difference, - CallsiteStepStatus); - } else { - // There may be multiple inlinees at the same offset, so we need to try - // matching all of them. This match is implemented through sort-merge - // algorithm because callsite records at the same offset are ordered by - // function names. - MatchStep CalleeIterStep( - CallsiteIterStep.getFirstIter()->second.cbegin(), - CallsiteIterStep.getFirstIter()->second.cend(), - CallsiteIterStep.getSecondIter()->second.cbegin(), - CallsiteIterStep.getSecondIter()->second.cend()); - CalleeIterStep.updateOneStep(); - while (!CalleeIterStep.areBothFinished()) { - MatchStatus CalleeStepStatus = CalleeIterStep.getMatchStatus(); - if (CalleeStepStatus != MS_Match) { - auto Callee = (CalleeStepStatus == MS_FirstUnique) - ? CalleeIterStep.getFirstIter() - : CalleeIterStep.getSecondIter(); - updateForUnmatchedCallee(Callee->second, FuncOverlap, Difference, - CalleeStepStatus); - } else { - // An inlined function can contain other inlinees inside, so compute - // the Difference recursively. - Difference += 2.0 - 2 * computeSampleFunctionInternalOverlap( - CalleeIterStep.getFirstIter()->second, - CalleeIterStep.getSecondIter()->second, - FuncOverlap); - } - CalleeIterStep.updateOneStep(); - } - } - CallsiteIterStep.updateOneStep(); - } - - // Difference reflects the total differences of line/block samples in this - // function and ranges in [0.0f to 2.0f]. Take (2.0 - Difference) / 2 to - // reflect the similarity between function profiles in [0.0f to 1.0f]. - return (2.0 - Difference) / 2; -} - -double SampleOverlapAggregator::weightForFuncSimilarity( - double FuncInternalSimilarity, uint64_t BaseFuncSample, - uint64_t TestFuncSample) const { - // Compute the weight as the distance between the function weights in two - // profiles. - double BaseFrac = 0.0; - double TestFrac = 0.0; - assert(ProfOverlap.BaseSample > 0 && - "Total samples in base profile should be greater than 0"); - BaseFrac = static_cast(BaseFuncSample) / ProfOverlap.BaseSample; - assert(ProfOverlap.TestSample > 0 && - "Total samples in test profile should be greater than 0"); - TestFrac = static_cast(TestFuncSample) / ProfOverlap.TestSample; - double WeightDistance = std::fabs(BaseFrac - TestFrac); - - // Take WeightDistance into the similarity. - return FuncInternalSimilarity * (1 - WeightDistance); -} - -double -SampleOverlapAggregator::weightByImportance(double FuncSimilarity, - uint64_t BaseFuncSample, - uint64_t TestFuncSample) const { - - double BaseFrac = 0.0; - double TestFrac = 0.0; - assert(ProfOverlap.BaseSample > 0 && - "Total samples in base profile should be greater than 0"); - BaseFrac = static_cast(BaseFuncSample) / ProfOverlap.BaseSample / 2.0; - assert(ProfOverlap.TestSample > 0 && - "Total samples in test profile should be greater than 0"); - TestFrac = static_cast(TestFuncSample) / ProfOverlap.TestSample / 2.0; - return FuncSimilarity * (BaseFrac + TestFrac); -} - -double SampleOverlapAggregator::computeSampleFunctionOverlap( - const sampleprof::FunctionSamples *BaseFunc, - const sampleprof::FunctionSamples *TestFunc, - SampleOverlapStats *FuncOverlap, uint64_t BaseFuncSample, - uint64_t TestFuncSample) { - // Default function internal similarity before weighted, meaning two functions - // has no overlap. - const double DefaultFuncInternalSimilarity = 0; - double FuncSimilarity; - double FuncInternalSimilarity; - - // If BaseFunc or TestFunc is nullptr, it means the functions do not overlap. - // In this case, we use DefaultFuncInternalSimilarity as the function internal - // similarity. - if (!BaseFunc || !TestFunc) { - FuncInternalSimilarity = DefaultFuncInternalSimilarity; - } else { - assert(FuncOverlap != nullptr && - "FuncOverlap should be provided in this case"); - FuncInternalSimilarity = computeSampleFunctionInternalOverlap( - *BaseFunc, *TestFunc, *FuncOverlap); - // Now, FuncInternalSimilarity may be a little less than 0 due to - // imprecision of floating point accumulations. Make it zero if the - // difference is below Epsilon. - FuncInternalSimilarity = (std::fabs(FuncInternalSimilarity - 0) < Epsilon) - ? 0 - : FuncInternalSimilarity; - } - FuncSimilarity = weightForFuncSimilarity(FuncInternalSimilarity, - BaseFuncSample, TestFuncSample); - return FuncSimilarity; -} - -void SampleOverlapAggregator::computeSampleProfileOverlap(raw_fd_ostream &OS) { - using namespace sampleprof; - - std::unordered_map - BaseFuncProf; - const auto &BaseProfiles = BaseReader->getProfiles(); - for (const auto &BaseFunc : BaseProfiles) { - BaseFuncProf.emplace(BaseFunc.second.getContext(), &(BaseFunc.second)); - } - ProfOverlap.UnionCount = BaseFuncProf.size(); - - const auto &TestProfiles = TestReader->getProfiles(); - for (const auto &TestFunc : TestProfiles) { - SampleOverlapStats FuncOverlap; - FuncOverlap.TestName = TestFunc.second.getContext(); - assert(TestStats.count(FuncOverlap.TestName) && - "TestStats should have records for all functions in test profile " - "except inlinees"); - FuncOverlap.TestSample = TestStats[FuncOverlap.TestName].SampleSum; - - bool Matched = false; - const auto Match = BaseFuncProf.find(FuncOverlap.TestName); - if (Match == BaseFuncProf.end()) { - const FuncSampleStats &FuncStats = TestStats[FuncOverlap.TestName]; - ++ProfOverlap.TestUniqueCount; - ProfOverlap.TestUniqueSample += FuncStats.SampleSum; - FuncOverlap.TestUniqueSample = FuncStats.SampleSum; - - updateHotBlockOverlap(0, FuncStats.SampleSum, FuncStats.HotBlockCount); - - double FuncSimilarity = computeSampleFunctionOverlap( - nullptr, nullptr, nullptr, 0, FuncStats.SampleSum); - ProfOverlap.Similarity += - weightByImportance(FuncSimilarity, 0, FuncStats.SampleSum); - - ++ProfOverlap.UnionCount; - ProfOverlap.UnionSample += FuncStats.SampleSum; - } else { - ++ProfOverlap.OverlapCount; - - // Two functions match with each other. Compute function-level overlap and - // aggregate them into profile-level overlap. - FuncOverlap.BaseName = Match->second->getContext(); - assert(BaseStats.count(FuncOverlap.BaseName) && - "BaseStats should have records for all functions in base profile " - "except inlinees"); - FuncOverlap.BaseSample = BaseStats[FuncOverlap.BaseName].SampleSum; - - FuncOverlap.Similarity = computeSampleFunctionOverlap( - Match->second, &TestFunc.second, &FuncOverlap, FuncOverlap.BaseSample, - FuncOverlap.TestSample); - ProfOverlap.Similarity += - weightByImportance(FuncOverlap.Similarity, FuncOverlap.BaseSample, - FuncOverlap.TestSample); - ProfOverlap.OverlapSample += FuncOverlap.OverlapSample; - ProfOverlap.UnionSample += FuncOverlap.UnionSample; - - // Accumulate the percentage of base unique and test unique samples into - // ProfOverlap. - ProfOverlap.BaseUniqueSample += FuncOverlap.BaseUniqueSample; - ProfOverlap.TestUniqueSample += FuncOverlap.TestUniqueSample; - - // Remove matched base functions for later reporting functions not found - // in test profile. - BaseFuncProf.erase(Match); - Matched = true; - } - - // Print function-level similarity information if specified by options. - assert(TestStats.count(FuncOverlap.TestName) && - "TestStats should have records for all functions in test profile " - "except inlinees"); - if (TestStats[FuncOverlap.TestName].MaxSample >= FuncFilter.ValueCutoff || - (Matched && FuncOverlap.Similarity < LowSimilarityThreshold) || - (Matched && !FuncFilter.NameFilter.empty() && - FuncOverlap.BaseName.toString().find(FuncFilter.NameFilter) != - std::string::npos)) { - assert(ProfOverlap.BaseSample > 0 && - "Total samples in base profile should be greater than 0"); - FuncOverlap.BaseWeight = - static_cast(FuncOverlap.BaseSample) / ProfOverlap.BaseSample; - assert(ProfOverlap.TestSample > 0 && - "Total samples in test profile should be greater than 0"); - FuncOverlap.TestWeight = - static_cast(FuncOverlap.TestSample) / ProfOverlap.TestSample; - FuncSimilarityDump.emplace(FuncOverlap.BaseWeight, FuncOverlap); - } - } - - // Traverse through functions in base profile but not in test profile. - for (const auto &F : BaseFuncProf) { - assert(BaseStats.count(F.second->getContext()) && - "BaseStats should have records for all functions in base profile " - "except inlinees"); - const FuncSampleStats &FuncStats = BaseStats[F.second->getContext()]; - ++ProfOverlap.BaseUniqueCount; - ProfOverlap.BaseUniqueSample += FuncStats.SampleSum; - - updateHotBlockOverlap(FuncStats.SampleSum, 0, FuncStats.HotBlockCount); - - double FuncSimilarity = computeSampleFunctionOverlap( - nullptr, nullptr, nullptr, FuncStats.SampleSum, 0); - ProfOverlap.Similarity += - weightByImportance(FuncSimilarity, FuncStats.SampleSum, 0); - - ProfOverlap.UnionSample += FuncStats.SampleSum; - } - - // Now, ProfSimilarity may be a little greater than 1 due to imprecision - // of floating point accumulations. Make it 1.0 if the difference is below - // Epsilon. - ProfOverlap.Similarity = (std::fabs(ProfOverlap.Similarity - 1) < Epsilon) - ? 1 - : ProfOverlap.Similarity; - - computeHotFuncOverlap(); -} - -void SampleOverlapAggregator::initializeSampleProfileOverlap() { - const auto &BaseProf = BaseReader->getProfiles(); - for (const auto &I : BaseProf) { - ++ProfOverlap.BaseCount; - FuncSampleStats FuncStats; - getFuncSampleStats(I.second, FuncStats, BaseHotThreshold); - ProfOverlap.BaseSample += FuncStats.SampleSum; - BaseStats.emplace(I.second.getContext(), FuncStats); - } - - const auto &TestProf = TestReader->getProfiles(); - for (const auto &I : TestProf) { - ++ProfOverlap.TestCount; - FuncSampleStats FuncStats; - getFuncSampleStats(I.second, FuncStats, TestHotThreshold); - ProfOverlap.TestSample += FuncStats.SampleSum; - TestStats.emplace(I.second.getContext(), FuncStats); - } - - ProfOverlap.BaseName = StringRef(BaseFilename); - ProfOverlap.TestName = StringRef(TestFilename); -} - -void SampleOverlapAggregator::dumpFuncSimilarity(raw_fd_ostream &OS) const { - using namespace sampleprof; - - if (FuncSimilarityDump.empty()) - return; - - formatted_raw_ostream FOS(OS); - FOS << "Function-level details:\n"; - FOS << "Base weight"; - FOS.PadToColumn(TestWeightCol); - FOS << "Test weight"; - FOS.PadToColumn(SimilarityCol); - FOS << "Similarity"; - FOS.PadToColumn(OverlapCol); - FOS << "Overlap"; - FOS.PadToColumn(BaseUniqueCol); - FOS << "Base unique"; - FOS.PadToColumn(TestUniqueCol); - FOS << "Test unique"; - FOS.PadToColumn(BaseSampleCol); - FOS << "Base samples"; - FOS.PadToColumn(TestSampleCol); - FOS << "Test samples"; - FOS.PadToColumn(FuncNameCol); - FOS << "Function name\n"; - for (const auto &F : FuncSimilarityDump) { - double OverlapPercent = - F.second.UnionSample > 0 - ? static_cast(F.second.OverlapSample) / F.second.UnionSample - : 0; - double BaseUniquePercent = - F.second.BaseSample > 0 - ? static_cast(F.second.BaseUniqueSample) / - F.second.BaseSample - : 0; - double TestUniquePercent = - F.second.TestSample > 0 - ? static_cast(F.second.TestUniqueSample) / - F.second.TestSample - : 0; - - FOS << format("%.2f%%", F.second.BaseWeight * 100); - FOS.PadToColumn(TestWeightCol); - FOS << format("%.2f%%", F.second.TestWeight * 100); - FOS.PadToColumn(SimilarityCol); - FOS << format("%.2f%%", F.second.Similarity * 100); - FOS.PadToColumn(OverlapCol); - FOS << format("%.2f%%", OverlapPercent * 100); - FOS.PadToColumn(BaseUniqueCol); - FOS << format("%.2f%%", BaseUniquePercent * 100); - FOS.PadToColumn(TestUniqueCol); - FOS << format("%.2f%%", TestUniquePercent * 100); - FOS.PadToColumn(BaseSampleCol); - FOS << F.second.BaseSample; - FOS.PadToColumn(TestSampleCol); - FOS << F.second.TestSample; - FOS.PadToColumn(FuncNameCol); - FOS << F.second.TestName.toString() << "\n"; - } -} - -void SampleOverlapAggregator::dumpProgramSummary(raw_fd_ostream &OS) const { - OS << "Profile overlap infomation for base_profile: " - << ProfOverlap.BaseName.toString() - << " and test_profile: " << ProfOverlap.TestName.toString() - << "\nProgram level:\n"; - - OS << " Whole program profile similarity: " - << format("%.3f%%", ProfOverlap.Similarity * 100) << "\n"; - - assert(ProfOverlap.UnionSample > 0 && - "Total samples in two profile should be greater than 0"); - double OverlapPercent = - static_cast(ProfOverlap.OverlapSample) / ProfOverlap.UnionSample; - assert(ProfOverlap.BaseSample > 0 && - "Total samples in base profile should be greater than 0"); - double BaseUniquePercent = static_cast(ProfOverlap.BaseUniqueSample) / - ProfOverlap.BaseSample; - assert(ProfOverlap.TestSample > 0 && - "Total samples in test profile should be greater than 0"); - double TestUniquePercent = static_cast(ProfOverlap.TestUniqueSample) / - ProfOverlap.TestSample; - - OS << " Whole program sample overlap: " - << format("%.3f%%", OverlapPercent * 100) << "\n"; - OS << " percentage of samples unique in base profile: " - << format("%.3f%%", BaseUniquePercent * 100) << "\n"; - OS << " percentage of samples unique in test profile: " - << format("%.3f%%", TestUniquePercent * 100) << "\n"; - OS << " total samples in base profile: " << ProfOverlap.BaseSample << "\n" - << " total samples in test profile: " << ProfOverlap.TestSample << "\n"; - - assert(ProfOverlap.UnionCount > 0 && - "There should be at least one function in two input profiles"); - double FuncOverlapPercent = - static_cast(ProfOverlap.OverlapCount) / ProfOverlap.UnionCount; - OS << " Function overlap: " << format("%.3f%%", FuncOverlapPercent * 100) - << "\n"; - OS << " overlap functions: " << ProfOverlap.OverlapCount << "\n"; - OS << " functions unique in base profile: " << ProfOverlap.BaseUniqueCount - << "\n"; - OS << " functions unique in test profile: " << ProfOverlap.TestUniqueCount - << "\n"; -} - -void SampleOverlapAggregator::dumpHotFuncAndBlockOverlap( - raw_fd_ostream &OS) const { - assert(HotFuncOverlap.UnionCount > 0 && - "There should be at least one hot function in two input profiles"); - OS << " Hot-function overlap: " - << format("%.3f%%", static_cast(HotFuncOverlap.OverlapCount) / - HotFuncOverlap.UnionCount * 100) - << "\n"; - OS << " overlap hot functions: " << HotFuncOverlap.OverlapCount << "\n"; - OS << " hot functions unique in base profile: " - << HotFuncOverlap.BaseCount - HotFuncOverlap.OverlapCount << "\n"; - OS << " hot functions unique in test profile: " - << HotFuncOverlap.TestCount - HotFuncOverlap.OverlapCount << "\n"; - - assert(HotBlockOverlap.UnionCount > 0 && - "There should be at least one hot block in two input profiles"); - OS << " Hot-block overlap: " - << format("%.3f%%", static_cast(HotBlockOverlap.OverlapCount) / - HotBlockOverlap.UnionCount * 100) - << "\n"; - OS << " overlap hot blocks: " << HotBlockOverlap.OverlapCount << "\n"; - OS << " hot blocks unique in base profile: " - << HotBlockOverlap.BaseCount - HotBlockOverlap.OverlapCount << "\n"; - OS << " hot blocks unique in test profile: " - << HotBlockOverlap.TestCount - HotBlockOverlap.OverlapCount << "\n"; -} - -std::error_code SampleOverlapAggregator::loadProfiles() { - using namespace sampleprof; - - LLVMContext Context; - auto BaseReaderOrErr = SampleProfileReader::create(BaseFilename, Context, - FSDiscriminatorPassOption); - if (std::error_code EC = BaseReaderOrErr.getError()) - exitWithErrorCode(EC, BaseFilename); - - auto TestReaderOrErr = SampleProfileReader::create(TestFilename, Context, - FSDiscriminatorPassOption); - if (std::error_code EC = TestReaderOrErr.getError()) - exitWithErrorCode(EC, TestFilename); - - BaseReader = std::move(BaseReaderOrErr.get()); - TestReader = std::move(TestReaderOrErr.get()); - - if (std::error_code EC = BaseReader->read()) - exitWithErrorCode(EC, BaseFilename); - if (std::error_code EC = TestReader->read()) - exitWithErrorCode(EC, TestFilename); - if (BaseReader->profileIsProbeBased() != TestReader->profileIsProbeBased()) - exitWithError( - "cannot compare probe-based profile with non-probe-based profile"); - if (BaseReader->profileIsCSFlat() != TestReader->profileIsCSFlat()) - exitWithError("cannot compare CS profile with non-CS profile"); - - // Load BaseHotThreshold and TestHotThreshold as 99-percentile threshold in - // profile summary. - ProfileSummary &BasePS = BaseReader->getSummary(); - ProfileSummary &TestPS = TestReader->getSummary(); - BaseHotThreshold = - ProfileSummaryBuilder::getHotCountThreshold(BasePS.getDetailedSummary()); - TestHotThreshold = - ProfileSummaryBuilder::getHotCountThreshold(TestPS.getDetailedSummary()); - - return std::error_code(); -} - -void overlapSampleProfile(const std::string &BaseFilename, - const std::string &TestFilename, - const OverlapFuncFilters &FuncFilter, - uint64_t SimilarityCutoff, raw_fd_ostream &OS) { - using namespace sampleprof; - - // We use 0.000005 to initialize OverlapAggr.Epsilon because the final metrics - // report 2--3 places after decimal point in percentage numbers. - SampleOverlapAggregator OverlapAggr( - BaseFilename, TestFilename, - static_cast(SimilarityCutoff) / 1000000, 0.000005, FuncFilter); - if (std::error_code EC = OverlapAggr.loadProfiles()) - exitWithErrorCode(EC); - - OverlapAggr.initializeSampleProfileOverlap(); - if (OverlapAggr.detectZeroSampleProfile(OS)) - return; - - OverlapAggr.computeSampleProfileOverlap(OS); - - OverlapAggr.dumpProgramSummary(OS); - OverlapAggr.dumpHotFuncAndBlockOverlap(OS); - OverlapAggr.dumpFuncSimilarity(OS); -} - -static int overlap_main(int argc, const char *argv[]) { - cl::opt BaseFilename(cl::Positional, cl::Required, - cl::desc("")); - cl::opt TestFilename(cl::Positional, cl::Required, - cl::desc("")); - cl::opt Output("output", cl::value_desc("output"), cl::init("-"), - cl::desc("Output file")); - cl::alias OutputA("o", cl::desc("Alias for --output"), cl::aliasopt(Output)); - cl::opt IsCS( - "cs", cl::init(false), - cl::desc("For context sensitive PGO counts. Does not work with CSSPGO.")); - cl::opt ValueCutoff( - "value-cutoff", cl::init(-1), - cl::desc( - "Function level overlap information for every function (with calling " - "context for csspgo) in test " - "profile with max count value greater then the parameter value")); - cl::opt FuncNameFilter( - "function", - cl::desc("Function level overlap information for matching functions. For " - "CSSPGO this takes a a function name with calling context")); - cl::opt SimilarityCutoff( - "similarity-cutoff", cl::init(0), - cl::desc("For sample profiles, list function names (with calling context " - "for csspgo) for overlapped functions " - "with similarities below the cutoff (percentage times 10000).")); - cl::opt ProfileKind( - cl::desc("Profile kind:"), cl::init(instr), - cl::values(clEnumVal(instr, "Instrumentation profile (default)"), - clEnumVal(sample, "Sample profile"))); - cl::ParseCommandLineOptions(argc, argv, "LLVM profile data overlap tool\n"); - - std::error_code EC; - raw_fd_ostream OS(Output.data(), EC, sys::fs::OF_TextWithCRLF); - if (EC) - exitWithErrorCode(EC, Output); - - if (ProfileKind == instr) - overlapInstrProfile(BaseFilename, TestFilename, - OverlapFuncFilters{ValueCutoff, FuncNameFilter}, OS, - IsCS); - else - overlapSampleProfile(BaseFilename, TestFilename, - OverlapFuncFilters{ValueCutoff, FuncNameFilter}, - SimilarityCutoff, OS); - - return 0; -} - -namespace { -struct ValueSitesStats { - ValueSitesStats() - : TotalNumValueSites(0), TotalNumValueSitesWithValueProfile(0), - TotalNumValues(0) {} - uint64_t TotalNumValueSites; - uint64_t TotalNumValueSitesWithValueProfile; - uint64_t TotalNumValues; - std::vector ValueSitesHistogram; -}; -} // namespace - -static void traverseAllValueSites(const InstrProfRecord &Func, uint32_t VK, - ValueSitesStats &Stats, raw_fd_ostream &OS, - InstrProfSymtab *Symtab) { - uint32_t NS = Func.getNumValueSites(VK); - Stats.TotalNumValueSites += NS; - for (size_t I = 0; I < NS; ++I) { - uint32_t NV = Func.getNumValueDataForSite(VK, I); - std::unique_ptr VD = Func.getValueForSite(VK, I); - Stats.TotalNumValues += NV; - if (NV) { - Stats.TotalNumValueSitesWithValueProfile++; - if (NV > Stats.ValueSitesHistogram.size()) - Stats.ValueSitesHistogram.resize(NV, 0); - Stats.ValueSitesHistogram[NV - 1]++; - } - - uint64_t SiteSum = 0; - for (uint32_t V = 0; V < NV; V++) - SiteSum += VD[V].Count; - if (SiteSum == 0) - SiteSum = 1; - - for (uint32_t V = 0; V < NV; V++) { - OS << "\t[ " << format("%2u", I) << ", "; - if (Symtab == nullptr) - OS << format("%4" PRIu64, VD[V].Value); - else - OS << Symtab->getFuncName(VD[V].Value); - OS << ", " << format("%10" PRId64, VD[V].Count) << " ] (" - << format("%.2f%%", (VD[V].Count * 100.0 / SiteSum)) << ")\n"; - } - } -} - -static void showValueSitesStats(raw_fd_ostream &OS, uint32_t VK, - ValueSitesStats &Stats) { - OS << " Total number of sites: " << Stats.TotalNumValueSites << "\n"; - OS << " Total number of sites with values: " - << Stats.TotalNumValueSitesWithValueProfile << "\n"; - OS << " Total number of profiled values: " << Stats.TotalNumValues << "\n"; - - OS << " Value sites histogram:\n\tNumTargets, SiteCount\n"; - for (unsigned I = 0; I < Stats.ValueSitesHistogram.size(); I++) { - if (Stats.ValueSitesHistogram[I] > 0) - OS << "\t" << I + 1 << ", " << Stats.ValueSitesHistogram[I] << "\n"; - } -} - -static int showInstrProfile(const std::string &Filename, bool ShowCounts, - uint32_t TopN, bool ShowIndirectCallTargets, - bool ShowMemOPSizes, bool ShowDetailedSummary, - std::vector DetailedSummaryCutoffs, - bool ShowAllFunctions, bool ShowCS, - uint64_t ValueCutoff, bool OnlyListBelow, - const std::string &ShowFunction, bool TextFormat, - bool ShowBinaryIds, bool ShowCovered, - raw_fd_ostream &OS) { - auto ReaderOrErr = InstrProfReader::create(Filename); - std::vector Cutoffs = std::move(DetailedSummaryCutoffs); - if (ShowDetailedSummary && Cutoffs.empty()) { - Cutoffs = {800000, 900000, 950000, 990000, 999000, 999900, 999990}; - } - InstrProfSummaryBuilder Builder(std::move(Cutoffs)); - if (Error E = ReaderOrErr.takeError()) - exitWithError(std::move(E), Filename); - - auto Reader = std::move(ReaderOrErr.get()); - bool IsIRInstr = Reader->isIRLevelProfile(); - size_t ShownFunctions = 0; - size_t BelowCutoffFunctions = 0; - int NumVPKind = IPVK_Last - IPVK_First + 1; - std::vector VPStats(NumVPKind); - - auto MinCmp = [](const std::pair &v1, - const std::pair &v2) { - return v1.second > v2.second; - }; - - std::priority_queue, - std::vector>, - decltype(MinCmp)> - HottestFuncs(MinCmp); - - if (!TextFormat && OnlyListBelow) { - OS << "The list of functions with the maximum counter less than " - << ValueCutoff << ":\n"; - } - - // Add marker so that IR-level instrumentation round-trips properly. - if (TextFormat && IsIRInstr) - OS << ":ir\n"; - - for (const auto &Func : *Reader) { - if (Reader->isIRLevelProfile()) { - bool FuncIsCS = NamedInstrProfRecord::hasCSFlagInHash(Func.Hash); - if (FuncIsCS != ShowCS) - continue; - } - bool Show = ShowAllFunctions || - (!ShowFunction.empty() && Func.Name.contains(ShowFunction)); - - bool doTextFormatDump = (Show && TextFormat); - - if (doTextFormatDump) { - InstrProfSymtab &Symtab = Reader->getSymtab(); - InstrProfWriter::writeRecordInText(Func.Name, Func.Hash, Func, Symtab, - OS); - continue; - } - - assert(Func.Counts.size() > 0 && "function missing entry counter"); - Builder.addRecord(Func); - - if (ShowCovered) { - if (std::any_of(Func.Counts.begin(), Func.Counts.end(), - [](uint64_t C) { return C; })) - OS << Func.Name << "\n"; - continue; - } - - uint64_t FuncMax = 0; - uint64_t FuncSum = 0; - for (size_t I = 0, E = Func.Counts.size(); I < E; ++I) { - if (Func.Counts[I] == (uint64_t)-1) - continue; - FuncMax = std::max(FuncMax, Func.Counts[I]); - FuncSum += Func.Counts[I]; - } - - if (FuncMax < ValueCutoff) { - ++BelowCutoffFunctions; - if (OnlyListBelow) { - OS << " " << Func.Name << ": (Max = " << FuncMax - << " Sum = " << FuncSum << ")\n"; - } - continue; - } else if (OnlyListBelow) - continue; - - if (TopN) { - if (HottestFuncs.size() == TopN) { - if (HottestFuncs.top().second < FuncMax) { - HottestFuncs.pop(); - HottestFuncs.emplace(std::make_pair(std::string(Func.Name), FuncMax)); - } - } else - HottestFuncs.emplace(std::make_pair(std::string(Func.Name), FuncMax)); - } - - if (Show) { - if (!ShownFunctions) - OS << "Counters:\n"; - - ++ShownFunctions; - - OS << " " << Func.Name << ":\n" - << " Hash: " << format("0x%016" PRIx64, Func.Hash) << "\n" - << " Counters: " << Func.Counts.size() << "\n"; - if (!IsIRInstr) - OS << " Function count: " << Func.Counts[0] << "\n"; - - if (ShowIndirectCallTargets) - OS << " Indirect Call Site Count: " - << Func.getNumValueSites(IPVK_IndirectCallTarget) << "\n"; - - uint32_t NumMemOPCalls = Func.getNumValueSites(IPVK_MemOPSize); - if (ShowMemOPSizes && NumMemOPCalls > 0) - OS << " Number of Memory Intrinsics Calls: " << NumMemOPCalls - << "\n"; - - if (ShowCounts) { - OS << " Block counts: ["; - size_t Start = (IsIRInstr ? 0 : 1); - for (size_t I = Start, E = Func.Counts.size(); I < E; ++I) { - OS << (I == Start ? "" : ", ") << Func.Counts[I]; - } - OS << "]\n"; - } - - if (ShowIndirectCallTargets) { - OS << " Indirect Target Results:\n"; - traverseAllValueSites(Func, IPVK_IndirectCallTarget, - VPStats[IPVK_IndirectCallTarget], OS, - &(Reader->getSymtab())); - } - - if (ShowMemOPSizes && NumMemOPCalls > 0) { - OS << " Memory Intrinsic Size Results:\n"; - traverseAllValueSites(Func, IPVK_MemOPSize, VPStats[IPVK_MemOPSize], OS, - nullptr); - } - } - } - if (Reader->hasError()) - exitWithError(Reader->getError(), Filename); - - if (TextFormat || ShowCovered) - return 0; - std::unique_ptr PS(Builder.getSummary()); - bool IsIR = Reader->isIRLevelProfile(); - OS << "Instrumentation level: " << (IsIR ? "IR" : "Front-end"); - if (IsIR) - OS << " entry_first = " << Reader->instrEntryBBEnabled(); - OS << "\n"; - if (ShowAllFunctions || !ShowFunction.empty()) - OS << "Functions shown: " << ShownFunctions << "\n"; - OS << "Total functions: " << PS->getNumFunctions() << "\n"; - if (ValueCutoff > 0) { - OS << "Number of functions with maximum count (< " << ValueCutoff - << "): " << BelowCutoffFunctions << "\n"; - OS << "Number of functions with maximum count (>= " << ValueCutoff - << "): " << PS->getNumFunctions() - BelowCutoffFunctions << "\n"; - } - OS << "Maximum function count: " << PS->getMaxFunctionCount() << "\n"; - OS << "Maximum internal block count: " << PS->getMaxInternalCount() << "\n"; - - if (TopN) { - std::vector> SortedHottestFuncs; - while (!HottestFuncs.empty()) { - SortedHottestFuncs.emplace_back(HottestFuncs.top()); - HottestFuncs.pop(); - } - OS << "Top " << TopN - << " functions with the largest internal block counts: \n"; - for (auto &hotfunc : llvm::reverse(SortedHottestFuncs)) - OS << " " << hotfunc.first << ", max count = " << hotfunc.second << "\n"; - } - - if (ShownFunctions && ShowIndirectCallTargets) { - OS << "Statistics for indirect call sites profile:\n"; - showValueSitesStats(OS, IPVK_IndirectCallTarget, - VPStats[IPVK_IndirectCallTarget]); - } - - if (ShownFunctions && ShowMemOPSizes) { - OS << "Statistics for memory intrinsic calls sizes profile:\n"; - showValueSitesStats(OS, IPVK_MemOPSize, VPStats[IPVK_MemOPSize]); - } - - if (ShowDetailedSummary) { - OS << "Total number of blocks: " << PS->getNumCounts() << "\n"; - OS << "Total count: " << PS->getTotalCount() << "\n"; - PS->printDetailedSummary(OS); - } - - if (ShowBinaryIds) - if (Error E = Reader->printBinaryIds(OS)) - exitWithError(std::move(E), Filename); - - return 0; -} - -static void showSectionInfo(sampleprof::SampleProfileReader *Reader, - raw_fd_ostream &OS) { - if (!Reader->dumpSectionInfo(OS)) { - WithColor::warning() << "-show-sec-info-only is only supported for " - << "sample profile in extbinary format and is " - << "ignored for other formats.\n"; - return; - } -} - -namespace { -struct HotFuncInfo { - std::string FuncName; - uint64_t TotalCount; - double TotalCountPercent; - uint64_t MaxCount; - uint64_t EntryCount; - - HotFuncInfo() - : TotalCount(0), TotalCountPercent(0.0f), MaxCount(0), EntryCount(0) {} - - HotFuncInfo(StringRef FN, uint64_t TS, double TSP, uint64_t MS, uint64_t ES) - : FuncName(FN.begin(), FN.end()), TotalCount(TS), TotalCountPercent(TSP), - MaxCount(MS), EntryCount(ES) {} -}; -} // namespace - -// Print out detailed information about hot functions in PrintValues vector. -// Users specify titles and offset of every columns through ColumnTitle and -// ColumnOffset. The size of ColumnTitle and ColumnOffset need to be the same -// and at least 4. Besides, users can optionally give a HotFuncMetric string to -// print out or let it be an empty string. -static void dumpHotFunctionList(const std::vector &ColumnTitle, - const std::vector &ColumnOffset, - const std::vector &PrintValues, - uint64_t HotFuncCount, uint64_t TotalFuncCount, - uint64_t HotProfCount, uint64_t TotalProfCount, - const std::string &HotFuncMetric, - uint32_t TopNFunctions, raw_fd_ostream &OS) { - assert(ColumnOffset.size() == ColumnTitle.size() && - "ColumnOffset and ColumnTitle should have the same size"); - assert(ColumnTitle.size() >= 4 && - "ColumnTitle should have at least 4 elements"); - assert(TotalFuncCount > 0 && - "There should be at least one function in the profile"); - double TotalProfPercent = 0; - if (TotalProfCount > 0) - TotalProfPercent = static_cast(HotProfCount) / TotalProfCount * 100; - - formatted_raw_ostream FOS(OS); - FOS << HotFuncCount << " out of " << TotalFuncCount - << " functions with profile (" - << format("%.2f%%", - (static_cast(HotFuncCount) / TotalFuncCount * 100)) - << ") are considered hot functions"; - if (!HotFuncMetric.empty()) - FOS << " (" << HotFuncMetric << ")"; - FOS << ".\n"; - FOS << HotProfCount << " out of " << TotalProfCount << " profile counts (" - << format("%.2f%%", TotalProfPercent) << ") are from hot functions.\n"; - - for (size_t I = 0; I < ColumnTitle.size(); ++I) { - FOS.PadToColumn(ColumnOffset[I]); - FOS << ColumnTitle[I]; - } - FOS << "\n"; - - uint32_t Count = 0; - for (const auto &R : PrintValues) { - if (TopNFunctions && (Count++ == TopNFunctions)) - break; - FOS.PadToColumn(ColumnOffset[0]); - FOS << R.TotalCount << " (" << format("%.2f%%", R.TotalCountPercent) << ")"; - FOS.PadToColumn(ColumnOffset[1]); - FOS << R.MaxCount; - FOS.PadToColumn(ColumnOffset[2]); - FOS << R.EntryCount; - FOS.PadToColumn(ColumnOffset[3]); - FOS << R.FuncName << "\n"; - } -} - -static int showHotFunctionList(const sampleprof::SampleProfileMap &Profiles, - ProfileSummary &PS, uint32_t TopN, - raw_fd_ostream &OS) { - using namespace sampleprof; - - const uint32_t HotFuncCutoff = 990000; - auto &SummaryVector = PS.getDetailedSummary(); - uint64_t MinCountThreshold = 0; - for (const ProfileSummaryEntry &SummaryEntry : SummaryVector) { - if (SummaryEntry.Cutoff == HotFuncCutoff) { - MinCountThreshold = SummaryEntry.MinCount; - break; - } - } - - // Traverse all functions in the profile and keep only hot functions. - // The following loop also calculates the sum of total samples of all - // functions. - std::multimap, - std::greater> - HotFunc; - uint64_t ProfileTotalSample = 0; - uint64_t HotFuncSample = 0; - uint64_t HotFuncCount = 0; - - for (const auto &I : Profiles) { - FuncSampleStats FuncStats; - const FunctionSamples &FuncProf = I.second; - ProfileTotalSample += FuncProf.getTotalSamples(); - getFuncSampleStats(FuncProf, FuncStats, MinCountThreshold); - - if (isFunctionHot(FuncStats, MinCountThreshold)) { - HotFunc.emplace(FuncProf.getTotalSamples(), - std::make_pair(&(I.second), FuncStats.MaxSample)); - HotFuncSample += FuncProf.getTotalSamples(); - ++HotFuncCount; - } - } - - std::vector ColumnTitle{"Total sample (%)", "Max sample", - "Entry sample", "Function name"}; - std::vector ColumnOffset{0, 24, 42, 58}; - std::string Metric = - std::string("max sample >= ") + std::to_string(MinCountThreshold); - std::vector PrintValues; - for (const auto &FuncPair : HotFunc) { - const FunctionSamples &Func = *FuncPair.second.first; - double TotalSamplePercent = - (ProfileTotalSample > 0) - ? (Func.getTotalSamples() * 100.0) / ProfileTotalSample - : 0; - PrintValues.emplace_back(HotFuncInfo( - Func.getContext().toString(), Func.getTotalSamples(), - TotalSamplePercent, FuncPair.second.second, Func.getEntrySamples())); - } - dumpHotFunctionList(ColumnTitle, ColumnOffset, PrintValues, HotFuncCount, - Profiles.size(), HotFuncSample, ProfileTotalSample, - Metric, TopN, OS); - - return 0; -} - -static int showSampleProfile(const std::string &Filename, bool ShowCounts, - uint32_t TopN, bool ShowAllFunctions, - bool ShowDetailedSummary, - const std::string &ShowFunction, - bool ShowProfileSymbolList, - bool ShowSectionInfoOnly, bool ShowHotFuncList, - raw_fd_ostream &OS) { - using namespace sampleprof; - LLVMContext Context; - auto ReaderOrErr = - SampleProfileReader::create(Filename, Context, FSDiscriminatorPassOption); - if (std::error_code EC = ReaderOrErr.getError()) - exitWithErrorCode(EC, Filename); - - auto Reader = std::move(ReaderOrErr.get()); - if (ShowSectionInfoOnly) { - showSectionInfo(Reader.get(), OS); - return 0; - } - - if (std::error_code EC = Reader->read()) - exitWithErrorCode(EC, Filename); - - if (ShowAllFunctions || ShowFunction.empty()) - Reader->dump(OS); - else - // TODO: parse context string to support filtering by contexts. - Reader->dumpFunctionProfile(StringRef(ShowFunction), OS); - - if (ShowProfileSymbolList) { - std::unique_ptr ReaderList = - Reader->getProfileSymbolList(); - ReaderList->dump(OS); - } - - if (ShowDetailedSummary) { - auto &PS = Reader->getSummary(); - PS.printSummary(OS); - PS.printDetailedSummary(OS); - } - - if (ShowHotFuncList || TopN) - showHotFunctionList(Reader->getProfiles(), Reader->getSummary(), TopN, OS); - - return 0; -} - -static int showMemProfProfile(const std::string &Filename, raw_fd_ostream &OS) { - auto ReaderOr = llvm::memprof::RawMemProfReader::create(Filename); - if (Error E = ReaderOr.takeError()) - exitWithError(std::move(E), Filename); - - std::unique_ptr Reader( - ReaderOr.get().release()); - Reader->printSummaries(OS); - return 0; -} - -static int showDebugInfoCorrelation(const std::string &Filename, - bool ShowDetailedSummary, - bool ShowProfileSymbolList, - raw_fd_ostream &OS) { - std::unique_ptr Correlator; - if (auto Err = InstrProfCorrelator::get(Filename).moveInto(Correlator)) - exitWithError(std::move(Err), Filename); - if (auto Err = Correlator->correlateProfileData()) - exitWithError(std::move(Err), Filename); - - InstrProfSymtab Symtab; - if (auto Err = Symtab.create( - StringRef(Correlator->getNamesPointer(), Correlator->getNamesSize()))) - exitWithError(std::move(Err), Filename); - - if (ShowProfileSymbolList) - Symtab.dumpNames(OS); - // TODO: Read "Profile Data Type" from debug info to compute and show how many - // counters the section holds. - if (ShowDetailedSummary) - OS << "Counters section size: 0x" - << Twine::utohexstr(Correlator->getCountersSectionSize()) << " bytes\n"; - OS << "Found " << Correlator->getDataSize() << " functions\n"; - - return 0; -} - -static int show_main(int argc, const char *argv[]) { - cl::opt Filename(cl::Positional, cl::desc("")); - - cl::opt ShowCounts("counts", cl::init(false), - cl::desc("Show counter values for shown functions")); - cl::opt TextFormat( - "text", cl::init(false), - cl::desc("Show instr profile data in text dump format")); - cl::opt ShowIndirectCallTargets( - "ic-targets", cl::init(false), - cl::desc("Show indirect call site target values for shown functions")); - cl::opt ShowMemOPSizes( - "memop-sizes", cl::init(false), - cl::desc("Show the profiled sizes of the memory intrinsic calls " - "for shown functions")); - cl::opt ShowDetailedSummary("detailed-summary", cl::init(false), - cl::desc("Show detailed profile summary")); - cl::list DetailedSummaryCutoffs( - cl::CommaSeparated, "detailed-summary-cutoffs", - cl::desc( - "Cutoff percentages (times 10000) for generating detailed summary"), - cl::value_desc("800000,901000,999999")); - cl::opt ShowHotFuncList( - "hot-func-list", cl::init(false), - cl::desc("Show profile summary of a list of hot functions")); - cl::opt ShowAllFunctions("all-functions", cl::init(false), - cl::desc("Details for every function")); - cl::opt ShowCS("showcs", cl::init(false), - cl::desc("Show context sensitive counts")); - cl::opt ShowFunction("function", - cl::desc("Details for matching functions")); - - cl::opt OutputFilename("output", cl::value_desc("output"), - cl::init("-"), cl::desc("Output file")); - cl::alias OutputFilenameA("o", cl::desc("Alias for --output"), - cl::aliasopt(OutputFilename)); - cl::opt ProfileKind( - cl::desc("Profile kind:"), cl::init(instr), - cl::values(clEnumVal(instr, "Instrumentation profile (default)"), - clEnumVal(sample, "Sample profile"), - clEnumVal(memory, "MemProf memory access profile"))); - cl::opt TopNFunctions( - "topn", cl::init(0), - cl::desc("Show the list of functions with the largest internal counts")); - cl::opt ValueCutoff( - "value-cutoff", cl::init(0), - cl::desc("Set the count value cutoff. Functions with the maximum count " - "less than this value will not be printed out. (Default is 0)")); - cl::opt OnlyListBelow( - "list-below-cutoff", cl::init(false), - cl::desc("Only output names of functions whose max count values are " - "below the cutoff value")); - cl::opt ShowProfileSymbolList( - "show-prof-sym-list", cl::init(false), - cl::desc("Show profile symbol list if it exists in the profile. ")); - cl::opt ShowSectionInfoOnly( - "show-sec-info-only", cl::init(false), - cl::desc("Show the information of each section in the sample profile. " - "The flag is only usable when the sample profile is in " - "extbinary format")); - cl::opt ShowBinaryIds("binary-ids", cl::init(false), - cl::desc("Show binary ids in the profile. ")); - cl::opt DebugInfoFilename( - "debug-info", cl::init(""), - cl::desc("Read and extract profile metadata from debug info and show " - "the functions it found.")); - cl::opt ShowCovered( - "covered", cl::init(false), - cl::desc("Show only the functions that have been executed.")); - - cl::ParseCommandLineOptions(argc, argv, "LLVM profile data summary\n"); - - if (Filename.empty() && DebugInfoFilename.empty()) - exitWithError( - "the positional argument '' is required unless '--" + - DebugInfoFilename.ArgStr + "' is provided"); - - if (Filename == OutputFilename) { - errs() << sys::path::filename(argv[0]) - << ": Input file name cannot be the same as the output file name!\n"; - return 1; - } - - std::error_code EC; - raw_fd_ostream OS(OutputFilename.data(), EC, sys::fs::OF_TextWithCRLF); - if (EC) - exitWithErrorCode(EC, OutputFilename); - - if (ShowAllFunctions && !ShowFunction.empty()) - WithColor::warning() << "-function argument ignored: showing all functions\n"; - - if (!DebugInfoFilename.empty()) - return showDebugInfoCorrelation(DebugInfoFilename, ShowDetailedSummary, - ShowProfileSymbolList, OS); - - if (ProfileKind == instr) - return showInstrProfile( - Filename, ShowCounts, TopNFunctions, ShowIndirectCallTargets, - ShowMemOPSizes, ShowDetailedSummary, DetailedSummaryCutoffs, - ShowAllFunctions, ShowCS, ValueCutoff, OnlyListBelow, ShowFunction, - TextFormat, ShowBinaryIds, ShowCovered, OS); - if (ProfileKind == sample) - return showSampleProfile(Filename, ShowCounts, TopNFunctions, - ShowAllFunctions, ShowDetailedSummary, - ShowFunction, ShowProfileSymbolList, - ShowSectionInfoOnly, ShowHotFuncList, OS); - return showMemProfProfile(Filename, OS); -} - -int main(int argc, const char *argv[]) { - InitLLVM X(argc, argv); - - StringRef ProgName(sys::path::filename(argv[0])); - if (argc > 1) { - int (*func)(int, const char *[]) = nullptr; - - if (strcmp(argv[1], "merge") == 0) - func = merge_main; - else if (strcmp(argv[1], "show") == 0) - func = show_main; - else if (strcmp(argv[1], "overlap") == 0) - func = overlap_main; - - if (func) { - std::string Invocation(ProgName.str() + " " + argv[1]); - argv[1] = Invocation.c_str(); - return func(argc - 1, argv + 1); - } - - if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "-help") == 0 || - strcmp(argv[1], "--help") == 0) { - - errs() << "OVERVIEW: LLVM profile data tools\n\n" - << "USAGE: " << ProgName << " [args...]\n" - << "USAGE: " << ProgName << " -help\n\n" - << "See each individual command --help for more details.\n" - << "Available commands: merge, show, overlap\n"; - return 0; - } - } - - if (argc < 2) - errs() << ProgName << ": No command specified!\n"; - else - errs() << ProgName << ": Unknown command!\n"; - - errs() << "USAGE: " << ProgName << " [args...]\n"; - return 1; -} diff --git a/tools/ldc-profgen/ldc-profgen-14.0/CMakeLists.txt b/tools/ldc-profgen/ldc-profgen-14.0/CMakeLists.txt deleted file mode 100644 index b3e05a94856..00000000000 --- a/tools/ldc-profgen/ldc-profgen-14.0/CMakeLists.txt +++ /dev/null @@ -1,23 +0,0 @@ - -set(LLVM_LINK_COMPONENTS - AllTargetsDescs - AllTargetsDisassemblers - AllTargetsInfos - DebugInfoDWARF - Core - MC - IPO - MCDisassembler - Object - ProfileData - Support - Symbolize - ) - -add_llvm_tool(llvm-profgen - llvm-profgen.cpp - PerfReader.cpp - CSPreInliner.cpp - ProfiledBinary.cpp - ProfileGenerator.cpp - ) diff --git a/tools/ldc-profgen/ldc-profgen-14.0/CSPreInliner.cpp b/tools/ldc-profgen/ldc-profgen-14.0/CSPreInliner.cpp deleted file mode 100644 index 1e642639902..00000000000 --- a/tools/ldc-profgen/ldc-profgen-14.0/CSPreInliner.cpp +++ /dev/null @@ -1,285 +0,0 @@ -//===-- CSPreInliner.cpp - Profile guided preinliner -------------- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "CSPreInliner.h" -#include "ProfiledBinary.h" -#include "llvm/ADT/SCCIterator.h" -#include "llvm/ADT/Statistic.h" -#include -#include - -#define DEBUG_TYPE "cs-preinliner" - -using namespace llvm; -using namespace sampleprof; - -STATISTIC(PreInlNumCSInlined, - "Number of functions inlined with context sensitive profile"); -STATISTIC(PreInlNumCSNotInlined, - "Number of functions not inlined with context sensitive profile"); -STATISTIC(PreInlNumCSInlinedHitMinLimit, - "Number of functions with FDO inline stopped due to min size limit"); -STATISTIC(PreInlNumCSInlinedHitMaxLimit, - "Number of functions with FDO inline stopped due to max size limit"); -STATISTIC( - PreInlNumCSInlinedHitGrowthLimit, - "Number of functions with FDO inline stopped due to growth size limit"); - -// The switches specify inline thresholds used in SampleProfileLoader inlining. -// TODO: the actual threshold to be tuned here because the size here is based -// on machine code not LLVM IR. -extern cl::opt SampleHotCallSiteThreshold; -extern cl::opt SampleColdCallSiteThreshold; -extern cl::opt ProfileInlineGrowthLimit; -extern cl::opt ProfileInlineLimitMin; -extern cl::opt ProfileInlineLimitMax; -extern cl::opt SortProfiledSCC; - -cl::opt EnableCSPreInliner( - "csspgo-preinliner", cl::Hidden, cl::init(true), - cl::desc("Run a global pre-inliner to merge context profile based on " - "estimated global top-down inline decisions")); - -cl::opt UseContextCostForPreInliner( - "use-context-cost-for-preinliner", cl::Hidden, cl::init(true), - cl::desc("Use context-sensitive byte size cost for preinliner decisions")); - -static cl::opt SamplePreInlineReplay( - "csspgo-replay-preinline", cl::Hidden, cl::init(false), - cl::desc( - "Replay previous inlining and adjust context profile accordingly")); - -CSPreInliner::CSPreInliner(SampleProfileMap &Profiles, ProfiledBinary &Binary, - uint64_t HotThreshold, uint64_t ColdThreshold) - : UseContextCost(UseContextCostForPreInliner), - // TODO: Pass in a guid-to-name map in order for - // ContextTracker.getFuncNameFor to work, if `Profiles` can have md5 codes - // as their profile context. - ContextTracker(Profiles, nullptr), ProfileMap(Profiles), Binary(Binary), - HotCountThreshold(HotThreshold), ColdCountThreshold(ColdThreshold) { - // Set default preinliner hot/cold call site threshold tuned with CSSPGO. - // for good performance with reasonable profile size. - if (!SampleHotCallSiteThreshold.getNumOccurrences()) - SampleHotCallSiteThreshold = 1500; - if (!SampleColdCallSiteThreshold.getNumOccurrences()) - SampleColdCallSiteThreshold = 0; -} - -std::vector CSPreInliner::buildTopDownOrder() { - std::vector Order; - ProfiledCallGraph ProfiledCG(ContextTracker); - - // Now that we have a profiled call graph, construct top-down order - // by building up SCC and reversing SCC order. - scc_iterator I = scc_begin(&ProfiledCG); - while (!I.isAtEnd()) { - auto Range = *I; - if (SortProfiledSCC) { - // Sort nodes in one SCC based on callsite hotness. - scc_member_iterator SI(*I); - Range = *SI; - } - for (auto *Node : Range) { - if (Node != ProfiledCG.getEntryNode()) - Order.push_back(Node->Name); - } - ++I; - } - std::reverse(Order.begin(), Order.end()); - - return Order; -} - -bool CSPreInliner::getInlineCandidates(ProfiledCandidateQueue &CQueue, - const FunctionSamples *CallerSamples) { - assert(CallerSamples && "Expect non-null caller samples"); - - // Ideally we want to consider everything a function calls, but as far as - // context profile is concerned, only those frames that are children of - // current one in the trie is relavent. So we walk the trie instead of call - // targets from function profile. - ContextTrieNode *CallerNode = - ContextTracker.getContextFor(CallerSamples->getContext()); - - bool HasNewCandidate = false; - for (auto &Child : CallerNode->getAllChildContext()) { - ContextTrieNode *CalleeNode = &Child.second; - FunctionSamples *CalleeSamples = CalleeNode->getFunctionSamples(); - if (!CalleeSamples) - continue; - - // Call site count is more reliable, so we look up the corresponding call - // target profile in caller's context profile to retrieve call site count. - uint64_t CalleeEntryCount = CalleeSamples->getEntrySamples(); - uint64_t CallsiteCount = 0; - LineLocation Callsite = CalleeNode->getCallSiteLoc(); - if (auto CallTargets = CallerSamples->findCallTargetMapAt(Callsite)) { - SampleRecord::CallTargetMap &TargetCounts = CallTargets.get(); - auto It = TargetCounts.find(CalleeSamples->getName()); - if (It != TargetCounts.end()) - CallsiteCount = It->second; - } - - // TODO: call site and callee entry count should be mostly consistent, add - // check for that. - HasNewCandidate = true; - uint32_t CalleeSize = getFuncSize(*CalleeSamples); - CQueue.emplace(CalleeSamples, std::max(CallsiteCount, CalleeEntryCount), - CalleeSize); - } - - return HasNewCandidate; -} - -uint32_t CSPreInliner::getFuncSize(const FunctionSamples &FSamples) { - if (UseContextCost) { - return Binary.getFuncSizeForContext(FSamples.getContext()); - } - - return FSamples.getBodySamples().size(); -} - -bool CSPreInliner::shouldInline(ProfiledInlineCandidate &Candidate) { - // If replay inline is requested, simply follow the inline decision of the - // profiled binary. - if (SamplePreInlineReplay) - return Candidate.CalleeSamples->getContext().hasAttribute( - ContextWasInlined); - - // Adjust threshold based on call site hotness, only do this for callsite - // prioritized inliner because otherwise cost-benefit check is done earlier. - unsigned int SampleThreshold = SampleColdCallSiteThreshold; - if (Candidate.CallsiteCount > HotCountThreshold) - SampleThreshold = SampleHotCallSiteThreshold; - - // TODO: for small cold functions, we may inlined them and we need to keep - // context profile accordingly. - if (Candidate.CallsiteCount < ColdCountThreshold) - SampleThreshold = SampleColdCallSiteThreshold; - - return (Candidate.SizeCost < SampleThreshold); -} - -void CSPreInliner::processFunction(const StringRef Name) { - FunctionSamples *FSamples = ContextTracker.getBaseSamplesFor(Name); - if (!FSamples) - return; - - unsigned FuncSize = getFuncSize(*FSamples); - unsigned FuncFinalSize = FuncSize; - unsigned SizeLimit = FuncSize * ProfileInlineGrowthLimit; - SizeLimit = std::min(SizeLimit, (unsigned)ProfileInlineLimitMax); - SizeLimit = std::max(SizeLimit, (unsigned)ProfileInlineLimitMin); - - LLVM_DEBUG(dbgs() << "Process " << Name - << " for context-sensitive pre-inlining (pre-inline size: " - << FuncSize << ", size limit: " << SizeLimit << ")\n"); - - ProfiledCandidateQueue CQueue; - getInlineCandidates(CQueue, FSamples); - - while (!CQueue.empty() && FuncFinalSize < SizeLimit) { - ProfiledInlineCandidate Candidate = CQueue.top(); - CQueue.pop(); - bool ShouldInline = false; - if ((ShouldInline = shouldInline(Candidate))) { - // We mark context as inlined as the corresponding context profile - // won't be merged into that function's base profile. - ++PreInlNumCSInlined; - ContextTracker.markContextSamplesInlined(Candidate.CalleeSamples); - Candidate.CalleeSamples->getContext().setAttribute( - ContextShouldBeInlined); - FuncFinalSize += Candidate.SizeCost; - getInlineCandidates(CQueue, Candidate.CalleeSamples); - } else { - ++PreInlNumCSNotInlined; - } - LLVM_DEBUG(dbgs() << (ShouldInline ? " Inlined" : " Outlined") - << " context profile for: " - << Candidate.CalleeSamples->getContext().toString() - << " (callee size: " << Candidate.SizeCost - << ", call count:" << Candidate.CallsiteCount << ")\n"); - } - - if (!CQueue.empty()) { - if (SizeLimit == (unsigned)ProfileInlineLimitMax) - ++PreInlNumCSInlinedHitMaxLimit; - else if (SizeLimit == (unsigned)ProfileInlineLimitMin) - ++PreInlNumCSInlinedHitMinLimit; - else - ++PreInlNumCSInlinedHitGrowthLimit; - } - - LLVM_DEBUG({ - if (!CQueue.empty()) - dbgs() << " Inline candidates ignored due to size limit (inliner " - "original size: " - << FuncSize << ", inliner final size: " << FuncFinalSize - << ", size limit: " << SizeLimit << ")\n"; - - while (!CQueue.empty()) { - ProfiledInlineCandidate Candidate = CQueue.top(); - CQueue.pop(); - bool WasInlined = - Candidate.CalleeSamples->getContext().hasAttribute(ContextWasInlined); - dbgs() << " " << Candidate.CalleeSamples->getContext().toString() - << " (candidate size:" << Candidate.SizeCost - << ", call count: " << Candidate.CallsiteCount << ", previously " - << (WasInlined ? "inlined)\n" : "not inlined)\n"); - } - }); -} - -void CSPreInliner::run() { -#ifndef NDEBUG - auto printProfileNames = [](SampleProfileMap &Profiles, bool IsInput) { - dbgs() << (IsInput ? "Input" : "Output") << " context-sensitive profiles (" - << Profiles.size() << " total):\n"; - for (auto &It : Profiles) { - const FunctionSamples &Samples = It.second; - dbgs() << " [" << Samples.getContext().toString() << "] " - << Samples.getTotalSamples() << ":" << Samples.getHeadSamples() - << "\n"; - } - }; -#endif - - LLVM_DEBUG(printProfileNames(ProfileMap, true)); - - // Execute global pre-inliner to estimate a global top-down inline - // decision and merge profiles accordingly. This helps with profile - // merge for ThinLTO otherwise we won't be able to merge profiles back - // to base profile across module/thin-backend boundaries. - // It also helps better compress context profile to control profile - // size, as we now only need context profile for functions going to - // be inlined. - for (StringRef FuncName : buildTopDownOrder()) { - processFunction(FuncName); - } - - // Not inlined context profiles are merged into its base, so we can - // trim out such profiles from the output. - std::vector ProfilesToBeRemoved; - for (auto &It : ProfileMap) { - SampleContext &Context = It.second.getContext(); - if (!Context.isBaseContext() && !Context.hasState(InlinedContext)) { - assert(Context.hasState(MergedContext) && - "Not inlined context profile should be merged already"); - ProfilesToBeRemoved.push_back(It.first); - } - } - - for (auto &ContextName : ProfilesToBeRemoved) { - ProfileMap.erase(ContextName); - } - - // Make sure ProfileMap's key is consistent with FunctionSamples' name. - SampleContextTrimmer(ProfileMap).canonicalizeContextProfiles(); - - LLVM_DEBUG(printProfileNames(ProfileMap, false)); -} diff --git a/tools/ldc-profgen/ldc-profgen-14.0/CSPreInliner.h b/tools/ldc-profgen/ldc-profgen-14.0/CSPreInliner.h deleted file mode 100644 index 9f63f7ef7be..00000000000 --- a/tools/ldc-profgen/ldc-profgen-14.0/CSPreInliner.h +++ /dev/null @@ -1,95 +0,0 @@ -//===-- CSPreInliner.h - Profile guided preinliner ---------------- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_TOOLS_LLVM_PROFGEN_PGOINLINEADVISOR_H -#define LLVM_TOOLS_LLVM_PROFGEN_PGOINLINEADVISOR_H - -#include "ProfiledBinary.h" -#include "llvm/ADT/PriorityQueue.h" -#include "llvm/ProfileData/ProfileCommon.h" -#include "llvm/ProfileData/SampleProf.h" -#include "llvm/Transforms/IPO/ProfiledCallGraph.h" -#include "llvm/Transforms/IPO/SampleContextTracker.h" - -using namespace llvm; -using namespace sampleprof; - -namespace llvm { -namespace sampleprof { - -// Inline candidate seen from profile -struct ProfiledInlineCandidate { - ProfiledInlineCandidate(const FunctionSamples *Samples, uint64_t Count, - uint32_t Size) - : CalleeSamples(Samples), CallsiteCount(Count), SizeCost(Size) {} - // Context-sensitive function profile for inline candidate - const FunctionSamples *CalleeSamples; - // Call site count for an inline candidate - // TODO: make sure entry count for context profile and call site - // target count for corresponding call are consistent. - uint64_t CallsiteCount; - // Size proxy for function under particular call context. - uint64_t SizeCost; -}; - -// Inline candidate comparer using call site weight -struct ProfiledCandidateComparer { - bool operator()(const ProfiledInlineCandidate &LHS, - const ProfiledInlineCandidate &RHS) { - if (LHS.CallsiteCount != RHS.CallsiteCount) - return LHS.CallsiteCount < RHS.CallsiteCount; - - if (LHS.SizeCost != RHS.SizeCost) - return LHS.SizeCost > RHS.SizeCost; - - // Tie breaker using GUID so we have stable/deterministic inlining order - assert(LHS.CalleeSamples && RHS.CalleeSamples && - "Expect non-null FunctionSamples"); - return LHS.CalleeSamples->getGUID(LHS.CalleeSamples->getName()) < - RHS.CalleeSamples->getGUID(RHS.CalleeSamples->getName()); - } -}; - -using ProfiledCandidateQueue = - PriorityQueue, - ProfiledCandidateComparer>; - -// Pre-compilation inliner based on context-sensitive profile. -// The PreInliner estimates inline decision using hotness from profile -// and cost estimation from machine code size. It helps merges context -// profile globally and achieves better post-inine profile quality, which -// otherwise won't be possible for ThinLTO. It also reduce context profile -// size by only keep context that is estimated to be inlined. -class CSPreInliner { -public: - CSPreInliner(SampleProfileMap &Profiles, ProfiledBinary &Binary, - uint64_t HotThreshold, uint64_t ColdThreshold); - void run(); - -private: - bool getInlineCandidates(ProfiledCandidateQueue &CQueue, - const FunctionSamples *FCallerContextSamples); - std::vector buildTopDownOrder(); - void processFunction(StringRef Name); - bool shouldInline(ProfiledInlineCandidate &Candidate); - uint32_t getFuncSize(const FunctionSamples &FSamples); - bool UseContextCost; - SampleContextTracker ContextTracker; - SampleProfileMap &ProfileMap; - ProfiledBinary &Binary; - - // Count thresholds to answer isHotCount and isColdCount queries. - // Mirrors the threshold in ProfileSummaryInfo. - uint64_t HotCountThreshold; - uint64_t ColdCountThreshold; -}; - -} // end namespace sampleprof -} // end namespace llvm - -#endif diff --git a/tools/ldc-profgen/ldc-profgen-14.0/CallContext.h b/tools/ldc-profgen/ldc-profgen-14.0/CallContext.h deleted file mode 100644 index 5e552130d03..00000000000 --- a/tools/ldc-profgen/ldc-profgen-14.0/CallContext.h +++ /dev/null @@ -1,59 +0,0 @@ -//===-- CallContext.h - Call Context Handler ---------------------*- C++-*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_TOOLS_LLVM_PROFGEN_CALLCONTEXT_H -#define LLVM_TOOLS_LLVM_PROFGEN_CALLCONTEXT_H - -#include "llvm/ProfileData/SampleProf.h" -#include -#include -#include - -namespace llvm { -namespace sampleprof { - -inline std::string getCallSite(const SampleContextFrame &Callsite) { - std::string CallsiteStr = Callsite.FuncName.str(); - CallsiteStr += ":"; - CallsiteStr += Twine(Callsite.Location.LineOffset).str(); - if (Callsite.Location.Discriminator > 0) { - CallsiteStr += "."; - CallsiteStr += Twine(Callsite.Location.Discriminator).str(); - } - return CallsiteStr; -} - -// TODO: This operation is expansive. If it ever gets called multiple times we -// may think of making a class wrapper with internal states for it. -inline std::string getLocWithContext(const SampleContextFrameVector &Context) { - std::ostringstream OContextStr; - for (const auto &Callsite : Context) { - if (OContextStr.str().size()) - OContextStr << " @ "; - OContextStr << getCallSite(Callsite); - } - return OContextStr.str(); -} - -// Reverse call context, i.e., in the order of callee frames to caller frames, -// is useful during instruction printing or pseudo probe printing. -inline std::string -getReversedLocWithContext(const SampleContextFrameVector &Context) { - std::ostringstream OContextStr; - for (const auto &Callsite : reverse(Context)) { - if (OContextStr.str().size()) - OContextStr << " @ "; - OContextStr << getCallSite(Callsite); - } - return OContextStr.str(); -} - -} // end namespace sampleprof -} // end namespace llvm - -#endif diff --git a/tools/ldc-profgen/ldc-profgen-14.0/ErrorHandling.h b/tools/ldc-profgen/ldc-profgen-14.0/ErrorHandling.h deleted file mode 100644 index b797add8a89..00000000000 --- a/tools/ldc-profgen/ldc-profgen-14.0/ErrorHandling.h +++ /dev/null @@ -1,56 +0,0 @@ -//===-- ErrorHandling.h - Error handler -------------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_TOOLS_LLVM_PROFGEN_ERRORHANDLING_H -#define LLVM_TOOLS_LLVM_PROFGEN_ERRORHANDLING_H - -#include "llvm/ADT/Twine.h" -#include "llvm/Support/Errc.h" -#include "llvm/Support/Error.h" -#include "llvm/Support/ErrorOr.h" -#include "llvm/Support/WithColor.h" -#include - -using namespace llvm; - -[[noreturn]] inline void exitWithError(const Twine &Message, - StringRef Whence = StringRef(), - StringRef Hint = StringRef()) { - WithColor::error(errs(), "llvm-profgen"); - if (!Whence.empty()) - errs() << Whence.str() << ": "; - errs() << Message << "\n"; - if (!Hint.empty()) - WithColor::note() << Hint.str() << "\n"; - ::exit(EXIT_FAILURE); -} - -[[noreturn]] inline void exitWithError(std::error_code EC, - StringRef Whence = StringRef()) { - exitWithError(EC.message(), Whence); -} - -[[noreturn]] inline void exitWithError(Error E, StringRef Whence) { - exitWithError(errorToErrorCode(std::move(E)), Whence); -} - -template -T unwrapOrError(Expected EO, Ts &&... Args) { - if (EO) - return std::move(*EO); - exitWithError(EO.takeError(), std::forward(Args)...); -} - -inline void emitWarningSummary(uint64_t Num, uint64_t Total, StringRef Msg) { - if (!Total || !Num) - return; - WithColor::warning() << format("%.2f", static_cast(Num) * 100 / Total) - << "%(" << Num << "/" << Total << ") " << Msg << "\n"; -} - -#endif diff --git a/tools/ldc-profgen/ldc-profgen-14.0/PerfReader.cpp b/tools/ldc-profgen/ldc-profgen-14.0/PerfReader.cpp deleted file mode 100644 index 98b4c7cdf16..00000000000 --- a/tools/ldc-profgen/ldc-profgen-14.0/PerfReader.cpp +++ /dev/null @@ -1,1222 +0,0 @@ -//===-- PerfReader.cpp - perfscript reader ---------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -#include "PerfReader.h" -#include "ProfileGenerator.h" -#include "llvm/Support/FileSystem.h" -#include "llvm/Support/Process.h" - -#define DEBUG_TYPE "perf-reader" - -cl::opt SkipSymbolization("skip-symbolization", cl::init(false), - cl::ZeroOrMore, - cl::desc("Dump the unsymbolized profile to the " - "output file. It will show unwinder " - "output for CS profile generation.")); - -static cl::opt ShowMmapEvents("show-mmap-events", cl::init(false), - cl::ZeroOrMore, - cl::desc("Print binary load events.")); - -static cl::opt - UseOffset("use-offset", cl::init(true), cl::ZeroOrMore, - cl::desc("Work with `--skip-symbolization` or " - "`--unsymbolized-profile` to write/read the " - "offset instead of virtual address.")); - -static cl::opt UseLoadableSegmentAsBase( - "use-first-loadable-segment-as-base", cl::init(false), cl::ZeroOrMore, - cl::desc("Use first loadable segment address as base address " - "for offsets in unsymbolized profile. By default " - "first executable segment address is used")); - -static cl::opt - IgnoreStackSamples("ignore-stack-samples", cl::init(false), cl::ZeroOrMore, - cl::desc("Ignore call stack samples for hybrid samples " - "and produce context-insensitive profile.")); -cl::opt ShowDetailedWarning("show-detailed-warning", cl::init(false), - cl::ZeroOrMore, - cl::desc("Show detailed warning message.")); - -extern cl::opt PerfTraceFilename; -extern cl::opt ShowDisassemblyOnly; -extern cl::opt ShowSourceLocations; -extern cl::opt OutputFilename; - -namespace llvm { -namespace sampleprof { - -void VirtualUnwinder::unwindCall(UnwindState &State) { - uint64_t Source = State.getCurrentLBRSource(); - // An artificial return should push an external frame and an artificial call - // will match it and pop the external frame so that the context before and - // after the external call will be the same. - if (State.getCurrentLBR().IsArtificial) { - NumExtCallBranch++; - // A return is matched and pop the external frame. - if (State.getParentFrame()->isExternalFrame()) { - State.popFrame(); - } else { - // An artificial return is missing, it happens that the sample is just hit - // in the middle of the external code. In this case, the leading branch is - // a call to external, we just keep unwinding use a context-less stack. - if (State.getParentFrame() != State.getDummyRootPtr()) - NumMissingExternalFrame++; - State.clearCallStack(); - State.pushFrame(Source); - State.InstPtr.update(Source); - return; - } - } - - auto *ParentFrame = State.getParentFrame(); - // The 2nd frame after leaf could be missing if stack sample is - // taken when IP is within prolog/epilog, as frame chain isn't - // setup yet. Fill in the missing frame in that case. - // TODO: Currently we just assume all the addr that can't match the - // 2nd frame is in prolog/epilog. In the future, we will switch to - // pro/epi tracker(Dwarf CFI) for the precise check. - if (ParentFrame == State.getDummyRootPtr() || - ParentFrame->Address != Source) { - State.switchToFrame(Source); - if (ParentFrame != State.getDummyRootPtr()) { - if (State.getCurrentLBR().IsArtificial) - NumMismatchedExtCallBranch++; - else - NumMismatchedProEpiBranch++; - } - } else { - State.popFrame(); - } - State.InstPtr.update(Source); -} - -void VirtualUnwinder::unwindLinear(UnwindState &State, uint64_t Repeat) { - InstructionPointer &IP = State.InstPtr; - uint64_t Target = State.getCurrentLBRTarget(); - uint64_t End = IP.Address; - if (Binary->usePseudoProbes()) { - // We don't need to top frame probe since it should be extracted - // from the range. - // The outcome of the virtual unwinding with pseudo probes is a - // map from a context key to the address range being unwound. - // This means basically linear unwinding is not needed for pseudo - // probes. The range will be simply recorded here and will be - // converted to a list of pseudo probes to report in ProfileGenerator. - State.getParentFrame()->recordRangeCount(Target, End, Repeat); - } else { - // Unwind linear execution part. - // Split and record the range by different inline context. For example: - // [0x01] ... main:1 # Target - // [0x02] ... main:2 - // [0x03] ... main:3 @ foo:1 - // [0x04] ... main:3 @ foo:2 - // [0x05] ... main:3 @ foo:3 - // [0x06] ... main:4 - // [0x07] ... main:5 # End - // It will be recorded: - // [main:*] : [0x06, 0x07], [0x01, 0x02] - // [main:3 @ foo:*] : [0x03, 0x05] - while (IP.Address > Target) { - uint64_t PrevIP = IP.Address; - IP.backward(); - // Break into segments for implicit call/return due to inlining - bool SameInlinee = Binary->inlineContextEqual(PrevIP, IP.Address); - if (!SameInlinee) { - State.switchToFrame(PrevIP); - State.CurrentLeafFrame->recordRangeCount(PrevIP, End, Repeat); - End = IP.Address; - } - } - assert(IP.Address == Target && "The last one must be the target address."); - // Record the remaining range, [0x01, 0x02] in the example - State.switchToFrame(IP.Address); - State.CurrentLeafFrame->recordRangeCount(IP.Address, End, Repeat); - } -} - -void VirtualUnwinder::unwindReturn(UnwindState &State) { - // Add extra frame as we unwind through the return - const LBREntry &LBR = State.getCurrentLBR(); - uint64_t CallAddr = Binary->getCallAddrFromFrameAddr(LBR.Target); - State.switchToFrame(CallAddr); - // Push an external frame for the case of returning to external - // address(callback), later if an aitificial call is matched and it will be - // popped up. This is to 1)avoid context being interrupted by callback, - // context before or after the callback should be the same. 2) the call stack - // of function called by callback should be truncated which is done during - // recording the context on trie. For example: - // main (call)--> foo (call)--> callback (call)--> bar (return)--> callback - // (return)--> foo (return)--> main - // Context for bar should not include main and foo. - // For the code of foo, the context of before and after callback should both - // be [foo, main]. - if (LBR.IsArtificial) - State.pushFrame(ExternalAddr); - State.pushFrame(LBR.Source); - State.InstPtr.update(LBR.Source); -} - -void VirtualUnwinder::unwindBranch(UnwindState &State) { - // TODO: Tolerate tail call for now, as we may see tail call from libraries. - // This is only for intra function branches, excluding tail calls. - uint64_t Source = State.getCurrentLBRSource(); - State.switchToFrame(Source); - State.InstPtr.update(Source); -} - -std::shared_ptr FrameStack::getContextKey() { - std::shared_ptr KeyStr = - std::make_shared(); - KeyStr->Context = Binary->getExpandedContext(Stack, KeyStr->WasLeafInlined); - if (KeyStr->Context.empty()) - return nullptr; - return KeyStr; -} - -std::shared_ptr ProbeStack::getContextKey() { - std::shared_ptr ProbeBasedKey = - std::make_shared(); - for (auto CallProbe : Stack) { - ProbeBasedKey->Probes.emplace_back(CallProbe); - } - CSProfileGenerator::compressRecursionContext( - ProbeBasedKey->Probes); - CSProfileGenerator::trimContext( - ProbeBasedKey->Probes); - return ProbeBasedKey; -} - -template -void VirtualUnwinder::collectSamplesFromFrame(UnwindState::ProfiledFrame *Cur, - T &Stack) { - if (Cur->RangeSamples.empty() && Cur->BranchSamples.empty()) - return; - - std::shared_ptr Key = Stack.getContextKey(); - if (Key == nullptr) - return; - auto Ret = CtxCounterMap->emplace(Hashable(Key), SampleCounter()); - SampleCounter &SCounter = Ret.first->second; - for (auto &Item : Cur->RangeSamples) { - uint64_t StartOffset = Binary->virtualAddrToOffset(std::get<0>(Item)); - uint64_t EndOffset = Binary->virtualAddrToOffset(std::get<1>(Item)); - SCounter.recordRangeCount(StartOffset, EndOffset, std::get<2>(Item)); - } - - for (auto &Item : Cur->BranchSamples) { - uint64_t SourceOffset = Binary->virtualAddrToOffset(std::get<0>(Item)); - uint64_t TargetOffset = Binary->virtualAddrToOffset(std::get<1>(Item)); - SCounter.recordBranchCount(SourceOffset, TargetOffset, std::get<2>(Item)); - } -} - -template -void VirtualUnwinder::collectSamplesFromFrameTrie( - UnwindState::ProfiledFrame *Cur, T &Stack) { - if (!Cur->isDummyRoot()) { - // Truncate the context for external frame since this isn't a real call - // context the compiler will see. - if (Cur->isExternalFrame() || !Stack.pushFrame(Cur)) { - // Process truncated context - // Start a new traversal ignoring its bottom context - T EmptyStack(Binary); - collectSamplesFromFrame(Cur, EmptyStack); - for (const auto &Item : Cur->Children) { - collectSamplesFromFrameTrie(Item.second.get(), EmptyStack); - } - - // Keep note of untracked call site and deduplicate them - // for warning later. - if (!Cur->isLeafFrame()) - UntrackedCallsites.insert(Cur->Address); - - return; - } - } - - collectSamplesFromFrame(Cur, Stack); - // Process children frame - for (const auto &Item : Cur->Children) { - collectSamplesFromFrameTrie(Item.second.get(), Stack); - } - // Recover the call stack - Stack.popFrame(); -} - -void VirtualUnwinder::collectSamplesFromFrameTrie( - UnwindState::ProfiledFrame *Cur) { - if (Binary->usePseudoProbes()) { - ProbeStack Stack(Binary); - collectSamplesFromFrameTrie(Cur, Stack); - } else { - FrameStack Stack(Binary); - collectSamplesFromFrameTrie(Cur, Stack); - } -} - -void VirtualUnwinder::recordBranchCount(const LBREntry &Branch, - UnwindState &State, uint64_t Repeat) { - if (Branch.IsArtificial || Branch.Target == ExternalAddr) - return; - - if (Binary->usePseudoProbes()) { - // Same as recordRangeCount, We don't need to top frame probe since we will - // extract it from branch's source address - State.getParentFrame()->recordBranchCount(Branch.Source, Branch.Target, - Repeat); - } else { - State.CurrentLeafFrame->recordBranchCount(Branch.Source, Branch.Target, - Repeat); - } -} - -bool VirtualUnwinder::unwind(const PerfSample *Sample, uint64_t Repeat) { - // Capture initial state as starting point for unwinding. - UnwindState State(Sample, Binary); - - // Sanity check - making sure leaf of LBR aligns with leaf of stack sample - // Stack sample sometimes can be unreliable, so filter out bogus ones. - if (!State.validateInitialState()) - return false; - - // Now process the LBR samples in parrallel with stack sample - // Note that we do not reverse the LBR entry order so we can - // unwind the sample stack as we walk through LBR entries. - while (State.hasNextLBR()) { - State.checkStateConsistency(); - - // Do not attempt linear unwind for the leaf range as it's incomplete. - if (!State.IsLastLBR()) { - // Unwind implicit calls/returns from inlining, along the linear path, - // break into smaller sub section each with its own calling context. - unwindLinear(State, Repeat); - } - - // Save the LBR branch before it gets unwound. - const LBREntry &Branch = State.getCurrentLBR(); - - if (isCallState(State)) { - // Unwind calls - we know we encountered call if LBR overlaps with - // transition between leaf the 2nd frame. Note that for calls that - // were not in the original stack sample, we should have added the - // extra frame when processing the return paired with this call. - unwindCall(State); - } else if (isReturnState(State)) { - // Unwind returns - check whether the IP is indeed at a return instruction - unwindReturn(State); - } else { - // Unwind branches - // For regular intra function branches, we only need to record branch with - // context. For an artificial branch cross function boundaries, we got an - // issue with returning to external code. Take the two LBR enties for - // example: [foo:8(RETURN), ext:1] [ext:3(CALL), bar:1] After perf reader, - // we only get[foo:8(RETURN), bar:1], unwinder will be confused like foo - // return to bar. Here we detect and treat this case as BRANCH instead of - // RETURN which only update the source address. - unwindBranch(State); - } - State.advanceLBR(); - // Record `branch` with calling context after unwinding. - recordBranchCount(Branch, State, Repeat); - } - // As samples are aggregated on trie, record them into counter map - collectSamplesFromFrameTrie(State.getDummyRootPtr()); - - return true; -} - -std::unique_ptr -PerfReaderBase::create(ProfiledBinary *Binary, PerfInputFile &PerfInput) { - std::unique_ptr PerfReader; - - if (PerfInput.Format == PerfFormat::UnsymbolizedProfile) { - PerfReader.reset( - new UnsymbolizedProfileReader(Binary, PerfInput.InputFile)); - return PerfReader; - } - - // For perf data input, we need to convert them into perf script first. - if (PerfInput.Format == PerfFormat::PerfData) - PerfInput = PerfScriptReader::convertPerfDataToTrace(Binary, PerfInput); - - assert((PerfInput.Format == PerfFormat::PerfScript) && - "Should be a perfscript!"); - - PerfInput.Content = - PerfScriptReader::checkPerfScriptType(PerfInput.InputFile); - if (PerfInput.Content == PerfContent::LBRStack) { - PerfReader.reset(new HybridPerfReader(Binary, PerfInput.InputFile)); - } else if (PerfInput.Content == PerfContent::LBR) { - PerfReader.reset(new LBRPerfReader(Binary, PerfInput.InputFile)); - } else { - exitWithError("Unsupported perfscript!"); - } - - return PerfReader; -} - -PerfInputFile PerfScriptReader::convertPerfDataToTrace(ProfiledBinary *Binary, - PerfInputFile &File) { - StringRef PerfData = File.InputFile; - // Run perf script to retrieve PIDs matching binary we're interested in. - auto PerfExecutable = sys::Process::FindInEnvPath("PATH", "perf"); - if (!PerfExecutable) { - exitWithError("Perf not found."); - } - std::string PerfPath = *PerfExecutable; - std::string PerfTraceFile = PerfData.str() + ".script.tmp"; - StringRef ScriptMMapArgs[] = {PerfPath, "script", "--show-mmap-events", - "-F", "comm,pid", "-i", - PerfData}; - Optional Redirects[] = {llvm::None, // Stdin - StringRef(PerfTraceFile), // Stdout - StringRef(PerfTraceFile)}; // Stderr - sys::ExecuteAndWait(PerfPath, ScriptMMapArgs, llvm::None, Redirects); - - // Collect the PIDs - TraceStream TraceIt(PerfTraceFile); - std::string PIDs; - std::unordered_set PIDSet; - while (!TraceIt.isAtEoF()) { - MMapEvent MMap; - if (isMMap2Event(TraceIt.getCurrentLine()) && - extractMMap2EventForBinary(Binary, TraceIt.getCurrentLine(), MMap)) { - auto It = PIDSet.emplace(MMap.PID); - if (It.second) { - if (!PIDs.empty()) { - PIDs.append(","); - } - PIDs.append(utostr(MMap.PID)); - } - } - TraceIt.advance(); - } - - if (PIDs.empty()) { - exitWithError("No relevant mmap event is found in perf data."); - } - - // Run perf script again to retrieve events for PIDs collected above - StringRef ScriptSampleArgs[] = {PerfPath, "script", "--show-mmap-events", - "-F", "ip,brstack", "--pid", - PIDs, "-i", PerfData}; - sys::ExecuteAndWait(PerfPath, ScriptSampleArgs, llvm::None, Redirects); - - return {PerfTraceFile, PerfFormat::PerfScript, PerfContent::UnknownContent}; -} - -void PerfScriptReader::updateBinaryAddress(const MMapEvent &Event) { - // Drop the event which doesn't belong to user-provided binary - StringRef BinaryName = llvm::sys::path::filename(Event.BinaryPath); - if (Binary->getName() != BinaryName) - return; - - // Drop the event if its image is loaded at the same address - if (Event.Address == Binary->getBaseAddress()) { - Binary->setIsLoadedByMMap(true); - return; - } - - if (Event.Offset == Binary->getTextSegmentOffset()) { - // A binary image could be unloaded and then reloaded at different - // place, so update binary load address. - // Only update for the first executable segment and assume all other - // segments are loaded at consecutive memory addresses, which is the case on - // X64. - Binary->setBaseAddress(Event.Address); - Binary->setIsLoadedByMMap(true); - } else { - // Verify segments are loaded consecutively. - const auto &Offsets = Binary->getTextSegmentOffsets(); - auto It = std::lower_bound(Offsets.begin(), Offsets.end(), Event.Offset); - if (It != Offsets.end() && *It == Event.Offset) { - // The event is for loading a separate executable segment. - auto I = std::distance(Offsets.begin(), It); - const auto &PreferredAddrs = Binary->getPreferredTextSegmentAddresses(); - if (PreferredAddrs[I] - Binary->getPreferredBaseAddress() != - Event.Address - Binary->getBaseAddress()) - exitWithError("Executable segments not loaded consecutively"); - } else { - if (It == Offsets.begin()) - exitWithError("File offset not found"); - else { - // Find the segment the event falls in. A large segment could be loaded - // via multiple mmap calls with consecutive memory addresses. - --It; - assert(*It < Event.Offset); - if (Event.Offset - *It != Event.Address - Binary->getBaseAddress()) - exitWithError("Segment not loaded by consecutive mmaps"); - } - } - } -} - -static std::string getContextKeyStr(ContextKey *K, - const ProfiledBinary *Binary) { - if (const auto *CtxKey = dyn_cast(K)) { - return SampleContext::getContextString(CtxKey->Context); - } else if (const auto *CtxKey = dyn_cast(K)) { - SampleContextFrameVector ContextStack; - for (const auto *Probe : CtxKey->Probes) { - Binary->getInlineContextForProbe(Probe, ContextStack, true); - } - // Probe context key at this point does not have leaf probe, so do not - // include the leaf inline location. - return SampleContext::getContextString(ContextStack, true); - } else { - llvm_unreachable("unexpected key type"); - } -} - -void HybridPerfReader::unwindSamples() { - if (Binary->useFSDiscriminator()) - exitWithError("FS discriminator is not supported in CS profile."); - VirtualUnwinder Unwinder(&SampleCounters, Binary); - for (const auto &Item : AggregatedSamples) { - const PerfSample *Sample = Item.first.getPtr(); - Unwinder.unwind(Sample, Item.second); - } - - // Warn about untracked frames due to missing probes. - if (ShowDetailedWarning) { - for (auto Address : Unwinder.getUntrackedCallsites()) - WithColor::warning() << "Profile context truncated due to missing probe " - << "for call instruction at " - << format("0x%" PRIx64, Address) << "\n"; - } - - emitWarningSummary(Unwinder.getUntrackedCallsites().size(), - SampleCounters.size(), - "of profiled contexts are truncated due to missing probe " - "for call instruction."); - - emitWarningSummary( - Unwinder.NumMismatchedExtCallBranch, Unwinder.NumTotalBranches, - "of branches'source is a call instruction but doesn't match call frame " - "stack, likely due to unwinding error of external frame."); - - emitWarningSummary( - Unwinder.NumMismatchedProEpiBranch, Unwinder.NumTotalBranches, - "of branches'source is a call instruction but doesn't match call frame " - "stack, likely due to frame in prolog/epilog."); - - emitWarningSummary(Unwinder.NumMissingExternalFrame, - Unwinder.NumExtCallBranch, - "of artificial call branches but doesn't have an external " - "frame to match."); -} - -bool PerfScriptReader::extractLBRStack(TraceStream &TraceIt, - SmallVectorImpl &LBRStack) { - // The raw format of LBR stack is like: - // 0x4005c8/0x4005dc/P/-/-/0 0x40062f/0x4005b0/P/-/-/0 ... - // ... 0x4005c8/0x4005dc/P/-/-/0 - // It's in FIFO order and seperated by whitespace. - SmallVector Records; - TraceIt.getCurrentLine().split(Records, " ", -1, false); - auto WarnInvalidLBR = [](TraceStream &TraceIt) { - WithColor::warning() << "Invalid address in LBR record at line " - << TraceIt.getLineNumber() << ": " - << TraceIt.getCurrentLine() << "\n"; - }; - - // Skip the leading instruction pointer. - size_t Index = 0; - uint64_t LeadingAddr; - if (!Records.empty() && !Records[0].contains('/')) { - if (Records[0].getAsInteger(16, LeadingAddr)) { - WarnInvalidLBR(TraceIt); - TraceIt.advance(); - return false; - } - Index = 1; - } - // Now extract LBR samples - note that we do not reverse the - // LBR entry order so we can unwind the sample stack as we walk - // through LBR entries. - uint64_t PrevTrDst = 0; - - while (Index < Records.size()) { - auto &Token = Records[Index++]; - if (Token.size() == 0) - continue; - - SmallVector Addresses; - Token.split(Addresses, "/"); - uint64_t Src; - uint64_t Dst; - - // Stop at broken LBR records. - if (Addresses.size() < 2 || Addresses[0].substr(2).getAsInteger(16, Src) || - Addresses[1].substr(2).getAsInteger(16, Dst)) { - WarnInvalidLBR(TraceIt); - break; - } - - bool SrcIsInternal = Binary->addressIsCode(Src); - bool DstIsInternal = Binary->addressIsCode(Dst); - bool IsExternal = !SrcIsInternal && !DstIsInternal; - bool IsIncoming = !SrcIsInternal && DstIsInternal; - bool IsOutgoing = SrcIsInternal && !DstIsInternal; - bool IsArtificial = false; - - // Ignore branches outside the current binary. - if (IsExternal) { - if (!PrevTrDst && !LBRStack.empty()) { - WithColor::warning() - << "Invalid transfer to external code in LBR record at line " - << TraceIt.getLineNumber() << ": " << TraceIt.getCurrentLine() - << "\n"; - } - // Do not ignore the entire samples, the remaining LBR can still be - // unwound using a context-less stack. - continue; - } - - if (IsOutgoing) { - if (!PrevTrDst) { - // This is a leading outgoing LBR, we should keep processing the LBRs. - if (LBRStack.empty()) { - NumLeadingOutgoingLBR++; - // Record this LBR since current source and next LBR' target is still - // a valid range. - LBRStack.emplace_back(LBREntry(Src, ExternalAddr, false)); - continue; - } - // This is middle unpaired outgoing jump which is likely due to - // interrupt or incomplete LBR trace. Ignore current and subsequent - // entries since they are likely in different contexts. - break; - } - - // For transition to external code, group the Source with the next - // availabe transition target. - Dst = PrevTrDst; - PrevTrDst = 0; - IsArtificial = true; - } else { - if (PrevTrDst) { - // If we have seen an incoming transition from external code to internal - // code, but not a following outgoing transition, the incoming - // transition is likely due to interrupt which is usually unpaired. - // Ignore current and subsequent entries since they are likely in - // different contexts. - break; - } - - if (IsIncoming) { - // For transition from external code (such as dynamic libraries) to - // the current binary, keep track of the branch target which will be - // grouped with the Source of the last transition from the current - // binary. - PrevTrDst = Dst; - continue; - } - } - - // TODO: filter out buggy duplicate branches on Skylake - - LBRStack.emplace_back(LBREntry(Src, Dst, IsArtificial)); - } - TraceIt.advance(); - return !LBRStack.empty(); -} - -bool PerfScriptReader::extractCallstack(TraceStream &TraceIt, - SmallVectorImpl &CallStack) { - // The raw format of call stack is like: - // 4005dc # leaf frame - // 400634 - // 400684 # root frame - // It's in bottom-up order with each frame in one line. - - // Extract stack frames from sample - while (!TraceIt.isAtEoF() && !TraceIt.getCurrentLine().startswith(" 0x")) { - StringRef FrameStr = TraceIt.getCurrentLine().ltrim(); - uint64_t FrameAddr = 0; - if (FrameStr.getAsInteger(16, FrameAddr)) { - // We might parse a non-perf sample line like empty line and comments, - // skip it - TraceIt.advance(); - return false; - } - TraceIt.advance(); - // Currently intermixed frame from different binaries is not supported. - if (!Binary->addressIsCode(FrameAddr)) { - if (CallStack.empty()) - NumLeafExternalFrame++; - // Push a special value(ExternalAddr) for the external frames so that - // unwinder can still work on this with artificial Call/Return branch. - // After unwinding, the context will be truncated for external frame. - // Also deduplicate the consecutive external addresses. - if (CallStack.empty() || CallStack.back() != ExternalAddr) - CallStack.emplace_back(ExternalAddr); - continue; - } - - // We need to translate return address to call address for non-leaf frames. - if (!CallStack.empty()) { - auto CallAddr = Binary->getCallAddrFromFrameAddr(FrameAddr); - if (!CallAddr) { - // Stop at an invalid return address caused by bad unwinding. This could - // happen to frame-pointer-based unwinding and the callee functions that - // do not have the frame pointer chain set up. - InvalidReturnAddresses.insert(FrameAddr); - break; - } - FrameAddr = CallAddr; - } - - CallStack.emplace_back(FrameAddr); - } - - // Strip out the bottom external addr. - if (CallStack.size() > 1 && CallStack.back() == ExternalAddr) - CallStack.pop_back(); - - // Skip other unrelated line, find the next valid LBR line - // Note that even for empty call stack, we should skip the address at the - // bottom, otherwise the following pass may generate a truncated callstack - while (!TraceIt.isAtEoF() && !TraceIt.getCurrentLine().startswith(" 0x")) { - TraceIt.advance(); - } - // Filter out broken stack sample. We may not have complete frame info - // if sample end up in prolog/epilog, the result is dangling context not - // connected to entry point. This should be relatively rare thus not much - // impact on overall profile quality. However we do want to filter them - // out to reduce the number of different calling contexts. One instance - // of such case - when sample landed in prolog/epilog, somehow stack - // walking will be broken in an unexpected way that higher frames will be - // missing. - return !CallStack.empty() && - !Binary->addressInPrologEpilog(CallStack.front()); -} - -void PerfScriptReader::warnIfMissingMMap() { - if (!Binary->getMissingMMapWarned() && !Binary->getIsLoadedByMMap()) { - WithColor::warning() << "No relevant mmap event is matched for " - << Binary->getName() - << ", will use preferred address (" - << format("0x%" PRIx64, - Binary->getPreferredBaseAddress()) - << ") as the base loading address!\n"; - // Avoid redundant warning, only warn at the first unmatched sample. - Binary->setMissingMMapWarned(true); - } -} - -void HybridPerfReader::parseSample(TraceStream &TraceIt, uint64_t Count) { - // The raw hybird sample started with call stack in FILO order and followed - // intermediately by LBR sample - // e.g. - // 4005dc # call stack leaf - // 400634 - // 400684 # call stack root - // 0x4005c8/0x4005dc/P/-/-/0 0x40062f/0x4005b0/P/-/-/0 ... - // ... 0x4005c8/0x4005dc/P/-/-/0 # LBR Entries - // - std::shared_ptr Sample = std::make_shared(); - - // Parsing call stack and populate into PerfSample.CallStack - if (!extractCallstack(TraceIt, Sample->CallStack)) { - // Skip the next LBR line matched current call stack - if (!TraceIt.isAtEoF() && TraceIt.getCurrentLine().startswith(" 0x")) - TraceIt.advance(); - return; - } - - warnIfMissingMMap(); - - if (!TraceIt.isAtEoF() && TraceIt.getCurrentLine().startswith(" 0x")) { - // Parsing LBR stack and populate into PerfSample.LBRStack - if (extractLBRStack(TraceIt, Sample->LBRStack)) { - if (IgnoreStackSamples) { - Sample->CallStack.clear(); - } else { - // Canonicalize stack leaf to avoid 'random' IP from leaf frame skew LBR - // ranges - Sample->CallStack.front() = Sample->LBRStack[0].Target; - } - // Record samples by aggregation - AggregatedSamples[Hashable(Sample)] += Count; - } - } else { - // LBR sample is encoded in single line after stack sample - exitWithError("'Hybrid perf sample is corrupted, No LBR sample line"); - } -} - -void PerfScriptReader::writeUnsymbolizedProfile(StringRef Filename) { - std::error_code EC; - raw_fd_ostream OS(Filename, EC, llvm::sys::fs::OF_TextWithCRLF); - if (EC) - exitWithError(EC, Filename); - writeUnsymbolizedProfile(OS); -} - -// Use ordered map to make the output deterministic -using OrderedCounterForPrint = std::map; - -void PerfScriptReader::writeUnsymbolizedProfile(raw_fd_ostream &OS) { - OrderedCounterForPrint OrderedCounters; - for (auto &CI : SampleCounters) { - OrderedCounters[getContextKeyStr(CI.first.getPtr(), Binary)] = &CI.second; - } - - auto SCounterPrinter = [&](RangeSample &Counter, StringRef Separator, - uint32_t Indent) { - OS.indent(Indent); - OS << Counter.size() << "\n"; - for (auto &I : Counter) { - uint64_t Start = I.first.first; - uint64_t End = I.first.second; - - if (!UseOffset || (UseOffset && UseLoadableSegmentAsBase)) { - Start = Binary->offsetToVirtualAddr(Start); - End = Binary->offsetToVirtualAddr(End); - } - - if (UseOffset && UseLoadableSegmentAsBase) { - Start -= Binary->getFirstLoadableAddress(); - End -= Binary->getFirstLoadableAddress(); - } - - OS.indent(Indent); - OS << Twine::utohexstr(Start) << Separator << Twine::utohexstr(End) << ":" - << I.second << "\n"; - } - }; - - for (auto &CI : OrderedCounters) { - uint32_t Indent = 0; - if (ProfileIsCSFlat) { - // Context string key - OS << "[" << CI.first << "]\n"; - Indent = 2; - } - - SampleCounter &Counter = *CI.second; - SCounterPrinter(Counter.RangeCounter, "-", Indent); - SCounterPrinter(Counter.BranchCounter, "->", Indent); - } -} - -// Format of input: -// number of entries in RangeCounter -// from_1-to_1:count_1 -// from_2-to_2:count_2 -// ...... -// from_n-to_n:count_n -// number of entries in BranchCounter -// src_1->dst_1:count_1 -// src_2->dst_2:count_2 -// ...... -// src_n->dst_n:count_n -void UnsymbolizedProfileReader::readSampleCounters(TraceStream &TraceIt, - SampleCounter &SCounters) { - auto exitWithErrorForTraceLine = [](TraceStream &TraceIt) { - std::string Msg = TraceIt.isAtEoF() - ? "Invalid raw profile!" - : "Invalid raw profile at line " + - Twine(TraceIt.getLineNumber()).str() + ": " + - TraceIt.getCurrentLine().str(); - exitWithError(Msg); - }; - auto ReadNumber = [&](uint64_t &Num) { - if (TraceIt.isAtEoF()) - exitWithErrorForTraceLine(TraceIt); - if (TraceIt.getCurrentLine().ltrim().getAsInteger(10, Num)) - exitWithErrorForTraceLine(TraceIt); - TraceIt.advance(); - }; - - auto ReadCounter = [&](RangeSample &Counter, StringRef Separator) { - uint64_t Num = 0; - ReadNumber(Num); - while (Num--) { - if (TraceIt.isAtEoF()) - exitWithErrorForTraceLine(TraceIt); - StringRef Line = TraceIt.getCurrentLine().ltrim(); - - uint64_t Count = 0; - auto LineSplit = Line.split(":"); - if (LineSplit.second.empty() || LineSplit.second.getAsInteger(10, Count)) - exitWithErrorForTraceLine(TraceIt); - - uint64_t Source = 0; - uint64_t Target = 0; - auto Range = LineSplit.first.split(Separator); - if (Range.second.empty() || Range.first.getAsInteger(16, Source) || - Range.second.getAsInteger(16, Target)) - exitWithErrorForTraceLine(TraceIt); - - if (!UseOffset || (UseOffset && UseLoadableSegmentAsBase)) { - uint64_t BaseAddr = 0; - if (UseOffset && UseLoadableSegmentAsBase) - BaseAddr = Binary->getFirstLoadableAddress(); - - Source = Binary->virtualAddrToOffset(Source + BaseAddr); - Target = Binary->virtualAddrToOffset(Target + BaseAddr); - } - - Counter[{Source, Target}] += Count; - TraceIt.advance(); - } - }; - - ReadCounter(SCounters.RangeCounter, "-"); - ReadCounter(SCounters.BranchCounter, "->"); -} - -void UnsymbolizedProfileReader::readUnsymbolizedProfile(StringRef FileName) { - TraceStream TraceIt(FileName); - while (!TraceIt.isAtEoF()) { - std::shared_ptr Key = - std::make_shared(); - StringRef Line = TraceIt.getCurrentLine(); - // Read context stack for CS profile. - if (Line.startswith("[")) { - ProfileIsCSFlat = true; - auto I = ContextStrSet.insert(Line.str()); - SampleContext::createCtxVectorFromStr(*I.first, Key->Context); - TraceIt.advance(); - } - auto Ret = - SampleCounters.emplace(Hashable(Key), SampleCounter()); - readSampleCounters(TraceIt, Ret.first->second); - } -} - -void UnsymbolizedProfileReader::parsePerfTraces() { - readUnsymbolizedProfile(PerfTraceFile); -} - -void PerfScriptReader::computeCounterFromLBR(const PerfSample *Sample, - uint64_t Repeat) { - SampleCounter &Counter = SampleCounters.begin()->second; - uint64_t EndOffeset = 0; - for (const LBREntry &LBR : Sample->LBRStack) { - assert(LBR.Source != ExternalAddr && - "Branch' source should not be an external address, it should be " - "converted to aritificial branch."); - uint64_t SourceOffset = Binary->virtualAddrToOffset(LBR.Source); - uint64_t TargetOffset = LBR.Target == static_cast(ExternalAddr) - ? static_cast(ExternalAddr) - : Binary->virtualAddrToOffset(LBR.Target); - - if (!LBR.IsArtificial && TargetOffset != ExternalAddr) { - Counter.recordBranchCount(SourceOffset, TargetOffset, Repeat); - } - - // If this not the first LBR, update the range count between TO of current - // LBR and FROM of next LBR. - uint64_t StartOffset = TargetOffset; - if (EndOffeset != 0) - Counter.recordRangeCount(StartOffset, EndOffeset, Repeat); - EndOffeset = SourceOffset; - } -} - -void LBRPerfReader::parseSample(TraceStream &TraceIt, uint64_t Count) { - std::shared_ptr Sample = std::make_shared(); - // Parsing LBR stack and populate into PerfSample.LBRStack - if (extractLBRStack(TraceIt, Sample->LBRStack)) { - warnIfMissingMMap(); - // Record LBR only samples by aggregation - AggregatedSamples[Hashable(Sample)] += Count; - } -} - -void PerfScriptReader::generateUnsymbolizedProfile() { - // There is no context for LBR only sample, so initialize one entry with - // fake "empty" context key. - assert(SampleCounters.empty() && - "Sample counter map should be empty before raw profile generation"); - std::shared_ptr Key = - std::make_shared(); - SampleCounters.emplace(Hashable(Key), SampleCounter()); - for (const auto &Item : AggregatedSamples) { - const PerfSample *Sample = Item.first.getPtr(); - computeCounterFromLBR(Sample, Item.second); - } -} - -uint64_t PerfScriptReader::parseAggregatedCount(TraceStream &TraceIt) { - // The aggregated count is optional, so do not skip the line and return 1 if - // it's unmatched - uint64_t Count = 1; - if (!TraceIt.getCurrentLine().getAsInteger(10, Count)) - TraceIt.advance(); - return Count; -} - -void PerfScriptReader::parseSample(TraceStream &TraceIt) { - NumTotalSample++; - uint64_t Count = parseAggregatedCount(TraceIt); - assert(Count >= 1 && "Aggregated count should be >= 1!"); - parseSample(TraceIt, Count); -} - -bool PerfScriptReader::extractMMap2EventForBinary(ProfiledBinary *Binary, - StringRef Line, - MMapEvent &MMap) { - // Parse a line like: - // PERF_RECORD_MMAP2 2113428/2113428: [0x7fd4efb57000(0x204000) @ 0 - // 08:04 19532229 3585508847]: r-xp /usr/lib64/libdl-2.17.so - constexpr static const char *const Pattern = - "PERF_RECORD_MMAP2 ([0-9]+)/[0-9]+: " - "\\[(0x[a-f0-9]+)\\((0x[a-f0-9]+)\\) @ " - "(0x[a-f0-9]+|0) .*\\]: [-a-z]+ (.*)"; - // Field 0 - whole line - // Field 1 - PID - // Field 2 - base address - // Field 3 - mmapped size - // Field 4 - page offset - // Field 5 - binary path - enum EventIndex { - WHOLE_LINE = 0, - PID = 1, - MMAPPED_ADDRESS = 2, - MMAPPED_SIZE = 3, - PAGE_OFFSET = 4, - BINARY_PATH = 5 - }; - - Regex RegMmap2(Pattern); - SmallVector Fields; - bool R = RegMmap2.match(Line, &Fields); - if (!R) { - std::string ErrorMsg = "Cannot parse mmap event: " + Line.str() + " \n"; - exitWithError(ErrorMsg); - } - Fields[PID].getAsInteger(10, MMap.PID); - Fields[MMAPPED_ADDRESS].getAsInteger(0, MMap.Address); - Fields[MMAPPED_SIZE].getAsInteger(0, MMap.Size); - Fields[PAGE_OFFSET].getAsInteger(0, MMap.Offset); - MMap.BinaryPath = Fields[BINARY_PATH]; - if (ShowMmapEvents) { - outs() << "Mmap: Binary " << MMap.BinaryPath << " loaded at " - << format("0x%" PRIx64 ":", MMap.Address) << " \n"; - } - - StringRef BinaryName = llvm::sys::path::filename(MMap.BinaryPath); - return Binary->getName() == BinaryName; -} - -void PerfScriptReader::parseMMap2Event(TraceStream &TraceIt) { - MMapEvent MMap; - if (extractMMap2EventForBinary(Binary, TraceIt.getCurrentLine(), MMap)) - updateBinaryAddress(MMap); - TraceIt.advance(); -} - -void PerfScriptReader::parseEventOrSample(TraceStream &TraceIt) { - if (isMMap2Event(TraceIt.getCurrentLine())) - parseMMap2Event(TraceIt); - else - parseSample(TraceIt); -} - -void PerfScriptReader::parseAndAggregateTrace() { - // Trace line iterator - TraceStream TraceIt(PerfTraceFile); - while (!TraceIt.isAtEoF()) - parseEventOrSample(TraceIt); -} - -// A LBR sample is like: -// 40062f 0x5c6313f/0x5c63170/P/-/-/0 0x5c630e7/0x5c63130/P/-/-/0 ... -// A heuristic for fast detection by checking whether a -// leading " 0x" and the '/' exist. -bool PerfScriptReader::isLBRSample(StringRef Line) { - // Skip the leading instruction pointer - SmallVector Records; - Line.trim().split(Records, " ", 2, false); - if (Records.size() < 2) - return false; - if (Records[1].startswith("0x") && Records[1].contains('/')) - return true; - return false; -} - -bool PerfScriptReader::isMMap2Event(StringRef Line) { - // Short cut to avoid string find is possible. - if (Line.empty() || Line.size() < 50) - return false; - - if (std::isdigit(Line[0])) - return false; - - // PERF_RECORD_MMAP2 does not appear at the beginning of the line - // for ` perf script --show-mmap-events -i ...` - return Line.contains("PERF_RECORD_MMAP2"); -} - -// The raw hybird sample is like -// e.g. -// 4005dc # call stack leaf -// 400634 -// 400684 # call stack root -// 0x4005c8/0x4005dc/P/-/-/0 0x40062f/0x4005b0/P/-/-/0 ... -// ... 0x4005c8/0x4005dc/P/-/-/0 # LBR Entries -// Determine the perfscript contains hybrid samples(call stack + LBRs) by -// checking whether there is a non-empty call stack immediately followed by -// a LBR sample -PerfContent PerfScriptReader::checkPerfScriptType(StringRef FileName) { - TraceStream TraceIt(FileName); - uint64_t FrameAddr = 0; - while (!TraceIt.isAtEoF()) { - // Skip the aggregated count - if (!TraceIt.getCurrentLine().getAsInteger(10, FrameAddr)) - TraceIt.advance(); - - // Detect sample with call stack - int32_t Count = 0; - while (!TraceIt.isAtEoF() && - !TraceIt.getCurrentLine().ltrim().getAsInteger(16, FrameAddr)) { - Count++; - TraceIt.advance(); - } - if (!TraceIt.isAtEoF()) { - if (isLBRSample(TraceIt.getCurrentLine())) { - if (Count > 0) - return PerfContent::LBRStack; - else - return PerfContent::LBR; - } - TraceIt.advance(); - } - } - - exitWithError("Invalid perf script input!"); - return PerfContent::UnknownContent; -} - -void HybridPerfReader::generateUnsymbolizedProfile() { - ProfileIsCSFlat = !IgnoreStackSamples; - if (ProfileIsCSFlat) - unwindSamples(); - else - PerfScriptReader::generateUnsymbolizedProfile(); -} - -void PerfScriptReader::warnTruncatedStack() { - if (ShowDetailedWarning) { - for (auto Address : InvalidReturnAddresses) { - WithColor::warning() - << "Truncated stack sample due to invalid return address at " - << format("0x%" PRIx64, Address) - << ", likely caused by frame pointer omission\n"; - } - } - emitWarningSummary( - InvalidReturnAddresses.size(), AggregatedSamples.size(), - "of truncated stack samples due to invalid return address, " - "likely caused by frame pointer omission."); -} - -void PerfScriptReader::warnInvalidRange() { - std::unordered_map, uint64_t, - pair_hash> - Ranges; - - for (const auto &Item : AggregatedSamples) { - const PerfSample *Sample = Item.first.getPtr(); - uint64_t Count = Item.second; - uint64_t EndOffeset = 0; - for (const LBREntry &LBR : Sample->LBRStack) { - uint64_t SourceOffset = Binary->virtualAddrToOffset(LBR.Source); - uint64_t StartOffset = Binary->virtualAddrToOffset(LBR.Target); - if (EndOffeset != 0) - Ranges[{StartOffset, EndOffeset}] += Count; - EndOffeset = SourceOffset; - } - } - - if (Ranges.empty()) { - WithColor::warning() << "No samples in perf script!\n"; - return; - } - - auto WarnInvalidRange = - [&](uint64_t StartOffset, uint64_t EndOffset, StringRef Msg) { - if (!ShowDetailedWarning) - return; - WithColor::warning() - << "[" - << format("%8" PRIx64, Binary->offsetToVirtualAddr(StartOffset)) - << "," - << format("%8" PRIx64, Binary->offsetToVirtualAddr(EndOffset)) - << "]: " << Msg << "\n"; - }; - - const char *EndNotBoundaryMsg = "Range is not on instruction boundary, " - "likely due to profile and binary mismatch."; - const char *DanglingRangeMsg = "Range does not belong to any functions, " - "likely from PLT, .init or .fini section."; - const char *RangeCrossFuncMsg = - "Fall through range should not cross function boundaries, likely due to " - "profile and binary mismatch."; - - uint64_t InstNotBoundary = 0; - uint64_t UnmatchedRange = 0; - uint64_t RangeCrossFunc = 0; - - for (auto &I : Ranges) { - uint64_t StartOffset = I.first.first; - uint64_t EndOffset = I.first.second; - - if (!Binary->offsetIsCode(StartOffset) || - !Binary->offsetIsTransfer(EndOffset)) { - InstNotBoundary++; - WarnInvalidRange(StartOffset, EndOffset, EndNotBoundaryMsg); - } - - auto *FRange = Binary->findFuncRangeForOffset(StartOffset); - if (!FRange) { - UnmatchedRange++; - WarnInvalidRange(StartOffset, EndOffset, DanglingRangeMsg); - continue; - } - - if (EndOffset >= FRange->EndOffset) { - RangeCrossFunc++; - WarnInvalidRange(StartOffset, EndOffset, RangeCrossFuncMsg); - } - } - - uint64_t TotalRangeNum = Ranges.size(); - emitWarningSummary(InstNotBoundary, TotalRangeNum, - "of profiled ranges are not on instruction boundary."); - emitWarningSummary(UnmatchedRange, TotalRangeNum, - "of profiled ranges do not belong to any functions."); - emitWarningSummary(RangeCrossFunc, TotalRangeNum, - "of profiled ranges do cross function boundaries."); -} - -void PerfScriptReader::parsePerfTraces() { - // Parse perf traces and do aggregation. - parseAndAggregateTrace(); - - emitWarningSummary(NumLeafExternalFrame, NumTotalSample, - "of samples have leaf external frame in call stack."); - emitWarningSummary(NumLeadingOutgoingLBR, NumTotalSample, - "of samples have leading external LBR."); - - // Generate unsymbolized profile. - warnTruncatedStack(); - warnInvalidRange(); - generateUnsymbolizedProfile(); - AggregatedSamples.clear(); - - if (SkipSymbolization) - writeUnsymbolizedProfile(OutputFilename); -} - -} // end namespace sampleprof -} // end namespace llvm diff --git a/tools/ldc-profgen/ldc-profgen-14.0/PerfReader.h b/tools/ldc-profgen/ldc-profgen-14.0/PerfReader.h deleted file mode 100644 index 9d84ad34bb3..00000000000 --- a/tools/ldc-profgen/ldc-profgen-14.0/PerfReader.h +++ /dev/null @@ -1,728 +0,0 @@ -//===-- PerfReader.h - perfscript reader -----------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_TOOLS_LLVM_PROFGEN_PERFREADER_H -#define LLVM_TOOLS_LLVM_PROFGEN_PERFREADER_H -#include "ErrorHandling.h" -#include "ProfiledBinary.h" -#include "llvm/Support/Casting.h" -#include "llvm/Support/CommandLine.h" -#include "llvm/Support/Regex.h" -#include -#include -#include -#include -#include - -using namespace llvm; -using namespace sampleprof; - -namespace llvm { -namespace sampleprof { - -// Stream based trace line iterator -class TraceStream { - std::string CurrentLine; - std::ifstream Fin; - bool IsAtEoF = false; - uint64_t LineNumber = 0; - -public: - TraceStream(StringRef Filename) : Fin(Filename.str()) { - if (!Fin.good()) - exitWithError("Error read input perf script file", Filename); - advance(); - } - - StringRef getCurrentLine() { - assert(!IsAtEoF && "Line iterator reaches the End-of-File!"); - return CurrentLine; - } - - uint64_t getLineNumber() { return LineNumber; } - - bool isAtEoF() { return IsAtEoF; } - - // Read the next line - void advance() { - if (!std::getline(Fin, CurrentLine)) { - IsAtEoF = true; - return; - } - LineNumber++; - } -}; - -// The type of input format. -enum PerfFormat { - UnknownFormat = 0, - PerfData = 1, // Raw linux perf.data. - PerfScript = 2, // Perf script create by `perf script` command. - UnsymbolizedProfile = 3, // Unsymbolized profile generated by llvm-profgen. - -}; - -// The type of perfscript content. -enum PerfContent { - UnknownContent = 0, - LBR = 1, // Only LBR sample. - LBRStack = 2, // Hybrid sample including call stack and LBR stack. -}; - -struct PerfInputFile { - std::string InputFile; - PerfFormat Format = PerfFormat::UnknownFormat; - PerfContent Content = PerfContent::UnknownContent; -}; - -// The parsed LBR sample entry. -struct LBREntry { - uint64_t Source = 0; - uint64_t Target = 0; - // An artificial branch stands for a series of consecutive branches starting - // from the current binary with a transition through external code and - // eventually landing back in the current binary. - bool IsArtificial = false; - LBREntry(uint64_t S, uint64_t T, bool I) - : Source(S), Target(T), IsArtificial(I) {} - -#ifndef NDEBUG - void print() const { - dbgs() << "from " << format("%#010x", Source) << " to " - << format("%#010x", Target); - if (IsArtificial) - dbgs() << " Artificial"; - } -#endif -}; - -#ifndef NDEBUG -static inline void printLBRStack(const SmallVectorImpl &LBRStack) { - for (size_t I = 0; I < LBRStack.size(); I++) { - dbgs() << "[" << I << "] "; - LBRStack[I].print(); - dbgs() << "\n"; - } -} - -static inline void printCallStack(const SmallVectorImpl &CallStack) { - for (size_t I = 0; I < CallStack.size(); I++) { - dbgs() << "[" << I << "] " << format("%#010x", CallStack[I]) << "\n"; - } -} -#endif - -// Hash interface for generic data of type T -// Data should implement a \fn getHashCode and a \fn isEqual -// Currently getHashCode is non-virtual to avoid the overhead of calling vtable, -// i.e we explicitly calculate hash of derived class, assign to base class's -// HashCode. This also provides the flexibility for calculating the hash code -// incrementally(like rolling hash) during frame stack unwinding since unwinding -// only changes the leaf of frame stack. \fn isEqual is a virtual function, -// which will have perf overhead. In the future, if we redesign a better hash -// function, then we can just skip this or switch to non-virtual function(like -// just ignore comparision if hash conflicts probabilities is low) -template class Hashable { -public: - std::shared_ptr Data; - Hashable(const std::shared_ptr &D) : Data(D) {} - - // Hash code generation - struct Hash { - uint64_t operator()(const Hashable &Key) const { - // Don't make it virtual for getHashCode - uint64_t Hash = Key.Data->getHashCode(); - assert(Hash && "Should generate HashCode for it!"); - return Hash; - } - }; - - // Hash equal - struct Equal { - bool operator()(const Hashable &LHS, const Hashable &RHS) const { - // Precisely compare the data, vtable will have overhead. - return LHS.Data->isEqual(RHS.Data.get()); - } - }; - - T *getPtr() const { return Data.get(); } -}; - -struct PerfSample { - // LBR stack recorded in FIFO order. - SmallVector LBRStack; - // Call stack recorded in FILO(leaf to root) order, it's used for CS-profile - // generation - SmallVector CallStack; - - virtual ~PerfSample() = default; - uint64_t getHashCode() const { - // Use simple DJB2 hash - auto HashCombine = [](uint64_t H, uint64_t V) { - return ((H << 5) + H) + V; - }; - uint64_t Hash = 5381; - for (const auto &Value : CallStack) { - Hash = HashCombine(Hash, Value); - } - for (const auto &Entry : LBRStack) { - Hash = HashCombine(Hash, Entry.Source); - Hash = HashCombine(Hash, Entry.Target); - } - return Hash; - } - - bool isEqual(const PerfSample *Other) const { - const SmallVector &OtherCallStack = Other->CallStack; - const SmallVector &OtherLBRStack = Other->LBRStack; - - if (CallStack.size() != OtherCallStack.size() || - LBRStack.size() != OtherLBRStack.size()) - return false; - - if (!std::equal(CallStack.begin(), CallStack.end(), OtherCallStack.begin())) - return false; - - for (size_t I = 0; I < OtherLBRStack.size(); I++) { - if (LBRStack[I].Source != OtherLBRStack[I].Source || - LBRStack[I].Target != OtherLBRStack[I].Target) - return false; - } - return true; - } - -#ifndef NDEBUG - void print() const { - dbgs() << "LBR stack\n"; - printLBRStack(LBRStack); - dbgs() << "Call stack\n"; - printCallStack(CallStack); - } -#endif -}; -// After parsing the sample, we record the samples by aggregating them -// into this counter. The key stores the sample data and the value is -// the sample repeat times. -using AggregatedCounter = - std::unordered_map, uint64_t, - Hashable::Hash, Hashable::Equal>; - -using SampleVector = SmallVector, 16>; - -// The state for the unwinder, it doesn't hold the data but only keep the -// pointer/index of the data, While unwinding, the CallStack is changed -// dynamicially and will be recorded as the context of the sample -struct UnwindState { - // Profiled binary that current frame address belongs to - const ProfiledBinary *Binary; - // Call stack trie node - struct ProfiledFrame { - const uint64_t Address = DummyRoot; - ProfiledFrame *Parent; - SampleVector RangeSamples; - SampleVector BranchSamples; - std::unordered_map> Children; - - ProfiledFrame(uint64_t Addr = 0, ProfiledFrame *P = nullptr) - : Address(Addr), Parent(P) {} - ProfiledFrame *getOrCreateChildFrame(uint64_t Address) { - assert(Address && "Address can't be zero!"); - auto Ret = Children.emplace( - Address, std::make_unique(Address, this)); - return Ret.first->second.get(); - } - void recordRangeCount(uint64_t Start, uint64_t End, uint64_t Count) { - RangeSamples.emplace_back(std::make_tuple(Start, End, Count)); - } - void recordBranchCount(uint64_t Source, uint64_t Target, uint64_t Count) { - BranchSamples.emplace_back(std::make_tuple(Source, Target, Count)); - } - bool isDummyRoot() { return Address == DummyRoot; } - bool isExternalFrame() { return Address == ExternalAddr; } - bool isLeafFrame() { return Children.empty(); } - }; - - ProfiledFrame DummyTrieRoot; - ProfiledFrame *CurrentLeafFrame; - // Used to fall through the LBR stack - uint32_t LBRIndex = 0; - // Reference to PerfSample.LBRStack - const SmallVector &LBRStack; - // Used to iterate the address range - InstructionPointer InstPtr; - UnwindState(const PerfSample *Sample, const ProfiledBinary *Binary) - : Binary(Binary), LBRStack(Sample->LBRStack), - InstPtr(Binary, Sample->CallStack.front()) { - initFrameTrie(Sample->CallStack); - } - - bool validateInitialState() { - uint64_t LBRLeaf = LBRStack[LBRIndex].Target; - uint64_t LeafAddr = CurrentLeafFrame->Address; - assert((LBRLeaf != ExternalAddr || LBRLeaf == LeafAddr) && - "External leading LBR should match the leaf frame."); - - // When we take a stack sample, ideally the sampling distance between the - // leaf IP of stack and the last LBR target shouldn't be very large. - // Use a heuristic size (0x100) to filter out broken records. - if (LeafAddr < LBRLeaf || LeafAddr >= LBRLeaf + 0x100) { - WithColor::warning() << "Bogus trace: stack tip = " - << format("%#010x", LeafAddr) - << ", LBR tip = " << format("%#010x\n", LBRLeaf); - return false; - } - return true; - } - - void checkStateConsistency() { - assert(InstPtr.Address == CurrentLeafFrame->Address && - "IP should align with context leaf"); - } - - bool hasNextLBR() const { return LBRIndex < LBRStack.size(); } - uint64_t getCurrentLBRSource() const { return LBRStack[LBRIndex].Source; } - uint64_t getCurrentLBRTarget() const { return LBRStack[LBRIndex].Target; } - const LBREntry &getCurrentLBR() const { return LBRStack[LBRIndex]; } - bool IsLastLBR() const { return LBRIndex == 0; } - bool getLBRStackSize() const { return LBRStack.size(); } - void advanceLBR() { LBRIndex++; } - ProfiledFrame *getParentFrame() { return CurrentLeafFrame->Parent; } - - void pushFrame(uint64_t Address) { - CurrentLeafFrame = CurrentLeafFrame->getOrCreateChildFrame(Address); - } - - void switchToFrame(uint64_t Address) { - if (CurrentLeafFrame->Address == Address) - return; - CurrentLeafFrame = CurrentLeafFrame->Parent->getOrCreateChildFrame(Address); - } - - void popFrame() { CurrentLeafFrame = CurrentLeafFrame->Parent; } - - void clearCallStack() { CurrentLeafFrame = &DummyTrieRoot; } - - void initFrameTrie(const SmallVectorImpl &CallStack) { - ProfiledFrame *Cur = &DummyTrieRoot; - for (auto Address : reverse(CallStack)) { - Cur = Cur->getOrCreateChildFrame(Address); - } - CurrentLeafFrame = Cur; - } - - ProfiledFrame *getDummyRootPtr() { return &DummyTrieRoot; } -}; - -// Base class for sample counter key with context -struct ContextKey { - uint64_t HashCode = 0; - virtual ~ContextKey() = default; - uint64_t getHashCode() { - if (HashCode == 0) - genHashCode(); - return HashCode; - } - virtual void genHashCode() = 0; - virtual bool isEqual(const ContextKey *K) const { - return HashCode == K->HashCode; - }; - - // Utilities for LLVM-style RTTI - enum ContextKind { CK_StringBased, CK_ProbeBased }; - const ContextKind Kind; - ContextKind getKind() const { return Kind; } - ContextKey(ContextKind K) : Kind(K){}; -}; - -// String based context id -struct StringBasedCtxKey : public ContextKey { - SampleContextFrameVector Context; - - bool WasLeafInlined; - StringBasedCtxKey() : ContextKey(CK_StringBased), WasLeafInlined(false){}; - static bool classof(const ContextKey *K) { - return K->getKind() == CK_StringBased; - } - - bool isEqual(const ContextKey *K) const override { - const StringBasedCtxKey *Other = dyn_cast(K); - return Context == Other->Context; - } - - void genHashCode() override { - HashCode = hash_value(SampleContextFrames(Context)); - } -}; - -// Probe based context key as the intermediate key of context -// String based context key will introduce redundant string handling -// since the callee context is inferred from the context string which -// need to be splitted by '@' to get the last location frame, so we -// can just use probe instead and generate the string in the end. -struct ProbeBasedCtxKey : public ContextKey { - SmallVector Probes; - - ProbeBasedCtxKey() : ContextKey(CK_ProbeBased) {} - static bool classof(const ContextKey *K) { - return K->getKind() == CK_ProbeBased; - } - - bool isEqual(const ContextKey *K) const override { - const ProbeBasedCtxKey *O = dyn_cast(K); - assert(O != nullptr && "Probe based key shouldn't be null in isEqual"); - return std::equal(Probes.begin(), Probes.end(), O->Probes.begin(), - O->Probes.end()); - } - - void genHashCode() override { - for (const auto *P : Probes) { - HashCode = hash_combine(HashCode, P); - } - if (HashCode == 0) { - // Avoid zero value of HashCode when it's an empty list - HashCode = 1; - } - } -}; - -// The counter of branch samples for one function indexed by the branch, -// which is represented as the source and target offset pair. -using BranchSample = std::map, uint64_t>; -// The counter of range samples for one function indexed by the range, -// which is represented as the start and end offset pair. -using RangeSample = std::map, uint64_t>; -// Wrapper for sample counters including range counter and branch counter -struct SampleCounter { - RangeSample RangeCounter; - BranchSample BranchCounter; - - void recordRangeCount(uint64_t Start, uint64_t End, uint64_t Repeat) { - assert(Start <= End && "Invalid instruction range"); - RangeCounter[{Start, End}] += Repeat; - } - void recordBranchCount(uint64_t Source, uint64_t Target, uint64_t Repeat) { - BranchCounter[{Source, Target}] += Repeat; - } -}; - -// Sample counter with context to support context-sensitive profile -using ContextSampleCounterMap = - std::unordered_map, SampleCounter, - Hashable::Hash, Hashable::Equal>; - -struct FrameStack { - SmallVector Stack; - ProfiledBinary *Binary; - FrameStack(ProfiledBinary *B) : Binary(B) {} - bool pushFrame(UnwindState::ProfiledFrame *Cur) { - assert(!Cur->isExternalFrame() && - "External frame's not expected for context stack."); - Stack.push_back(Cur->Address); - return true; - } - - void popFrame() { - if (!Stack.empty()) - Stack.pop_back(); - } - std::shared_ptr getContextKey(); -}; - -struct ProbeStack { - SmallVector Stack; - ProfiledBinary *Binary; - ProbeStack(ProfiledBinary *B) : Binary(B) {} - bool pushFrame(UnwindState::ProfiledFrame *Cur) { - assert(!Cur->isExternalFrame() && - "External frame's not expected for context stack."); - const MCDecodedPseudoProbe *CallProbe = - Binary->getCallProbeForAddr(Cur->Address); - // We may not find a probe for a merged or external callsite. - // Callsite merging may cause the loss of original probe IDs. - // Cutting off the context from here since the inliner will - // not know how to consume a context with unknown callsites. - if (!CallProbe) - return false; - Stack.push_back(CallProbe); - return true; - } - - void popFrame() { - if (!Stack.empty()) - Stack.pop_back(); - } - // Use pseudo probe based context key to get the sample counter - // A context stands for a call path from 'main' to an uninlined - // callee with all inline frames recovered on that path. The probes - // belonging to that call path is the probes either originated from - // the callee or from any functions inlined into the callee. Since - // pseudo probes are organized in a tri-tree style after decoded, - // the tree path from the tri-tree root (which is the uninlined - // callee) to the probe node forms an inline context. - // Here we use a list of probe(pointer) as the context key to speed up - // aggregation and the final context string will be generate in - // ProfileGenerator - std::shared_ptr getContextKey(); -}; - -/* -As in hybrid sample we have a group of LBRs and the most recent sampling call -stack, we can walk through those LBRs to infer more call stacks which would be -used as context for profile. VirtualUnwinder is the class to do the call stack -unwinding based on LBR state. Two types of unwinding are processd here: -1) LBR unwinding and 2) linear range unwinding. -Specifically, for each LBR entry(can be classified into call, return, regular -branch), LBR unwinding will replay the operation by pushing, popping or -switching leaf frame towards the call stack and since the initial call stack -is most recently sampled, the replay should be in anti-execution order, i.e. for -the regular case, pop the call stack when LBR is call, push frame on call stack -when LBR is return. After each LBR processed, it also needs to align with the -next LBR by going through instructions from previous LBR's target to current -LBR's source, which is the linear unwinding. As instruction from linear range -can come from different function by inlining, linear unwinding will do the range -splitting and record counters by the range with same inline context. Over those -unwinding process we will record each call stack as context id and LBR/linear -range as sample counter for further CS profile generation. -*/ -class VirtualUnwinder { -public: - VirtualUnwinder(ContextSampleCounterMap *Counter, ProfiledBinary *B) - : CtxCounterMap(Counter), Binary(B) {} - bool unwind(const PerfSample *Sample, uint64_t Repeat); - std::set &getUntrackedCallsites() { return UntrackedCallsites; } - - uint64_t NumTotalBranches = 0; - uint64_t NumExtCallBranch = 0; - uint64_t NumMissingExternalFrame = 0; - uint64_t NumMismatchedProEpiBranch = 0; - uint64_t NumMismatchedExtCallBranch = 0; - -private: - bool isCallState(UnwindState &State) const { - // The tail call frame is always missing here in stack sample, we will - // use a specific tail call tracker to infer it. - return Binary->addressIsCall(State.getCurrentLBRSource()); - } - - bool isReturnState(UnwindState &State) const { - // Simply check addressIsReturn, as ret is always reliable, both for - // regular call and tail call. - if (!Binary->addressIsReturn(State.getCurrentLBRSource())) - return false; - - // In a callback case, a return from internal code, say A, to external - // runtime can happen. The external runtime can then call back to - // another internal routine, say B. Making an artificial branch that - // looks like a return from A to B can confuse the unwinder to treat - // the instruction before B as the call instruction. Here we detect this - // case if the return target is not the next inst of call inst, then we just - // do not treat it as a return. - uint64_t CallAddr = - Binary->getCallAddrFromFrameAddr(State.getCurrentLBRTarget()); - return (CallAddr != 0); - } - - void unwindCall(UnwindState &State); - void unwindLinear(UnwindState &State, uint64_t Repeat); - void unwindReturn(UnwindState &State); - void unwindBranch(UnwindState &State); - - template - void collectSamplesFromFrame(UnwindState::ProfiledFrame *Cur, T &Stack); - // Collect each samples on trie node by DFS traversal - template - void collectSamplesFromFrameTrie(UnwindState::ProfiledFrame *Cur, T &Stack); - void collectSamplesFromFrameTrie(UnwindState::ProfiledFrame *Cur); - - void recordRangeCount(uint64_t Start, uint64_t End, UnwindState &State, - uint64_t Repeat); - void recordBranchCount(const LBREntry &Branch, UnwindState &State, - uint64_t Repeat); - - ContextSampleCounterMap *CtxCounterMap; - // Profiled binary that current frame address belongs to - ProfiledBinary *Binary; - // Keep track of all untracked callsites - std::set UntrackedCallsites; -}; - -// Read perf trace to parse the events and samples. -class PerfReaderBase { -public: - PerfReaderBase(ProfiledBinary *B, StringRef PerfTrace) - : Binary(B), PerfTraceFile(PerfTrace) { - // Initialize the base address to preferred address. - Binary->setBaseAddress(Binary->getPreferredBaseAddress()); - }; - virtual ~PerfReaderBase() = default; - static std::unique_ptr create(ProfiledBinary *Binary, - PerfInputFile &PerfInput); - - // Entry of the reader to parse multiple perf traces - virtual void parsePerfTraces() = 0; - const ContextSampleCounterMap &getSampleCounters() const { - return SampleCounters; - } - bool profileIsCSFlat() { return ProfileIsCSFlat; } - -protected: - ProfiledBinary *Binary = nullptr; - StringRef PerfTraceFile; - - ContextSampleCounterMap SampleCounters; - bool ProfileIsCSFlat = false; - - uint64_t NumTotalSample = 0; - uint64_t NumLeafExternalFrame = 0; - uint64_t NumLeadingOutgoingLBR = 0; -}; - -// Read perf script to parse the events and samples. -class PerfScriptReader : public PerfReaderBase { -public: - PerfScriptReader(ProfiledBinary *B, StringRef PerfTrace) - : PerfReaderBase(B, PerfTrace){}; - - // Entry of the reader to parse multiple perf traces - virtual void parsePerfTraces() override; - // Generate perf script from perf data - static PerfInputFile convertPerfDataToTrace(ProfiledBinary *Binary, - PerfInputFile &File); - // Extract perf script type by peaking at the input - static PerfContent checkPerfScriptType(StringRef FileName); - -protected: - // The parsed MMap event - struct MMapEvent { - uint64_t PID = 0; - uint64_t Address = 0; - uint64_t Size = 0; - uint64_t Offset = 0; - StringRef BinaryPath; - }; - - // Check whether a given line is LBR sample - static bool isLBRSample(StringRef Line); - // Check whether a given line is MMAP event - static bool isMMap2Event(StringRef Line); - // Parse a single line of a PERF_RECORD_MMAP2 event looking for a - // mapping between the binary name and its memory layout. - static bool extractMMap2EventForBinary(ProfiledBinary *Binary, StringRef Line, - MMapEvent &MMap); - // Update base address based on mmap events - void updateBinaryAddress(const MMapEvent &Event); - // Parse mmap event and update binary address - void parseMMap2Event(TraceStream &TraceIt); - // Parse perf events/samples and do aggregation - void parseAndAggregateTrace(); - // Parse either an MMAP event or a perf sample - void parseEventOrSample(TraceStream &TraceIt); - // Warn if the relevant mmap event is missing. - void warnIfMissingMMap(); - // Emit accumulate warnings. - void warnTruncatedStack(); - // Warn if range is invalid. - void warnInvalidRange(); - // Extract call stack from the perf trace lines - bool extractCallstack(TraceStream &TraceIt, - SmallVectorImpl &CallStack); - // Extract LBR stack from one perf trace line - bool extractLBRStack(TraceStream &TraceIt, - SmallVectorImpl &LBRStack); - uint64_t parseAggregatedCount(TraceStream &TraceIt); - // Parse one sample from multiple perf lines, override this for different - // sample type - void parseSample(TraceStream &TraceIt); - // An aggregated count is given to indicate how many times the sample is - // repeated. - virtual void parseSample(TraceStream &TraceIt, uint64_t Count){}; - void computeCounterFromLBR(const PerfSample *Sample, uint64_t Repeat); - // Post process the profile after trace aggregation, we will do simple range - // overlap computation for AutoFDO, or unwind for CSSPGO(hybrid sample). - virtual void generateUnsymbolizedProfile(); - void writeUnsymbolizedProfile(StringRef Filename); - void writeUnsymbolizedProfile(raw_fd_ostream &OS); - - // Samples with the repeating time generated by the perf reader - AggregatedCounter AggregatedSamples; - // Keep track of all invalid return addresses - std::set InvalidReturnAddresses; -}; - -/* - The reader of LBR only perf script. - A typical LBR sample is like: - 40062f 0x4005c8/0x4005dc/P/-/-/0 0x40062f/0x4005b0/P/-/-/0 ... - ... 0x4005c8/0x4005dc/P/-/-/0 -*/ -class LBRPerfReader : public PerfScriptReader { -public: - LBRPerfReader(ProfiledBinary *Binary, StringRef PerfTrace) - : PerfScriptReader(Binary, PerfTrace){}; - // Parse the LBR only sample. - virtual void parseSample(TraceStream &TraceIt, uint64_t Count) override; -}; - -/* - Hybrid perf script includes a group of hybrid samples(LBRs + call stack), - which is used to generate CS profile. An example of hybrid sample: - 4005dc # call stack leaf - 400634 - 400684 # call stack root - 0x4005c8/0x4005dc/P/-/-/0 0x40062f/0x4005b0/P/-/-/0 ... - ... 0x4005c8/0x4005dc/P/-/-/0 # LBR Entries -*/ -class HybridPerfReader : public PerfScriptReader { -public: - HybridPerfReader(ProfiledBinary *Binary, StringRef PerfTrace) - : PerfScriptReader(Binary, PerfTrace){}; - // Parse the hybrid sample including the call and LBR line - void parseSample(TraceStream &TraceIt, uint64_t Count) override; - void generateUnsymbolizedProfile() override; - -private: - // Unwind the hybrid samples after aggregration - void unwindSamples(); -}; - -/* - Format of unsymbolized profile: - - [frame1 @ frame2 @ ...] # If it's a CS profile - number of entries in RangeCounter - from_1-to_1:count_1 - from_2-to_2:count_2 - ...... - from_n-to_n:count_n - number of entries in BranchCounter - src_1->dst_1:count_1 - src_2->dst_2:count_2 - ...... - src_n->dst_n:count_n - [frame1 @ frame2 @ ...] # Next context - ...... - -Note that non-CS profile doesn't have the empty `[]` context. -*/ -class UnsymbolizedProfileReader : public PerfReaderBase { -public: - UnsymbolizedProfileReader(ProfiledBinary *Binary, StringRef PerfTrace) - : PerfReaderBase(Binary, PerfTrace){}; - void parsePerfTraces() override; - -private: - void readSampleCounters(TraceStream &TraceIt, SampleCounter &SCounters); - void readUnsymbolizedProfile(StringRef Filename); - - std::unordered_set ContextStrSet; -}; - -} // end namespace sampleprof -} // end namespace llvm - -#endif diff --git a/tools/ldc-profgen/ldc-profgen-14.0/ProfileGenerator.cpp b/tools/ldc-profgen/ldc-profgen-14.0/ProfileGenerator.cpp deleted file mode 100644 index 1248e37dc50..00000000000 --- a/tools/ldc-profgen/ldc-profgen-14.0/ProfileGenerator.cpp +++ /dev/null @@ -1,979 +0,0 @@ -//===-- ProfileGenerator.cpp - Profile Generator ---------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "ProfileGenerator.h" -#include "ErrorHandling.h" -#include "ProfiledBinary.h" -#include "llvm/ProfileData/ProfileCommon.h" -#include -#include - -cl::opt OutputFilename("output", cl::value_desc("output"), - cl::Required, - cl::desc("Output profile file")); -static cl::alias OutputA("o", cl::desc("Alias for --output"), - cl::aliasopt(OutputFilename)); - -static cl::opt OutputFormat( - "format", cl::desc("Format of output profile"), cl::init(SPF_Ext_Binary), - cl::values( - clEnumValN(SPF_Binary, "binary", "Binary encoding (default)"), - clEnumValN(SPF_Compact_Binary, "compbinary", "Compact binary encoding"), - clEnumValN(SPF_Ext_Binary, "extbinary", "Extensible binary encoding"), - clEnumValN(SPF_Text, "text", "Text encoding"), - clEnumValN(SPF_GCC, "gcc", - "GCC encoding (only meaningful for -sample)"))); - -cl::opt UseMD5( - "use-md5", cl::init(false), cl::Hidden, - cl::desc("Use md5 to represent function names in the output profile (only " - "meaningful for -extbinary)")); - -static cl::opt PopulateProfileSymbolList( - "populate-profile-symbol-list", cl::init(false), cl::Hidden, - cl::desc("Populate profile symbol list (only meaningful for -extbinary)")); - -static cl::opt FillZeroForAllFuncs( - "fill-zero-for-all-funcs", cl::init(false), cl::Hidden, - cl::desc("Attribute all functions' range with zero count " - "even it's not hit by any samples.")); - -static cl::opt RecursionCompression( - "compress-recursion", - cl::desc("Compressing recursion by deduplicating adjacent frame " - "sequences up to the specified size. -1 means no size limit."), - cl::Hidden, - cl::location(llvm::sampleprof::CSProfileGenerator::MaxCompressionSize)); - -static cl::opt - TrimColdProfile("trim-cold-profile", cl::init(false), cl::ZeroOrMore, - cl::desc("If the total count of the profile is smaller " - "than threshold, it will be trimmed.")); - -static cl::opt CSProfMergeColdContext( - "csprof-merge-cold-context", cl::init(true), cl::ZeroOrMore, - cl::desc("If the total count of context profile is smaller than " - "the threshold, it will be merged into context-less base " - "profile.")); - -static cl::opt CSProfMaxColdContextDepth( - "csprof-max-cold-context-depth", cl::init(1), cl::ZeroOrMore, - cl::desc("Keep the last K contexts while merging cold profile. 1 means the " - "context-less base profile")); - -static cl::opt CSProfMaxContextDepth( - "csprof-max-context-depth", cl::ZeroOrMore, - cl::desc("Keep the last K contexts while merging profile. -1 means no " - "depth limit."), - cl::location(llvm::sampleprof::CSProfileGenerator::MaxContextDepth)); - -static cl::opt HotFunctionDensityThreshold( - "hot-function-density-threshold", llvm::cl::init(1000), - llvm::cl::desc( - "specify density threshold for hot functions (default: 1000)"), - llvm::cl::Optional); -static cl::opt ShowDensity("show-density", llvm::cl::init(false), - llvm::cl::desc("show profile density details"), - llvm::cl::Optional); - -static cl::opt UpdateTotalSamples( - "update-total-samples", llvm::cl::init(false), - llvm::cl::desc( - "Update total samples by accumulating all its body samples."), - llvm::cl::Optional); - -extern cl::opt ProfileSummaryCutoffHot; - -static cl::opt GenCSNestedProfile( - "gen-cs-nested-profile", cl::Hidden, cl::init(false), - cl::desc("Generate nested function profiles for CSSPGO")); - -using namespace llvm; -using namespace sampleprof; - -namespace llvm { -namespace sampleprof { - -// Initialize the MaxCompressionSize to -1 which means no size limit -int32_t CSProfileGenerator::MaxCompressionSize = -1; - -int CSProfileGenerator::MaxContextDepth = -1; - -bool ProfileGeneratorBase::UseFSDiscriminator = false; - -std::unique_ptr -ProfileGeneratorBase::create(ProfiledBinary *Binary, - const ContextSampleCounterMap &SampleCounters, - bool ProfileIsCSFlat) { - std::unique_ptr Generator; - if (ProfileIsCSFlat) { - if (Binary->useFSDiscriminator()) - exitWithError("FS discriminator is not supported in CS profile."); - Generator.reset(new CSProfileGenerator(Binary, SampleCounters)); - } else { - Generator.reset(new ProfileGenerator(Binary, SampleCounters)); - } - ProfileGeneratorBase::UseFSDiscriminator = Binary->useFSDiscriminator(); - FunctionSamples::ProfileIsFS = Binary->useFSDiscriminator(); - - return Generator; -} - -void ProfileGeneratorBase::write(std::unique_ptr Writer, - SampleProfileMap &ProfileMap) { - // Populate profile symbol list if extended binary format is used. - ProfileSymbolList SymbolList; - - if (PopulateProfileSymbolList && OutputFormat == SPF_Ext_Binary) { - Binary->populateSymbolListFromDWARF(SymbolList); - Writer->setProfileSymbolList(&SymbolList); - } - - if (std::error_code EC = Writer->write(ProfileMap)) - exitWithError(std::move(EC)); -} - -void ProfileGeneratorBase::write() { - auto WriterOrErr = SampleProfileWriter::create(OutputFilename, OutputFormat); - if (std::error_code EC = WriterOrErr.getError()) - exitWithError(EC, OutputFilename); - - if (UseMD5) { - if (OutputFormat != SPF_Ext_Binary) - WithColor::warning() << "-use-md5 is ignored. Specify " - "--format=extbinary to enable it\n"; - else - WriterOrErr.get()->setUseMD5(); - } - - write(std::move(WriterOrErr.get()), ProfileMap); -} - -void ProfileGeneratorBase::showDensitySuggestion(double Density) { - if (Density == 0.0) - WithColor::warning() << "The --profile-summary-cutoff-hot option may be " - "set too low. Please check your command.\n"; - else if (Density < HotFunctionDensityThreshold) - WithColor::warning() - << "AutoFDO is estimated to optimize better with " - << format("%.1f", HotFunctionDensityThreshold / Density) - << "x more samples. Please consider increasing sampling rate or " - "profiling for longer duration to get more samples.\n"; - - if (ShowDensity) - outs() << "Minimum profile density for hot functions with top " - << format("%.2f", - static_cast(ProfileSummaryCutoffHot.getValue()) / - 10000) - << "% total samples: " << format("%.1f", Density) << "\n"; -} - -double ProfileGeneratorBase::calculateDensity(const SampleProfileMap &Profiles, - uint64_t HotCntThreshold) { - double Density = DBL_MAX; - std::vector HotFuncs; - for (auto &I : Profiles) { - auto &FuncSamples = I.second; - if (FuncSamples.getTotalSamples() < HotCntThreshold) - continue; - HotFuncs.emplace_back(&FuncSamples); - } - - for (auto *FuncSamples : HotFuncs) { - auto *Func = Binary->getBinaryFunction(FuncSamples->getName()); - if (!Func) - continue; - uint64_t FuncSize = Func->getFuncSize(); - if (FuncSize == 0) - continue; - Density = - std::min(Density, static_cast(FuncSamples->getTotalSamples()) / - FuncSize); - } - - return Density == DBL_MAX ? 0.0 : Density; -} - -void ProfileGeneratorBase::findDisjointRanges(RangeSample &DisjointRanges, - const RangeSample &Ranges) { - - /* - Regions may overlap with each other. Using the boundary info, find all - disjoint ranges and their sample count. BoundaryPoint contains the count - multiple samples begin/end at this points. - - |<--100-->| Sample1 - |<------200------>| Sample2 - A B C - - In the example above, - Sample1 begins at A, ends at B, its value is 100. - Sample2 beings at A, ends at C, its value is 200. - For A, BeginCount is the sum of sample begins at A, which is 300 and no - samples ends at A, so EndCount is 0. - Then boundary points A, B, and C with begin/end counts are: - A: (300, 0) - B: (0, 100) - C: (0, 200) - */ - struct BoundaryPoint { - // Sum of sample counts beginning at this point - uint64_t BeginCount = UINT64_MAX; - // Sum of sample counts ending at this point - uint64_t EndCount = UINT64_MAX; - // Is the begin point of a zero range. - bool IsZeroRangeBegin = false; - // Is the end point of a zero range. - bool IsZeroRangeEnd = false; - - void addBeginCount(uint64_t Count) { - if (BeginCount == UINT64_MAX) - BeginCount = 0; - BeginCount += Count; - } - - void addEndCount(uint64_t Count) { - if (EndCount == UINT64_MAX) - EndCount = 0; - EndCount += Count; - } - }; - - /* - For the above example. With boundary points, follwing logic finds two - disjoint region of - - [A,B]: 300 - [B+1,C]: 200 - - If there is a boundary point that both begin and end, the point itself - becomes a separate disjoint region. For example, if we have original - ranges of - - |<--- 100 --->| - |<--- 200 --->| - A B C - - there are three boundary points with their begin/end counts of - - A: (100, 0) - B: (200, 100) - C: (0, 200) - - the disjoint ranges would be - - [A, B-1]: 100 - [B, B]: 300 - [B+1, C]: 200. - - Example for zero value range: - - |<--- 100 --->| - |<--- 200 --->| - |<--------------- 0 ----------------->| - A B C D E F - - [A, B-1] : 0 - [B, C] : 100 - [C+1, D-1]: 0 - [D, E] : 200 - [E+1, F] : 0 - */ - std::map Boundaries; - - for (const auto &Item : Ranges) { - assert(Item.first.first <= Item.first.second && - "Invalid instruction range"); - auto &BeginPoint = Boundaries[Item.first.first]; - auto &EndPoint = Boundaries[Item.first.second]; - uint64_t Count = Item.second; - - BeginPoint.addBeginCount(Count); - EndPoint.addEndCount(Count); - if (Count == 0) { - BeginPoint.IsZeroRangeBegin = true; - EndPoint.IsZeroRangeEnd = true; - } - } - - // Use UINT64_MAX to indicate there is no existing range between BeginAddress - // and the next valid address - uint64_t BeginAddress = UINT64_MAX; - int ZeroRangeDepth = 0; - uint64_t Count = 0; - for (const auto &Item : Boundaries) { - uint64_t Address = Item.first; - const BoundaryPoint &Point = Item.second; - if (Point.BeginCount != UINT64_MAX) { - if (BeginAddress != UINT64_MAX) - DisjointRanges[{BeginAddress, Address - 1}] = Count; - Count += Point.BeginCount; - BeginAddress = Address; - ZeroRangeDepth += Point.IsZeroRangeBegin; - } - if (Point.EndCount != UINT64_MAX) { - assert((BeginAddress != UINT64_MAX) && - "First boundary point cannot be 'end' point"); - DisjointRanges[{BeginAddress, Address}] = Count; - assert(Count >= Point.EndCount && "Mismatched live ranges"); - Count -= Point.EndCount; - BeginAddress = Address + 1; - ZeroRangeDepth -= Point.IsZeroRangeEnd; - // If the remaining count is zero and it's no longer in a zero range, this - // means we consume all the ranges before, thus mark BeginAddress as - // UINT64_MAX. e.g. supposing we have two non-overlapping ranges: - // [<---- 10 ---->] - // [<---- 20 ---->] - // A B C D - // The BeginAddress(B+1) will reset to invalid(UINT64_MAX), so we won't - // have the [B+1, C-1] zero range. - if (Count == 0 && ZeroRangeDepth == 0) - BeginAddress = UINT64_MAX; - } - } -} - -void ProfileGeneratorBase::updateBodySamplesforFunctionProfile( - FunctionSamples &FunctionProfile, const SampleContextFrame &LeafLoc, - uint64_t Count) { - // Use the maximum count of samples with same line location - uint32_t Discriminator = getBaseDiscriminator(LeafLoc.Location.Discriminator); - - // Use duplication factor to compensated for loop unroll/vectorization. - // Note that this is only needed when we're taking MAX of the counts at - // the location instead of SUM. - Count *= getDuplicationFactor(LeafLoc.Location.Discriminator); - - ErrorOr R = - FunctionProfile.findSamplesAt(LeafLoc.Location.LineOffset, Discriminator); - - uint64_t PreviousCount = R ? R.get() : 0; - if (PreviousCount <= Count) { - FunctionProfile.addBodySamples(LeafLoc.Location.LineOffset, Discriminator, - Count - PreviousCount); - } -} - -void ProfileGeneratorBase::updateTotalSamples() { - if (!UpdateTotalSamples) - return; - - for (auto &Item : ProfileMap) { - FunctionSamples &FunctionProfile = Item.second; - FunctionProfile.updateTotalSamples(); - } -} - -FunctionSamples & -ProfileGenerator::getTopLevelFunctionProfile(StringRef FuncName) { - SampleContext Context(FuncName); - auto Ret = ProfileMap.emplace(Context, FunctionSamples()); - if (Ret.second) { - FunctionSamples &FProfile = Ret.first->second; - FProfile.setContext(Context); - } - return Ret.first->second; -} - -void ProfileGenerator::generateProfile() { - if (Binary->usePseudoProbes()) { - // TODO: Support probe based profile generation - exitWithError("Probe based profile generation not supported for AutoFDO, " - "consider dropping `--ignore-stack-samples` or adding `--use-dwarf-correlation`."); - } else { - generateLineNumBasedProfile(); - } - postProcessProfiles(); -} - -void ProfileGenerator::postProcessProfiles() { - computeSummaryAndThreshold(); - trimColdProfiles(ProfileMap, ColdCountThreshold); - calculateAndShowDensity(ProfileMap); -} - -void ProfileGenerator::trimColdProfiles(const SampleProfileMap &Profiles, - uint64_t ColdCntThreshold) { - if (!TrimColdProfile) - return; - - // Move cold profiles into a tmp container. - std::vector ColdProfiles; - for (const auto &I : ProfileMap) { - if (I.second.getTotalSamples() < ColdCntThreshold) - ColdProfiles.emplace_back(I.first); - } - - // Remove the cold profile from ProfileMap. - for (const auto &I : ColdProfiles) - ProfileMap.erase(I); -} - -void ProfileGenerator::generateLineNumBasedProfile() { - assert(SampleCounters.size() == 1 && - "Must have one entry for profile generation."); - const SampleCounter &SC = SampleCounters.begin()->second; - // Fill in function body samples - populateBodySamplesForAllFunctions(SC.RangeCounter); - // Fill in boundary sample counts as well as call site samples for calls - populateBoundarySamplesForAllFunctions(SC.BranchCounter); - - updateTotalSamples(); -} - -FunctionSamples &ProfileGenerator::getLeafProfileAndAddTotalSamples( - const SampleContextFrameVector &FrameVec, uint64_t Count) { - // Get top level profile - FunctionSamples *FunctionProfile = - &getTopLevelFunctionProfile(FrameVec[0].FuncName); - FunctionProfile->addTotalSamples(Count); - - for (size_t I = 1; I < FrameVec.size(); I++) { - LineLocation Callsite( - FrameVec[I - 1].Location.LineOffset, - getBaseDiscriminator(FrameVec[I - 1].Location.Discriminator)); - FunctionSamplesMap &SamplesMap = - FunctionProfile->functionSamplesAt(Callsite); - auto Ret = - SamplesMap.emplace(FrameVec[I].FuncName.str(), FunctionSamples()); - if (Ret.second) { - SampleContext Context(FrameVec[I].FuncName); - Ret.first->second.setContext(Context); - } - FunctionProfile = &Ret.first->second; - FunctionProfile->addTotalSamples(Count); - } - - return *FunctionProfile; -} - -RangeSample -ProfileGenerator::preprocessRangeCounter(const RangeSample &RangeCounter) { - RangeSample Ranges(RangeCounter.begin(), RangeCounter.end()); - if (FillZeroForAllFuncs) { - for (auto &FuncI : Binary->getAllBinaryFunctions()) { - for (auto &R : FuncI.second.Ranges) { - Ranges[{R.first, R.second - 1}] += 0; - } - } - } else { - // For each range, we search for all ranges of the function it belongs to - // and initialize it with zero count, so it remains zero if doesn't hit any - // samples. This is to be consistent with compiler that interpret zero count - // as unexecuted(cold). - for (const auto &I : RangeCounter) { - uint64_t StartOffset = I.first.first; - for (const auto &Range : Binary->getRangesForOffset(StartOffset)) - Ranges[{Range.first, Range.second - 1}] += 0; - } - } - RangeSample DisjointRanges; - findDisjointRanges(DisjointRanges, Ranges); - return DisjointRanges; -} - -void ProfileGenerator::populateBodySamplesForAllFunctions( - const RangeSample &RangeCounter) { - for (const auto &Range : preprocessRangeCounter(RangeCounter)) { - uint64_t RangeBegin = Binary->offsetToVirtualAddr(Range.first.first); - uint64_t RangeEnd = Binary->offsetToVirtualAddr(Range.first.second); - uint64_t Count = Range.second; - - InstructionPointer IP(Binary, RangeBegin, true); - // Disjoint ranges may have range in the middle of two instr, - // e.g. If Instr1 at Addr1, and Instr2 at Addr2, disjoint range - // can be Addr1+1 to Addr2-1. We should ignore such range. - if (IP.Address > RangeEnd) - continue; - - do { - uint64_t Offset = Binary->virtualAddrToOffset(IP.Address); - const SampleContextFrameVector &FrameVec = - Binary->getFrameLocationStack(Offset); - if (!FrameVec.empty()) { - // FIXME: As accumulating total count per instruction caused some - // regression, we changed to accumulate total count per byte as a - // workaround. Tuning hotness threshold on the compiler side might be - // necessary in the future. - FunctionSamples &FunctionProfile = getLeafProfileAndAddTotalSamples( - FrameVec, Count * Binary->getInstSize(Offset)); - updateBodySamplesforFunctionProfile(FunctionProfile, FrameVec.back(), - Count); - } - } while (IP.advance() && IP.Address <= RangeEnd); - } -} - -StringRef ProfileGeneratorBase::getCalleeNameForOffset(uint64_t TargetOffset) { - // Get the function range by branch target if it's a call branch. - auto *FRange = Binary->findFuncRangeForStartOffset(TargetOffset); - - // We won't accumulate sample count for a range whose start is not the real - // function entry such as outlined function or inner labels. - if (!FRange || !FRange->IsFuncEntry) - return StringRef(); - - return FunctionSamples::getCanonicalFnName(FRange->getFuncName()); -} - -void ProfileGenerator::populateBoundarySamplesForAllFunctions( - const BranchSample &BranchCounters) { - for (const auto &Entry : BranchCounters) { - uint64_t SourceOffset = Entry.first.first; - uint64_t TargetOffset = Entry.first.second; - uint64_t Count = Entry.second; - assert(Count != 0 && "Unexpected zero weight branch"); - - StringRef CalleeName = getCalleeNameForOffset(TargetOffset); - if (CalleeName.size() == 0) - continue; - // Record called target sample and its count. - const SampleContextFrameVector &FrameVec = - Binary->getFrameLocationStack(SourceOffset); - if (!FrameVec.empty()) { - FunctionSamples &FunctionProfile = - getLeafProfileAndAddTotalSamples(FrameVec, 0); - FunctionProfile.addCalledTargetSamples( - FrameVec.back().Location.LineOffset, - getBaseDiscriminator(FrameVec.back().Location.Discriminator), - CalleeName, Count); - } - // Add head samples for callee. - FunctionSamples &CalleeProfile = getTopLevelFunctionProfile(CalleeName); - CalleeProfile.addHeadSamples(Count); - } -} - -void ProfileGeneratorBase::calculateAndShowDensity( - const SampleProfileMap &Profiles) { - double Density = calculateDensity(Profiles, HotCountThreshold); - showDensitySuggestion(Density); -} - -FunctionSamples &CSProfileGenerator::getFunctionProfileForContext( - const SampleContextFrameVector &Context, bool WasLeafInlined) { - auto I = ProfileMap.find(SampleContext(Context)); - if (I == ProfileMap.end()) { - // Save the new context for future references. - SampleContextFrames NewContext = *Contexts.insert(Context).first; - SampleContext FContext(NewContext, RawContext); - auto Ret = ProfileMap.emplace(FContext, FunctionSamples()); - if (WasLeafInlined) - FContext.setAttribute(ContextWasInlined); - FunctionSamples &FProfile = Ret.first->second; - FProfile.setContext(FContext); - return Ret.first->second; - } - return I->second; -} - -void CSProfileGenerator::generateProfile() { - FunctionSamples::ProfileIsCSFlat = true; - - if (Binary->getTrackFuncContextSize()) - computeSizeForProfiledFunctions(); - - if (Binary->usePseudoProbes()) { - // Enable pseudo probe functionalities in SampleProf - FunctionSamples::ProfileIsProbeBased = true; - generateProbeBasedProfile(); - } else { - generateLineNumBasedProfile(); - } - postProcessProfiles(); -} - -void CSProfileGenerator::computeSizeForProfiledFunctions() { - // Hash map to deduplicate the function range and the item is a pair of - // function start and end offset. - std::unordered_map AggregatedRanges; - // Go through all the ranges in the CS counters, use the start of the range to - // look up the function it belongs and record the function range. - for (const auto &CI : SampleCounters) { - for (const auto &Item : CI.second.RangeCounter) { - // FIXME: Filter the bogus crossing function range. - uint64_t StartOffset = Item.first.first; - // Note that a function can be spilt into multiple ranges, so get all - // ranges of the function. - for (const auto &Range : Binary->getRangesForOffset(StartOffset)) - AggregatedRanges[Range.first] = Range.second; - } - } - - for (const auto &I : AggregatedRanges) { - uint64_t StartOffset = I.first; - uint64_t EndOffset = I.second; - Binary->computeInlinedContextSizeForRange(StartOffset, EndOffset); - } -} - -void CSProfileGenerator::generateLineNumBasedProfile() { - for (const auto &CI : SampleCounters) { - const auto *CtxKey = cast(CI.first.getPtr()); - - // Get or create function profile for the range - FunctionSamples &FunctionProfile = - getFunctionProfileForContext(CtxKey->Context, CtxKey->WasLeafInlined); - - // Fill in function body samples - populateBodySamplesForFunction(FunctionProfile, CI.second.RangeCounter); - // Fill in boundary sample counts as well as call site samples for calls - populateBoundarySamplesForFunction(CtxKey->Context, FunctionProfile, - CI.second.BranchCounter); - } - // Fill in call site value sample for inlined calls and also use context to - // infer missing samples. Since we don't have call count for inlined - // functions, we estimate it from inlinee's profile using the entry of the - // body sample. - populateInferredFunctionSamples(); - - updateTotalSamples(); -} - -void CSProfileGenerator::populateBodySamplesForFunction( - FunctionSamples &FunctionProfile, const RangeSample &RangeCounter) { - // Compute disjoint ranges first, so we can use MAX - // for calculating count for each location. - RangeSample Ranges; - findDisjointRanges(Ranges, RangeCounter); - for (const auto &Range : Ranges) { - uint64_t RangeBegin = Binary->offsetToVirtualAddr(Range.first.first); - uint64_t RangeEnd = Binary->offsetToVirtualAddr(Range.first.second); - uint64_t Count = Range.second; - // Disjoint ranges have introduce zero-filled gap that - // doesn't belong to current context, filter them out. - if (Count == 0) - continue; - - InstructionPointer IP(Binary, RangeBegin, true); - // Disjoint ranges may have range in the middle of two instr, - // e.g. If Instr1 at Addr1, and Instr2 at Addr2, disjoint range - // can be Addr1+1 to Addr2-1. We should ignore such range. - if (IP.Address > RangeEnd) - continue; - - do { - uint64_t Offset = Binary->virtualAddrToOffset(IP.Address); - auto LeafLoc = Binary->getInlineLeafFrameLoc(Offset); - if (LeafLoc.hasValue()) { - // Recording body sample for this specific context - updateBodySamplesforFunctionProfile(FunctionProfile, *LeafLoc, Count); - FunctionProfile.addTotalSamples(Count); - } - } while (IP.advance() && IP.Address <= RangeEnd); - } -} - -void CSProfileGenerator::populateBoundarySamplesForFunction( - SampleContextFrames ContextId, FunctionSamples &FunctionProfile, - const BranchSample &BranchCounters) { - - for (const auto &Entry : BranchCounters) { - uint64_t SourceOffset = Entry.first.first; - uint64_t TargetOffset = Entry.first.second; - uint64_t Count = Entry.second; - assert(Count != 0 && "Unexpected zero weight branch"); - - StringRef CalleeName = getCalleeNameForOffset(TargetOffset); - if (CalleeName.size() == 0) - continue; - - // Record called target sample and its count - auto LeafLoc = Binary->getInlineLeafFrameLoc(SourceOffset); - if (!LeafLoc.hasValue()) - continue; - FunctionProfile.addCalledTargetSamples( - LeafLoc->Location.LineOffset, - getBaseDiscriminator(LeafLoc->Location.Discriminator), CalleeName, - Count); - - // Record head sample for called target(callee) - SampleContextFrameVector CalleeCtx(ContextId.begin(), ContextId.end()); - assert(CalleeCtx.back().FuncName == LeafLoc->FuncName && - "Leaf function name doesn't match"); - CalleeCtx.back() = *LeafLoc; - CalleeCtx.emplace_back(CalleeName, LineLocation(0, 0)); - FunctionSamples &CalleeProfile = getFunctionProfileForContext(CalleeCtx); - CalleeProfile.addHeadSamples(Count); - } -} - -static SampleContextFrame -getCallerContext(SampleContextFrames CalleeContext, - SampleContextFrameVector &CallerContext) { - assert(CalleeContext.size() > 1 && "Unexpected empty context"); - CalleeContext = CalleeContext.drop_back(); - CallerContext.assign(CalleeContext.begin(), CalleeContext.end()); - SampleContextFrame CallerFrame = CallerContext.back(); - CallerContext.back().Location = LineLocation(0, 0); - return CallerFrame; -} - -void CSProfileGenerator::populateInferredFunctionSamples() { - for (const auto &Item : ProfileMap) { - const auto &CalleeContext = Item.first; - const FunctionSamples &CalleeProfile = Item.second; - - // If we already have head sample counts, we must have value profile - // for call sites added already. Skip to avoid double counting. - if (CalleeProfile.getHeadSamples()) - continue; - // If we don't have context, nothing to do for caller's call site. - // This could happen for entry point function. - if (CalleeContext.isBaseContext()) - continue; - - // Infer Caller's frame loc and context ID through string splitting - SampleContextFrameVector CallerContextId; - SampleContextFrame &&CallerLeafFrameLoc = - getCallerContext(CalleeContext.getContextFrames(), CallerContextId); - SampleContextFrames CallerContext(CallerContextId); - - // It's possible that we haven't seen any sample directly in the caller, - // in which case CallerProfile will not exist. But we can't modify - // ProfileMap while iterating it. - // TODO: created function profile for those callers too - if (ProfileMap.find(CallerContext) == ProfileMap.end()) - continue; - FunctionSamples &CallerProfile = ProfileMap[CallerContext]; - - // Since we don't have call count for inlined functions, we - // estimate it from inlinee's profile using entry body sample. - uint64_t EstimatedCallCount = CalleeProfile.getEntrySamples(); - // If we don't have samples with location, use 1 to indicate live. - if (!EstimatedCallCount && !CalleeProfile.getBodySamples().size()) - EstimatedCallCount = 1; - CallerProfile.addCalledTargetSamples( - CallerLeafFrameLoc.Location.LineOffset, - CallerLeafFrameLoc.Location.Discriminator, - CalleeProfile.getContext().getName(), EstimatedCallCount); - CallerProfile.addBodySamples(CallerLeafFrameLoc.Location.LineOffset, - CallerLeafFrameLoc.Location.Discriminator, - EstimatedCallCount); - CallerProfile.addTotalSamples(EstimatedCallCount); - } -} - -void CSProfileGenerator::postProcessProfiles() { - // Compute hot/cold threshold based on profile. This will be used for cold - // context profile merging/trimming. - computeSummaryAndThreshold(); - - // Run global pre-inliner to adjust/merge context profile based on estimated - // inline decisions. - if (EnableCSPreInliner) { - CSPreInliner(ProfileMap, *Binary, HotCountThreshold, ColdCountThreshold) - .run(); - // Turn off the profile merger by default unless it is explicitly enabled. - if (!CSProfMergeColdContext.getNumOccurrences()) - CSProfMergeColdContext = false; - } - - // Trim and merge cold context profile using cold threshold above. - if (TrimColdProfile || CSProfMergeColdContext) { - SampleContextTrimmer(ProfileMap) - .trimAndMergeColdContextProfiles( - HotCountThreshold, TrimColdProfile, CSProfMergeColdContext, - CSProfMaxColdContextDepth, EnableCSPreInliner); - } - - // Merge function samples of CS profile to calculate profile density. - sampleprof::SampleProfileMap ContextLessProfiles; - for (const auto &I : ProfileMap) { - ContextLessProfiles[I.second.getName()].merge(I.second); - } - - calculateAndShowDensity(ContextLessProfiles); - if (GenCSNestedProfile) { - CSProfileConverter CSConverter(ProfileMap); - CSConverter.convertProfiles(); - FunctionSamples::ProfileIsCSFlat = false; - FunctionSamples::ProfileIsCSNested = EnableCSPreInliner; - } -} - -void ProfileGeneratorBase::computeSummaryAndThreshold() { - SampleProfileSummaryBuilder Builder(ProfileSummaryBuilder::DefaultCutoffs); - auto Summary = Builder.computeSummaryForProfiles(ProfileMap); - HotCountThreshold = ProfileSummaryBuilder::getHotCountThreshold( - (Summary->getDetailedSummary())); - ColdCountThreshold = ProfileSummaryBuilder::getColdCountThreshold( - (Summary->getDetailedSummary())); -} - -// Helper function to extract context prefix string stack -// Extract context stack for reusing, leaf context stack will -// be added compressed while looking up function profile -static void extractPrefixContextStack( - SampleContextFrameVector &ContextStack, - const SmallVectorImpl &Probes, - ProfiledBinary *Binary) { - for (const auto *P : Probes) { - Binary->getInlineContextForProbe(P, ContextStack, true); - } -} - -void CSProfileGenerator::generateProbeBasedProfile() { - for (const auto &CI : SampleCounters) { - const ProbeBasedCtxKey *CtxKey = - dyn_cast(CI.first.getPtr()); - SampleContextFrameVector ContextStack; - extractPrefixContextStack(ContextStack, CtxKey->Probes, Binary); - // Fill in function body samples from probes, also infer caller's samples - // from callee's probe - populateBodySamplesWithProbes(CI.second.RangeCounter, ContextStack); - // Fill in boundary samples for a call probe - populateBoundarySamplesWithProbes(CI.second.BranchCounter, ContextStack); - } -} - -void CSProfileGenerator::extractProbesFromRange(const RangeSample &RangeCounter, - ProbeCounterMap &ProbeCounter) { - RangeSample Ranges; - findDisjointRanges(Ranges, RangeCounter); - for (const auto &Range : Ranges) { - uint64_t RangeBegin = Binary->offsetToVirtualAddr(Range.first.first); - uint64_t RangeEnd = Binary->offsetToVirtualAddr(Range.first.second); - uint64_t Count = Range.second; - // Disjoint ranges have introduce zero-filled gap that - // doesn't belong to current context, filter them out. - if (Count == 0) - continue; - - InstructionPointer IP(Binary, RangeBegin, true); - // Disjoint ranges may have range in the middle of two instr, - // e.g. If Instr1 at Addr1, and Instr2 at Addr2, disjoint range - // can be Addr1+1 to Addr2-1. We should ignore such range. - if (IP.Address > RangeEnd) - continue; - - do { - const AddressProbesMap &Address2ProbesMap = - Binary->getAddress2ProbesMap(); - auto It = Address2ProbesMap.find(IP.Address); - if (It != Address2ProbesMap.end()) { - for (const auto &Probe : It->second) { - if (!Probe.isBlock()) - continue; - ProbeCounter[&Probe] += Count; - } - } - } while (IP.advance() && IP.Address <= RangeEnd); - } -} - -void CSProfileGenerator::populateBodySamplesWithProbes( - const RangeSample &RangeCounter, SampleContextFrames ContextStack) { - ProbeCounterMap ProbeCounter; - // Extract the top frame probes by looking up each address among the range in - // the Address2ProbeMap - extractProbesFromRange(RangeCounter, ProbeCounter); - std::unordered_map> - FrameSamples; - for (const auto &PI : ProbeCounter) { - const MCDecodedPseudoProbe *Probe = PI.first; - uint64_t Count = PI.second; - FunctionSamples &FunctionProfile = - getFunctionProfileForLeafProbe(ContextStack, Probe); - // Record the current frame and FunctionProfile whenever samples are - // collected for non-danglie probes. This is for reporting all of the - // zero count probes of the frame later. - FrameSamples[Probe->getInlineTreeNode()].insert(&FunctionProfile); - FunctionProfile.addBodySamplesForProbe(Probe->getIndex(), Count); - FunctionProfile.addTotalSamples(Count); - if (Probe->isEntry()) { - FunctionProfile.addHeadSamples(Count); - // Look up for the caller's function profile - const auto *InlinerDesc = Binary->getInlinerDescForProbe(Probe); - SampleContextFrames CalleeContextId = - FunctionProfile.getContext().getContextFrames(); - if (InlinerDesc != nullptr && CalleeContextId.size() > 1) { - // Since the context id will be compressed, we have to use callee's - // context id to infer caller's context id to ensure they share the - // same context prefix. - SampleContextFrameVector CallerContextId; - SampleContextFrame &&CallerLeafFrameLoc = - getCallerContext(CalleeContextId, CallerContextId); - uint64_t CallerIndex = CallerLeafFrameLoc.Location.LineOffset; - assert(CallerIndex && - "Inferred caller's location index shouldn't be zero!"); - FunctionSamples &CallerProfile = - getFunctionProfileForContext(CallerContextId); - CallerProfile.setFunctionHash(InlinerDesc->FuncHash); - CallerProfile.addBodySamples(CallerIndex, 0, Count); - CallerProfile.addTotalSamples(Count); - CallerProfile.addCalledTargetSamples( - CallerIndex, 0, FunctionProfile.getContext().getName(), Count); - } - } - } - - // Assign zero count for remaining probes without sample hits to - // differentiate from probes optimized away, of which the counts are unknown - // and will be inferred by the compiler. - for (auto &I : FrameSamples) { - for (auto *FunctionProfile : I.second) { - for (auto *Probe : I.first->getProbes()) { - FunctionProfile->addBodySamplesForProbe(Probe->getIndex(), 0); - } - } - } -} - -void CSProfileGenerator::populateBoundarySamplesWithProbes( - const BranchSample &BranchCounter, SampleContextFrames ContextStack) { - for (const auto &BI : BranchCounter) { - uint64_t SourceOffset = BI.first.first; - uint64_t TargetOffset = BI.first.second; - uint64_t Count = BI.second; - uint64_t SourceAddress = Binary->offsetToVirtualAddr(SourceOffset); - const MCDecodedPseudoProbe *CallProbe = - Binary->getCallProbeForAddr(SourceAddress); - if (CallProbe == nullptr) - continue; - FunctionSamples &FunctionProfile = - getFunctionProfileForLeafProbe(ContextStack, CallProbe); - FunctionProfile.addBodySamples(CallProbe->getIndex(), 0, Count); - FunctionProfile.addTotalSamples(Count); - StringRef CalleeName = getCalleeNameForOffset(TargetOffset); - if (CalleeName.size() == 0) - continue; - FunctionProfile.addCalledTargetSamples(CallProbe->getIndex(), 0, CalleeName, - Count); - } -} - -FunctionSamples &CSProfileGenerator::getFunctionProfileForLeafProbe( - SampleContextFrames ContextStack, const MCDecodedPseudoProbe *LeafProbe) { - - // Explicitly copy the context for appending the leaf context - SampleContextFrameVector NewContextStack(ContextStack.begin(), - ContextStack.end()); - Binary->getInlineContextForProbe(LeafProbe, NewContextStack, true); - // For leaf inlined context with the top frame, we should strip off the top - // frame's probe id, like: - // Inlined stack: [foo:1, bar:2], the ContextId will be "foo:1 @ bar" - auto LeafFrame = NewContextStack.back(); - LeafFrame.Location = LineLocation(0, 0); - NewContextStack.pop_back(); - // Compress the context string except for the leaf frame - CSProfileGenerator::compressRecursionContext(NewContextStack); - CSProfileGenerator::trimContext(NewContextStack); - NewContextStack.push_back(LeafFrame); - - const auto *FuncDesc = Binary->getFuncDescForGUID(LeafProbe->getGuid()); - bool WasLeafInlined = LeafProbe->getInlineTreeNode()->hasInlineSite(); - FunctionSamples &FunctionProile = - getFunctionProfileForContext(NewContextStack, WasLeafInlined); - FunctionProile.setFunctionHash(FuncDesc->FuncHash); - return FunctionProile; -} - -} // end namespace sampleprof -} // end namespace llvm diff --git a/tools/ldc-profgen/ldc-profgen-14.0/ProfileGenerator.h b/tools/ldc-profgen/ldc-profgen-14.0/ProfileGenerator.h deleted file mode 100644 index af349ac9911..00000000000 --- a/tools/ldc-profgen/ldc-profgen-14.0/ProfileGenerator.h +++ /dev/null @@ -1,312 +0,0 @@ -//===-- ProfileGenerator.h - Profile Generator -----------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_TOOLS_LLVM_PROGEN_PROFILEGENERATOR_H -#define LLVM_TOOLS_LLVM_PROGEN_PROFILEGENERATOR_H -#include "CSPreInliner.h" -#include "ErrorHandling.h" -#include "PerfReader.h" -#include "ProfiledBinary.h" -#include "llvm/ProfileData/SampleProfWriter.h" -#include -#include - -using namespace llvm; -using namespace sampleprof; - -namespace llvm { -namespace sampleprof { - -// This base class for profile generation of sample-based PGO. We reuse all -// structures relating to function profiles and profile writers as seen in -// /ProfileData/SampleProf.h. -class ProfileGeneratorBase { - -public: - ProfileGeneratorBase(ProfiledBinary *Binary, - const ContextSampleCounterMap &Counters) - : Binary(Binary), SampleCounters(Counters){}; - virtual ~ProfileGeneratorBase() = default; - static std::unique_ptr - create(ProfiledBinary *Binary, const ContextSampleCounterMap &SampleCounters, - bool ProfileIsCSFlat); - virtual void generateProfile() = 0; - void write(); - - static uint32_t - getDuplicationFactor(unsigned Discriminator, - bool UseFSD = ProfileGeneratorBase::UseFSDiscriminator) { - return UseFSD ? 1 - : llvm::DILocation::getDuplicationFactorFromDiscriminator( - Discriminator); - } - - static uint32_t - getBaseDiscriminator(unsigned Discriminator, - bool UseFSD = ProfileGeneratorBase::UseFSDiscriminator) { - return UseFSD ? Discriminator - : DILocation::getBaseDiscriminatorFromDiscriminator( - Discriminator, /* IsFSDiscriminator */ false); - } - - static bool UseFSDiscriminator; - -protected: - // Use SampleProfileWriter to serialize profile map - void write(std::unique_ptr Writer, - SampleProfileMap &ProfileMap); - /* - For each region boundary point, mark if it is begin or end (or both) of - the region. Boundary points are inclusive. Log the sample count as well - so we can use it when we compute the sample count of each disjoint region - later. Note that there might be multiple ranges with different sample - count that share same begin/end point. We need to accumulate the sample - count for the boundary point for such case, because for the example - below, - - |<--100-->| - |<------200------>| - A B C - - sample count for disjoint region [A,B] would be 300. - */ - void findDisjointRanges(RangeSample &DisjointRanges, - const RangeSample &Ranges); - // Helper function for updating body sample for a leaf location in - // FunctionProfile - void updateBodySamplesforFunctionProfile(FunctionSamples &FunctionProfile, - const SampleContextFrame &LeafLoc, - uint64_t Count); - void updateTotalSamples(); - - StringRef getCalleeNameForOffset(uint64_t TargetOffset); - - void computeSummaryAndThreshold(); - - void calculateAndShowDensity(const SampleProfileMap &Profiles); - - double calculateDensity(const SampleProfileMap &Profiles, - uint64_t HotCntThreshold); - - void showDensitySuggestion(double Density); - - // Thresholds from profile summary to answer isHotCount/isColdCount queries. - uint64_t HotCountThreshold; - - uint64_t ColdCountThreshold; - - // Used by SampleProfileWriter - SampleProfileMap ProfileMap; - - ProfiledBinary *Binary = nullptr; - - const ContextSampleCounterMap &SampleCounters; -}; - -class ProfileGenerator : public ProfileGeneratorBase { - -public: - ProfileGenerator(ProfiledBinary *Binary, - const ContextSampleCounterMap &Counters) - : ProfileGeneratorBase(Binary, Counters){}; - void generateProfile() override; - -private: - void generateLineNumBasedProfile(); - RangeSample preprocessRangeCounter(const RangeSample &RangeCounter); - FunctionSamples &getTopLevelFunctionProfile(StringRef FuncName); - // Helper function to get the leaf frame's FunctionProfile by traversing the - // inline stack and meanwhile it adds the total samples for each frame's - // function profile. - FunctionSamples & - getLeafProfileAndAddTotalSamples(const SampleContextFrameVector &FrameVec, - uint64_t Count); - void populateBodySamplesForAllFunctions(const RangeSample &RangeCounter); - void - populateBoundarySamplesForAllFunctions(const BranchSample &BranchCounters); - void postProcessProfiles(); - void trimColdProfiles(const SampleProfileMap &Profiles, - uint64_t ColdCntThreshold); -}; - -using ProbeCounterMap = - std::unordered_map; - -class CSProfileGenerator : public ProfileGeneratorBase { -public: - CSProfileGenerator(ProfiledBinary *Binary, - const ContextSampleCounterMap &Counters) - : ProfileGeneratorBase(Binary, Counters){}; - - void generateProfile() override; - - // Trim the context stack at a given depth. - template - static void trimContext(SmallVectorImpl &S, int Depth = MaxContextDepth) { - if (Depth < 0 || static_cast(Depth) >= S.size()) - return; - std::copy(S.begin() + S.size() - static_cast(Depth), S.end(), - S.begin()); - S.resize(Depth); - } - - // Remove adjacent repeated context sequences up to a given sequence length, - // -1 means no size limit. Note that repeated sequences are identified based - // on the exact call site, this is finer granularity than function recursion. - template - static void compressRecursionContext(SmallVectorImpl &Context, - int32_t CSize = MaxCompressionSize) { - uint32_t I = 1; - uint32_t HS = static_cast(Context.size() / 2); - uint32_t MaxDedupSize = - CSize == -1 ? HS : std::min(static_cast(CSize), HS); - auto BeginIter = Context.begin(); - // Use an in-place algorithm to save memory copy - // End indicates the end location of current iteration's data - uint32_t End = 0; - // Deduplicate from length 1 to the max possible size of a repeated - // sequence. - while (I <= MaxDedupSize) { - // This is a linear algorithm that deduplicates adjacent repeated - // sequences of size I. The deduplication detection runs on a sliding - // window whose size is 2*I and it keeps sliding the window to deduplicate - // the data inside. Once duplication is detected, deduplicate it by - // skipping the right half part of the window, otherwise just copy back - // the new one by appending them at the back of End pointer(for the next - // iteration). - // - // For example: - // Input: [a1, a2, b1, b2] - // (Added index to distinguish the same char, the origin is [a, a, b, - // b], the size of the dedup window is 2(I = 1) at the beginning) - // - // 1) The initial status is a dummy window[null, a1], then just copy the - // right half of the window(End = 0), then slide the window. - // Result: [a1], a2, b1, b2 (End points to the element right before ], - // after ] is the data of the previous iteration) - // - // 2) Next window is [a1, a2]. Since a1 == a2, then skip the right half of - // the window i.e the duplication happen. Only slide the window. - // Result: [a1], a2, b1, b2 - // - // 3) Next window is [a2, b1], copy the right half of the window(b1 is - // new) to the End and slide the window. - // Result: [a1, b1], b1, b2 - // - // 4) Next window is [b1, b2], same to 2), skip b2. - // Result: [a1, b1], b1, b2 - // After resize, it will be [a, b] - - // Use pointers like below to do comparison inside the window - // [a b c a b c] - // | | | | | - // LeftBoundary Left Right Left+I Right+I - // A duplication found if Left < LeftBoundry. - - int32_t Right = I - 1; - End = I; - int32_t LeftBoundary = 0; - while (Right + I < Context.size()) { - // To avoids scanning a part of a sequence repeatedly, it finds out - // the common suffix of two hald in the window. The common suffix will - // serve as the common prefix of next possible pair of duplicate - // sequences. The non-common part will be ignored and never scanned - // again. - - // For example. - // Input: [a, b1], c1, b2, c2 - // I = 2 - // - // 1) For the window [a, b1, c1, b2], non-common-suffix for the right - // part is 'c1', copy it and only slide the window 1 step. - // Result: [a, b1, c1], b2, c2 - // - // 2) Next window is [b1, c1, b2, c2], so duplication happen. - // Result after resize: [a, b, c] - - int32_t Left = Right; - while (Left >= LeftBoundary && Context[Left] == Context[Left + I]) { - // Find the longest suffix inside the window. When stops, Left points - // at the diverging point in the current sequence. - Left--; - } - - bool DuplicationFound = (Left < LeftBoundary); - // Don't need to recheck the data before Right - LeftBoundary = Right + 1; - if (DuplicationFound) { - // Duplication found, skip right half of the window. - Right += I; - } else { - // Copy the non-common-suffix part of the adjacent sequence. - std::copy(BeginIter + Right + 1, BeginIter + Left + I + 1, - BeginIter + End); - End += Left + I - Right; - // Only slide the window by the size of non-common-suffix - Right = Left + I; - } - } - // Don't forget the remaining part that's not scanned. - std::copy(BeginIter + Right + 1, Context.end(), BeginIter + End); - End += Context.size() - Right - 1; - I++; - Context.resize(End); - MaxDedupSize = std::min(static_cast(End / 2), MaxDedupSize); - } - } - -private: - void generateLineNumBasedProfile(); - // Lookup or create FunctionSamples for the context - FunctionSamples & - getFunctionProfileForContext(const SampleContextFrameVector &Context, - bool WasLeafInlined = false); - // For profiled only functions, on-demand compute their inline context - // function byte size which is used by the pre-inliner. - void computeSizeForProfiledFunctions(); - // Post processing for profiles before writing out, such as mermining - // and trimming cold profiles, running preinliner on profiles. - void postProcessProfiles(); - - void populateBodySamplesForFunction(FunctionSamples &FunctionProfile, - const RangeSample &RangeCounters); - void populateBoundarySamplesForFunction(SampleContextFrames ContextId, - FunctionSamples &FunctionProfile, - const BranchSample &BranchCounters); - void populateInferredFunctionSamples(); - - void generateProbeBasedProfile(); - // Go through each address from range to extract the top frame probe by - // looking up in the Address2ProbeMap - void extractProbesFromRange(const RangeSample &RangeCounter, - ProbeCounterMap &ProbeCounter); - // Fill in function body samples from probes - void populateBodySamplesWithProbes(const RangeSample &RangeCounter, - SampleContextFrames ContextStack); - // Fill in boundary samples for a call probe - void populateBoundarySamplesWithProbes(const BranchSample &BranchCounter, - SampleContextFrames ContextStack); - // Helper function to get FunctionSamples for the leaf probe - FunctionSamples & - getFunctionProfileForLeafProbe(SampleContextFrames ContextStack, - const MCDecodedPseudoProbe *LeafProbe); - - // Underlying context table serves for sample profile writer. - std::unordered_set Contexts; - -public: - // Deduplicate adjacent repeated context sequences up to a given sequence - // length. -1 means no size limit. - static int32_t MaxCompressionSize; - static int MaxContextDepth; -}; - -} // end namespace sampleprof -} // end namespace llvm - -#endif diff --git a/tools/ldc-profgen/ldc-profgen-14.0/ProfiledBinary.cpp b/tools/ldc-profgen/ldc-profgen-14.0/ProfiledBinary.cpp deleted file mode 100644 index a773a3c98d4..00000000000 --- a/tools/ldc-profgen/ldc-profgen-14.0/ProfiledBinary.cpp +++ /dev/null @@ -1,790 +0,0 @@ -//===-- ProfiledBinary.cpp - Binary decoder ---------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "ProfiledBinary.h" -#include "ErrorHandling.h" -#include "ProfileGenerator.h" -#include "llvm/ADT/Triple.h" -#include "llvm/Demangle/Demangle.h" -#include "llvm/IR/DebugInfoMetadata.h" -#include "llvm/MC/TargetRegistry.h" -#include "llvm/Support/CommandLine.h" -#include "llvm/Support/Format.h" -#include "llvm/Support/TargetSelect.h" - -#define DEBUG_TYPE "load-binary" - -using namespace llvm; -using namespace sampleprof; - -cl::opt ShowDisassemblyOnly("show-disassembly-only", cl::init(false), - cl::ZeroOrMore, - cl::desc("Print disassembled code.")); - -cl::opt ShowSourceLocations("show-source-locations", cl::init(false), - cl::ZeroOrMore, - cl::desc("Print source locations.")); - -static cl::opt - ShowCanonicalFnName("show-canonical-fname", cl::init(false), cl::ZeroOrMore, - cl::desc("Print canonical function name.")); - -static cl::opt ShowPseudoProbe( - "show-pseudo-probe", cl::init(false), cl::ZeroOrMore, - cl::desc("Print pseudo probe section and disassembled info.")); - -static cl::opt UseDwarfCorrelation( - "use-dwarf-correlation", cl::init(false), cl::ZeroOrMore, - cl::desc("Use dwarf for profile correlation even when binary contains " - "pseudo probe.")); - -static cl::list DisassembleFunctions( - "disassemble-functions", cl::CommaSeparated, - cl::desc("List of functions to print disassembly for. Accept demangled " - "names only. Only work with show-disassembly-only")); - -extern cl::opt ShowDetailedWarning; - -namespace llvm { -namespace sampleprof { - -static const Target *getTarget(const ObjectFile *Obj) { - Triple TheTriple = Obj->makeTriple(); - std::string Error; - std::string ArchName; - const Target *TheTarget = - TargetRegistry::lookupTarget(ArchName, TheTriple, Error); - if (!TheTarget) - exitWithError(Error, Obj->getFileName()); - return TheTarget; -} - -void BinarySizeContextTracker::addInstructionForContext( - const SampleContextFrameVector &Context, uint32_t InstrSize) { - ContextTrieNode *CurNode = &RootContext; - bool IsLeaf = true; - for (const auto &Callsite : reverse(Context)) { - StringRef CallerName = Callsite.FuncName; - LineLocation CallsiteLoc = IsLeaf ? LineLocation(0, 0) : Callsite.Location; - CurNode = CurNode->getOrCreateChildContext(CallsiteLoc, CallerName); - IsLeaf = false; - } - - CurNode->addFunctionSize(InstrSize); -} - -uint32_t -BinarySizeContextTracker::getFuncSizeForContext(const SampleContext &Context) { - ContextTrieNode *CurrNode = &RootContext; - ContextTrieNode *PrevNode = nullptr; - SampleContextFrames Frames = Context.getContextFrames(); - int32_t I = Frames.size() - 1; - Optional Size; - - // Start from top-level context-less function, traverse down the reverse - // context trie to find the best/longest match for given context, then - // retrieve the size. - - while (CurrNode && I >= 0) { - // Process from leaf function to callers (added to context). - const auto &ChildFrame = Frames[I--]; - PrevNode = CurrNode; - CurrNode = - CurrNode->getChildContext(ChildFrame.Location, ChildFrame.FuncName); - if (CurrNode && CurrNode->getFunctionSize().hasValue()) - Size = CurrNode->getFunctionSize().getValue(); - } - - // If we traversed all nodes along the path of the context and haven't - // found a size yet, pivot to look for size from sibling nodes, i.e size - // of inlinee under different context. - if (!Size.hasValue()) { - if (!CurrNode) - CurrNode = PrevNode; - while (!Size.hasValue() && CurrNode && - !CurrNode->getAllChildContext().empty()) { - CurrNode = &CurrNode->getAllChildContext().begin()->second; - if (CurrNode->getFunctionSize().hasValue()) - Size = CurrNode->getFunctionSize().getValue(); - } - } - - assert(Size.hasValue() && "We should at least find one context size."); - return Size.getValue(); -} - -void BinarySizeContextTracker::trackInlineesOptimizedAway( - MCPseudoProbeDecoder &ProbeDecoder) { - ProbeFrameStack ProbeContext; - for (const auto &Child : ProbeDecoder.getDummyInlineRoot().getChildren()) - trackInlineesOptimizedAway(ProbeDecoder, *Child.second.get(), ProbeContext); -} - -void BinarySizeContextTracker::trackInlineesOptimizedAway( - MCPseudoProbeDecoder &ProbeDecoder, - MCDecodedPseudoProbeInlineTree &ProbeNode, ProbeFrameStack &ProbeContext) { - StringRef FuncName = - ProbeDecoder.getFuncDescForGUID(ProbeNode.Guid)->FuncName; - ProbeContext.emplace_back(FuncName, 0); - - // This ProbeContext has a probe, so it has code before inlining and - // optimization. Make sure we mark its size as known. - if (!ProbeNode.getProbes().empty()) { - ContextTrieNode *SizeContext = &RootContext; - for (auto &ProbeFrame : reverse(ProbeContext)) { - StringRef CallerName = ProbeFrame.first; - LineLocation CallsiteLoc(ProbeFrame.second, 0); - SizeContext = - SizeContext->getOrCreateChildContext(CallsiteLoc, CallerName); - } - // Add 0 size to make known. - SizeContext->addFunctionSize(0); - } - - // DFS down the probe inline tree - for (const auto &ChildNode : ProbeNode.getChildren()) { - InlineSite Location = ChildNode.first; - ProbeContext.back().second = std::get<1>(Location); - trackInlineesOptimizedAway(ProbeDecoder, *ChildNode.second.get(), ProbeContext); - } - - ProbeContext.pop_back(); -} - -void ProfiledBinary::warnNoFuncEntry() { - uint64_t NoFuncEntryNum = 0; - for (auto &F : BinaryFunctions) { - if (F.second.Ranges.empty()) - continue; - bool hasFuncEntry = false; - for (auto &R : F.second.Ranges) { - if (FuncRange *FR = findFuncRangeForStartOffset(R.first)) { - if (FR->IsFuncEntry) { - hasFuncEntry = true; - break; - } - } - } - - if (!hasFuncEntry) { - NoFuncEntryNum++; - if (ShowDetailedWarning) - WithColor::warning() - << "Failed to determine function entry for " << F.first - << " due to inconsistent name from symbol table and dwarf info.\n"; - } - } - emitWarningSummary(NoFuncEntryNum, BinaryFunctions.size(), - "of functions failed to determine function entry due to " - "inconsistent name from symbol table and dwarf info."); -} - -void ProfiledBinary::load() { - // Attempt to open the binary. - OwningBinary OBinary = unwrapOrError(createBinary(Path), Path); - Binary &ExeBinary = *OBinary.getBinary(); - - auto *Obj = dyn_cast(&ExeBinary); - if (!Obj) - exitWithError("not a valid Elf image", Path); - - TheTriple = Obj->makeTriple(); - // Current only support X86 - if (!TheTriple.isX86()) - exitWithError("unsupported target", TheTriple.getTriple()); - LLVM_DEBUG(dbgs() << "Loading " << Path << "\n"); - - // Find the preferred load address for text sections. - setPreferredTextSegmentAddresses(Obj); - - // Decode pseudo probe related section - decodePseudoProbe(Obj); - - // Load debug info of subprograms from DWARF section. - // If path of debug info binary is specified, use the debug info from it, - // otherwise use the debug info from the executable binary. - if (!DebugBinaryPath.empty()) { - OwningBinary DebugPath = - unwrapOrError(createBinary(DebugBinaryPath), DebugBinaryPath); - loadSymbolsFromDWARF(*dyn_cast(DebugPath.getBinary())); - } else { - loadSymbolsFromDWARF(*dyn_cast(&ExeBinary)); - } - - // Disassemble the text sections. - disassemble(Obj); - - // Track size for optimized inlinees when probe is available - if (UsePseudoProbes && TrackFuncContextSize) - FuncSizeTracker.trackInlineesOptimizedAway(ProbeDecoder); - - // Use function start and return address to infer prolog and epilog - ProEpilogTracker.inferPrologOffsets(StartOffset2FuncRangeMap); - ProEpilogTracker.inferEpilogOffsets(RetOffsets); - - warnNoFuncEntry(); - - // TODO: decode other sections. -} - -bool ProfiledBinary::inlineContextEqual(uint64_t Address1, uint64_t Address2) { - uint64_t Offset1 = virtualAddrToOffset(Address1); - uint64_t Offset2 = virtualAddrToOffset(Address2); - const SampleContextFrameVector &Context1 = getFrameLocationStack(Offset1); - const SampleContextFrameVector &Context2 = getFrameLocationStack(Offset2); - if (Context1.size() != Context2.size()) - return false; - if (Context1.empty()) - return false; - // The leaf frame contains location within the leaf, and it - // needs to be remove that as it's not part of the calling context - return std::equal(Context1.begin(), Context1.begin() + Context1.size() - 1, - Context2.begin(), Context2.begin() + Context2.size() - 1); -} - -SampleContextFrameVector -ProfiledBinary::getExpandedContext(const SmallVectorImpl &Stack, - bool &WasLeafInlined) { - SampleContextFrameVector ContextVec; - // Process from frame root to leaf - for (auto Address : Stack) { - uint64_t Offset = virtualAddrToOffset(Address); - const SampleContextFrameVector &ExpandedContext = - getFrameLocationStack(Offset); - // An instruction without a valid debug line will be ignored by sample - // processing - if (ExpandedContext.empty()) - return SampleContextFrameVector(); - // Set WasLeafInlined to the size of inlined frame count for the last - // address which is leaf - WasLeafInlined = (ExpandedContext.size() > 1); - ContextVec.append(ExpandedContext); - } - - // Replace with decoded base discriminator - for (auto &Frame : ContextVec) { - Frame.Location.Discriminator = ProfileGeneratorBase::getBaseDiscriminator( - Frame.Location.Discriminator, UseFSDiscriminator); - } - - assert(ContextVec.size() && "Context length should be at least 1"); - - // Compress the context string except for the leaf frame - auto LeafFrame = ContextVec.back(); - LeafFrame.Location = LineLocation(0, 0); - ContextVec.pop_back(); - CSProfileGenerator::compressRecursionContext(ContextVec); - CSProfileGenerator::trimContext(ContextVec); - ContextVec.push_back(LeafFrame); - return ContextVec; -} - -template -void ProfiledBinary::setPreferredTextSegmentAddresses(const ELFFile &Obj, StringRef FileName) { - const auto &PhdrRange = unwrapOrError(Obj.program_headers(), FileName); - // FIXME: This should be the page size of the system running profiling. - // However such info isn't available at post-processing time, assuming - // 4K page now. Note that we don't use EXEC_PAGESIZE from - // because we may build the tools on non-linux. - uint32_t PageSize = 0x1000; - for (const typename ELFT::Phdr &Phdr : PhdrRange) { - if (Phdr.p_type == ELF::PT_LOAD) { - if (!FirstLoadableAddress) - FirstLoadableAddress = Phdr.p_vaddr & ~(PageSize - 1U); - if (Phdr.p_flags & ELF::PF_X) { - // Segments will always be loaded at a page boundary. - PreferredTextSegmentAddresses.push_back(Phdr.p_vaddr & - ~(PageSize - 1U)); - TextSegmentOffsets.push_back(Phdr.p_offset & ~(PageSize - 1U)); - } - } - } - - if (PreferredTextSegmentAddresses.empty()) - exitWithError("no executable segment found", FileName); -} - -void ProfiledBinary::setPreferredTextSegmentAddresses(const ELFObjectFileBase *Obj) { - if (const auto *ELFObj = dyn_cast(Obj)) - setPreferredTextSegmentAddresses(ELFObj->getELFFile(), Obj->getFileName()); - else if (const auto *ELFObj = dyn_cast(Obj)) - setPreferredTextSegmentAddresses(ELFObj->getELFFile(), Obj->getFileName()); - else if (const auto *ELFObj = dyn_cast(Obj)) - setPreferredTextSegmentAddresses(ELFObj->getELFFile(), Obj->getFileName()); - else if (const auto *ELFObj = cast(Obj)) - setPreferredTextSegmentAddresses(ELFObj->getELFFile(), Obj->getFileName()); - else - llvm_unreachable("invalid ELF object format"); -} - -void ProfiledBinary::decodePseudoProbe(const ELFObjectFileBase *Obj) { - if (UseDwarfCorrelation) - return; - - StringRef FileName = Obj->getFileName(); - for (section_iterator SI = Obj->section_begin(), SE = Obj->section_end(); - SI != SE; ++SI) { - const SectionRef &Section = *SI; - StringRef SectionName = unwrapOrError(Section.getName(), FileName); - - if (SectionName == ".pseudo_probe_desc") { - StringRef Contents = unwrapOrError(Section.getContents(), FileName); - if (!ProbeDecoder.buildGUID2FuncDescMap( - reinterpret_cast(Contents.data()), - Contents.size())) - exitWithError("Pseudo Probe decoder fail in .pseudo_probe_desc section"); - } else if (SectionName == ".pseudo_probe") { - StringRef Contents = unwrapOrError(Section.getContents(), FileName); - if (!ProbeDecoder.buildAddress2ProbeMap( - reinterpret_cast(Contents.data()), - Contents.size())) - exitWithError("Pseudo Probe decoder fail in .pseudo_probe section"); - // set UsePseudoProbes flag, used for PerfReader - UsePseudoProbes = true; - } - } - - if (ShowPseudoProbe) - ProbeDecoder.printGUID2FuncDescMap(outs()); -} - -void ProfiledBinary::setIsFuncEntry(uint64_t Offset, StringRef RangeSymName) { - // Note that the start offset of each ELF section can be a non-function - // symbol, we need to binary search for the start of a real function range. - auto *FuncRange = findFuncRangeForOffset(Offset); - // Skip external function symbol. - if (!FuncRange) - return; - - // Set IsFuncEntry to ture if there is only one range in the function or the - // RangeSymName from ELF is equal to its DWARF-based function name. - if (FuncRange->Func->Ranges.size() == 1 || - (!FuncRange->IsFuncEntry && FuncRange->getFuncName() == RangeSymName)) - FuncRange->IsFuncEntry = true; -} - -bool ProfiledBinary::dissassembleSymbol(std::size_t SI, ArrayRef Bytes, - SectionSymbolsTy &Symbols, - const SectionRef &Section) { - std::size_t SE = Symbols.size(); - uint64_t SectionOffset = Section.getAddress() - getPreferredBaseAddress(); - uint64_t SectSize = Section.getSize(); - uint64_t StartOffset = Symbols[SI].Addr - getPreferredBaseAddress(); - uint64_t NextStartOffset = - (SI + 1 < SE) ? Symbols[SI + 1].Addr - getPreferredBaseAddress() - : SectionOffset + SectSize; - setIsFuncEntry(StartOffset, - FunctionSamples::getCanonicalFnName(Symbols[SI].Name)); - - StringRef SymbolName = - ShowCanonicalFnName - ? FunctionSamples::getCanonicalFnName(Symbols[SI].Name) - : Symbols[SI].Name; - bool ShowDisassembly = - ShowDisassemblyOnly && (DisassembleFunctionSet.empty() || - DisassembleFunctionSet.count(SymbolName)); - if (ShowDisassembly) - outs() << '<' << SymbolName << ">:\n"; - - auto WarnInvalidInsts = [](uint64_t Start, uint64_t End) { - WithColor::warning() << "Invalid instructions at " - << format("%8" PRIx64, Start) << " - " - << format("%8" PRIx64, End) << "\n"; - }; - - uint64_t Offset = StartOffset; - // Size of a consecutive invalid instruction range starting from Offset -1 - // backwards. - uint64_t InvalidInstLength = 0; - while (Offset < NextStartOffset) { - MCInst Inst; - uint64_t Size; - // Disassemble an instruction. - bool Disassembled = - DisAsm->getInstruction(Inst, Size, Bytes.slice(Offset - SectionOffset), - Offset + getPreferredBaseAddress(), nulls()); - if (Size == 0) - Size = 1; - - if (ShowDisassembly) { - if (ShowPseudoProbe) { - ProbeDecoder.printProbeForAddress(outs(), - Offset + getPreferredBaseAddress()); - } - outs() << format("%8" PRIx64 ":", Offset + getPreferredBaseAddress()); - size_t Start = outs().tell(); - if (Disassembled) - IPrinter->printInst(&Inst, Offset + Size, "", *STI.get(), outs()); - else - outs() << "\t"; - if (ShowSourceLocations) { - unsigned Cur = outs().tell() - Start; - if (Cur < 40) - outs().indent(40 - Cur); - InstructionPointer IP(this, Offset); - outs() << getReversedLocWithContext( - symbolize(IP, ShowCanonicalFnName, ShowPseudoProbe)); - } - outs() << "\n"; - } - - if (Disassembled) { - const MCInstrDesc &MCDesc = MII->get(Inst.getOpcode()); - - // Record instruction size. - Offset2InstSizeMap[Offset] = Size; - - // Populate address maps. - CodeAddrOffsets.push_back(Offset); - if (MCDesc.isCall()) - CallOffsets.insert(Offset); - else if (MCDesc.isReturn()) - RetOffsets.insert(Offset); - else if (MCDesc.isBranch()) - BranchOffsets.insert(Offset); - - if (InvalidInstLength) { - WarnInvalidInsts(Offset - InvalidInstLength, Offset - 1); - InvalidInstLength = 0; - } - } else { - InvalidInstLength += Size; - } - - Offset += Size; - } - - if (InvalidInstLength) - WarnInvalidInsts(Offset - InvalidInstLength, Offset - 1); - - if (ShowDisassembly) - outs() << "\n"; - - return true; -} - -void ProfiledBinary::setUpDisassembler(const ELFObjectFileBase *Obj) { - const Target *TheTarget = getTarget(Obj); - std::string TripleName = TheTriple.getTriple(); - StringRef FileName = Obj->getFileName(); - - MRI.reset(TheTarget->createMCRegInfo(TripleName)); - if (!MRI) - exitWithError("no register info for target " + TripleName, FileName); - - MCTargetOptions MCOptions; - AsmInfo.reset(TheTarget->createMCAsmInfo(*MRI, TripleName, MCOptions)); - if (!AsmInfo) - exitWithError("no assembly info for target " + TripleName, FileName); - - SubtargetFeatures Features = Obj->getFeatures(); - STI.reset( - TheTarget->createMCSubtargetInfo(TripleName, "", Features.getString())); - if (!STI) - exitWithError("no subtarget info for target " + TripleName, FileName); - - MII.reset(TheTarget->createMCInstrInfo()); - if (!MII) - exitWithError("no instruction info for target " + TripleName, FileName); - - MCContext Ctx(Triple(TripleName), AsmInfo.get(), MRI.get(), STI.get()); - std::unique_ptr MOFI( - TheTarget->createMCObjectFileInfo(Ctx, /*PIC=*/false)); - Ctx.setObjectFileInfo(MOFI.get()); - DisAsm.reset(TheTarget->createMCDisassembler(*STI, Ctx)); - if (!DisAsm) - exitWithError("no disassembler for target " + TripleName, FileName); - - MIA.reset(TheTarget->createMCInstrAnalysis(MII.get())); - - int AsmPrinterVariant = AsmInfo->getAssemblerDialect(); - IPrinter.reset(TheTarget->createMCInstPrinter( - Triple(TripleName), AsmPrinterVariant, *AsmInfo, *MII, *MRI)); - IPrinter->setPrintBranchImmAsAddress(true); -} - -void ProfiledBinary::disassemble(const ELFObjectFileBase *Obj) { - // Set up disassembler and related components. - setUpDisassembler(Obj); - - // Create a mapping from virtual address to symbol name. The symbols in text - // sections are the candidates to dissassemble. - std::map AllSymbols; - StringRef FileName = Obj->getFileName(); - for (const SymbolRef &Symbol : Obj->symbols()) { - const uint64_t Addr = unwrapOrError(Symbol.getAddress(), FileName); - const StringRef Name = unwrapOrError(Symbol.getName(), FileName); - section_iterator SecI = unwrapOrError(Symbol.getSection(), FileName); - if (SecI != Obj->section_end()) - AllSymbols[*SecI].push_back(SymbolInfoTy(Addr, Name, ELF::STT_NOTYPE)); - } - - // Sort all the symbols. Use a stable sort to stabilize the output. - for (std::pair &SecSyms : AllSymbols) - stable_sort(SecSyms.second); - - DisassembleFunctionSet.insert(DisassembleFunctions.begin(), - DisassembleFunctions.end()); - assert((DisassembleFunctionSet.empty() || ShowDisassemblyOnly) && - "Functions to disassemble should be only specified together with " - "--show-disassembly-only"); - - if (ShowDisassemblyOnly) - outs() << "\nDisassembly of " << FileName << ":\n"; - - // Dissassemble a text section. - for (section_iterator SI = Obj->section_begin(), SE = Obj->section_end(); - SI != SE; ++SI) { - const SectionRef &Section = *SI; - if (!Section.isText()) - continue; - - uint64_t ImageLoadAddr = getPreferredBaseAddress(); - uint64_t SectionOffset = Section.getAddress() - ImageLoadAddr; - uint64_t SectSize = Section.getSize(); - if (!SectSize) - continue; - - // Register the text section. - TextSections.insert({SectionOffset, SectSize}); - - StringRef SectionName = unwrapOrError(Section.getName(), FileName); - - if (ShowDisassemblyOnly) { - outs() << "\nDisassembly of section " << SectionName; - outs() << " [" << format("0x%" PRIx64, Section.getAddress()) << ", " - << format("0x%" PRIx64, Section.getAddress() + SectSize) - << "]:\n\n"; - } - - if (SectionName == ".plt") - continue; - - // Get the section data. - ArrayRef Bytes = - arrayRefFromStringRef(unwrapOrError(Section.getContents(), FileName)); - - // Get the list of all the symbols in this section. - SectionSymbolsTy &Symbols = AllSymbols[Section]; - - // Disassemble symbol by symbol. - for (std::size_t SI = 0, SE = Symbols.size(); SI != SE; ++SI) { - if (!dissassembleSymbol(SI, Bytes, Symbols, Section)) - exitWithError("disassembling error", FileName); - } - } - - // Dissassemble rodata section to check if FS discriminator symbol exists. - checkUseFSDiscriminator(Obj, AllSymbols); -} - -void ProfiledBinary::checkUseFSDiscriminator( - const ELFObjectFileBase *Obj, - std::map &AllSymbols) { - const char *FSDiscriminatorVar = "__llvm_fs_discriminator__"; - for (section_iterator SI = Obj->section_begin(), SE = Obj->section_end(); - SI != SE; ++SI) { - const SectionRef &Section = *SI; - if (!Section.isData() || Section.getSize() == 0) - continue; - SectionSymbolsTy &Symbols = AllSymbols[Section]; - - for (std::size_t SI = 0, SE = Symbols.size(); SI != SE; ++SI) { - if (Symbols[SI].Name == FSDiscriminatorVar) { - UseFSDiscriminator = true; - return; - } - } - } -} - -void ProfiledBinary::loadSymbolsFromDWARF(ObjectFile &Obj) { - auto DebugContext = llvm::DWARFContext::create(Obj); - if (!DebugContext) - exitWithError("Misssing debug info.", Path); - - for (const auto &CompilationUnit : DebugContext->compile_units()) { - for (const auto &DieInfo : CompilationUnit->dies()) { - llvm::DWARFDie Die(CompilationUnit.get(), &DieInfo); - - if (!Die.isSubprogramDIE()) - continue; - auto Name = Die.getName(llvm::DINameKind::LinkageName); - if (!Name) - Name = Die.getName(llvm::DINameKind::ShortName); - if (!Name) - continue; - - auto RangesOrError = Die.getAddressRanges(); - if (!RangesOrError) - continue; - const DWARFAddressRangesVector &Ranges = RangesOrError.get(); - - if (Ranges.empty()) - continue; - - // Different DWARF symbols can have same function name, search or create - // BinaryFunction indexed by the name. - auto Ret = BinaryFunctions.emplace(Name, BinaryFunction()); - auto &Func = Ret.first->second; - if (Ret.second) - Func.FuncName = Ret.first->first; - - for (const auto &Range : Ranges) { - uint64_t FuncStart = Range.LowPC; - uint64_t FuncSize = Range.HighPC - FuncStart; - - if (FuncSize == 0 || FuncStart < getPreferredBaseAddress()) - continue; - - uint64_t StartOffset = FuncStart - getPreferredBaseAddress(); - uint64_t EndOffset = Range.HighPC - getPreferredBaseAddress(); - - // We may want to know all ranges for one function. Here group the - // ranges and store them into BinaryFunction. - Func.Ranges.emplace_back(StartOffset, EndOffset); - - auto R = StartOffset2FuncRangeMap.emplace(StartOffset, FuncRange()); - if (R.second) { - FuncRange &FRange = R.first->second; - FRange.Func = &Func; - FRange.StartOffset = StartOffset; - FRange.EndOffset = EndOffset; - } else { - WithColor::warning() - << "Duplicated symbol start address at " - << format("%8" PRIx64, StartOffset + getPreferredBaseAddress()) - << " " << R.first->second.getFuncName() << " and " << Name - << "\n"; - } - } - } - } - assert(!StartOffset2FuncRangeMap.empty() && "Misssing debug info."); -} - -void ProfiledBinary::populateSymbolListFromDWARF( - ProfileSymbolList &SymbolList) { - for (auto &I : StartOffset2FuncRangeMap) - SymbolList.add(I.second.getFuncName()); -} - -void ProfiledBinary::setupSymbolizer() { - symbolize::LLVMSymbolizer::Options SymbolizerOpts; - SymbolizerOpts.PrintFunctions = - DILineInfoSpecifier::FunctionNameKind::LinkageName; - SymbolizerOpts.Demangle = false; - SymbolizerOpts.DefaultArch = TheTriple.getArchName().str(); - SymbolizerOpts.UseSymbolTable = false; - SymbolizerOpts.RelativeAddresses = false; - Symbolizer = std::make_unique(SymbolizerOpts); -} - -SampleContextFrameVector ProfiledBinary::symbolize(const InstructionPointer &IP, - bool UseCanonicalFnName, - bool UseProbeDiscriminator) { - assert(this == IP.Binary && - "Binary should only symbolize its own instruction"); - auto Addr = object::SectionedAddress{IP.Offset + getPreferredBaseAddress(), - object::SectionedAddress::UndefSection}; - DIInliningInfo InlineStack = unwrapOrError( - Symbolizer->symbolizeInlinedCode(SymbolizerPath.str(), Addr), - SymbolizerPath); - - SampleContextFrameVector CallStack; - for (int32_t I = InlineStack.getNumberOfFrames() - 1; I >= 0; I--) { - const auto &CallerFrame = InlineStack.getFrame(I); - if (CallerFrame.FunctionName == "") - break; - - StringRef FunctionName(CallerFrame.FunctionName); - if (UseCanonicalFnName) - FunctionName = FunctionSamples::getCanonicalFnName(FunctionName); - - uint32_t Discriminator = CallerFrame.Discriminator; - uint32_t LineOffset = (CallerFrame.Line - CallerFrame.StartLine) & 0xffff; - if (UseProbeDiscriminator) { - LineOffset = - PseudoProbeDwarfDiscriminator::extractProbeIndex(Discriminator); - Discriminator = 0; - } - - LineLocation Line(LineOffset, Discriminator); - auto It = NameStrings.insert(FunctionName.str()); - CallStack.emplace_back(*It.first, Line); - } - - return CallStack; -} - -void ProfiledBinary::computeInlinedContextSizeForRange(uint64_t StartOffset, - uint64_t EndOffset) { - uint64_t RangeBegin = offsetToVirtualAddr(StartOffset); - uint64_t RangeEnd = offsetToVirtualAddr(EndOffset); - InstructionPointer IP(this, RangeBegin, true); - - if (IP.Address != RangeBegin) - WithColor::warning() << "Invalid start instruction at " - << format("%8" PRIx64, RangeBegin) << "\n"; - - if (IP.Address >= RangeEnd) - return; - - do { - uint64_t Offset = virtualAddrToOffset(IP.Address); - const SampleContextFrameVector &SymbolizedCallStack = - getFrameLocationStack(Offset, UsePseudoProbes); - uint64_t Size = Offset2InstSizeMap[Offset]; - - // Record instruction size for the corresponding context - FuncSizeTracker.addInstructionForContext(SymbolizedCallStack, Size); - - } while (IP.advance() && IP.Address < RangeEnd); -} - -InstructionPointer::InstructionPointer(const ProfiledBinary *Binary, - uint64_t Address, bool RoundToNext) - : Binary(Binary), Address(Address) { - Index = Binary->getIndexForAddr(Address); - if (RoundToNext) { - // we might get address which is not the code - // it should round to the next valid address - if (Index >= Binary->getCodeOffsetsSize()) - this->Address = UINT64_MAX; - else - this->Address = Binary->getAddressforIndex(Index); - } -} - -bool InstructionPointer::advance() { - Index++; - if (Index >= Binary->getCodeOffsetsSize()) { - Address = UINT64_MAX; - return false; - } - Address = Binary->getAddressforIndex(Index); - return true; -} - -bool InstructionPointer::backward() { - if (Index == 0) { - Address = 0; - return false; - } - Index--; - Address = Binary->getAddressforIndex(Index); - return true; -} - -void InstructionPointer::update(uint64_t Addr) { - Address = Addr; - Index = Binary->getIndexForAddr(Address); -} - -} // end namespace sampleprof -} // end namespace llvm diff --git a/tools/ldc-profgen/ldc-profgen-14.0/ProfiledBinary.h b/tools/ldc-profgen/ldc-profgen-14.0/ProfiledBinary.h deleted file mode 100644 index d3d1c6f1fd2..00000000000 --- a/tools/ldc-profgen/ldc-profgen-14.0/ProfiledBinary.h +++ /dev/null @@ -1,541 +0,0 @@ -//===-- ProfiledBinary.h - Binary decoder -----------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_TOOLS_LLVM_PROFGEN_PROFILEDBINARY_H -#define LLVM_TOOLS_LLVM_PROFGEN_PROFILEDBINARY_H - -#include "CallContext.h" -#include "ErrorHandling.h" -#include "llvm/ADT/Optional.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/DebugInfo/DWARF/DWARFContext.h" -#include "llvm/DebugInfo/Symbolize/Symbolize.h" -#include "llvm/MC/MCAsmInfo.h" -#include "llvm/MC/MCContext.h" -#include "llvm/MC/MCDisassembler/MCDisassembler.h" -#include "llvm/MC/MCInst.h" -#include "llvm/MC/MCInstPrinter.h" -#include "llvm/MC/MCInstrAnalysis.h" -#include "llvm/MC/MCInstrInfo.h" -#include "llvm/MC/MCObjectFileInfo.h" -#include "llvm/MC/MCPseudoProbe.h" -#include "llvm/MC/MCRegisterInfo.h" -#include "llvm/MC/MCSubtargetInfo.h" -#include "llvm/MC/MCTargetOptions.h" -#include "llvm/Object/ELFObjectFile.h" -#include "llvm/ProfileData/SampleProf.h" -#include "llvm/Support/CommandLine.h" -#include "llvm/Support/Path.h" -#include "llvm/Transforms/IPO/SampleContextTracker.h" -#include -#include -#include -#include -#include -#include -#include -#include - -extern cl::opt EnableCSPreInliner; -extern cl::opt UseContextCostForPreInliner; - -using namespace llvm; -using namespace sampleprof; -using namespace llvm::object; - -namespace llvm { -namespace sampleprof { - -class ProfiledBinary; - -struct InstructionPointer { - const ProfiledBinary *Binary; - union { - // Offset of the executable segment of the binary. - uint64_t Offset = 0; - // Also used as address in unwinder - uint64_t Address; - }; - // Index to the sorted code address array of the binary. - uint64_t Index = 0; - InstructionPointer(const ProfiledBinary *Binary, uint64_t Address, - bool RoundToNext = false); - bool advance(); - bool backward(); - void update(uint64_t Addr); -}; - -// The special frame addresses. -enum SpecialFrameAddr { - // Dummy root of frame trie. - DummyRoot = 0, - // Represent all the addresses outside of current binary. - // This's also used to indicate the call stack should be truncated since this - // isn't a real call context the compiler will see. - ExternalAddr = 1, -}; - -using RangesTy = std::vector>; - -struct BinaryFunction { - StringRef FuncName; - // End of range is an exclusive bound. - RangesTy Ranges; - - uint64_t getFuncSize() { - uint64_t Sum = 0; - for (auto &R : Ranges) { - Sum += R.second - R.first; - } - return Sum; - } -}; - -// Info about function range. A function can be split into multiple -// non-continuous ranges, each range corresponds to one FuncRange. -struct FuncRange { - uint64_t StartOffset; - // EndOffset is an exclusive bound. - uint64_t EndOffset; - // Function the range belongs to - BinaryFunction *Func; - // Whether the start offset is the real entry of the function. - bool IsFuncEntry = false; - - StringRef getFuncName() { return Func->FuncName; } -}; - -// PrologEpilog offset tracker, used to filter out broken stack samples -// Currently we use a heuristic size (two) to infer prolog and epilog -// based on the start address and return address. In the future, -// we will switch to Dwarf CFI based tracker -struct PrologEpilogTracker { - // A set of prolog and epilog offsets. Used by virtual unwinding. - std::unordered_set PrologEpilogSet; - ProfiledBinary *Binary; - PrologEpilogTracker(ProfiledBinary *Bin) : Binary(Bin){}; - - // Take the two addresses from the start of function as prolog - void inferPrologOffsets(std::map &FuncStartOffsetMap) { - for (auto I : FuncStartOffsetMap) { - PrologEpilogSet.insert(I.first); - InstructionPointer IP(Binary, I.first); - if (!IP.advance()) - break; - PrologEpilogSet.insert(IP.Offset); - } - } - - // Take the last two addresses before the return address as epilog - void inferEpilogOffsets(std::unordered_set &RetAddrs) { - for (auto Addr : RetAddrs) { - PrologEpilogSet.insert(Addr); - InstructionPointer IP(Binary, Addr); - if (!IP.backward()) - break; - PrologEpilogSet.insert(IP.Offset); - } - } -}; - -// Track function byte size under different context (outlined version as well as -// various inlined versions). It also provides query support to get function -// size with the best matching context, which is used to help pre-inliner use -// accurate post-optimization size to make decisions. -// TODO: If an inlinee is completely optimized away, ideally we should have zero -// for its context size, currently we would misss such context since it doesn't -// have instructions. To fix this, we need to mark all inlinee with entry probe -// but without instructions as having zero size. -class BinarySizeContextTracker { -public: - // Add instruction with given size to a context - void addInstructionForContext(const SampleContextFrameVector &Context, - uint32_t InstrSize); - - // Get function size with a specific context. When there's no exact match - // for the given context, try to retrieve the size of that function from - // closest matching context. - uint32_t getFuncSizeForContext(const SampleContext &Context); - - // For inlinees that are full optimized away, we can establish zero size using - // their remaining probes. - void trackInlineesOptimizedAway(MCPseudoProbeDecoder &ProbeDecoder); - - void dump() { RootContext.dumpTree(); } - -private: - using ProbeFrameStack = SmallVector>; - void trackInlineesOptimizedAway(MCPseudoProbeDecoder &ProbeDecoder, - MCDecodedPseudoProbeInlineTree &ProbeNode, - ProbeFrameStack &Context); - - // Root node for context trie tree, node that this is a reverse context trie - // with callee as parent and caller as child. This way we can traverse from - // root to find the best/longest matching context if an exact match does not - // exist. It gives us the best possible estimate for function's post-inline, - // post-optimization byte size. - ContextTrieNode RootContext; -}; - -using OffsetRange = std::pair; - -class ProfiledBinary { - // Absolute path of the executable binary. - std::string Path; - // Path of the debug info binary. - std::string DebugBinaryPath; - // Path of symbolizer path which should be pointed to binary with debug info. - StringRef SymbolizerPath; - // The target triple. - Triple TheTriple; - // The runtime base address that the first executable segment is loaded at. - uint64_t BaseAddress = 0; - // The runtime base address that the first loadabe segment is loaded at. - uint64_t FirstLoadableAddress = 0; - // The preferred load address of each executable segment. - std::vector PreferredTextSegmentAddresses; - // The file offset of each executable segment. - std::vector TextSegmentOffsets; - - // Mutiple MC component info - std::unique_ptr MRI; - std::unique_ptr AsmInfo; - std::unique_ptr STI; - std::unique_ptr MII; - std::unique_ptr DisAsm; - std::unique_ptr MIA; - std::unique_ptr IPrinter; - // A list of text sections sorted by start RVA and size. Used to check - // if a given RVA is a valid code address. - std::set> TextSections; - - // A map of mapping function name to BinaryFunction info. - std::unordered_map BinaryFunctions; - - // An ordered map of mapping function's start offset to function range - // relevant info. Currently to determine if the offset of ELF is the start of - // a real function, we leverage the function range info from DWARF. - std::map StartOffset2FuncRangeMap; - - // Offset to context location map. Used to expand the context. - std::unordered_map Offset2LocStackMap; - - // Offset to instruction size map. Also used for quick offset lookup. - std::unordered_map Offset2InstSizeMap; - - // An array of offsets of all instructions sorted in increasing order. The - // sorting is needed to fast advance to the next forward/backward instruction. - std::vector CodeAddrOffsets; - // A set of call instruction offsets. Used by virtual unwinding. - std::unordered_set CallOffsets; - // A set of return instruction offsets. Used by virtual unwinding. - std::unordered_set RetOffsets; - // A set of branch instruction offsets. - std::unordered_set BranchOffsets; - - // Estimate and track function prolog and epilog ranges. - PrologEpilogTracker ProEpilogTracker; - - // Track function sizes under different context - BinarySizeContextTracker FuncSizeTracker; - - // The symbolizer used to get inline context for an instruction. - std::unique_ptr Symbolizer; - - // String table owning function name strings created from the symbolizer. - std::unordered_set NameStrings; - - // A collection of functions to print disassembly for. - StringSet<> DisassembleFunctionSet; - - // Pseudo probe decoder - MCPseudoProbeDecoder ProbeDecoder; - - bool UsePseudoProbes = false; - - bool UseFSDiscriminator = false; - - // Whether we need to symbolize all instructions to get function context size. - bool TrackFuncContextSize = false; - - // Indicate if the base loading address is parsed from the mmap event or uses - // the preferred address - bool IsLoadedByMMap = false; - // Use to avoid redundant warning. - bool MissingMMapWarned = false; - - void setPreferredTextSegmentAddresses(const ELFObjectFileBase *O); - - template - void setPreferredTextSegmentAddresses(const ELFFile &Obj, StringRef FileName); - - void decodePseudoProbe(const ELFObjectFileBase *Obj); - - void - checkUseFSDiscriminator(const ELFObjectFileBase *Obj, - std::map &AllSymbols); - - // Set up disassembler and related components. - void setUpDisassembler(const ELFObjectFileBase *Obj); - void setupSymbolizer(); - - // Load debug info of subprograms from DWARF section. - void loadSymbolsFromDWARF(ObjectFile &Obj); - - // A function may be spilt into multiple non-continuous address ranges. We use - // this to set whether start offset of a function is the real entry of the - // function and also set false to the non-function label. - void setIsFuncEntry(uint64_t Offset, StringRef RangeSymName); - - // Warn if no entry range exists in the function. - void warnNoFuncEntry(); - - /// Dissassemble the text section and build various address maps. - void disassemble(const ELFObjectFileBase *O); - - /// Helper function to dissassemble the symbol and extract info for unwinding - bool dissassembleSymbol(std::size_t SI, ArrayRef Bytes, - SectionSymbolsTy &Symbols, const SectionRef &Section); - /// Symbolize a given instruction pointer and return a full call context. - SampleContextFrameVector symbolize(const InstructionPointer &IP, - bool UseCanonicalFnName = false, - bool UseProbeDiscriminator = false); - /// Decode the interesting parts of the binary and build internal data - /// structures. On high level, the parts of interest are: - /// 1. Text sections, including the main code section and the PLT - /// entries that will be used to handle cross-module call transitions. - /// 2. The .debug_line section, used by Dwarf-based profile generation. - /// 3. Pseudo probe related sections, used by probe-based profile - /// generation. - void load(); - -public: - ProfiledBinary(const StringRef ExeBinPath, const StringRef DebugBinPath) - : Path(ExeBinPath), DebugBinaryPath(DebugBinPath), ProEpilogTracker(this), - TrackFuncContextSize(EnableCSPreInliner && - UseContextCostForPreInliner) { - // Point to executable binary if debug info binary is not specified. - SymbolizerPath = DebugBinPath.empty() ? ExeBinPath : DebugBinPath; - setupSymbolizer(); - load(); - } - uint64_t virtualAddrToOffset(uint64_t VirtualAddress) const { - return VirtualAddress - BaseAddress; - } - uint64_t offsetToVirtualAddr(uint64_t Offset) const { - return Offset + BaseAddress; - } - StringRef getPath() const { return Path; } - StringRef getName() const { return llvm::sys::path::filename(Path); } - uint64_t getBaseAddress() const { return BaseAddress; } - void setBaseAddress(uint64_t Address) { BaseAddress = Address; } - - // Return the preferred load address for the first executable segment. - uint64_t getPreferredBaseAddress() const { return PreferredTextSegmentAddresses[0]; } - // Return the preferred load address for the first loadable segment. - uint64_t getFirstLoadableAddress() const { return FirstLoadableAddress; } - // Return the file offset for the first executable segment. - uint64_t getTextSegmentOffset() const { return TextSegmentOffsets[0]; } - const std::vector &getPreferredTextSegmentAddresses() const { - return PreferredTextSegmentAddresses; - } - const std::vector &getTextSegmentOffsets() const { - return TextSegmentOffsets; - } - - uint64_t getInstSize(uint64_t Offset) const { - auto I = Offset2InstSizeMap.find(Offset); - if (I == Offset2InstSizeMap.end()) - return 0; - return I->second; - } - - bool offsetIsCode(uint64_t Offset) const { - return Offset2InstSizeMap.find(Offset) != Offset2InstSizeMap.end(); - } - bool addressIsCode(uint64_t Address) const { - uint64_t Offset = virtualAddrToOffset(Address); - return offsetIsCode(Offset); - } - bool addressIsCall(uint64_t Address) const { - uint64_t Offset = virtualAddrToOffset(Address); - return CallOffsets.count(Offset); - } - bool addressIsReturn(uint64_t Address) const { - uint64_t Offset = virtualAddrToOffset(Address); - return RetOffsets.count(Offset); - } - bool addressInPrologEpilog(uint64_t Address) const { - uint64_t Offset = virtualAddrToOffset(Address); - return ProEpilogTracker.PrologEpilogSet.count(Offset); - } - - bool offsetIsTransfer(uint64_t Offset) { - return BranchOffsets.count(Offset) || RetOffsets.count(Offset) || - CallOffsets.count(Offset); - } - - uint64_t getAddressforIndex(uint64_t Index) const { - return offsetToVirtualAddr(CodeAddrOffsets[Index]); - } - - size_t getCodeOffsetsSize() const { return CodeAddrOffsets.size(); } - - bool usePseudoProbes() const { return UsePseudoProbes; } - bool useFSDiscriminator() const { return UseFSDiscriminator; } - // Get the index in CodeAddrOffsets for the address - // As we might get an address which is not the code - // here it would round to the next valid code address by - // using lower bound operation - uint32_t getIndexForOffset(uint64_t Offset) const { - auto Low = llvm::lower_bound(CodeAddrOffsets, Offset); - return Low - CodeAddrOffsets.begin(); - } - uint32_t getIndexForAddr(uint64_t Address) const { - uint64_t Offset = virtualAddrToOffset(Address); - return getIndexForOffset(Offset); - } - - uint64_t getCallAddrFromFrameAddr(uint64_t FrameAddr) const { - if (FrameAddr == ExternalAddr) - return ExternalAddr; - auto I = getIndexForAddr(FrameAddr); - FrameAddr = I ? getAddressforIndex(I - 1) : 0; - if (FrameAddr && addressIsCall(FrameAddr)) - return FrameAddr; - return 0; - } - - FuncRange *findFuncRangeForStartOffset(uint64_t Offset) { - auto I = StartOffset2FuncRangeMap.find(Offset); - if (I == StartOffset2FuncRangeMap.end()) - return nullptr; - return &I->second; - } - - // Binary search the function range which includes the input offset. - FuncRange *findFuncRangeForOffset(uint64_t Offset) { - auto I = StartOffset2FuncRangeMap.upper_bound(Offset); - if (I == StartOffset2FuncRangeMap.begin()) - return nullptr; - I--; - - if (Offset >= I->second.EndOffset) - return nullptr; - - return &I->second; - } - - // Get all ranges of one function. - RangesTy getRangesForOffset(uint64_t Offset) { - auto *FRange = findFuncRangeForOffset(Offset); - // Ignore the range which falls into plt section or system lib. - if (!FRange) - return RangesTy(); - - return FRange->Func->Ranges; - } - - const std::unordered_map & - getAllBinaryFunctions() { - return BinaryFunctions; - } - - BinaryFunction *getBinaryFunction(StringRef FName) { - auto I = BinaryFunctions.find(FName.str()); - if (I == BinaryFunctions.end()) - return nullptr; - return &I->second; - } - - uint32_t getFuncSizeForContext(SampleContext &Context) { - return FuncSizeTracker.getFuncSizeForContext(Context); - } - - // Load the symbols from debug table and populate into symbol list. - void populateSymbolListFromDWARF(ProfileSymbolList &SymbolList); - - const SampleContextFrameVector & - getFrameLocationStack(uint64_t Offset, bool UseProbeDiscriminator = false) { - auto I = Offset2LocStackMap.emplace(Offset, SampleContextFrameVector()); - if (I.second) { - InstructionPointer IP(this, Offset); - I.first->second = symbolize(IP, true, UseProbeDiscriminator); - } - return I.first->second; - } - - Optional getInlineLeafFrameLoc(uint64_t Offset) { - const auto &Stack = getFrameLocationStack(Offset); - if (Stack.empty()) - return {}; - return Stack.back(); - } - - // Compare two addresses' inline context - bool inlineContextEqual(uint64_t Add1, uint64_t Add2); - - // Get the full context of the current stack with inline context filled in. - // It will search the disassembling info stored in Offset2LocStackMap. This is - // used as the key of function sample map - SampleContextFrameVector - getExpandedContext(const SmallVectorImpl &Stack, - bool &WasLeafInlined); - // Go through instructions among the given range and record its size for the - // inline context. - void computeInlinedContextSizeForRange(uint64_t StartOffset, - uint64_t EndOffset); - - const MCDecodedPseudoProbe *getCallProbeForAddr(uint64_t Address) const { - return ProbeDecoder.getCallProbeForAddr(Address); - } - - void getInlineContextForProbe(const MCDecodedPseudoProbe *Probe, - SampleContextFrameVector &InlineContextStack, - bool IncludeLeaf = false) const { - SmallVector ProbeInlineContext; - ProbeDecoder.getInlineContextForProbe(Probe, ProbeInlineContext, - IncludeLeaf); - for (uint32_t I = 0; I < ProbeInlineContext.size(); I++) { - auto &Callsite = ProbeInlineContext[I]; - // Clear the current context for an unknown probe. - if (Callsite.second == 0 && I != ProbeInlineContext.size() - 1) { - InlineContextStack.clear(); - continue; - } - InlineContextStack.emplace_back(Callsite.first, - LineLocation(Callsite.second, 0)); - } - } - const AddressProbesMap &getAddress2ProbesMap() const { - return ProbeDecoder.getAddress2ProbesMap(); - } - const MCPseudoProbeFuncDesc *getFuncDescForGUID(uint64_t GUID) { - return ProbeDecoder.getFuncDescForGUID(GUID); - } - - const MCPseudoProbeFuncDesc * - getInlinerDescForProbe(const MCDecodedPseudoProbe *Probe) { - return ProbeDecoder.getInlinerDescForProbe(Probe); - } - - bool getTrackFuncContextSize() { return TrackFuncContextSize; } - - bool getIsLoadedByMMap() { return IsLoadedByMMap; } - - void setIsLoadedByMMap(bool Value) { IsLoadedByMMap = Value; } - - bool getMissingMMapWarned() { return MissingMMapWarned; } - - void setMissingMMapWarned(bool Value) { MissingMMapWarned = Value; } -}; - -} // end namespace sampleprof -} // end namespace llvm - -#endif diff --git a/tools/ldc-profgen/ldc-profgen-14.0/llvm-profgen.cpp b/tools/ldc-profgen/ldc-profgen-14.0/llvm-profgen.cpp deleted file mode 100644 index f092df04d52..00000000000 --- a/tools/ldc-profgen/ldc-profgen-14.0/llvm-profgen.cpp +++ /dev/null @@ -1,164 +0,0 @@ -//===- llvm-profgen.cpp - LLVM SPGO profile generation tool -----*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// llvm-profgen generates SPGO profiles from perf script ouput. -// -//===----------------------------------------------------------------------===// - -#include "ErrorHandling.h" -#include "PerfReader.h" -#include "ProfileGenerator.h" -#include "ProfiledBinary.h" -#include "llvm/Support/CommandLine.h" -#include "llvm/Support/FileSystem.h" -#include "llvm/Support/InitLLVM.h" -#include "llvm/Support/TargetSelect.h" - -static cl::OptionCategory ProfGenCategory("ProfGen Options"); - -static cl::opt PerfScriptFilename( - "perfscript", cl::value_desc("perfscript"), cl::ZeroOrMore, - llvm::cl::MiscFlags::CommaSeparated, - cl::desc("Path of perf-script trace created by Linux perf tool with " - "`script` command(the raw perf.data should be profiled with -b)"), - cl::cat(ProfGenCategory)); -static cl::alias PSA("ps", cl::desc("Alias for --perfscript"), - cl::aliasopt(PerfScriptFilename)); - -static cl::opt PerfDataFilename( - "perfdata", cl::value_desc("perfdata"), cl::ZeroOrMore, - llvm::cl::MiscFlags::CommaSeparated, - cl::desc("Path of raw perf data created by Linux perf tool (it should be " - "profiled with -b)"), - cl::cat(ProfGenCategory)); -static cl::alias PDA("pd", cl::desc("Alias for --perfdata"), - cl::aliasopt(PerfDataFilename)); - -static cl::opt UnsymbolizedProfFilename( - "unsymbolized-profile", cl::value_desc("unsymbolized profile"), - cl::ZeroOrMore, llvm::cl::MiscFlags::CommaSeparated, - cl::desc("Path of the unsymbolized profile created by " - "`llvm-profgen` with `--skip-symbolization`"), - cl::cat(ProfGenCategory)); -static cl::alias UPA("up", cl::desc("Alias for --unsymbolized-profile"), - cl::aliasopt(UnsymbolizedProfFilename)); - -static cl::opt - BinaryPath("binary", cl::value_desc("binary"), cl::Required, - cl::desc("Path of profiled executable binary."), - cl::cat(ProfGenCategory)); - -static cl::opt DebugBinPath( - "debug-binary", cl::value_desc("debug-binary"), cl::ZeroOrMore, - cl::desc("Path of debug info binary, llvm-profgen will load the DWARF info " - "from it instead of the executable binary."), - cl::cat(ProfGenCategory)); - -extern cl::opt ShowDisassemblyOnly; -extern cl::opt ShowSourceLocations; -extern cl::opt SkipSymbolization; - -using namespace llvm; -using namespace sampleprof; - -// Validate the command line input. -static void validateCommandLine() { - // Allow the missing perfscript if we only use to show binary disassembly. - if (!ShowDisassemblyOnly) { - // Validate input profile is provided only once - uint16_t HasPerfData = PerfDataFilename.getNumOccurrences(); - uint16_t HasPerfScript = PerfScriptFilename.getNumOccurrences(); - uint16_t HasUnsymbolizedProfile = - UnsymbolizedProfFilename.getNumOccurrences(); - uint16_t S = HasPerfData + HasPerfScript + HasUnsymbolizedProfile; - if (S != 1) { - std::string Msg = - S > 1 - ? "`--perfscript`, `--perfdata` and `--unsymbolized-profile` " - "cannot be used together." - : "Perf input file is missing, please use one of `--perfscript`, " - "`--perfdata` and `--unsymbolized-profile` for the input."; - exitWithError(Msg); - } - - auto CheckFileExists = [](bool H, StringRef File) { - if (H && !llvm::sys::fs::exists(File)) { - std::string Msg = "Input perf file(" + File.str() + ") doesn't exist."; - exitWithError(Msg); - } - }; - - CheckFileExists(HasPerfData, PerfDataFilename); - CheckFileExists(HasPerfScript, PerfScriptFilename); - CheckFileExists(HasUnsymbolizedProfile, UnsymbolizedProfFilename); - } - - if (!llvm::sys::fs::exists(BinaryPath)) { - std::string Msg = "Input binary(" + BinaryPath + ") doesn't exist."; - exitWithError(Msg); - } - - if (CSProfileGenerator::MaxCompressionSize < -1) { - exitWithError("Value of --compress-recursion should >= -1"); - } - if (ShowSourceLocations && !ShowDisassemblyOnly) { - exitWithError("--show-source-locations should work together with " - "--show-disassembly-only!"); - } -} - -static PerfInputFile getPerfInputFile() { - PerfInputFile File; - if (PerfDataFilename.getNumOccurrences()) { - File.InputFile = PerfDataFilename; - File.Format = PerfFormat::PerfData; - } else if (PerfScriptFilename.getNumOccurrences()) { - File.InputFile = PerfScriptFilename; - File.Format = PerfFormat::PerfScript; - } else if (UnsymbolizedProfFilename.getNumOccurrences()) { - File.InputFile = UnsymbolizedProfFilename; - File.Format = PerfFormat::UnsymbolizedProfile; - } - return File; -} - -int main(int argc, const char *argv[]) { - InitLLVM X(argc, argv); - - // Initialize targets and assembly printers/parsers. - InitializeAllTargetInfos(); - InitializeAllTargetMCs(); - InitializeAllDisassemblers(); - - cl::HideUnrelatedOptions({&ProfGenCategory, &getColorCategory()}); - cl::ParseCommandLineOptions(argc, argv, "llvm SPGO profile generator\n"); - validateCommandLine(); - - // Load symbols and disassemble the code of a given binary. - std::unique_ptr Binary = - std::make_unique(BinaryPath, DebugBinPath); - if (ShowDisassemblyOnly) - return EXIT_SUCCESS; - - PerfInputFile PerfFile = getPerfInputFile(); - std::unique_ptr Reader = - PerfReaderBase::create(Binary.get(), PerfFile); - // Parse perf events and samples - Reader->parsePerfTraces(); - - if (SkipSymbolization) - return EXIT_SUCCESS; - - std::unique_ptr Generator = - ProfileGeneratorBase::create(Binary.get(), Reader->getSampleCounters(), - Reader->profileIsCSFlat()); - Generator->generateProfile(); - Generator->write(); - - return EXIT_SUCCESS; -} diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 6b61a068373..fbde76d6c39 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -44,9 +44,7 @@ if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${FILECHECK_SRC}) COMPILE_FLAGS "${LLVM_CXXFLAGS} ${LDC_CXXFLAGS}" LINK_FLAGS "${SANITIZE_LDFLAGS}" ) - if(NOT (LDC_LLVM_VER LESS 1200)) - target_link_libraries(FileCheck LLVMFileCheck) - endif() + target_link_libraries(FileCheck LLVMFileCheck) target_link_libraries(FileCheck ${LLVM_LIBRARIES} ${CMAKE_DL_LIBS} ${LLVM_LDFLAGS}) message(STATUS "Building FileCheck from LDC source tree") else() @@ -68,7 +66,7 @@ set_target_properties( target_link_libraries(not ${LLVM_LIBRARIES} ${CMAKE_DL_LIBS} ${LLVM_LDFLAGS}) endif() -if ((TARGET split-file) OR (EXISTS ${LLVM_ROOT_DIR}/bin/split-filea)) +if ((TARGET split-file) OR (EXISTS ${LLVM_ROOT_DIR}/bin/split-file)) # already provided by LLVM message(STATUS "Skip building split-file, it is already provided by LLVM") else() diff --git a/utils/FileCheck-11.cpp b/utils/FileCheck-11.cpp deleted file mode 100644 index fa79c5e8948..00000000000 --- a/utils/FileCheck-11.cpp +++ /dev/null @@ -1,859 +0,0 @@ -//===- FileCheck.cpp - Check that File's Contents match what is expected --===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// FileCheck does a line-by line check of a file that validates whether it -// contains the expected content. This is useful for regression tests etc. -// -// This program exits with an exit status of 2 on error, exit status of 0 if -// the file matched the expected contents, and exit status of 1 if it did not -// contain the expected contents. -// -//===----------------------------------------------------------------------===// - -#include "llvm/Support/CommandLine.h" -#include "llvm/Support/InitLLVM.h" -#include "llvm/Support/Process.h" -#include "llvm/Support/WithColor.h" -#include "llvm/Support/raw_ostream.h" -#include "llvm/Support/FileCheck.h" -#include -using namespace llvm; - -static cl::extrahelp FileCheckOptsEnv( - "\nOptions are parsed from the environment variable FILECHECK_OPTS and\n" - "from the command line.\n"); - -static cl::opt - CheckFilename(cl::Positional, cl::desc(""), cl::Optional); - -static cl::opt - InputFilename("input-file", cl::desc("File to check (defaults to stdin)"), - cl::init("-"), cl::value_desc("filename")); - -static cl::list CheckPrefixes( - "check-prefix", - cl::desc("Prefix to use from check file (defaults to 'CHECK')")); -static cl::alias CheckPrefixesAlias( - "check-prefixes", cl::aliasopt(CheckPrefixes), cl::CommaSeparated, - cl::NotHidden, - cl::desc( - "Alias for -check-prefix permitting multiple comma separated values")); - -static cl::list CommentPrefixes( - "comment-prefixes", cl::CommaSeparated, cl::Hidden, - cl::desc("Comma-separated list of comment prefixes to use from check file\n" - "(defaults to 'COM,RUN'). Please avoid using this feature in\n" - "LLVM's LIT-based test suites, which should be easier to\n" - "maintain if they all follow a consistent comment style. This\n" - "feature is meant for non-LIT test suites using FileCheck.")); - -static cl::opt NoCanonicalizeWhiteSpace( - "strict-whitespace", - cl::desc("Do not treat all horizontal whitespace as equivalent")); - -static cl::opt IgnoreCase( - "ignore-case", - cl::desc("Use case-insensitive matching")); - -static cl::list ImplicitCheckNot( - "implicit-check-not", - cl::desc("Add an implicit negative check with this pattern to every\n" - "positive check. This can be used to ensure that no instances of\n" - "this pattern occur which are not matched by a positive pattern"), - cl::value_desc("pattern")); - -static cl::list - GlobalDefines("D", cl::AlwaysPrefix, - cl::desc("Define a variable to be used in capture patterns."), - cl::value_desc("VAR=VALUE")); - -static cl::opt AllowEmptyInput( - "allow-empty", cl::init(false), - cl::desc("Allow the input file to be empty. This is useful when making\n" - "checks that some error message does not occur, for example.")); - -static cl::opt MatchFullLines( - "match-full-lines", cl::init(false), - cl::desc("Require all positive matches to cover an entire input line.\n" - "Allows leading and trailing whitespace if --strict-whitespace\n" - "is not also passed.")); - -static cl::opt EnableVarScope( - "enable-var-scope", cl::init(false), - cl::desc("Enables scope for regex variables. Variables with names that\n" - "do not start with '$' will be reset at the beginning of\n" - "each CHECK-LABEL block.")); - -static cl::opt AllowDeprecatedDagOverlap( - "allow-deprecated-dag-overlap", cl::init(false), - cl::desc("Enable overlapping among matches in a group of consecutive\n" - "CHECK-DAG directives. This option is deprecated and is only\n" - "provided for convenience as old tests are migrated to the new\n" - "non-overlapping CHECK-DAG implementation.\n")); - -static cl::opt Verbose( - "v", cl::init(false), cl::ZeroOrMore, - cl::desc("Print directive pattern matches, or add them to the input dump\n" - "if enabled.\n")); - -static cl::opt VerboseVerbose( - "vv", cl::init(false), cl::ZeroOrMore, - cl::desc("Print information helpful in diagnosing internal FileCheck\n" - "issues, or add it to the input dump if enabled. Implies\n" - "-v.\n")); - -// The order of DumpInputValue members affects their precedence, as documented -// for -dump-input below. -enum DumpInputValue { - DumpInputNever, - DumpInputFail, - DumpInputAlways, - DumpInputHelp -}; - -static cl::list DumpInputs( - "dump-input", - cl::desc("Dump input to stderr, adding annotations representing\n" - "currently enabled diagnostics. When there are multiple\n" - "occurrences of this option, the that appears earliest\n" - "in the list below has precedence. The default is 'fail'.\n"), - cl::value_desc("mode"), - cl::values(clEnumValN(DumpInputHelp, "help", "Explain input dump and quit"), - clEnumValN(DumpInputAlways, "always", "Always dump input"), - clEnumValN(DumpInputFail, "fail", "Dump input on failure"), - clEnumValN(DumpInputNever, "never", "Never dump input"))); - -// The order of DumpInputFilterValue members affects their precedence, as -// documented for -dump-input-filter below. -enum DumpInputFilterValue { - DumpInputFilterError, - DumpInputFilterAnnotation, - DumpInputFilterAnnotationFull, - DumpInputFilterAll -}; - -static cl::list DumpInputFilters( - "dump-input-filter", - cl::desc("In the dump requested by -dump-input, print only input lines of\n" - "kind plus any context specified by -dump-input-context.\n" - "When there are multiple occurrences of this option, the \n" - "that appears earliest in the list below has precedence. The\n" - "default is 'error' when -dump-input=fail, and it's 'all' when\n" - "-dump-input=always.\n"), - cl::values(clEnumValN(DumpInputFilterAll, "all", "All input lines"), - clEnumValN(DumpInputFilterAnnotationFull, "annotation-full", - "Input lines with annotations"), - clEnumValN(DumpInputFilterAnnotation, "annotation", - "Input lines with starting points of annotations"), - clEnumValN(DumpInputFilterError, "error", - "Input lines with starting points of error " - "annotations"))); - -static cl::list DumpInputContexts( - "dump-input-context", cl::value_desc("N"), - cl::desc("In the dump requested by -dump-input, print input lines\n" - "before and input lines after any lines specified by\n" - "-dump-input-filter. When there are multiple occurrences of\n" - "this option, the largest specified has precedence. The\n" - "default is 5.\n")); - -typedef cl::list::const_iterator prefix_iterator; - - - - - - - -static void DumpCommandLine(int argc, char **argv) { - errs() << "FileCheck command line: "; - for (int I = 0; I < argc; I++) - errs() << " " << argv[I]; - errs() << "\n"; -} - -struct MarkerStyle { - /// The starting char (before tildes) for marking the line. - char Lead; - /// What color to use for this annotation. - raw_ostream::Colors Color; - /// A note to follow the marker, or empty string if none. - std::string Note; - /// Does this marker indicate inclusion by -dump-input-filter=error? - bool FiltersAsError; - MarkerStyle() {} - MarkerStyle(char Lead, raw_ostream::Colors Color, - const std::string &Note = "", bool FiltersAsError = false) - : Lead(Lead), Color(Color), Note(Note), FiltersAsError(FiltersAsError) { - assert((!FiltersAsError || !Note.empty()) && - "expected error diagnostic to have note"); - } -}; - -static MarkerStyle GetMarker(FileCheckDiag::MatchType MatchTy) { - switch (MatchTy) { - case FileCheckDiag::MatchFoundAndExpected: - return MarkerStyle('^', raw_ostream::GREEN); - case FileCheckDiag::MatchFoundButExcluded: - return MarkerStyle('!', raw_ostream::RED, "error: no match expected", - /*FiltersAsError=*/true); - case FileCheckDiag::MatchFoundButWrongLine: - return MarkerStyle('!', raw_ostream::RED, "error: match on wrong line", - /*FiltersAsError=*/true); - case FileCheckDiag::MatchFoundButDiscarded: - return MarkerStyle('!', raw_ostream::CYAN, - "discard: overlaps earlier match"); - case FileCheckDiag::MatchNoneAndExcluded: - return MarkerStyle('X', raw_ostream::GREEN); - case FileCheckDiag::MatchNoneButExpected: - return MarkerStyle('X', raw_ostream::RED, "error: no match found", - /*FiltersAsError=*/true); - case FileCheckDiag::MatchFuzzy: - return MarkerStyle('?', raw_ostream::MAGENTA, "possible intended match", - /*FiltersAsError=*/true); - } - llvm_unreachable_internal("unexpected match type"); -} - -static void DumpInputAnnotationHelp(raw_ostream &OS) { - OS << "The following description was requested by -dump-input=help to\n" - << "explain the input dump printed by FileCheck.\n" - << "\n" - << "Related command-line options:\n" - << "\n" - << " - -dump-input= enables or disables the input dump\n" - << " - -dump-input-filter= filters the input lines\n" - << " - -dump-input-context= adjusts the context of filtered lines\n" - << " - -v and -vv add more annotations\n" - << " - -color forces colors to be enabled both in the dump and below\n" - << " - -help documents the above options in more detail\n" - << "\n" - << "These options can also be set via FILECHECK_OPTS. For example, for\n" - << "maximum debugging output on failures:\n" - << "\n" - << " $ FILECHECK_OPTS='-dump-input-filter=all -vv -color' ninja check\n" - << "\n" - << "Input dump annotation format:\n" - << "\n"; - - // Labels for input lines. - OS << " - "; - WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "L:"; - OS << " labels line number L of the input file\n"; - - // Labels for annotation lines. - OS << " - "; - WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L"; - OS << " labels the only match result for either (1) a pattern of type T" - << " from\n" - << " line L of the check file if L is an integer or (2) the" - << " I-th implicit\n" - << " pattern if L is \"imp\" followed by an integer " - << "I (index origin one)\n"; - OS << " - "; - WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L'N"; - OS << " labels the Nth match result for such a pattern\n"; - - // Markers on annotation lines. - OS << " - "; - WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "^~~"; - OS << " marks good match (reported if -v)\n" - << " - "; - WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "!~~"; - OS << " marks bad match, such as:\n" - << " - CHECK-NEXT on same line as previous match (error)\n" - << " - CHECK-NOT found (error)\n" - << " - CHECK-DAG overlapping match (discarded, reported if " - << "-vv)\n" - << " - "; - WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "X~~"; - OS << " marks search range when no match is found, such as:\n" - << " - CHECK-NEXT not found (error)\n" - << " - CHECK-NOT not found (success, reported if -vv)\n" - << " - CHECK-DAG not found after discarded matches (error)\n" - << " - "; - WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "?"; - OS << " marks fuzzy match when no match is found\n"; - - // Elided lines. - OS << " - "; - WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "..."; - OS << " indicates elided input lines and annotations, as specified by\n" - << " -dump-input-filter and -dump-input-context\n"; - - // Colors. - OS << " - colors "; - WithColor(OS, raw_ostream::GREEN, true) << "success"; - OS << ", "; - WithColor(OS, raw_ostream::RED, true) << "error"; - OS << ", "; - WithColor(OS, raw_ostream::MAGENTA, true) << "fuzzy match"; - OS << ", "; - WithColor(OS, raw_ostream::CYAN, true, false) << "discarded match"; - OS << ", "; - WithColor(OS, raw_ostream::CYAN, true, true) << "unmatched input"; - OS << "\n"; -} - -/// An annotation for a single input line. -struct InputAnnotation { - /// The index of the match result across all checks - unsigned DiagIndex; - /// The label for this annotation. - std::string Label; - /// Is this the initial fragment of a diagnostic that has been broken across - /// multiple lines? - bool IsFirstLine; - /// What input line (one-origin indexing) this annotation marks. This might - /// be different from the starting line of the original diagnostic if - /// !IsFirstLine. - unsigned InputLine; - /// The column range (one-origin indexing, open end) in which to mark the - /// input line. If InputEndCol is UINT_MAX, treat it as the last column - /// before the newline. - unsigned InputStartCol, InputEndCol; - /// The marker to use. - MarkerStyle Marker; - /// Whether this annotation represents a good match for an expected pattern. - bool FoundAndExpectedMatch; -}; - -/// Get an abbreviation for the check type. -std::string GetCheckTypeAbbreviation(Check::FileCheckType Ty) { - switch (Ty) { - case Check::CheckPlain: - if (Ty.getCount() > 1) - return "count"; - return "check"; - case Check::CheckNext: - return "next"; - case Check::CheckSame: - return "same"; - case Check::CheckNot: - return "not"; - case Check::CheckDAG: - return "dag"; - case Check::CheckLabel: - return "label"; - case Check::CheckEmpty: - return "empty"; - case Check::CheckComment: - return "com"; - case Check::CheckEOF: - return "eof"; - case Check::CheckBadNot: - return "bad-not"; - case Check::CheckBadCount: - return "bad-count"; - case Check::CheckNone: - llvm_unreachable("invalid FileCheckType"); - } - llvm_unreachable("unknown FileCheckType"); -} - -static void -BuildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID, - const std::pair &ImpPatBufferIDRange, - const std::vector &Diags, - std::vector &Annotations, - unsigned &LabelWidth) { - // How many diagnostics have we seen so far? - unsigned DiagCount = 0; - // How many diagnostics has the current check seen so far? - unsigned CheckDiagCount = 0; - // What's the widest label? - LabelWidth = 0; - for (auto DiagItr = Diags.begin(), DiagEnd = Diags.end(); DiagItr != DiagEnd; - ++DiagItr) { - InputAnnotation A; - A.DiagIndex = DiagCount++; - - // Build label, which uniquely identifies this check result. - unsigned CheckBufferID = SM.FindBufferContainingLoc(DiagItr->CheckLoc); - auto CheckLineAndCol = - SM.getLineAndColumn(DiagItr->CheckLoc, CheckBufferID); - llvm::raw_string_ostream Label(A.Label); - Label << GetCheckTypeAbbreviation(DiagItr->CheckTy) << ":"; - if (CheckBufferID == CheckFileBufferID) - Label << CheckLineAndCol.first; - else if (ImpPatBufferIDRange.first <= CheckBufferID && - CheckBufferID < ImpPatBufferIDRange.second) - Label << "imp" << (CheckBufferID - ImpPatBufferIDRange.first + 1); - else - llvm_unreachable("expected diagnostic's check location to be either in " - "the check file or for an implicit pattern"); - unsigned CheckDiagIndex = UINT_MAX; - auto DiagNext = std::next(DiagItr); - if (DiagNext != DiagEnd && DiagItr->CheckTy == DiagNext->CheckTy && - DiagItr->CheckLoc == DiagNext->CheckLoc) - CheckDiagIndex = CheckDiagCount++; - else if (CheckDiagCount) { - CheckDiagIndex = CheckDiagCount; - CheckDiagCount = 0; - } - if (CheckDiagIndex != UINT_MAX) - Label << "'" << CheckDiagIndex; - Label.flush(); - LabelWidth = std::max((std::string::size_type)LabelWidth, A.Label.size()); - - A.Marker = GetMarker(DiagItr->MatchTy); - A.FoundAndExpectedMatch = - DiagItr->MatchTy == FileCheckDiag::MatchFoundAndExpected; - - // Compute the mark location, and break annotation into multiple - // annotations if it spans multiple lines. - A.IsFirstLine = true; - A.InputLine = DiagItr->InputStartLine; - A.InputStartCol = DiagItr->InputStartCol; - if (DiagItr->InputStartLine == DiagItr->InputEndLine) { - // Sometimes ranges are empty in order to indicate a specific point, but - // that would mean nothing would be marked, so adjust the range to - // include the following character. - A.InputEndCol = - std::max(DiagItr->InputStartCol + 1, DiagItr->InputEndCol); - Annotations.push_back(A); - } else { - assert(DiagItr->InputStartLine < DiagItr->InputEndLine && - "expected input range not to be inverted"); - A.InputEndCol = UINT_MAX; - Annotations.push_back(A); - for (unsigned L = DiagItr->InputStartLine + 1, E = DiagItr->InputEndLine; - L <= E; ++L) { - // If a range ends before the first column on a line, then it has no - // characters on that line, so there's nothing to render. - if (DiagItr->InputEndCol == 1 && L == E) - break; - InputAnnotation B; - B.DiagIndex = A.DiagIndex; - B.Label = A.Label; - B.IsFirstLine = false; - B.InputLine = L; - B.Marker = A.Marker; - B.Marker.Lead = '~'; - B.Marker.Note = ""; - B.InputStartCol = 1; - if (L != E) - B.InputEndCol = UINT_MAX; - else - B.InputEndCol = DiagItr->InputEndCol; - B.FoundAndExpectedMatch = A.FoundAndExpectedMatch; - Annotations.push_back(B); - } - } - } -} - -static unsigned FindInputLineInFilter( - DumpInputFilterValue DumpInputFilter, unsigned CurInputLine, - const std::vector::iterator &AnnotationBeg, - const std::vector::iterator &AnnotationEnd) { - if (DumpInputFilter == DumpInputFilterAll) - return CurInputLine; - for (auto AnnotationItr = AnnotationBeg; AnnotationItr != AnnotationEnd; - ++AnnotationItr) { - switch (DumpInputFilter) { - case DumpInputFilterAll: - llvm_unreachable("unexpected DumpInputFilterAll"); - break; - case DumpInputFilterAnnotationFull: - return AnnotationItr->InputLine; - case DumpInputFilterAnnotation: - if (AnnotationItr->IsFirstLine) - return AnnotationItr->InputLine; - break; - case DumpInputFilterError: - if (AnnotationItr->IsFirstLine && AnnotationItr->Marker.FiltersAsError) - return AnnotationItr->InputLine; - break; - } - } - return UINT_MAX; -} - -/// To OS, print a vertical ellipsis (right-justified at LabelWidth) if it would -/// occupy less lines than ElidedLines, but print ElidedLines otherwise. Either -/// way, clear ElidedLines. Thus, if ElidedLines is empty, do nothing. -static void DumpEllipsisOrElidedLines(raw_ostream &OS, std::string &ElidedLines, - unsigned LabelWidth) { - if (ElidedLines.empty()) - return; - unsigned EllipsisLines = 3; - if (EllipsisLines < StringRef(ElidedLines).count('\n')) { - for (unsigned i = 0; i < EllipsisLines; ++i) { - WithColor(OS, raw_ostream::BLACK, /*Bold=*/true) - << right_justify(".", LabelWidth); - OS << '\n'; - } - } else - OS << ElidedLines; - ElidedLines.clear(); -} - -static void DumpAnnotatedInput(raw_ostream &OS, const FileCheckRequest &Req, - DumpInputFilterValue DumpInputFilter, - unsigned DumpInputContext, - StringRef InputFileText, - std::vector &Annotations, - unsigned LabelWidth) { - OS << "Input was:\n<<<<<<\n"; - - // Sort annotations. - std::sort(Annotations.begin(), Annotations.end(), - [](const InputAnnotation &A, const InputAnnotation &B) { - // 1. Sort annotations in the order of the input lines. - // - // This makes it easier to find relevant annotations while - // iterating input lines in the implementation below. FileCheck - // does not always produce diagnostics in the order of input - // lines due to, for example, CHECK-DAG and CHECK-NOT. - if (A.InputLine != B.InputLine) - return A.InputLine < B.InputLine; - // 2. Sort annotations in the temporal order FileCheck produced - // their associated diagnostics. - // - // This sort offers several benefits: - // - // A. On a single input line, the order of annotations reflects - // the FileCheck logic for processing directives/patterns. - // This can be helpful in understanding cases in which the - // order of the associated directives/patterns in the check - // file or on the command line either (i) does not match the - // temporal order in which FileCheck looks for matches for the - // directives/patterns (due to, for example, CHECK-LABEL, - // CHECK-NOT, or `--implicit-check-not`) or (ii) does match - // that order but does not match the order of those - // diagnostics along an input line (due to, for example, - // CHECK-DAG). - // - // On the other hand, because our presentation format presents - // input lines in order, there's no clear way to offer the - // same benefit across input lines. For consistency, it might - // then seem worthwhile to have annotations on a single line - // also sorted in input order (that is, by input column). - // However, in practice, this appears to be more confusing - // than helpful. Perhaps it's intuitive to expect annotations - // to be listed in the temporal order in which they were - // produced except in cases the presentation format obviously - // and inherently cannot support it (that is, across input - // lines). - // - // B. When diagnostics' annotations are split among multiple - // input lines, the user must track them from one input line - // to the next. One property of the sort chosen here is that - // it facilitates the user in this regard by ensuring the - // following: when comparing any two input lines, a - // diagnostic's annotations are sorted in the same position - // relative to all other diagnostics' annotations. - return A.DiagIndex < B.DiagIndex; - }); - - // Compute the width of the label column. - const unsigned char *InputFilePtr = InputFileText.bytes_begin(), - *InputFileEnd = InputFileText.bytes_end(); - unsigned LineCount = InputFileText.count('\n'); - if (InputFileEnd[-1] != '\n') - ++LineCount; - unsigned LineNoWidth = std::log10(LineCount) + 1; - // +3 below adds spaces (1) to the left of the (right-aligned) line numbers - // on input lines and (2) to the right of the (left-aligned) labels on - // annotation lines so that input lines and annotation lines are more - // visually distinct. For example, the spaces on the annotation lines ensure - // that input line numbers and check directive line numbers never align - // horizontally. Those line numbers might not even be for the same file. - // One space would be enough to achieve that, but more makes it even easier - // to see. - LabelWidth = std::max(LabelWidth, LineNoWidth) + 3; - - // Print annotated input lines. - unsigned PrevLineInFilter = 0; // 0 means none so far - unsigned NextLineInFilter = 0; // 0 means uncomputed, UINT_MAX means none - std::string ElidedLines; - raw_string_ostream ElidedLinesOS(ElidedLines); - ColorMode TheColorMode = - WithColor(OS).colorsEnabled() ? ColorMode::Enable : ColorMode::Disable; - if (TheColorMode == ColorMode::Enable) - ElidedLinesOS.enable_colors(true); - auto AnnotationItr = Annotations.begin(), AnnotationEnd = Annotations.end(); - for (unsigned Line = 1; - InputFilePtr != InputFileEnd || AnnotationItr != AnnotationEnd; - ++Line) { - const unsigned char *InputFileLine = InputFilePtr; - - // Compute the previous and next line included by the filter. - if (NextLineInFilter < Line) - NextLineInFilter = FindInputLineInFilter(DumpInputFilter, Line, - AnnotationItr, AnnotationEnd); - assert(NextLineInFilter && "expected NextLineInFilter to be computed"); - if (NextLineInFilter == Line) - PrevLineInFilter = Line; - - // Elide this input line and its annotations if it's not within the - // context specified by -dump-input-context of an input line included by - // -dump-input-filter. However, in case the resulting ellipsis would occupy - // more lines than the input lines and annotations it elides, buffer the - // elided lines and annotations so we can print them instead. - raw_ostream *LineOS = &OS; - if ((!PrevLineInFilter || PrevLineInFilter + DumpInputContext < Line) && - (NextLineInFilter == UINT_MAX || - Line + DumpInputContext < NextLineInFilter)) - LineOS = &ElidedLinesOS; - else { - LineOS = &OS; - DumpEllipsisOrElidedLines(OS, ElidedLinesOS.str(), LabelWidth); - } - - // Print right-aligned line number. - WithColor(*LineOS, raw_ostream::BLACK, /*Bold=*/true, /*BF=*/false, - TheColorMode) - << format_decimal(Line, LabelWidth) << ": "; - - // For the case where -v and colors are enabled, find the annotations for - // good matches for expected patterns in order to highlight everything - // else in the line. There are no such annotations if -v is disabled. - std::vector FoundAndExpectedMatches; - if (Req.Verbose && TheColorMode == ColorMode::Enable) { - for (auto I = AnnotationItr; I != AnnotationEnd && I->InputLine == Line; - ++I) { - if (I->FoundAndExpectedMatch) - FoundAndExpectedMatches.push_back(*I); - } - } - - // Print numbered line with highlighting where there are no matches for - // expected patterns. - bool Newline = false; - { - WithColor COS(*LineOS, raw_ostream::SAVEDCOLOR, /*Bold=*/false, - /*BG=*/false, TheColorMode); - bool InMatch = false; - if (Req.Verbose) - COS.changeColor(raw_ostream::CYAN, true, true); - for (unsigned Col = 1; InputFilePtr != InputFileEnd && !Newline; ++Col) { - bool WasInMatch = InMatch; - InMatch = false; - for (auto M : FoundAndExpectedMatches) { - if (M.InputStartCol <= Col && Col < M.InputEndCol) { - InMatch = true; - break; - } - } - if (!WasInMatch && InMatch) - COS.resetColor(); - else if (WasInMatch && !InMatch) - COS.changeColor(raw_ostream::CYAN, true, true); - if (*InputFilePtr == '\n') - Newline = true; - else - COS << *InputFilePtr; - ++InputFilePtr; - } - } - *LineOS << '\n'; - unsigned InputLineWidth = InputFilePtr - InputFileLine - Newline; - - // Print any annotations. - while (AnnotationItr != AnnotationEnd && - AnnotationItr->InputLine == Line) { - WithColor COS(*LineOS, AnnotationItr->Marker.Color, /*Bold=*/true, - /*BG=*/false, TheColorMode); - // The two spaces below are where the ": " appears on input lines. - COS << left_justify(AnnotationItr->Label, LabelWidth) << " "; - unsigned Col; - for (Col = 1; Col < AnnotationItr->InputStartCol; ++Col) - COS << ' '; - COS << AnnotationItr->Marker.Lead; - // If InputEndCol=UINT_MAX, stop at InputLineWidth. - for (++Col; Col < AnnotationItr->InputEndCol && Col <= InputLineWidth; - ++Col) - COS << '~'; - const std::string &Note = AnnotationItr->Marker.Note; - if (!Note.empty()) { - // Put the note at the end of the input line. If we were to instead - // put the note right after the marker, subsequent annotations for the - // same input line might appear to mark this note instead of the input - // line. - for (; Col <= InputLineWidth; ++Col) - COS << ' '; - COS << ' ' << Note; - } - COS << '\n'; - ++AnnotationItr; - } - } - DumpEllipsisOrElidedLines(OS, ElidedLinesOS.str(), LabelWidth); - - OS << ">>>>>>\n"; -} - -int main(int argc, char **argv) { - // Enable use of ANSI color codes because FileCheck is using them to - // highlight text. - llvm::sys::Process::UseANSIEscapeCodes(true); - - InitLLVM X(argc, argv); - cl::ParseCommandLineOptions(argc, argv, /*Overview*/ "", /*Errs*/ nullptr, - "FILECHECK_OPTS"); - - // Select -dump-input* values. The -help documentation specifies the default - // value and which value to choose if an option is specified multiple times. - // In the latter case, the general rule of thumb is to choose the value that - // provides the most information. - DumpInputValue DumpInput = - DumpInputs.empty() - ? DumpInputFail - : *std::max_element(DumpInputs.begin(), DumpInputs.end()); - DumpInputFilterValue DumpInputFilter; - if (DumpInputFilters.empty()) - DumpInputFilter = DumpInput == DumpInputAlways ? DumpInputFilterAll - : DumpInputFilterError; - else - DumpInputFilter = - *std::max_element(DumpInputFilters.begin(), DumpInputFilters.end()); - unsigned DumpInputContext = DumpInputContexts.empty() - ? 5 - : *std::max_element(DumpInputContexts.begin(), - DumpInputContexts.end()); - - if (DumpInput == DumpInputHelp) { - DumpInputAnnotationHelp(outs()); - return 0; - } - if (CheckFilename.empty()) { - errs() << " not specified\n"; - return 2; - } - - FileCheckRequest Req; - for (StringRef Prefix : CheckPrefixes) - Req.CheckPrefixes.push_back(Prefix); - - for (StringRef Prefix : CommentPrefixes) - Req.CommentPrefixes.push_back(Prefix); - - for (StringRef CheckNot : ImplicitCheckNot) - Req.ImplicitCheckNot.push_back(CheckNot); - - bool GlobalDefineError = false; - for (StringRef G : GlobalDefines) { - size_t EqIdx = G.find('='); - if (EqIdx == std::string::npos) { - errs() << "Missing equal sign in command-line definition '-D" << G - << "'\n"; - GlobalDefineError = true; - continue; - } - if (EqIdx == 0) { - errs() << "Missing variable name in command-line definition '-D" << G - << "'\n"; - GlobalDefineError = true; - continue; - } - Req.GlobalDefines.push_back(G); - } - if (GlobalDefineError) - return 2; - - Req.AllowEmptyInput = AllowEmptyInput; - Req.EnableVarScope = EnableVarScope; - Req.AllowDeprecatedDagOverlap = AllowDeprecatedDagOverlap; - Req.Verbose = Verbose; - Req.VerboseVerbose = VerboseVerbose; - Req.NoCanonicalizeWhiteSpace = NoCanonicalizeWhiteSpace; - Req.MatchFullLines = MatchFullLines; - Req.IgnoreCase = IgnoreCase; - - if (VerboseVerbose) - Req.Verbose = true; - - FileCheck FC(Req); - if (!FC.ValidateCheckPrefixes()) - return 2; - - Regex PrefixRE = FC.buildCheckPrefixRegex(); - std::string REError; - if (!PrefixRE.isValid(REError)) { - errs() << "Unable to combine check-prefix strings into a prefix regular " - "expression! This is likely a bug in FileCheck's verification of " - "the check-prefix strings. Regular expression parsing failed " - "with the following error: " - << REError << "\n"; - return 2; - } - - SourceMgr SM; - - // Read the expected strings from the check file. - ErrorOr> CheckFileOrErr = - MemoryBuffer::getFileOrSTDIN(CheckFilename); - if (std::error_code EC = CheckFileOrErr.getError()) { - errs() << "Could not open check file '" << CheckFilename - << "': " << EC.message() << '\n'; - return 2; - } - MemoryBuffer &CheckFile = *CheckFileOrErr.get(); - - SmallString<4096> CheckFileBuffer; - StringRef CheckFileText = FC.CanonicalizeFile(CheckFile, CheckFileBuffer); - - unsigned CheckFileBufferID = - SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer( - CheckFileText, CheckFile.getBufferIdentifier()), - SMLoc()); - - std::pair ImpPatBufferIDRange; - if (FC.readCheckFile(SM, CheckFileText, PrefixRE, &ImpPatBufferIDRange)) - return 2; - - // Open the file to check and add it to SourceMgr. - ErrorOr> InputFileOrErr = - MemoryBuffer::getFileOrSTDIN(InputFilename); - if (InputFilename == "-") - InputFilename = ""; // Overwrite for improved diagnostic messages - if (std::error_code EC = InputFileOrErr.getError()) { - errs() << "Could not open input file '" << InputFilename - << "': " << EC.message() << '\n'; - return 2; - } - MemoryBuffer &InputFile = *InputFileOrErr.get(); - - if (InputFile.getBufferSize() == 0 && !AllowEmptyInput) { - errs() << "FileCheck error: '" << InputFilename << "' is empty.\n"; - DumpCommandLine(argc, argv); - return 2; - } - - SmallString<4096> InputFileBuffer; - StringRef InputFileText = FC.CanonicalizeFile(InputFile, InputFileBuffer); - - SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer( - InputFileText, InputFile.getBufferIdentifier()), - SMLoc()); - - std::vector Diags; - int ExitCode = FC.checkInput(SM, InputFileText, - DumpInput == DumpInputNever ? nullptr : &Diags) - ? EXIT_SUCCESS - : 1; - if (DumpInput == DumpInputAlways || - (ExitCode == 1 && DumpInput == DumpInputFail)) { - errs() << "\n" - << "Input file: " << InputFilename << "\n" - << "Check file: " << CheckFilename << "\n" - << "\n" - << "-dump-input=help explains the following input dump.\n" - << "\n"; - std::vector Annotations; - unsigned LabelWidth; - BuildInputAnnotations(SM, CheckFileBufferID, ImpPatBufferIDRange, Diags, - Annotations, LabelWidth); - DumpAnnotatedInput(errs(), Req, DumpInputFilter, DumpInputContext, - InputFileText, Annotations, LabelWidth); - } - - return ExitCode; -} diff --git a/utils/FileCheck-12.cpp b/utils/FileCheck-12.cpp deleted file mode 100644 index be277566620..00000000000 --- a/utils/FileCheck-12.cpp +++ /dev/null @@ -1,876 +0,0 @@ -//===- FileCheck.cpp - Check that File's Contents match what is expected --===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// FileCheck does a line-by line check of a file that validates whether it -// contains the expected content. This is useful for regression tests etc. -// -// This program exits with an exit status of 2 on error, exit status of 0 if -// the file matched the expected contents, and exit status of 1 if it did not -// contain the expected contents. -// -//===----------------------------------------------------------------------===// - -#include "llvm/FileCheck/FileCheck.h" -#include "llvm/Support/CommandLine.h" -#include "llvm/Support/InitLLVM.h" -#include "llvm/Support/Process.h" -#include "llvm/Support/WithColor.h" -#include "llvm/Support/raw_ostream.h" -#include -using namespace llvm; - -static cl::extrahelp FileCheckOptsEnv( - "\nOptions are parsed from the environment variable FILECHECK_OPTS and\n" - "from the command line.\n"); - -static cl::opt - CheckFilename(cl::Positional, cl::desc(""), cl::Optional); - -static cl::opt - InputFilename("input-file", cl::desc("File to check (defaults to stdin)"), - cl::init("-"), cl::value_desc("filename")); - -static cl::list CheckPrefixes( - "check-prefix", - cl::desc("Prefix to use from check file (defaults to 'CHECK')")); -static cl::alias CheckPrefixesAlias( - "check-prefixes", cl::aliasopt(CheckPrefixes), cl::CommaSeparated, - cl::NotHidden, - cl::desc( - "Alias for -check-prefix permitting multiple comma separated values")); - -static cl::list CommentPrefixes( - "comment-prefixes", cl::CommaSeparated, cl::Hidden, - cl::desc("Comma-separated list of comment prefixes to use from check file\n" - "(defaults to 'COM,RUN'). Please avoid using this feature in\n" - "LLVM's LIT-based test suites, which should be easier to\n" - "maintain if they all follow a consistent comment style. This\n" - "feature is meant for non-LIT test suites using FileCheck.")); - -static cl::opt NoCanonicalizeWhiteSpace( - "strict-whitespace", - cl::desc("Do not treat all horizontal whitespace as equivalent")); - -static cl::opt IgnoreCase( - "ignore-case", - cl::desc("Use case-insensitive matching")); - -static cl::list ImplicitCheckNot( - "implicit-check-not", - cl::desc("Add an implicit negative check with this pattern to every\n" - "positive check. This can be used to ensure that no instances of\n" - "this pattern occur which are not matched by a positive pattern"), - cl::value_desc("pattern")); - -static cl::list - GlobalDefines("D", cl::AlwaysPrefix, - cl::desc("Define a variable to be used in capture patterns."), - cl::value_desc("VAR=VALUE")); - -static cl::opt AllowEmptyInput( - "allow-empty", cl::init(false), - cl::desc("Allow the input file to be empty. This is useful when making\n" - "checks that some error message does not occur, for example.")); - -static cl::opt AllowUnusedPrefixes( - "allow-unused-prefixes", cl::init(true), - cl::desc("Allow prefixes to be specified but not appear in the test.")); - -static cl::opt MatchFullLines( - "match-full-lines", cl::init(false), - cl::desc("Require all positive matches to cover an entire input line.\n" - "Allows leading and trailing whitespace if --strict-whitespace\n" - "is not also passed.")); - -static cl::opt EnableVarScope( - "enable-var-scope", cl::init(false), - cl::desc("Enables scope for regex variables. Variables with names that\n" - "do not start with '$' will be reset at the beginning of\n" - "each CHECK-LABEL block.")); - -static cl::opt AllowDeprecatedDagOverlap( - "allow-deprecated-dag-overlap", cl::init(false), - cl::desc("Enable overlapping among matches in a group of consecutive\n" - "CHECK-DAG directives. This option is deprecated and is only\n" - "provided for convenience as old tests are migrated to the new\n" - "non-overlapping CHECK-DAG implementation.\n")); - -static cl::opt Verbose( - "v", cl::init(false), cl::ZeroOrMore, - cl::desc("Print directive pattern matches, or add them to the input dump\n" - "if enabled.\n")); - -static cl::opt VerboseVerbose( - "vv", cl::init(false), cl::ZeroOrMore, - cl::desc("Print information helpful in diagnosing internal FileCheck\n" - "issues, or add it to the input dump if enabled. Implies\n" - "-v.\n")); - -// The order of DumpInputValue members affects their precedence, as documented -// for -dump-input below. -enum DumpInputValue { - DumpInputNever, - DumpInputFail, - DumpInputAlways, - DumpInputHelp -}; - -static cl::list DumpInputs( - "dump-input", - cl::desc("Dump input to stderr, adding annotations representing\n" - "currently enabled diagnostics. When there are multiple\n" - "occurrences of this option, the that appears earliest\n" - "in the list below has precedence. The default is 'fail'.\n"), - cl::value_desc("mode"), - cl::values(clEnumValN(DumpInputHelp, "help", "Explain input dump and quit"), - clEnumValN(DumpInputAlways, "always", "Always dump input"), - clEnumValN(DumpInputFail, "fail", "Dump input on failure"), - clEnumValN(DumpInputNever, "never", "Never dump input"))); - -// The order of DumpInputFilterValue members affects their precedence, as -// documented for -dump-input-filter below. -enum DumpInputFilterValue { - DumpInputFilterError, - DumpInputFilterAnnotation, - DumpInputFilterAnnotationFull, - DumpInputFilterAll -}; - -static cl::list DumpInputFilters( - "dump-input-filter", - cl::desc("In the dump requested by -dump-input, print only input lines of\n" - "kind plus any context specified by -dump-input-context.\n" - "When there are multiple occurrences of this option, the \n" - "that appears earliest in the list below has precedence. The\n" - "default is 'error' when -dump-input=fail, and it's 'all' when\n" - "-dump-input=always.\n"), - cl::values(clEnumValN(DumpInputFilterAll, "all", "All input lines"), - clEnumValN(DumpInputFilterAnnotationFull, "annotation-full", - "Input lines with annotations"), - clEnumValN(DumpInputFilterAnnotation, "annotation", - "Input lines with starting points of annotations"), - clEnumValN(DumpInputFilterError, "error", - "Input lines with starting points of error " - "annotations"))); - -static cl::list DumpInputContexts( - "dump-input-context", cl::value_desc("N"), - cl::desc("In the dump requested by -dump-input, print input lines\n" - "before and input lines after any lines specified by\n" - "-dump-input-filter. When there are multiple occurrences of\n" - "this option, the largest specified has precedence. The\n" - "default is 5.\n")); - -typedef cl::list::const_iterator prefix_iterator; - - - - - - - -static void DumpCommandLine(int argc, char **argv) { - errs() << "FileCheck command line: "; - for (int I = 0; I < argc; I++) - errs() << " " << argv[I]; - errs() << "\n"; -} - -struct MarkerStyle { - /// The starting char (before tildes) for marking the line. - char Lead; - /// What color to use for this annotation. - raw_ostream::Colors Color; - /// A note to follow the marker, or empty string if none. - std::string Note; - /// Does this marker indicate inclusion by -dump-input-filter=error? - bool FiltersAsError; - MarkerStyle() {} - MarkerStyle(char Lead, raw_ostream::Colors Color, - const std::string &Note = "", bool FiltersAsError = false) - : Lead(Lead), Color(Color), Note(Note), FiltersAsError(FiltersAsError) { - assert((!FiltersAsError || !Note.empty()) && - "expected error diagnostic to have note"); - } -}; - -static MarkerStyle GetMarker(FileCheckDiag::MatchType MatchTy) { - switch (MatchTy) { - case FileCheckDiag::MatchFoundAndExpected: - return MarkerStyle('^', raw_ostream::GREEN); - case FileCheckDiag::MatchFoundButExcluded: - return MarkerStyle('!', raw_ostream::RED, "error: no match expected", - /*FiltersAsError=*/true); - case FileCheckDiag::MatchFoundButWrongLine: - return MarkerStyle('!', raw_ostream::RED, "error: match on wrong line", - /*FiltersAsError=*/true); - case FileCheckDiag::MatchFoundButDiscarded: - return MarkerStyle('!', raw_ostream::CYAN, - "discard: overlaps earlier match"); - case FileCheckDiag::MatchNoneAndExcluded: - return MarkerStyle('X', raw_ostream::GREEN); - case FileCheckDiag::MatchNoneButExpected: - return MarkerStyle('X', raw_ostream::RED, "error: no match found", - /*FiltersAsError=*/true); - case FileCheckDiag::MatchFuzzy: - return MarkerStyle('?', raw_ostream::MAGENTA, "possible intended match", - /*FiltersAsError=*/true); - } - llvm_unreachable_internal("unexpected match type"); -} - -static void DumpInputAnnotationHelp(raw_ostream &OS) { - OS << "The following description was requested by -dump-input=help to\n" - << "explain the input dump printed by FileCheck.\n" - << "\n" - << "Related command-line options:\n" - << "\n" - << " - -dump-input= enables or disables the input dump\n" - << " - -dump-input-filter= filters the input lines\n" - << " - -dump-input-context= adjusts the context of filtered lines\n" - << " - -v and -vv add more annotations\n" - << " - -color forces colors to be enabled both in the dump and below\n" - << " - -help documents the above options in more detail\n" - << "\n" - << "These options can also be set via FILECHECK_OPTS. For example, for\n" - << "maximum debugging output on failures:\n" - << "\n" - << " $ FILECHECK_OPTS='-dump-input-filter=all -vv -color' ninja check\n" - << "\n" - << "Input dump annotation format:\n" - << "\n"; - - // Labels for input lines. - OS << " - "; - WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "L:"; - OS << " labels line number L of the input file\n"; - - // Labels for annotation lines. - OS << " - "; - WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L"; - OS << " labels the only match result for either (1) a pattern of type T" - << " from\n" - << " line L of the check file if L is an integer or (2) the" - << " I-th implicit\n" - << " pattern if L is \"imp\" followed by an integer " - << "I (index origin one)\n"; - OS << " - "; - WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L'N"; - OS << " labels the Nth match result for such a pattern\n"; - - // Markers on annotation lines. - OS << " - "; - WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "^~~"; - OS << " marks good match (reported if -v)\n" - << " - "; - WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "!~~"; - OS << " marks bad match, such as:\n" - << " - CHECK-NEXT on same line as previous match (error)\n" - << " - CHECK-NOT found (error)\n" - << " - CHECK-DAG overlapping match (discarded, reported if " - << "-vv)\n" - << " - "; - WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "X~~"; - OS << " marks search range when no match is found, such as:\n" - << " - CHECK-NEXT not found (error)\n" - << " - CHECK-NOT not found (success, reported if -vv)\n" - << " - CHECK-DAG not found after discarded matches (error)\n" - << " - "; - WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "?"; - OS << " marks fuzzy match when no match is found\n"; - - // Elided lines. - OS << " - "; - WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "..."; - OS << " indicates elided input lines and annotations, as specified by\n" - << " -dump-input-filter and -dump-input-context\n"; - - // Colors. - OS << " - colors "; - WithColor(OS, raw_ostream::GREEN, true) << "success"; - OS << ", "; - WithColor(OS, raw_ostream::RED, true) << "error"; - OS << ", "; - WithColor(OS, raw_ostream::MAGENTA, true) << "fuzzy match"; - OS << ", "; - WithColor(OS, raw_ostream::CYAN, true, false) << "discarded match"; - OS << ", "; - WithColor(OS, raw_ostream::CYAN, true, true) << "unmatched input"; - OS << "\n"; -} - -/// An annotation for a single input line. -struct InputAnnotation { - /// The index of the match result across all checks - unsigned DiagIndex; - /// The label for this annotation. - std::string Label; - /// Is this the initial fragment of a diagnostic that has been broken across - /// multiple lines? - bool IsFirstLine; - /// What input line (one-origin indexing) this annotation marks. This might - /// be different from the starting line of the original diagnostic if - /// !IsFirstLine. - unsigned InputLine; - /// The column range (one-origin indexing, open end) in which to mark the - /// input line. If InputEndCol is UINT_MAX, treat it as the last column - /// before the newline. - unsigned InputStartCol, InputEndCol; - /// The marker to use. - MarkerStyle Marker; - /// Whether this annotation represents a good match for an expected pattern. - bool FoundAndExpectedMatch; -}; - -/// Get an abbreviation for the check type. -static std::string GetCheckTypeAbbreviation(Check::FileCheckType Ty) { - switch (Ty) { - case Check::CheckPlain: - if (Ty.getCount() > 1) - return "count"; - return "check"; - case Check::CheckNext: - return "next"; - case Check::CheckSame: - return "same"; - case Check::CheckNot: - return "not"; - case Check::CheckDAG: - return "dag"; - case Check::CheckLabel: - return "label"; - case Check::CheckEmpty: - return "empty"; - case Check::CheckComment: - return "com"; - case Check::CheckEOF: - return "eof"; - case Check::CheckBadNot: - return "bad-not"; - case Check::CheckBadCount: - return "bad-count"; - case Check::CheckNone: - llvm_unreachable("invalid FileCheckType"); - } - llvm_unreachable("unknown FileCheckType"); -} - -static void -BuildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID, - const std::pair &ImpPatBufferIDRange, - const std::vector &Diags, - std::vector &Annotations, - unsigned &LabelWidth) { - // How many diagnostics have we seen so far? - unsigned DiagCount = 0; - // How many diagnostics has the current check seen so far? - unsigned CheckDiagCount = 0; - // What's the widest label? - LabelWidth = 0; - for (auto DiagItr = Diags.begin(), DiagEnd = Diags.end(); DiagItr != DiagEnd; - ++DiagItr) { - InputAnnotation A; - A.DiagIndex = DiagCount++; - - // Build label, which uniquely identifies this check result. - unsigned CheckBufferID = SM.FindBufferContainingLoc(DiagItr->CheckLoc); - auto CheckLineAndCol = - SM.getLineAndColumn(DiagItr->CheckLoc, CheckBufferID); - llvm::raw_string_ostream Label(A.Label); - Label << GetCheckTypeAbbreviation(DiagItr->CheckTy) << ":"; - if (CheckBufferID == CheckFileBufferID) - Label << CheckLineAndCol.first; - else if (ImpPatBufferIDRange.first <= CheckBufferID && - CheckBufferID < ImpPatBufferIDRange.second) - Label << "imp" << (CheckBufferID - ImpPatBufferIDRange.first + 1); - else - llvm_unreachable("expected diagnostic's check location to be either in " - "the check file or for an implicit pattern"); - unsigned CheckDiagIndex = UINT_MAX; - auto DiagNext = std::next(DiagItr); - if (DiagNext != DiagEnd && DiagItr->CheckTy == DiagNext->CheckTy && - DiagItr->CheckLoc == DiagNext->CheckLoc) - CheckDiagIndex = CheckDiagCount++; - else if (CheckDiagCount) { - CheckDiagIndex = CheckDiagCount; - CheckDiagCount = 0; - } - if (CheckDiagIndex != UINT_MAX) - Label << "'" << CheckDiagIndex; - Label.flush(); - LabelWidth = std::max((std::string::size_type)LabelWidth, A.Label.size()); - - A.Marker = GetMarker(DiagItr->MatchTy); - if (!DiagItr->Note.empty()) { - A.Marker.Note = DiagItr->Note; - // It's less confusing if notes that don't actually have ranges don't have - // markers. For example, a marker for 'with "VAR" equal to "5"' would - // seem to indicate where "VAR" matches, but the location we actually have - // for the marker simply points to the start of the match/search range for - // the full pattern of which the substitution is potentially just one - // component. - if (DiagItr->InputStartLine == DiagItr->InputEndLine && - DiagItr->InputStartCol == DiagItr->InputEndCol) - A.Marker.Lead = ' '; - } - A.FoundAndExpectedMatch = - DiagItr->MatchTy == FileCheckDiag::MatchFoundAndExpected; - - // Compute the mark location, and break annotation into multiple - // annotations if it spans multiple lines. - A.IsFirstLine = true; - A.InputLine = DiagItr->InputStartLine; - A.InputStartCol = DiagItr->InputStartCol; - if (DiagItr->InputStartLine == DiagItr->InputEndLine) { - // Sometimes ranges are empty in order to indicate a specific point, but - // that would mean nothing would be marked, so adjust the range to - // include the following character. - A.InputEndCol = - std::max(DiagItr->InputStartCol + 1, DiagItr->InputEndCol); - Annotations.push_back(A); - } else { - assert(DiagItr->InputStartLine < DiagItr->InputEndLine && - "expected input range not to be inverted"); - A.InputEndCol = UINT_MAX; - Annotations.push_back(A); - for (unsigned L = DiagItr->InputStartLine + 1, E = DiagItr->InputEndLine; - L <= E; ++L) { - // If a range ends before the first column on a line, then it has no - // characters on that line, so there's nothing to render. - if (DiagItr->InputEndCol == 1 && L == E) - break; - InputAnnotation B; - B.DiagIndex = A.DiagIndex; - B.Label = A.Label; - B.IsFirstLine = false; - B.InputLine = L; - B.Marker = A.Marker; - B.Marker.Lead = '~'; - B.Marker.Note = ""; - B.InputStartCol = 1; - if (L != E) - B.InputEndCol = UINT_MAX; - else - B.InputEndCol = DiagItr->InputEndCol; - B.FoundAndExpectedMatch = A.FoundAndExpectedMatch; - Annotations.push_back(B); - } - } - } -} - -static unsigned FindInputLineInFilter( - DumpInputFilterValue DumpInputFilter, unsigned CurInputLine, - const std::vector::iterator &AnnotationBeg, - const std::vector::iterator &AnnotationEnd) { - if (DumpInputFilter == DumpInputFilterAll) - return CurInputLine; - for (auto AnnotationItr = AnnotationBeg; AnnotationItr != AnnotationEnd; - ++AnnotationItr) { - switch (DumpInputFilter) { - case DumpInputFilterAll: - llvm_unreachable("unexpected DumpInputFilterAll"); - break; - case DumpInputFilterAnnotationFull: - return AnnotationItr->InputLine; - case DumpInputFilterAnnotation: - if (AnnotationItr->IsFirstLine) - return AnnotationItr->InputLine; - break; - case DumpInputFilterError: - if (AnnotationItr->IsFirstLine && AnnotationItr->Marker.FiltersAsError) - return AnnotationItr->InputLine; - break; - } - } - return UINT_MAX; -} - -/// To OS, print a vertical ellipsis (right-justified at LabelWidth) if it would -/// occupy less lines than ElidedLines, but print ElidedLines otherwise. Either -/// way, clear ElidedLines. Thus, if ElidedLines is empty, do nothing. -static void DumpEllipsisOrElidedLines(raw_ostream &OS, std::string &ElidedLines, - unsigned LabelWidth) { - if (ElidedLines.empty()) - return; - unsigned EllipsisLines = 3; - if (EllipsisLines < StringRef(ElidedLines).count('\n')) { - for (unsigned i = 0; i < EllipsisLines; ++i) { - WithColor(OS, raw_ostream::BLACK, /*Bold=*/true) - << right_justify(".", LabelWidth); - OS << '\n'; - } - } else - OS << ElidedLines; - ElidedLines.clear(); -} - -static void DumpAnnotatedInput(raw_ostream &OS, const FileCheckRequest &Req, - DumpInputFilterValue DumpInputFilter, - unsigned DumpInputContext, - StringRef InputFileText, - std::vector &Annotations, - unsigned LabelWidth) { - OS << "Input was:\n<<<<<<\n"; - - // Sort annotations. - llvm::sort(Annotations, - [](const InputAnnotation &A, const InputAnnotation &B) { - // 1. Sort annotations in the order of the input lines. - // - // This makes it easier to find relevant annotations while - // iterating input lines in the implementation below. FileCheck - // does not always produce diagnostics in the order of input - // lines due to, for example, CHECK-DAG and CHECK-NOT. - if (A.InputLine != B.InputLine) - return A.InputLine < B.InputLine; - // 2. Sort annotations in the temporal order FileCheck produced - // their associated diagnostics. - // - // This sort offers several benefits: - // - // A. On a single input line, the order of annotations reflects - // the FileCheck logic for processing directives/patterns. - // This can be helpful in understanding cases in which the - // order of the associated directives/patterns in the check - // file or on the command line either (i) does not match the - // temporal order in which FileCheck looks for matches for the - // directives/patterns (due to, for example, CHECK-LABEL, - // CHECK-NOT, or `--implicit-check-not`) or (ii) does match - // that order but does not match the order of those - // diagnostics along an input line (due to, for example, - // CHECK-DAG). - // - // On the other hand, because our presentation format presents - // input lines in order, there's no clear way to offer the - // same benefit across input lines. For consistency, it might - // then seem worthwhile to have annotations on a single line - // also sorted in input order (that is, by input column). - // However, in practice, this appears to be more confusing - // than helpful. Perhaps it's intuitive to expect annotations - // to be listed in the temporal order in which they were - // produced except in cases the presentation format obviously - // and inherently cannot support it (that is, across input - // lines). - // - // B. When diagnostics' annotations are split among multiple - // input lines, the user must track them from one input line - // to the next. One property of the sort chosen here is that - // it facilitates the user in this regard by ensuring the - // following: when comparing any two input lines, a - // diagnostic's annotations are sorted in the same position - // relative to all other diagnostics' annotations. - return A.DiagIndex < B.DiagIndex; - }); - - // Compute the width of the label column. - const unsigned char *InputFilePtr = InputFileText.bytes_begin(), - *InputFileEnd = InputFileText.bytes_end(); - unsigned LineCount = InputFileText.count('\n'); - if (InputFileEnd[-1] != '\n') - ++LineCount; - unsigned LineNoWidth = std::log10(LineCount) + 1; - // +3 below adds spaces (1) to the left of the (right-aligned) line numbers - // on input lines and (2) to the right of the (left-aligned) labels on - // annotation lines so that input lines and annotation lines are more - // visually distinct. For example, the spaces on the annotation lines ensure - // that input line numbers and check directive line numbers never align - // horizontally. Those line numbers might not even be for the same file. - // One space would be enough to achieve that, but more makes it even easier - // to see. - LabelWidth = std::max(LabelWidth, LineNoWidth) + 3; - - // Print annotated input lines. - unsigned PrevLineInFilter = 0; // 0 means none so far - unsigned NextLineInFilter = 0; // 0 means uncomputed, UINT_MAX means none - std::string ElidedLines; - raw_string_ostream ElidedLinesOS(ElidedLines); - ColorMode TheColorMode = - WithColor(OS).colorsEnabled() ? ColorMode::Enable : ColorMode::Disable; - if (TheColorMode == ColorMode::Enable) - ElidedLinesOS.enable_colors(true); - auto AnnotationItr = Annotations.begin(), AnnotationEnd = Annotations.end(); - for (unsigned Line = 1; - InputFilePtr != InputFileEnd || AnnotationItr != AnnotationEnd; - ++Line) { - const unsigned char *InputFileLine = InputFilePtr; - - // Compute the previous and next line included by the filter. - if (NextLineInFilter < Line) - NextLineInFilter = FindInputLineInFilter(DumpInputFilter, Line, - AnnotationItr, AnnotationEnd); - assert(NextLineInFilter && "expected NextLineInFilter to be computed"); - if (NextLineInFilter == Line) - PrevLineInFilter = Line; - - // Elide this input line and its annotations if it's not within the - // context specified by -dump-input-context of an input line included by - // -dump-input-filter. However, in case the resulting ellipsis would occupy - // more lines than the input lines and annotations it elides, buffer the - // elided lines and annotations so we can print them instead. - raw_ostream *LineOS = &OS; - if ((!PrevLineInFilter || PrevLineInFilter + DumpInputContext < Line) && - (NextLineInFilter == UINT_MAX || - Line + DumpInputContext < NextLineInFilter)) - LineOS = &ElidedLinesOS; - else { - LineOS = &OS; - DumpEllipsisOrElidedLines(OS, ElidedLinesOS.str(), LabelWidth); - } - - // Print right-aligned line number. - WithColor(*LineOS, raw_ostream::BLACK, /*Bold=*/true, /*BF=*/false, - TheColorMode) - << format_decimal(Line, LabelWidth) << ": "; - - // For the case where -v and colors are enabled, find the annotations for - // good matches for expected patterns in order to highlight everything - // else in the line. There are no such annotations if -v is disabled. - std::vector FoundAndExpectedMatches; - if (Req.Verbose && TheColorMode == ColorMode::Enable) { - for (auto I = AnnotationItr; I != AnnotationEnd && I->InputLine == Line; - ++I) { - if (I->FoundAndExpectedMatch) - FoundAndExpectedMatches.push_back(*I); - } - } - - // Print numbered line with highlighting where there are no matches for - // expected patterns. - bool Newline = false; - { - WithColor COS(*LineOS, raw_ostream::SAVEDCOLOR, /*Bold=*/false, - /*BG=*/false, TheColorMode); - bool InMatch = false; - if (Req.Verbose) - COS.changeColor(raw_ostream::CYAN, true, true); - for (unsigned Col = 1; InputFilePtr != InputFileEnd && !Newline; ++Col) { - bool WasInMatch = InMatch; - InMatch = false; - for (auto M : FoundAndExpectedMatches) { - if (M.InputStartCol <= Col && Col < M.InputEndCol) { - InMatch = true; - break; - } - } - if (!WasInMatch && InMatch) - COS.resetColor(); - else if (WasInMatch && !InMatch) - COS.changeColor(raw_ostream::CYAN, true, true); - if (*InputFilePtr == '\n') - Newline = true; - else - COS << *InputFilePtr; - ++InputFilePtr; - } - } - *LineOS << '\n'; - unsigned InputLineWidth = InputFilePtr - InputFileLine - Newline; - - // Print any annotations. - while (AnnotationItr != AnnotationEnd && - AnnotationItr->InputLine == Line) { - WithColor COS(*LineOS, AnnotationItr->Marker.Color, /*Bold=*/true, - /*BG=*/false, TheColorMode); - // The two spaces below are where the ": " appears on input lines. - COS << left_justify(AnnotationItr->Label, LabelWidth) << " "; - unsigned Col; - for (Col = 1; Col < AnnotationItr->InputStartCol; ++Col) - COS << ' '; - COS << AnnotationItr->Marker.Lead; - // If InputEndCol=UINT_MAX, stop at InputLineWidth. - for (++Col; Col < AnnotationItr->InputEndCol && Col <= InputLineWidth; - ++Col) - COS << '~'; - const std::string &Note = AnnotationItr->Marker.Note; - if (!Note.empty()) { - // Put the note at the end of the input line. If we were to instead - // put the note right after the marker, subsequent annotations for the - // same input line might appear to mark this note instead of the input - // line. - for (; Col <= InputLineWidth; ++Col) - COS << ' '; - COS << ' ' << Note; - } - COS << '\n'; - ++AnnotationItr; - } - } - DumpEllipsisOrElidedLines(OS, ElidedLinesOS.str(), LabelWidth); - - OS << ">>>>>>\n"; -} - -int main(int argc, char **argv) { - // Enable use of ANSI color codes because FileCheck is using them to - // highlight text. - llvm::sys::Process::UseANSIEscapeCodes(true); - - InitLLVM X(argc, argv); - cl::ParseCommandLineOptions(argc, argv, /*Overview*/ "", /*Errs*/ nullptr, - "FILECHECK_OPTS"); - - // Select -dump-input* values. The -help documentation specifies the default - // value and which value to choose if an option is specified multiple times. - // In the latter case, the general rule of thumb is to choose the value that - // provides the most information. - DumpInputValue DumpInput = - DumpInputs.empty() - ? DumpInputFail - : *std::max_element(DumpInputs.begin(), DumpInputs.end()); - DumpInputFilterValue DumpInputFilter; - if (DumpInputFilters.empty()) - DumpInputFilter = DumpInput == DumpInputAlways ? DumpInputFilterAll - : DumpInputFilterError; - else - DumpInputFilter = - *std::max_element(DumpInputFilters.begin(), DumpInputFilters.end()); - unsigned DumpInputContext = DumpInputContexts.empty() - ? 5 - : *std::max_element(DumpInputContexts.begin(), - DumpInputContexts.end()); - - if (DumpInput == DumpInputHelp) { - DumpInputAnnotationHelp(outs()); - return 0; - } - if (CheckFilename.empty()) { - errs() << " not specified\n"; - return 2; - } - - FileCheckRequest Req; - for (StringRef Prefix : CheckPrefixes) - Req.CheckPrefixes.push_back(Prefix); - - for (StringRef Prefix : CommentPrefixes) - Req.CommentPrefixes.push_back(Prefix); - - for (StringRef CheckNot : ImplicitCheckNot) - Req.ImplicitCheckNot.push_back(CheckNot); - - bool GlobalDefineError = false; - for (StringRef G : GlobalDefines) { - size_t EqIdx = G.find('='); - if (EqIdx == std::string::npos) { - errs() << "Missing equal sign in command-line definition '-D" << G - << "'\n"; - GlobalDefineError = true; - continue; - } - if (EqIdx == 0) { - errs() << "Missing variable name in command-line definition '-D" << G - << "'\n"; - GlobalDefineError = true; - continue; - } - Req.GlobalDefines.push_back(G); - } - if (GlobalDefineError) - return 2; - - Req.AllowEmptyInput = AllowEmptyInput; - Req.AllowUnusedPrefixes = AllowUnusedPrefixes; - Req.EnableVarScope = EnableVarScope; - Req.AllowDeprecatedDagOverlap = AllowDeprecatedDagOverlap; - Req.Verbose = Verbose; - Req.VerboseVerbose = VerboseVerbose; - Req.NoCanonicalizeWhiteSpace = NoCanonicalizeWhiteSpace; - Req.MatchFullLines = MatchFullLines; - Req.IgnoreCase = IgnoreCase; - - if (VerboseVerbose) - Req.Verbose = true; - - FileCheck FC(Req); - if (!FC.ValidateCheckPrefixes()) - return 2; - - Regex PrefixRE = FC.buildCheckPrefixRegex(); - std::string REError; - if (!PrefixRE.isValid(REError)) { - errs() << "Unable to combine check-prefix strings into a prefix regular " - "expression! This is likely a bug in FileCheck's verification of " - "the check-prefix strings. Regular expression parsing failed " - "with the following error: " - << REError << "\n"; - return 2; - } - - SourceMgr SM; - - // Read the expected strings from the check file. - ErrorOr> CheckFileOrErr = - MemoryBuffer::getFileOrSTDIN(CheckFilename); - if (std::error_code EC = CheckFileOrErr.getError()) { - errs() << "Could not open check file '" << CheckFilename - << "': " << EC.message() << '\n'; - return 2; - } - MemoryBuffer &CheckFile = *CheckFileOrErr.get(); - - SmallString<4096> CheckFileBuffer; - StringRef CheckFileText = FC.CanonicalizeFile(CheckFile, CheckFileBuffer); - - unsigned CheckFileBufferID = - SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer( - CheckFileText, CheckFile.getBufferIdentifier()), - SMLoc()); - - std::pair ImpPatBufferIDRange; - if (FC.readCheckFile(SM, CheckFileText, PrefixRE, &ImpPatBufferIDRange)) - return 2; - - // Open the file to check and add it to SourceMgr. - ErrorOr> InputFileOrErr = - MemoryBuffer::getFileOrSTDIN(InputFilename); - if (InputFilename == "-") - InputFilename = ""; // Overwrite for improved diagnostic messages - if (std::error_code EC = InputFileOrErr.getError()) { - errs() << "Could not open input file '" << InputFilename - << "': " << EC.message() << '\n'; - return 2; - } - MemoryBuffer &InputFile = *InputFileOrErr.get(); - - if (InputFile.getBufferSize() == 0 && !AllowEmptyInput) { - errs() << "FileCheck error: '" << InputFilename << "' is empty.\n"; - DumpCommandLine(argc, argv); - return 2; - } - - SmallString<4096> InputFileBuffer; - StringRef InputFileText = FC.CanonicalizeFile(InputFile, InputFileBuffer); - - SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer( - InputFileText, InputFile.getBufferIdentifier()), - SMLoc()); - - std::vector Diags; - int ExitCode = FC.checkInput(SM, InputFileText, - DumpInput == DumpInputNever ? nullptr : &Diags) - ? EXIT_SUCCESS - : 1; - if (DumpInput == DumpInputAlways || - (ExitCode == 1 && DumpInput == DumpInputFail)) { - errs() << "\n" - << "Input file: " << InputFilename << "\n" - << "Check file: " << CheckFilename << "\n" - << "\n" - << "-dump-input=help explains the following input dump.\n" - << "\n"; - std::vector Annotations; - unsigned LabelWidth; - BuildInputAnnotations(SM, CheckFileBufferID, ImpPatBufferIDRange, Diags, - Annotations, LabelWidth); - DumpAnnotatedInput(errs(), Req, DumpInputFilter, DumpInputContext, - InputFileText, Annotations, LabelWidth); - } - - return ExitCode; -} diff --git a/utils/FileCheck-13.cpp b/utils/FileCheck-13.cpp deleted file mode 100644 index 4dcc4812502..00000000000 --- a/utils/FileCheck-13.cpp +++ /dev/null @@ -1,892 +0,0 @@ -//===- FileCheck.cpp - Check that File's Contents match what is expected --===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// FileCheck does a line-by line check of a file that validates whether it -// contains the expected content. This is useful for regression tests etc. -// -// This program exits with an exit status of 2 on error, exit status of 0 if -// the file matched the expected contents, and exit status of 1 if it did not -// contain the expected contents. -// -//===----------------------------------------------------------------------===// - -#include "llvm/FileCheck/FileCheck.h" -#include "llvm/Support/CommandLine.h" -#include "llvm/Support/InitLLVM.h" -#include "llvm/Support/Process.h" -#include "llvm/Support/WithColor.h" -#include "llvm/Support/raw_ostream.h" -#include -#include -using namespace llvm; - -static cl::extrahelp FileCheckOptsEnv( - "\nOptions are parsed from the environment variable FILECHECK_OPTS and\n" - "from the command line.\n"); - -static cl::opt - CheckFilename(cl::Positional, cl::desc(""), cl::Optional); - -static cl::opt - InputFilename("input-file", cl::desc("File to check (defaults to stdin)"), - cl::init("-"), cl::value_desc("filename")); - -static cl::list CheckPrefixes( - "check-prefix", - cl::desc("Prefix to use from check file (defaults to 'CHECK')")); -static cl::alias CheckPrefixesAlias( - "check-prefixes", cl::aliasopt(CheckPrefixes), cl::CommaSeparated, - cl::NotHidden, - cl::desc( - "Alias for -check-prefix permitting multiple comma separated values")); - -static cl::list CommentPrefixes( - "comment-prefixes", cl::CommaSeparated, cl::Hidden, - cl::desc("Comma-separated list of comment prefixes to use from check file\n" - "(defaults to 'COM,RUN'). Please avoid using this feature in\n" - "LLVM's LIT-based test suites, which should be easier to\n" - "maintain if they all follow a consistent comment style. This\n" - "feature is meant for non-LIT test suites using FileCheck.")); - -static cl::opt NoCanonicalizeWhiteSpace( - "strict-whitespace", - cl::desc("Do not treat all horizontal whitespace as equivalent")); - -static cl::opt IgnoreCase( - "ignore-case", - cl::desc("Use case-insensitive matching")); - -static cl::list ImplicitCheckNot( - "implicit-check-not", - cl::desc("Add an implicit negative check with this pattern to every\n" - "positive check. This can be used to ensure that no instances of\n" - "this pattern occur which are not matched by a positive pattern"), - cl::value_desc("pattern")); - -static cl::list - GlobalDefines("D", cl::AlwaysPrefix, - cl::desc("Define a variable to be used in capture patterns."), - cl::value_desc("VAR=VALUE")); - -static cl::opt AllowEmptyInput( - "allow-empty", cl::init(false), - cl::desc("Allow the input file to be empty. This is useful when making\n" - "checks that some error message does not occur, for example.")); - -static cl::opt AllowUnusedPrefixes( - "allow-unused-prefixes", cl::init(false), cl::ZeroOrMore, - cl::desc("Allow prefixes to be specified but not appear in the test.")); - -static cl::opt MatchFullLines( - "match-full-lines", cl::init(false), - cl::desc("Require all positive matches to cover an entire input line.\n" - "Allows leading and trailing whitespace if --strict-whitespace\n" - "is not also passed.")); - -static cl::opt EnableVarScope( - "enable-var-scope", cl::init(false), - cl::desc("Enables scope for regex variables. Variables with names that\n" - "do not start with '$' will be reset at the beginning of\n" - "each CHECK-LABEL block.")); - -static cl::opt AllowDeprecatedDagOverlap( - "allow-deprecated-dag-overlap", cl::init(false), - cl::desc("Enable overlapping among matches in a group of consecutive\n" - "CHECK-DAG directives. This option is deprecated and is only\n" - "provided for convenience as old tests are migrated to the new\n" - "non-overlapping CHECK-DAG implementation.\n")); - -static cl::opt Verbose( - "v", cl::init(false), cl::ZeroOrMore, - cl::desc("Print directive pattern matches, or add them to the input dump\n" - "if enabled.\n")); - -static cl::opt VerboseVerbose( - "vv", cl::init(false), cl::ZeroOrMore, - cl::desc("Print information helpful in diagnosing internal FileCheck\n" - "issues, or add it to the input dump if enabled. Implies\n" - "-v.\n")); - -// The order of DumpInputValue members affects their precedence, as documented -// for -dump-input below. -enum DumpInputValue { - DumpInputNever, - DumpInputFail, - DumpInputAlways, - DumpInputHelp -}; - -static cl::list DumpInputs( - "dump-input", - cl::desc("Dump input to stderr, adding annotations representing\n" - "currently enabled diagnostics. When there are multiple\n" - "occurrences of this option, the that appears earliest\n" - "in the list below has precedence. The default is 'fail'.\n"), - cl::value_desc("mode"), - cl::values(clEnumValN(DumpInputHelp, "help", "Explain input dump and quit"), - clEnumValN(DumpInputAlways, "always", "Always dump input"), - clEnumValN(DumpInputFail, "fail", "Dump input on failure"), - clEnumValN(DumpInputNever, "never", "Never dump input"))); - -// The order of DumpInputFilterValue members affects their precedence, as -// documented for -dump-input-filter below. -enum DumpInputFilterValue { - DumpInputFilterError, - DumpInputFilterAnnotation, - DumpInputFilterAnnotationFull, - DumpInputFilterAll -}; - -static cl::list DumpInputFilters( - "dump-input-filter", - cl::desc("In the dump requested by -dump-input, print only input lines of\n" - "kind plus any context specified by -dump-input-context.\n" - "When there are multiple occurrences of this option, the \n" - "that appears earliest in the list below has precedence. The\n" - "default is 'error' when -dump-input=fail, and it's 'all' when\n" - "-dump-input=always.\n"), - cl::values(clEnumValN(DumpInputFilterAll, "all", "All input lines"), - clEnumValN(DumpInputFilterAnnotationFull, "annotation-full", - "Input lines with annotations"), - clEnumValN(DumpInputFilterAnnotation, "annotation", - "Input lines with starting points of annotations"), - clEnumValN(DumpInputFilterError, "error", - "Input lines with starting points of error " - "annotations"))); - -static cl::list DumpInputContexts( - "dump-input-context", cl::value_desc("N"), - cl::desc("In the dump requested by -dump-input, print input lines\n" - "before and input lines after any lines specified by\n" - "-dump-input-filter. When there are multiple occurrences of\n" - "this option, the largest specified has precedence. The\n" - "default is 5.\n")); - -typedef cl::list::const_iterator prefix_iterator; - - - - - - - -static void DumpCommandLine(int argc, char **argv) { - errs() << "FileCheck command line: "; - for (int I = 0; I < argc; I++) - errs() << " " << argv[I]; - errs() << "\n"; -} - -struct MarkerStyle { - /// The starting char (before tildes) for marking the line. - char Lead; - /// What color to use for this annotation. - raw_ostream::Colors Color; - /// A note to follow the marker, or empty string if none. - std::string Note; - /// Does this marker indicate inclusion by -dump-input-filter=error? - bool FiltersAsError; - MarkerStyle() {} - MarkerStyle(char Lead, raw_ostream::Colors Color, - const std::string &Note = "", bool FiltersAsError = false) - : Lead(Lead), Color(Color), Note(Note), FiltersAsError(FiltersAsError) { - assert((!FiltersAsError || !Note.empty()) && - "expected error diagnostic to have note"); - } -}; - -static MarkerStyle GetMarker(FileCheckDiag::MatchType MatchTy) { - switch (MatchTy) { - case FileCheckDiag::MatchFoundAndExpected: - return MarkerStyle('^', raw_ostream::GREEN); - case FileCheckDiag::MatchFoundButExcluded: - return MarkerStyle('!', raw_ostream::RED, "error: no match expected", - /*FiltersAsError=*/true); - case FileCheckDiag::MatchFoundButWrongLine: - return MarkerStyle('!', raw_ostream::RED, "error: match on wrong line", - /*FiltersAsError=*/true); - case FileCheckDiag::MatchFoundButDiscarded: - return MarkerStyle('!', raw_ostream::CYAN, - "discard: overlaps earlier match"); - case FileCheckDiag::MatchFoundErrorNote: - // Note should always be overridden within the FileCheckDiag. - return MarkerStyle('!', raw_ostream::RED, - "error: unknown error after match", - /*FiltersAsError=*/true); - case FileCheckDiag::MatchNoneAndExcluded: - return MarkerStyle('X', raw_ostream::GREEN); - case FileCheckDiag::MatchNoneButExpected: - return MarkerStyle('X', raw_ostream::RED, "error: no match found", - /*FiltersAsError=*/true); - case FileCheckDiag::MatchNoneForInvalidPattern: - return MarkerStyle('X', raw_ostream::RED, - "error: match failed for invalid pattern", - /*FiltersAsError=*/true); - case FileCheckDiag::MatchFuzzy: - return MarkerStyle('?', raw_ostream::MAGENTA, "possible intended match", - /*FiltersAsError=*/true); - } - llvm_unreachable_internal("unexpected match type"); -} - -static void DumpInputAnnotationHelp(raw_ostream &OS) { - OS << "The following description was requested by -dump-input=help to\n" - << "explain the input dump printed by FileCheck.\n" - << "\n" - << "Related command-line options:\n" - << "\n" - << " - -dump-input= enables or disables the input dump\n" - << " - -dump-input-filter= filters the input lines\n" - << " - -dump-input-context= adjusts the context of filtered lines\n" - << " - -v and -vv add more annotations\n" - << " - -color forces colors to be enabled both in the dump and below\n" - << " - -help documents the above options in more detail\n" - << "\n" - << "These options can also be set via FILECHECK_OPTS. For example, for\n" - << "maximum debugging output on failures:\n" - << "\n" - << " $ FILECHECK_OPTS='-dump-input-filter=all -vv -color' ninja check\n" - << "\n" - << "Input dump annotation format:\n" - << "\n"; - - // Labels for input lines. - OS << " - "; - WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "L:"; - OS << " labels line number L of the input file\n" - << " An extra space is added after each input line to represent" - << " the\n" - << " newline character\n"; - - // Labels for annotation lines. - OS << " - "; - WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L"; - OS << " labels the only match result for either (1) a pattern of type T" - << " from\n" - << " line L of the check file if L is an integer or (2) the" - << " I-th implicit\n" - << " pattern if L is \"imp\" followed by an integer " - << "I (index origin one)\n"; - OS << " - "; - WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L'N"; - OS << " labels the Nth match result for such a pattern\n"; - - // Markers on annotation lines. - OS << " - "; - WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "^~~"; - OS << " marks good match (reported if -v)\n" - << " - "; - WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "!~~"; - OS << " marks bad match, such as:\n" - << " - CHECK-NEXT on same line as previous match (error)\n" - << " - CHECK-NOT found (error)\n" - << " - CHECK-DAG overlapping match (discarded, reported if " - << "-vv)\n" - << " - "; - WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "X~~"; - OS << " marks search range when no match is found, such as:\n" - << " - CHECK-NEXT not found (error)\n" - << " - CHECK-NOT not found (success, reported if -vv)\n" - << " - CHECK-DAG not found after discarded matches (error)\n" - << " - "; - WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "?"; - OS << " marks fuzzy match when no match is found\n"; - - // Elided lines. - OS << " - "; - WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "..."; - OS << " indicates elided input lines and annotations, as specified by\n" - << " -dump-input-filter and -dump-input-context\n"; - - // Colors. - OS << " - colors "; - WithColor(OS, raw_ostream::GREEN, true) << "success"; - OS << ", "; - WithColor(OS, raw_ostream::RED, true) << "error"; - OS << ", "; - WithColor(OS, raw_ostream::MAGENTA, true) << "fuzzy match"; - OS << ", "; - WithColor(OS, raw_ostream::CYAN, true, false) << "discarded match"; - OS << ", "; - WithColor(OS, raw_ostream::CYAN, true, true) << "unmatched input"; - OS << "\n"; -} - -/// An annotation for a single input line. -struct InputAnnotation { - /// The index of the match result across all checks - unsigned DiagIndex; - /// The label for this annotation. - std::string Label; - /// Is this the initial fragment of a diagnostic that has been broken across - /// multiple lines? - bool IsFirstLine; - /// What input line (one-origin indexing) this annotation marks. This might - /// be different from the starting line of the original diagnostic if - /// !IsFirstLine. - unsigned InputLine; - /// The column range (one-origin indexing, open end) in which to mark the - /// input line. If InputEndCol is UINT_MAX, treat it as the last column - /// before the newline. - unsigned InputStartCol, InputEndCol; - /// The marker to use. - MarkerStyle Marker; - /// Whether this annotation represents a good match for an expected pattern. - bool FoundAndExpectedMatch; -}; - -/// Get an abbreviation for the check type. -static std::string GetCheckTypeAbbreviation(Check::FileCheckType Ty) { - switch (Ty) { - case Check::CheckPlain: - if (Ty.getCount() > 1) - return "count"; - return "check"; - case Check::CheckNext: - return "next"; - case Check::CheckSame: - return "same"; - case Check::CheckNot: - return "not"; - case Check::CheckDAG: - return "dag"; - case Check::CheckLabel: - return "label"; - case Check::CheckEmpty: - return "empty"; - case Check::CheckComment: - return "com"; - case Check::CheckEOF: - return "eof"; - case Check::CheckBadNot: - return "bad-not"; - case Check::CheckBadCount: - return "bad-count"; - case Check::CheckNone: - llvm_unreachable("invalid FileCheckType"); - } - llvm_unreachable("unknown FileCheckType"); -} - -static void -BuildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID, - const std::pair &ImpPatBufferIDRange, - const std::vector &Diags, - std::vector &Annotations, - unsigned &LabelWidth) { - struct CompareSMLoc { - bool operator()(const SMLoc &LHS, const SMLoc &RHS) const { - return LHS.getPointer() < RHS.getPointer(); - } - }; - // How many diagnostics does each pattern have? - std::map DiagCountPerPattern; - for (auto Diag : Diags) - ++DiagCountPerPattern[Diag.CheckLoc]; - // How many diagnostics have we seen so far per pattern? - std::map DiagIndexPerPattern; - // How many total diagnostics have we seen so far? - unsigned DiagIndex = 0; - // What's the widest label? - LabelWidth = 0; - for (auto DiagItr = Diags.begin(), DiagEnd = Diags.end(); DiagItr != DiagEnd; - ++DiagItr) { - InputAnnotation A; - A.DiagIndex = DiagIndex++; - - // Build label, which uniquely identifies this check result. - unsigned CheckBufferID = SM.FindBufferContainingLoc(DiagItr->CheckLoc); - auto CheckLineAndCol = - SM.getLineAndColumn(DiagItr->CheckLoc, CheckBufferID); - llvm::raw_string_ostream Label(A.Label); - Label << GetCheckTypeAbbreviation(DiagItr->CheckTy) << ":"; - if (CheckBufferID == CheckFileBufferID) - Label << CheckLineAndCol.first; - else if (ImpPatBufferIDRange.first <= CheckBufferID && - CheckBufferID < ImpPatBufferIDRange.second) - Label << "imp" << (CheckBufferID - ImpPatBufferIDRange.first + 1); - else - llvm_unreachable("expected diagnostic's check location to be either in " - "the check file or for an implicit pattern"); - if (DiagCountPerPattern[DiagItr->CheckLoc] > 1) - Label << "'" << DiagIndexPerPattern[DiagItr->CheckLoc]++; - Label.flush(); - LabelWidth = std::max((std::string::size_type)LabelWidth, A.Label.size()); - - A.Marker = GetMarker(DiagItr->MatchTy); - if (!DiagItr->Note.empty()) { - A.Marker.Note = DiagItr->Note; - // It's less confusing if notes that don't actually have ranges don't have - // markers. For example, a marker for 'with "VAR" equal to "5"' would - // seem to indicate where "VAR" matches, but the location we actually have - // for the marker simply points to the start of the match/search range for - // the full pattern of which the substitution is potentially just one - // component. - if (DiagItr->InputStartLine == DiagItr->InputEndLine && - DiagItr->InputStartCol == DiagItr->InputEndCol) - A.Marker.Lead = ' '; - } - if (DiagItr->MatchTy == FileCheckDiag::MatchFoundErrorNote) { - assert(!DiagItr->Note.empty() && - "expected custom note for MatchFoundErrorNote"); - A.Marker.Note = "error: " + A.Marker.Note; - } - A.FoundAndExpectedMatch = - DiagItr->MatchTy == FileCheckDiag::MatchFoundAndExpected; - - // Compute the mark location, and break annotation into multiple - // annotations if it spans multiple lines. - A.IsFirstLine = true; - A.InputLine = DiagItr->InputStartLine; - A.InputStartCol = DiagItr->InputStartCol; - if (DiagItr->InputStartLine == DiagItr->InputEndLine) { - // Sometimes ranges are empty in order to indicate a specific point, but - // that would mean nothing would be marked, so adjust the range to - // include the following character. - A.InputEndCol = - std::max(DiagItr->InputStartCol + 1, DiagItr->InputEndCol); - Annotations.push_back(A); - } else { - assert(DiagItr->InputStartLine < DiagItr->InputEndLine && - "expected input range not to be inverted"); - A.InputEndCol = UINT_MAX; - Annotations.push_back(A); - for (unsigned L = DiagItr->InputStartLine + 1, E = DiagItr->InputEndLine; - L <= E; ++L) { - // If a range ends before the first column on a line, then it has no - // characters on that line, so there's nothing to render. - if (DiagItr->InputEndCol == 1 && L == E) - break; - InputAnnotation B; - B.DiagIndex = A.DiagIndex; - B.Label = A.Label; - B.IsFirstLine = false; - B.InputLine = L; - B.Marker = A.Marker; - B.Marker.Lead = '~'; - B.Marker.Note = ""; - B.InputStartCol = 1; - if (L != E) - B.InputEndCol = UINT_MAX; - else - B.InputEndCol = DiagItr->InputEndCol; - B.FoundAndExpectedMatch = A.FoundAndExpectedMatch; - Annotations.push_back(B); - } - } - } -} - -static unsigned FindInputLineInFilter( - DumpInputFilterValue DumpInputFilter, unsigned CurInputLine, - const std::vector::iterator &AnnotationBeg, - const std::vector::iterator &AnnotationEnd) { - if (DumpInputFilter == DumpInputFilterAll) - return CurInputLine; - for (auto AnnotationItr = AnnotationBeg; AnnotationItr != AnnotationEnd; - ++AnnotationItr) { - switch (DumpInputFilter) { - case DumpInputFilterAll: - llvm_unreachable("unexpected DumpInputFilterAll"); - break; - case DumpInputFilterAnnotationFull: - return AnnotationItr->InputLine; - case DumpInputFilterAnnotation: - if (AnnotationItr->IsFirstLine) - return AnnotationItr->InputLine; - break; - case DumpInputFilterError: - if (AnnotationItr->IsFirstLine && AnnotationItr->Marker.FiltersAsError) - return AnnotationItr->InputLine; - break; - } - } - return UINT_MAX; -} - -/// To OS, print a vertical ellipsis (right-justified at LabelWidth) if it would -/// occupy less lines than ElidedLines, but print ElidedLines otherwise. Either -/// way, clear ElidedLines. Thus, if ElidedLines is empty, do nothing. -static void DumpEllipsisOrElidedLines(raw_ostream &OS, std::string &ElidedLines, - unsigned LabelWidth) { - if (ElidedLines.empty()) - return; - unsigned EllipsisLines = 3; - if (EllipsisLines < StringRef(ElidedLines).count('\n')) { - for (unsigned i = 0; i < EllipsisLines; ++i) { - WithColor(OS, raw_ostream::BLACK, /*Bold=*/true) - << right_justify(".", LabelWidth); - OS << '\n'; - } - } else - OS << ElidedLines; - ElidedLines.clear(); -} - -static void DumpAnnotatedInput(raw_ostream &OS, const FileCheckRequest &Req, - DumpInputFilterValue DumpInputFilter, - unsigned DumpInputContext, - StringRef InputFileText, - std::vector &Annotations, - unsigned LabelWidth) { - OS << "Input was:\n<<<<<<\n"; - - // Sort annotations. - llvm::sort(Annotations, - [](const InputAnnotation &A, const InputAnnotation &B) { - // 1. Sort annotations in the order of the input lines. - // - // This makes it easier to find relevant annotations while - // iterating input lines in the implementation below. FileCheck - // does not always produce diagnostics in the order of input - // lines due to, for example, CHECK-DAG and CHECK-NOT. - if (A.InputLine != B.InputLine) - return A.InputLine < B.InputLine; - // 2. Sort annotations in the temporal order FileCheck produced - // their associated diagnostics. - // - // This sort offers several benefits: - // - // A. On a single input line, the order of annotations reflects - // the FileCheck logic for processing directives/patterns. - // This can be helpful in understanding cases in which the - // order of the associated directives/patterns in the check - // file or on the command line either (i) does not match the - // temporal order in which FileCheck looks for matches for the - // directives/patterns (due to, for example, CHECK-LABEL, - // CHECK-NOT, or `--implicit-check-not`) or (ii) does match - // that order but does not match the order of those - // diagnostics along an input line (due to, for example, - // CHECK-DAG). - // - // On the other hand, because our presentation format presents - // input lines in order, there's no clear way to offer the - // same benefit across input lines. For consistency, it might - // then seem worthwhile to have annotations on a single line - // also sorted in input order (that is, by input column). - // However, in practice, this appears to be more confusing - // than helpful. Perhaps it's intuitive to expect annotations - // to be listed in the temporal order in which they were - // produced except in cases the presentation format obviously - // and inherently cannot support it (that is, across input - // lines). - // - // B. When diagnostics' annotations are split among multiple - // input lines, the user must track them from one input line - // to the next. One property of the sort chosen here is that - // it facilitates the user in this regard by ensuring the - // following: when comparing any two input lines, a - // diagnostic's annotations are sorted in the same position - // relative to all other diagnostics' annotations. - return A.DiagIndex < B.DiagIndex; - }); - - // Compute the width of the label column. - const unsigned char *InputFilePtr = InputFileText.bytes_begin(), - *InputFileEnd = InputFileText.bytes_end(); - unsigned LineCount = InputFileText.count('\n'); - if (InputFileEnd[-1] != '\n') - ++LineCount; - unsigned LineNoWidth = std::log10(LineCount) + 1; - // +3 below adds spaces (1) to the left of the (right-aligned) line numbers - // on input lines and (2) to the right of the (left-aligned) labels on - // annotation lines so that input lines and annotation lines are more - // visually distinct. For example, the spaces on the annotation lines ensure - // that input line numbers and check directive line numbers never align - // horizontally. Those line numbers might not even be for the same file. - // One space would be enough to achieve that, but more makes it even easier - // to see. - LabelWidth = std::max(LabelWidth, LineNoWidth) + 3; - - // Print annotated input lines. - unsigned PrevLineInFilter = 0; // 0 means none so far - unsigned NextLineInFilter = 0; // 0 means uncomputed, UINT_MAX means none - std::string ElidedLines; - raw_string_ostream ElidedLinesOS(ElidedLines); - ColorMode TheColorMode = - WithColor(OS).colorsEnabled() ? ColorMode::Enable : ColorMode::Disable; - if (TheColorMode == ColorMode::Enable) - ElidedLinesOS.enable_colors(true); - auto AnnotationItr = Annotations.begin(), AnnotationEnd = Annotations.end(); - for (unsigned Line = 1; - InputFilePtr != InputFileEnd || AnnotationItr != AnnotationEnd; - ++Line) { - const unsigned char *InputFileLine = InputFilePtr; - - // Compute the previous and next line included by the filter. - if (NextLineInFilter < Line) - NextLineInFilter = FindInputLineInFilter(DumpInputFilter, Line, - AnnotationItr, AnnotationEnd); - assert(NextLineInFilter && "expected NextLineInFilter to be computed"); - if (NextLineInFilter == Line) - PrevLineInFilter = Line; - - // Elide this input line and its annotations if it's not within the - // context specified by -dump-input-context of an input line included by - // -dump-input-filter. However, in case the resulting ellipsis would occupy - // more lines than the input lines and annotations it elides, buffer the - // elided lines and annotations so we can print them instead. - raw_ostream *LineOS = &OS; - if ((!PrevLineInFilter || PrevLineInFilter + DumpInputContext < Line) && - (NextLineInFilter == UINT_MAX || - Line + DumpInputContext < NextLineInFilter)) - LineOS = &ElidedLinesOS; - else { - LineOS = &OS; - DumpEllipsisOrElidedLines(OS, ElidedLinesOS.str(), LabelWidth); - } - - // Print right-aligned line number. - WithColor(*LineOS, raw_ostream::BLACK, /*Bold=*/true, /*BF=*/false, - TheColorMode) - << format_decimal(Line, LabelWidth) << ": "; - - // For the case where -v and colors are enabled, find the annotations for - // good matches for expected patterns in order to highlight everything - // else in the line. There are no such annotations if -v is disabled. - std::vector FoundAndExpectedMatches; - if (Req.Verbose && TheColorMode == ColorMode::Enable) { - for (auto I = AnnotationItr; I != AnnotationEnd && I->InputLine == Line; - ++I) { - if (I->FoundAndExpectedMatch) - FoundAndExpectedMatches.push_back(*I); - } - } - - // Print numbered line with highlighting where there are no matches for - // expected patterns. - bool Newline = false; - { - WithColor COS(*LineOS, raw_ostream::SAVEDCOLOR, /*Bold=*/false, - /*BG=*/false, TheColorMode); - bool InMatch = false; - if (Req.Verbose) - COS.changeColor(raw_ostream::CYAN, true, true); - for (unsigned Col = 1; InputFilePtr != InputFileEnd && !Newline; ++Col) { - bool WasInMatch = InMatch; - InMatch = false; - for (auto M : FoundAndExpectedMatches) { - if (M.InputStartCol <= Col && Col < M.InputEndCol) { - InMatch = true; - break; - } - } - if (!WasInMatch && InMatch) - COS.resetColor(); - else if (WasInMatch && !InMatch) - COS.changeColor(raw_ostream::CYAN, true, true); - if (*InputFilePtr == '\n') { - Newline = true; - COS << ' '; - } else - COS << *InputFilePtr; - ++InputFilePtr; - } - } - *LineOS << '\n'; - unsigned InputLineWidth = InputFilePtr - InputFileLine; - - // Print any annotations. - while (AnnotationItr != AnnotationEnd && - AnnotationItr->InputLine == Line) { - WithColor COS(*LineOS, AnnotationItr->Marker.Color, /*Bold=*/true, - /*BG=*/false, TheColorMode); - // The two spaces below are where the ": " appears on input lines. - COS << left_justify(AnnotationItr->Label, LabelWidth) << " "; - unsigned Col; - for (Col = 1; Col < AnnotationItr->InputStartCol; ++Col) - COS << ' '; - COS << AnnotationItr->Marker.Lead; - // If InputEndCol=UINT_MAX, stop at InputLineWidth. - for (++Col; Col < AnnotationItr->InputEndCol && Col <= InputLineWidth; - ++Col) - COS << '~'; - const std::string &Note = AnnotationItr->Marker.Note; - if (!Note.empty()) { - // Put the note at the end of the input line. If we were to instead - // put the note right after the marker, subsequent annotations for the - // same input line might appear to mark this note instead of the input - // line. - for (; Col <= InputLineWidth; ++Col) - COS << ' '; - COS << ' ' << Note; - } - COS << '\n'; - ++AnnotationItr; - } - } - DumpEllipsisOrElidedLines(OS, ElidedLinesOS.str(), LabelWidth); - - OS << ">>>>>>\n"; -} - -int main(int argc, char **argv) { - // Enable use of ANSI color codes because FileCheck is using them to - // highlight text. - llvm::sys::Process::UseANSIEscapeCodes(true); - - InitLLVM X(argc, argv); - cl::ParseCommandLineOptions(argc, argv, /*Overview*/ "", /*Errs*/ nullptr, - "FILECHECK_OPTS"); - - // Select -dump-input* values. The -help documentation specifies the default - // value and which value to choose if an option is specified multiple times. - // In the latter case, the general rule of thumb is to choose the value that - // provides the most information. - DumpInputValue DumpInput = - DumpInputs.empty() - ? DumpInputFail - : *std::max_element(DumpInputs.begin(), DumpInputs.end()); - DumpInputFilterValue DumpInputFilter; - if (DumpInputFilters.empty()) - DumpInputFilter = DumpInput == DumpInputAlways ? DumpInputFilterAll - : DumpInputFilterError; - else - DumpInputFilter = - *std::max_element(DumpInputFilters.begin(), DumpInputFilters.end()); - unsigned DumpInputContext = DumpInputContexts.empty() - ? 5 - : *std::max_element(DumpInputContexts.begin(), - DumpInputContexts.end()); - - if (DumpInput == DumpInputHelp) { - DumpInputAnnotationHelp(outs()); - return 0; - } - if (CheckFilename.empty()) { - errs() << " not specified\n"; - return 2; - } - - FileCheckRequest Req; - append_range(Req.CheckPrefixes, CheckPrefixes); - - append_range(Req.CommentPrefixes, CommentPrefixes); - - append_range(Req.ImplicitCheckNot, ImplicitCheckNot); - - bool GlobalDefineError = false; - for (StringRef G : GlobalDefines) { - size_t EqIdx = G.find('='); - if (EqIdx == std::string::npos) { - errs() << "Missing equal sign in command-line definition '-D" << G - << "'\n"; - GlobalDefineError = true; - continue; - } - if (EqIdx == 0) { - errs() << "Missing variable name in command-line definition '-D" << G - << "'\n"; - GlobalDefineError = true; - continue; - } - Req.GlobalDefines.push_back(G); - } - if (GlobalDefineError) - return 2; - - Req.AllowEmptyInput = AllowEmptyInput; - Req.AllowUnusedPrefixes = AllowUnusedPrefixes; - Req.EnableVarScope = EnableVarScope; - Req.AllowDeprecatedDagOverlap = AllowDeprecatedDagOverlap; - Req.Verbose = Verbose; - Req.VerboseVerbose = VerboseVerbose; - Req.NoCanonicalizeWhiteSpace = NoCanonicalizeWhiteSpace; - Req.MatchFullLines = MatchFullLines; - Req.IgnoreCase = IgnoreCase; - - if (VerboseVerbose) - Req.Verbose = true; - - FileCheck FC(Req); - if (!FC.ValidateCheckPrefixes()) - return 2; - - Regex PrefixRE = FC.buildCheckPrefixRegex(); - std::string REError; - if (!PrefixRE.isValid(REError)) { - errs() << "Unable to combine check-prefix strings into a prefix regular " - "expression! This is likely a bug in FileCheck's verification of " - "the check-prefix strings. Regular expression parsing failed " - "with the following error: " - << REError << "\n"; - return 2; - } - - SourceMgr SM; - - // Read the expected strings from the check file. - ErrorOr> CheckFileOrErr = - MemoryBuffer::getFileOrSTDIN(CheckFilename, /*IsText=*/true); - if (std::error_code EC = CheckFileOrErr.getError()) { - errs() << "Could not open check file '" << CheckFilename - << "': " << EC.message() << '\n'; - return 2; - } - MemoryBuffer &CheckFile = *CheckFileOrErr.get(); - - SmallString<4096> CheckFileBuffer; - StringRef CheckFileText = FC.CanonicalizeFile(CheckFile, CheckFileBuffer); - - unsigned CheckFileBufferID = - SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer( - CheckFileText, CheckFile.getBufferIdentifier()), - SMLoc()); - - std::pair ImpPatBufferIDRange; - if (FC.readCheckFile(SM, CheckFileText, PrefixRE, &ImpPatBufferIDRange)) - return 2; - - // Open the file to check and add it to SourceMgr. - ErrorOr> InputFileOrErr = - MemoryBuffer::getFileOrSTDIN(InputFilename, /*IsText=*/true); - if (InputFilename == "-") - InputFilename = ""; // Overwrite for improved diagnostic messages - if (std::error_code EC = InputFileOrErr.getError()) { - errs() << "Could not open input file '" << InputFilename - << "': " << EC.message() << '\n'; - return 2; - } - MemoryBuffer &InputFile = *InputFileOrErr.get(); - - if (InputFile.getBufferSize() == 0 && !AllowEmptyInput) { - errs() << "FileCheck error: '" << InputFilename << "' is empty.\n"; - DumpCommandLine(argc, argv); - return 2; - } - - SmallString<4096> InputFileBuffer; - StringRef InputFileText = FC.CanonicalizeFile(InputFile, InputFileBuffer); - - SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer( - InputFileText, InputFile.getBufferIdentifier()), - SMLoc()); - - std::vector Diags; - int ExitCode = FC.checkInput(SM, InputFileText, - DumpInput == DumpInputNever ? nullptr : &Diags) - ? EXIT_SUCCESS - : 1; - if (DumpInput == DumpInputAlways || - (ExitCode == 1 && DumpInput == DumpInputFail)) { - errs() << "\n" - << "Input file: " << InputFilename << "\n" - << "Check file: " << CheckFilename << "\n" - << "\n" - << "-dump-input=help explains the following input dump.\n" - << "\n"; - std::vector Annotations; - unsigned LabelWidth; - BuildInputAnnotations(SM, CheckFileBufferID, ImpPatBufferIDRange, Diags, - Annotations, LabelWidth); - DumpAnnotatedInput(errs(), Req, DumpInputFilter, DumpInputContext, - InputFileText, Annotations, LabelWidth); - } - - return ExitCode; -} diff --git a/utils/FileCheck-14.cpp b/utils/FileCheck-14.cpp deleted file mode 100644 index 6742853c9b6..00000000000 --- a/utils/FileCheck-14.cpp +++ /dev/null @@ -1,891 +0,0 @@ -//===- FileCheck.cpp - Check that File's Contents match what is expected --===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// FileCheck does a line-by line check of a file that validates whether it -// contains the expected content. This is useful for regression tests etc. -// -// This program exits with an exit status of 2 on error, exit status of 0 if -// the file matched the expected contents, and exit status of 1 if it did not -// contain the expected contents. -// -//===----------------------------------------------------------------------===// - -#include "llvm/FileCheck/FileCheck.h" -#include "llvm/Support/CommandLine.h" -#include "llvm/Support/InitLLVM.h" -#include "llvm/Support/Process.h" -#include "llvm/Support/WithColor.h" -#include "llvm/Support/raw_ostream.h" -#include -#include -using namespace llvm; - -static cl::extrahelp FileCheckOptsEnv( - "\nOptions are parsed from the environment variable FILECHECK_OPTS and\n" - "from the command line.\n"); - -static cl::opt - CheckFilename(cl::Positional, cl::desc(""), cl::Optional); - -static cl::opt - InputFilename("input-file", cl::desc("File to check (defaults to stdin)"), - cl::init("-"), cl::value_desc("filename")); - -static cl::list CheckPrefixes( - "check-prefix", - cl::desc("Prefix to use from check file (defaults to 'CHECK')")); -static cl::alias CheckPrefixesAlias( - "check-prefixes", cl::aliasopt(CheckPrefixes), cl::CommaSeparated, - cl::NotHidden, - cl::desc( - "Alias for -check-prefix permitting multiple comma separated values")); - -static cl::list CommentPrefixes( - "comment-prefixes", cl::CommaSeparated, cl::Hidden, - cl::desc("Comma-separated list of comment prefixes to use from check file\n" - "(defaults to 'COM,RUN'). Please avoid using this feature in\n" - "LLVM's LIT-based test suites, which should be easier to\n" - "maintain if they all follow a consistent comment style. This\n" - "feature is meant for non-LIT test suites using FileCheck.")); - -static cl::opt NoCanonicalizeWhiteSpace( - "strict-whitespace", - cl::desc("Do not treat all horizontal whitespace as equivalent")); - -static cl::opt IgnoreCase( - "ignore-case", - cl::desc("Use case-insensitive matching")); - -static cl::list ImplicitCheckNot( - "implicit-check-not", - cl::desc("Add an implicit negative check with this pattern to every\n" - "positive check. This can be used to ensure that no instances of\n" - "this pattern occur which are not matched by a positive pattern"), - cl::value_desc("pattern")); - -static cl::list - GlobalDefines("D", cl::AlwaysPrefix, - cl::desc("Define a variable to be used in capture patterns."), - cl::value_desc("VAR=VALUE")); - -static cl::opt AllowEmptyInput( - "allow-empty", cl::init(false), - cl::desc("Allow the input file to be empty. This is useful when making\n" - "checks that some error message does not occur, for example.")); - -static cl::opt AllowUnusedPrefixes( - "allow-unused-prefixes", cl::init(false), cl::ZeroOrMore, - cl::desc("Allow prefixes to be specified but not appear in the test.")); - -static cl::opt MatchFullLines( - "match-full-lines", cl::init(false), - cl::desc("Require all positive matches to cover an entire input line.\n" - "Allows leading and trailing whitespace if --strict-whitespace\n" - "is not also passed.")); - -static cl::opt EnableVarScope( - "enable-var-scope", cl::init(false), - cl::desc("Enables scope for regex variables. Variables with names that\n" - "do not start with '$' will be reset at the beginning of\n" - "each CHECK-LABEL block.")); - -static cl::opt AllowDeprecatedDagOverlap( - "allow-deprecated-dag-overlap", cl::init(false), - cl::desc("Enable overlapping among matches in a group of consecutive\n" - "CHECK-DAG directives. This option is deprecated and is only\n" - "provided for convenience as old tests are migrated to the new\n" - "non-overlapping CHECK-DAG implementation.\n")); - -static cl::opt Verbose( - "v", cl::init(false), cl::ZeroOrMore, - cl::desc("Print directive pattern matches, or add them to the input dump\n" - "if enabled.\n")); - -static cl::opt VerboseVerbose( - "vv", cl::init(false), cl::ZeroOrMore, - cl::desc("Print information helpful in diagnosing internal FileCheck\n" - "issues, or add it to the input dump if enabled. Implies\n" - "-v.\n")); - -// The order of DumpInputValue members affects their precedence, as documented -// for -dump-input below. -enum DumpInputValue { - DumpInputNever, - DumpInputFail, - DumpInputAlways, - DumpInputHelp -}; - -static cl::list DumpInputs( - "dump-input", - cl::desc("Dump input to stderr, adding annotations representing\n" - "currently enabled diagnostics. When there are multiple\n" - "occurrences of this option, the that appears earliest\n" - "in the list below has precedence. The default is 'fail'.\n"), - cl::value_desc("mode"), - cl::values(clEnumValN(DumpInputHelp, "help", "Explain input dump and quit"), - clEnumValN(DumpInputAlways, "always", "Always dump input"), - clEnumValN(DumpInputFail, "fail", "Dump input on failure"), - clEnumValN(DumpInputNever, "never", "Never dump input"))); - -// The order of DumpInputFilterValue members affects their precedence, as -// documented for -dump-input-filter below. -enum DumpInputFilterValue { - DumpInputFilterError, - DumpInputFilterAnnotation, - DumpInputFilterAnnotationFull, - DumpInputFilterAll -}; - -static cl::list DumpInputFilters( - "dump-input-filter", - cl::desc("In the dump requested by -dump-input, print only input lines of\n" - "kind plus any context specified by -dump-input-context.\n" - "When there are multiple occurrences of this option, the \n" - "that appears earliest in the list below has precedence. The\n" - "default is 'error' when -dump-input=fail, and it's 'all' when\n" - "-dump-input=always.\n"), - cl::values(clEnumValN(DumpInputFilterAll, "all", "All input lines"), - clEnumValN(DumpInputFilterAnnotationFull, "annotation-full", - "Input lines with annotations"), - clEnumValN(DumpInputFilterAnnotation, "annotation", - "Input lines with starting points of annotations"), - clEnumValN(DumpInputFilterError, "error", - "Input lines with starting points of error " - "annotations"))); - -static cl::list DumpInputContexts( - "dump-input-context", cl::value_desc("N"), - cl::desc("In the dump requested by -dump-input, print input lines\n" - "before and input lines after any lines specified by\n" - "-dump-input-filter. When there are multiple occurrences of\n" - "this option, the largest specified has precedence. The\n" - "default is 5.\n")); - -typedef cl::list::const_iterator prefix_iterator; - - - - - - - -static void DumpCommandLine(int argc, char **argv) { - errs() << "FileCheck command line: "; - for (int I = 0; I < argc; I++) - errs() << " " << argv[I]; - errs() << "\n"; -} - -struct MarkerStyle { - /// The starting char (before tildes) for marking the line. - char Lead; - /// What color to use for this annotation. - raw_ostream::Colors Color; - /// A note to follow the marker, or empty string if none. - std::string Note; - /// Does this marker indicate inclusion by -dump-input-filter=error? - bool FiltersAsError; - MarkerStyle() {} - MarkerStyle(char Lead, raw_ostream::Colors Color, - const std::string &Note = "", bool FiltersAsError = false) - : Lead(Lead), Color(Color), Note(Note), FiltersAsError(FiltersAsError) { - assert((!FiltersAsError || !Note.empty()) && - "expected error diagnostic to have note"); - } -}; - -static MarkerStyle GetMarker(FileCheckDiag::MatchType MatchTy) { - switch (MatchTy) { - case FileCheckDiag::MatchFoundAndExpected: - return MarkerStyle('^', raw_ostream::GREEN); - case FileCheckDiag::MatchFoundButExcluded: - return MarkerStyle('!', raw_ostream::RED, "error: no match expected", - /*FiltersAsError=*/true); - case FileCheckDiag::MatchFoundButWrongLine: - return MarkerStyle('!', raw_ostream::RED, "error: match on wrong line", - /*FiltersAsError=*/true); - case FileCheckDiag::MatchFoundButDiscarded: - return MarkerStyle('!', raw_ostream::CYAN, - "discard: overlaps earlier match"); - case FileCheckDiag::MatchFoundErrorNote: - // Note should always be overridden within the FileCheckDiag. - return MarkerStyle('!', raw_ostream::RED, - "error: unknown error after match", - /*FiltersAsError=*/true); - case FileCheckDiag::MatchNoneAndExcluded: - return MarkerStyle('X', raw_ostream::GREEN); - case FileCheckDiag::MatchNoneButExpected: - return MarkerStyle('X', raw_ostream::RED, "error: no match found", - /*FiltersAsError=*/true); - case FileCheckDiag::MatchNoneForInvalidPattern: - return MarkerStyle('X', raw_ostream::RED, - "error: match failed for invalid pattern", - /*FiltersAsError=*/true); - case FileCheckDiag::MatchFuzzy: - return MarkerStyle('?', raw_ostream::MAGENTA, "possible intended match", - /*FiltersAsError=*/true); - } - llvm_unreachable_internal("unexpected match type"); -} - -static void DumpInputAnnotationHelp(raw_ostream &OS) { - OS << "The following description was requested by -dump-input=help to\n" - << "explain the input dump printed by FileCheck.\n" - << "\n" - << "Related command-line options:\n" - << "\n" - << " - -dump-input= enables or disables the input dump\n" - << " - -dump-input-filter= filters the input lines\n" - << " - -dump-input-context= adjusts the context of filtered lines\n" - << " - -v and -vv add more annotations\n" - << " - -color forces colors to be enabled both in the dump and below\n" - << " - -help documents the above options in more detail\n" - << "\n" - << "These options can also be set via FILECHECK_OPTS. For example, for\n" - << "maximum debugging output on failures:\n" - << "\n" - << " $ FILECHECK_OPTS='-dump-input-filter=all -vv -color' ninja check\n" - << "\n" - << "Input dump annotation format:\n" - << "\n"; - - // Labels for input lines. - OS << " - "; - WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "L:"; - OS << " labels line number L of the input file\n" - << " An extra space is added after each input line to represent" - << " the\n" - << " newline character\n"; - - // Labels for annotation lines. - OS << " - "; - WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L"; - OS << " labels the only match result for either (1) a pattern of type T" - << " from\n" - << " line L of the check file if L is an integer or (2) the" - << " I-th implicit\n" - << " pattern if L is \"imp\" followed by an integer " - << "I (index origin one)\n"; - OS << " - "; - WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L'N"; - OS << " labels the Nth match result for such a pattern\n"; - - // Markers on annotation lines. - OS << " - "; - WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "^~~"; - OS << " marks good match (reported if -v)\n" - << " - "; - WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "!~~"; - OS << " marks bad match, such as:\n" - << " - CHECK-NEXT on same line as previous match (error)\n" - << " - CHECK-NOT found (error)\n" - << " - CHECK-DAG overlapping match (discarded, reported if " - << "-vv)\n" - << " - "; - WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "X~~"; - OS << " marks search range when no match is found, such as:\n" - << " - CHECK-NEXT not found (error)\n" - << " - CHECK-NOT not found (success, reported if -vv)\n" - << " - CHECK-DAG not found after discarded matches (error)\n" - << " - "; - WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "?"; - OS << " marks fuzzy match when no match is found\n"; - - // Elided lines. - OS << " - "; - WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "..."; - OS << " indicates elided input lines and annotations, as specified by\n" - << " -dump-input-filter and -dump-input-context\n"; - - // Colors. - OS << " - colors "; - WithColor(OS, raw_ostream::GREEN, true) << "success"; - OS << ", "; - WithColor(OS, raw_ostream::RED, true) << "error"; - OS << ", "; - WithColor(OS, raw_ostream::MAGENTA, true) << "fuzzy match"; - OS << ", "; - WithColor(OS, raw_ostream::CYAN, true, false) << "discarded match"; - OS << ", "; - WithColor(OS, raw_ostream::CYAN, true, true) << "unmatched input"; - OS << "\n"; -} - -/// An annotation for a single input line. -struct InputAnnotation { - /// The index of the match result across all checks - unsigned DiagIndex; - /// The label for this annotation. - std::string Label; - /// Is this the initial fragment of a diagnostic that has been broken across - /// multiple lines? - bool IsFirstLine; - /// What input line (one-origin indexing) this annotation marks. This might - /// be different from the starting line of the original diagnostic if - /// !IsFirstLine. - unsigned InputLine; - /// The column range (one-origin indexing, open end) in which to mark the - /// input line. If InputEndCol is UINT_MAX, treat it as the last column - /// before the newline. - unsigned InputStartCol, InputEndCol; - /// The marker to use. - MarkerStyle Marker; - /// Whether this annotation represents a good match for an expected pattern. - bool FoundAndExpectedMatch; -}; - -/// Get an abbreviation for the check type. -static std::string GetCheckTypeAbbreviation(Check::FileCheckType Ty) { - switch (Ty) { - case Check::CheckPlain: - if (Ty.getCount() > 1) - return "count"; - return "check"; - case Check::CheckNext: - return "next"; - case Check::CheckSame: - return "same"; - case Check::CheckNot: - return "not"; - case Check::CheckDAG: - return "dag"; - case Check::CheckLabel: - return "label"; - case Check::CheckEmpty: - return "empty"; - case Check::CheckComment: - return "com"; - case Check::CheckEOF: - return "eof"; - case Check::CheckBadNot: - return "bad-not"; - case Check::CheckBadCount: - return "bad-count"; - case Check::CheckNone: - llvm_unreachable("invalid FileCheckType"); - } - llvm_unreachable("unknown FileCheckType"); -} - -static void -BuildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID, - const std::pair &ImpPatBufferIDRange, - const std::vector &Diags, - std::vector &Annotations, - unsigned &LabelWidth) { - struct CompareSMLoc { - bool operator()(const SMLoc &LHS, const SMLoc &RHS) const { - return LHS.getPointer() < RHS.getPointer(); - } - }; - // How many diagnostics does each pattern have? - std::map DiagCountPerPattern; - for (auto Diag : Diags) - ++DiagCountPerPattern[Diag.CheckLoc]; - // How many diagnostics have we seen so far per pattern? - std::map DiagIndexPerPattern; - // How many total diagnostics have we seen so far? - unsigned DiagIndex = 0; - // What's the widest label? - LabelWidth = 0; - for (auto DiagItr = Diags.begin(), DiagEnd = Diags.end(); DiagItr != DiagEnd; - ++DiagItr) { - InputAnnotation A; - A.DiagIndex = DiagIndex++; - - // Build label, which uniquely identifies this check result. - unsigned CheckBufferID = SM.FindBufferContainingLoc(DiagItr->CheckLoc); - auto CheckLineAndCol = - SM.getLineAndColumn(DiagItr->CheckLoc, CheckBufferID); - llvm::raw_string_ostream Label(A.Label); - Label << GetCheckTypeAbbreviation(DiagItr->CheckTy) << ":"; - if (CheckBufferID == CheckFileBufferID) - Label << CheckLineAndCol.first; - else if (ImpPatBufferIDRange.first <= CheckBufferID && - CheckBufferID < ImpPatBufferIDRange.second) - Label << "imp" << (CheckBufferID - ImpPatBufferIDRange.first + 1); - else - llvm_unreachable("expected diagnostic's check location to be either in " - "the check file or for an implicit pattern"); - if (DiagCountPerPattern[DiagItr->CheckLoc] > 1) - Label << "'" << DiagIndexPerPattern[DiagItr->CheckLoc]++; - LabelWidth = std::max((std::string::size_type)LabelWidth, A.Label.size()); - - A.Marker = GetMarker(DiagItr->MatchTy); - if (!DiagItr->Note.empty()) { - A.Marker.Note = DiagItr->Note; - // It's less confusing if notes that don't actually have ranges don't have - // markers. For example, a marker for 'with "VAR" equal to "5"' would - // seem to indicate where "VAR" matches, but the location we actually have - // for the marker simply points to the start of the match/search range for - // the full pattern of which the substitution is potentially just one - // component. - if (DiagItr->InputStartLine == DiagItr->InputEndLine && - DiagItr->InputStartCol == DiagItr->InputEndCol) - A.Marker.Lead = ' '; - } - if (DiagItr->MatchTy == FileCheckDiag::MatchFoundErrorNote) { - assert(!DiagItr->Note.empty() && - "expected custom note for MatchFoundErrorNote"); - A.Marker.Note = "error: " + A.Marker.Note; - } - A.FoundAndExpectedMatch = - DiagItr->MatchTy == FileCheckDiag::MatchFoundAndExpected; - - // Compute the mark location, and break annotation into multiple - // annotations if it spans multiple lines. - A.IsFirstLine = true; - A.InputLine = DiagItr->InputStartLine; - A.InputStartCol = DiagItr->InputStartCol; - if (DiagItr->InputStartLine == DiagItr->InputEndLine) { - // Sometimes ranges are empty in order to indicate a specific point, but - // that would mean nothing would be marked, so adjust the range to - // include the following character. - A.InputEndCol = - std::max(DiagItr->InputStartCol + 1, DiagItr->InputEndCol); - Annotations.push_back(A); - } else { - assert(DiagItr->InputStartLine < DiagItr->InputEndLine && - "expected input range not to be inverted"); - A.InputEndCol = UINT_MAX; - Annotations.push_back(A); - for (unsigned L = DiagItr->InputStartLine + 1, E = DiagItr->InputEndLine; - L <= E; ++L) { - // If a range ends before the first column on a line, then it has no - // characters on that line, so there's nothing to render. - if (DiagItr->InputEndCol == 1 && L == E) - break; - InputAnnotation B; - B.DiagIndex = A.DiagIndex; - B.Label = A.Label; - B.IsFirstLine = false; - B.InputLine = L; - B.Marker = A.Marker; - B.Marker.Lead = '~'; - B.Marker.Note = ""; - B.InputStartCol = 1; - if (L != E) - B.InputEndCol = UINT_MAX; - else - B.InputEndCol = DiagItr->InputEndCol; - B.FoundAndExpectedMatch = A.FoundAndExpectedMatch; - Annotations.push_back(B); - } - } - } -} - -static unsigned FindInputLineInFilter( - DumpInputFilterValue DumpInputFilter, unsigned CurInputLine, - const std::vector::iterator &AnnotationBeg, - const std::vector::iterator &AnnotationEnd) { - if (DumpInputFilter == DumpInputFilterAll) - return CurInputLine; - for (auto AnnotationItr = AnnotationBeg; AnnotationItr != AnnotationEnd; - ++AnnotationItr) { - switch (DumpInputFilter) { - case DumpInputFilterAll: - llvm_unreachable("unexpected DumpInputFilterAll"); - break; - case DumpInputFilterAnnotationFull: - return AnnotationItr->InputLine; - case DumpInputFilterAnnotation: - if (AnnotationItr->IsFirstLine) - return AnnotationItr->InputLine; - break; - case DumpInputFilterError: - if (AnnotationItr->IsFirstLine && AnnotationItr->Marker.FiltersAsError) - return AnnotationItr->InputLine; - break; - } - } - return UINT_MAX; -} - -/// To OS, print a vertical ellipsis (right-justified at LabelWidth) if it would -/// occupy less lines than ElidedLines, but print ElidedLines otherwise. Either -/// way, clear ElidedLines. Thus, if ElidedLines is empty, do nothing. -static void DumpEllipsisOrElidedLines(raw_ostream &OS, std::string &ElidedLines, - unsigned LabelWidth) { - if (ElidedLines.empty()) - return; - unsigned EllipsisLines = 3; - if (EllipsisLines < StringRef(ElidedLines).count('\n')) { - for (unsigned i = 0; i < EllipsisLines; ++i) { - WithColor(OS, raw_ostream::BLACK, /*Bold=*/true) - << right_justify(".", LabelWidth); - OS << '\n'; - } - } else - OS << ElidedLines; - ElidedLines.clear(); -} - -static void DumpAnnotatedInput(raw_ostream &OS, const FileCheckRequest &Req, - DumpInputFilterValue DumpInputFilter, - unsigned DumpInputContext, - StringRef InputFileText, - std::vector &Annotations, - unsigned LabelWidth) { - OS << "Input was:\n<<<<<<\n"; - - // Sort annotations. - llvm::sort(Annotations, - [](const InputAnnotation &A, const InputAnnotation &B) { - // 1. Sort annotations in the order of the input lines. - // - // This makes it easier to find relevant annotations while - // iterating input lines in the implementation below. FileCheck - // does not always produce diagnostics in the order of input - // lines due to, for example, CHECK-DAG and CHECK-NOT. - if (A.InputLine != B.InputLine) - return A.InputLine < B.InputLine; - // 2. Sort annotations in the temporal order FileCheck produced - // their associated diagnostics. - // - // This sort offers several benefits: - // - // A. On a single input line, the order of annotations reflects - // the FileCheck logic for processing directives/patterns. - // This can be helpful in understanding cases in which the - // order of the associated directives/patterns in the check - // file or on the command line either (i) does not match the - // temporal order in which FileCheck looks for matches for the - // directives/patterns (due to, for example, CHECK-LABEL, - // CHECK-NOT, or `--implicit-check-not`) or (ii) does match - // that order but does not match the order of those - // diagnostics along an input line (due to, for example, - // CHECK-DAG). - // - // On the other hand, because our presentation format presents - // input lines in order, there's no clear way to offer the - // same benefit across input lines. For consistency, it might - // then seem worthwhile to have annotations on a single line - // also sorted in input order (that is, by input column). - // However, in practice, this appears to be more confusing - // than helpful. Perhaps it's intuitive to expect annotations - // to be listed in the temporal order in which they were - // produced except in cases the presentation format obviously - // and inherently cannot support it (that is, across input - // lines). - // - // B. When diagnostics' annotations are split among multiple - // input lines, the user must track them from one input line - // to the next. One property of the sort chosen here is that - // it facilitates the user in this regard by ensuring the - // following: when comparing any two input lines, a - // diagnostic's annotations are sorted in the same position - // relative to all other diagnostics' annotations. - return A.DiagIndex < B.DiagIndex; - }); - - // Compute the width of the label column. - const unsigned char *InputFilePtr = InputFileText.bytes_begin(), - *InputFileEnd = InputFileText.bytes_end(); - unsigned LineCount = InputFileText.count('\n'); - if (InputFileEnd[-1] != '\n') - ++LineCount; - unsigned LineNoWidth = std::log10(LineCount) + 1; - // +3 below adds spaces (1) to the left of the (right-aligned) line numbers - // on input lines and (2) to the right of the (left-aligned) labels on - // annotation lines so that input lines and annotation lines are more - // visually distinct. For example, the spaces on the annotation lines ensure - // that input line numbers and check directive line numbers never align - // horizontally. Those line numbers might not even be for the same file. - // One space would be enough to achieve that, but more makes it even easier - // to see. - LabelWidth = std::max(LabelWidth, LineNoWidth) + 3; - - // Print annotated input lines. - unsigned PrevLineInFilter = 0; // 0 means none so far - unsigned NextLineInFilter = 0; // 0 means uncomputed, UINT_MAX means none - std::string ElidedLines; - raw_string_ostream ElidedLinesOS(ElidedLines); - ColorMode TheColorMode = - WithColor(OS).colorsEnabled() ? ColorMode::Enable : ColorMode::Disable; - if (TheColorMode == ColorMode::Enable) - ElidedLinesOS.enable_colors(true); - auto AnnotationItr = Annotations.begin(), AnnotationEnd = Annotations.end(); - for (unsigned Line = 1; - InputFilePtr != InputFileEnd || AnnotationItr != AnnotationEnd; - ++Line) { - const unsigned char *InputFileLine = InputFilePtr; - - // Compute the previous and next line included by the filter. - if (NextLineInFilter < Line) - NextLineInFilter = FindInputLineInFilter(DumpInputFilter, Line, - AnnotationItr, AnnotationEnd); - assert(NextLineInFilter && "expected NextLineInFilter to be computed"); - if (NextLineInFilter == Line) - PrevLineInFilter = Line; - - // Elide this input line and its annotations if it's not within the - // context specified by -dump-input-context of an input line included by - // -dump-input-filter. However, in case the resulting ellipsis would occupy - // more lines than the input lines and annotations it elides, buffer the - // elided lines and annotations so we can print them instead. - raw_ostream *LineOS = &OS; - if ((!PrevLineInFilter || PrevLineInFilter + DumpInputContext < Line) && - (NextLineInFilter == UINT_MAX || - Line + DumpInputContext < NextLineInFilter)) - LineOS = &ElidedLinesOS; - else { - LineOS = &OS; - DumpEllipsisOrElidedLines(OS, ElidedLinesOS.str(), LabelWidth); - } - - // Print right-aligned line number. - WithColor(*LineOS, raw_ostream::BLACK, /*Bold=*/true, /*BF=*/false, - TheColorMode) - << format_decimal(Line, LabelWidth) << ": "; - - // For the case where -v and colors are enabled, find the annotations for - // good matches for expected patterns in order to highlight everything - // else in the line. There are no such annotations if -v is disabled. - std::vector FoundAndExpectedMatches; - if (Req.Verbose && TheColorMode == ColorMode::Enable) { - for (auto I = AnnotationItr; I != AnnotationEnd && I->InputLine == Line; - ++I) { - if (I->FoundAndExpectedMatch) - FoundAndExpectedMatches.push_back(*I); - } - } - - // Print numbered line with highlighting where there are no matches for - // expected patterns. - bool Newline = false; - { - WithColor COS(*LineOS, raw_ostream::SAVEDCOLOR, /*Bold=*/false, - /*BG=*/false, TheColorMode); - bool InMatch = false; - if (Req.Verbose) - COS.changeColor(raw_ostream::CYAN, true, true); - for (unsigned Col = 1; InputFilePtr != InputFileEnd && !Newline; ++Col) { - bool WasInMatch = InMatch; - InMatch = false; - for (auto M : FoundAndExpectedMatches) { - if (M.InputStartCol <= Col && Col < M.InputEndCol) { - InMatch = true; - break; - } - } - if (!WasInMatch && InMatch) - COS.resetColor(); - else if (WasInMatch && !InMatch) - COS.changeColor(raw_ostream::CYAN, true, true); - if (*InputFilePtr == '\n') { - Newline = true; - COS << ' '; - } else - COS << *InputFilePtr; - ++InputFilePtr; - } - } - *LineOS << '\n'; - unsigned InputLineWidth = InputFilePtr - InputFileLine; - - // Print any annotations. - while (AnnotationItr != AnnotationEnd && - AnnotationItr->InputLine == Line) { - WithColor COS(*LineOS, AnnotationItr->Marker.Color, /*Bold=*/true, - /*BG=*/false, TheColorMode); - // The two spaces below are where the ": " appears on input lines. - COS << left_justify(AnnotationItr->Label, LabelWidth) << " "; - unsigned Col; - for (Col = 1; Col < AnnotationItr->InputStartCol; ++Col) - COS << ' '; - COS << AnnotationItr->Marker.Lead; - // If InputEndCol=UINT_MAX, stop at InputLineWidth. - for (++Col; Col < AnnotationItr->InputEndCol && Col <= InputLineWidth; - ++Col) - COS << '~'; - const std::string &Note = AnnotationItr->Marker.Note; - if (!Note.empty()) { - // Put the note at the end of the input line. If we were to instead - // put the note right after the marker, subsequent annotations for the - // same input line might appear to mark this note instead of the input - // line. - for (; Col <= InputLineWidth; ++Col) - COS << ' '; - COS << ' ' << Note; - } - COS << '\n'; - ++AnnotationItr; - } - } - DumpEllipsisOrElidedLines(OS, ElidedLinesOS.str(), LabelWidth); - - OS << ">>>>>>\n"; -} - -int main(int argc, char **argv) { - // Enable use of ANSI color codes because FileCheck is using them to - // highlight text. - llvm::sys::Process::UseANSIEscapeCodes(true); - - InitLLVM X(argc, argv); - cl::ParseCommandLineOptions(argc, argv, /*Overview*/ "", /*Errs*/ nullptr, - "FILECHECK_OPTS"); - - // Select -dump-input* values. The -help documentation specifies the default - // value and which value to choose if an option is specified multiple times. - // In the latter case, the general rule of thumb is to choose the value that - // provides the most information. - DumpInputValue DumpInput = - DumpInputs.empty() - ? DumpInputFail - : *std::max_element(DumpInputs.begin(), DumpInputs.end()); - DumpInputFilterValue DumpInputFilter; - if (DumpInputFilters.empty()) - DumpInputFilter = DumpInput == DumpInputAlways ? DumpInputFilterAll - : DumpInputFilterError; - else - DumpInputFilter = - *std::max_element(DumpInputFilters.begin(), DumpInputFilters.end()); - unsigned DumpInputContext = DumpInputContexts.empty() - ? 5 - : *std::max_element(DumpInputContexts.begin(), - DumpInputContexts.end()); - - if (DumpInput == DumpInputHelp) { - DumpInputAnnotationHelp(outs()); - return 0; - } - if (CheckFilename.empty()) { - errs() << " not specified\n"; - return 2; - } - - FileCheckRequest Req; - append_range(Req.CheckPrefixes, CheckPrefixes); - - append_range(Req.CommentPrefixes, CommentPrefixes); - - append_range(Req.ImplicitCheckNot, ImplicitCheckNot); - - bool GlobalDefineError = false; - for (StringRef G : GlobalDefines) { - size_t EqIdx = G.find('='); - if (EqIdx == std::string::npos) { - errs() << "Missing equal sign in command-line definition '-D" << G - << "'\n"; - GlobalDefineError = true; - continue; - } - if (EqIdx == 0) { - errs() << "Missing variable name in command-line definition '-D" << G - << "'\n"; - GlobalDefineError = true; - continue; - } - Req.GlobalDefines.push_back(G); - } - if (GlobalDefineError) - return 2; - - Req.AllowEmptyInput = AllowEmptyInput; - Req.AllowUnusedPrefixes = AllowUnusedPrefixes; - Req.EnableVarScope = EnableVarScope; - Req.AllowDeprecatedDagOverlap = AllowDeprecatedDagOverlap; - Req.Verbose = Verbose; - Req.VerboseVerbose = VerboseVerbose; - Req.NoCanonicalizeWhiteSpace = NoCanonicalizeWhiteSpace; - Req.MatchFullLines = MatchFullLines; - Req.IgnoreCase = IgnoreCase; - - if (VerboseVerbose) - Req.Verbose = true; - - FileCheck FC(Req); - if (!FC.ValidateCheckPrefixes()) - return 2; - - Regex PrefixRE = FC.buildCheckPrefixRegex(); - std::string REError; - if (!PrefixRE.isValid(REError)) { - errs() << "Unable to combine check-prefix strings into a prefix regular " - "expression! This is likely a bug in FileCheck's verification of " - "the check-prefix strings. Regular expression parsing failed " - "with the following error: " - << REError << "\n"; - return 2; - } - - SourceMgr SM; - - // Read the expected strings from the check file. - ErrorOr> CheckFileOrErr = - MemoryBuffer::getFileOrSTDIN(CheckFilename, /*IsText=*/true); - if (std::error_code EC = CheckFileOrErr.getError()) { - errs() << "Could not open check file '" << CheckFilename - << "': " << EC.message() << '\n'; - return 2; - } - MemoryBuffer &CheckFile = *CheckFileOrErr.get(); - - SmallString<4096> CheckFileBuffer; - StringRef CheckFileText = FC.CanonicalizeFile(CheckFile, CheckFileBuffer); - - unsigned CheckFileBufferID = - SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer( - CheckFileText, CheckFile.getBufferIdentifier()), - SMLoc()); - - std::pair ImpPatBufferIDRange; - if (FC.readCheckFile(SM, CheckFileText, PrefixRE, &ImpPatBufferIDRange)) - return 2; - - // Open the file to check and add it to SourceMgr. - ErrorOr> InputFileOrErr = - MemoryBuffer::getFileOrSTDIN(InputFilename, /*IsText=*/true); - if (InputFilename == "-") - InputFilename = ""; // Overwrite for improved diagnostic messages - if (std::error_code EC = InputFileOrErr.getError()) { - errs() << "Could not open input file '" << InputFilename - << "': " << EC.message() << '\n'; - return 2; - } - MemoryBuffer &InputFile = *InputFileOrErr.get(); - - if (InputFile.getBufferSize() == 0 && !AllowEmptyInput) { - errs() << "FileCheck error: '" << InputFilename << "' is empty.\n"; - DumpCommandLine(argc, argv); - return 2; - } - - SmallString<4096> InputFileBuffer; - StringRef InputFileText = FC.CanonicalizeFile(InputFile, InputFileBuffer); - - SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer( - InputFileText, InputFile.getBufferIdentifier()), - SMLoc()); - - std::vector Diags; - int ExitCode = FC.checkInput(SM, InputFileText, - DumpInput == DumpInputNever ? nullptr : &Diags) - ? EXIT_SUCCESS - : 1; - if (DumpInput == DumpInputAlways || - (ExitCode == 1 && DumpInput == DumpInputFail)) { - errs() << "\n" - << "Input file: " << InputFilename << "\n" - << "Check file: " << CheckFilename << "\n" - << "\n" - << "-dump-input=help explains the following input dump.\n" - << "\n"; - std::vector Annotations; - unsigned LabelWidth; - BuildInputAnnotations(SM, CheckFileBufferID, ImpPatBufferIDRange, Diags, - Annotations, LabelWidth); - DumpAnnotatedInput(errs(), Req, DumpInputFilter, DumpInputContext, - InputFileText, Annotations, LabelWidth); - } - - return ExitCode; -} diff --git a/utils/gen_gccbuiltins.cpp b/utils/gen_gccbuiltins.cpp index 5c973641dd7..e483abbe26e 100644 --- a/utils/gen_gccbuiltins.cpp +++ b/utils/gen_gccbuiltins.cpp @@ -29,11 +29,7 @@ using namespace std; using namespace llvm; -#if LDC_LLVM_VER >= 1500 #define BUILTIN_NAME_STRING "ClangBuiltinName" -#else -#define BUILTIN_NAME_STRING "GCCBuiltinName" -#endif string dtype(Record* rec, bool readOnlyMem) {