diff --git a/.ci-defaults-template.yml b/.ci-defaults-template.yml deleted file mode 100644 index 6569cc9b..00000000 --- a/.ci-defaults-template.yml +++ /dev/null @@ -1,12 +0,0 @@ -default: - before_script: - - module purge - - module load gcc/$GCC - - module load git cmake python/3 - - module load libcube/4.5 extrap/3 - tags: - - general - -variables: - GIT_STRATEGY: none - GIT_CLONE_PATH: $CI_BUILDS_DIR/$CI_PIPELINE_ID diff --git a/.ci/gitlab/.gitlab-ci.yml b/.ci/gitlab/.gitlab-ci.yml new file mode 100644 index 00000000..0f3ab675 --- /dev/null +++ b/.ci/gitlab/.gitlab-ci.yml @@ -0,0 +1,355 @@ +# File: .gitlab-ci.yml +# License: Part of the MetaCG proect. Licensed under BSD 3 clause license. See LICENSE.txt file at https://github.com/tudasc/metacg/LICENSE.txt +# Description: File to configure our Gitlab CI + +variables: + GIT_STRATEGY: none + GIT_CLONE_PATH: $CI_BUILDS_DIR/$CI_PIPELINE_ID + + CMAKE_GENERATOR: Ninja + CMAKE_BUILD_PARALLEL_LEVEL: 8 + + CMAKE_C_COMPILER_LAUNCHER: ccache + CMAKE_CXX_COMPILER_LAUNCHER: ccache + + +# Setup for job matrix, covering different versions of GCC and LLVM +.parallel-setup: ¶llel-setup + parallel: + matrix: + - GCC: 11 + LLVM: [15.0.7, 16.0.6, 17.0.6, 18.1.8, 19.1.7, 20.1.8, 21.1.4] + _CC: clang + _CXX: clang++ + - GCC: 15 + LLVM: [21.1.4] + _CC: clang + _CXX: clang++ + - GCC: 15 + LLVM: [21.1.4] + _CC: gcc + _CXX: g++ + + variables: + VARIANT: "$_CC-GCC-$GCC-LLVM-$LLVM" + SPENV: "spenv-$VARIANT" + BUILD: "build-$VARIANT" + INSTALL: "install-$VARIANT" + +# Setup for all jobs (except those preparing the environment) +.job-setup: &job-setup + <<: *parallel-setup + + before_script: + - spack env activate $SPENV + + +# Stages +stages: + - prepare + - configure-build-lint + - test + - integration-test + - install + +# Stage: Prepare +# ============== +mcg-download: + stage: prepare + variables: + GIT_STRATEGY: fetch + GIT_CLONE_PATH: $CI_BUILDS_DIR/$CI_PIPELINE_ID + script: + - echo 'Done.' + +prepare-environment: + stage: prepare + needs: ["mcg-download"] + <<: *parallel-setup + script: + - which spack + - spack --version + - mkdir -p $SPENV + - | + ./.ci/gitlab/spack/replace.sh \ + < ./.ci/gitlab/spack/spack_env_template.yaml \ + > $SPENV/spack.yaml \ + "" $GCC \ + "" $LLVM + - spack env activate $SPENV + - ./.ci/gitlab/spack/prepare.sh + + cache: + key: + files: + - ".ci/gitlab/spack/spack_env_template.yaml" + - ".ci/gitlab/.gitlab-ci.yaml" + prefix: "$VARIANT" + paths: + - $SPENV/spack.yaml + - $SPENV/spack.lock + policy: pull-push + + +# Stage: configure-build-lint +# =========================== +mcg-cmake-lint: + stage: configure-build-lint + script: + - spack load --first py-cmake-format + - for f in $(find {cgcollector,graph,pgis} -name "CMakeLists.txt"); do cmake-format --check $f || exit 1; done ; cmake-format --check CMakeLists.txt + - for f in $(find ./cmake -type f); do cmake-format --check $f || exit 1; done + +build: + stage: configure-build-lint + <<: *job-setup + + script: + - echo $SPENV + - echo $BUILD + - export CC=$(which ${_CC}) + - export CXX=$(which ${_CXX}) + - echo $CC + - echo $CXX + - cmake -S . -B $BUILD + -DCMAKE_INSTALL_PREFIX=$INSTALL + -DCMAKE_C_COMPILER=$_CC + -DCMAKE_CXX_COMPILER=$_CXX + -DMETACG_BUILD_CGCOLLECTOR=ON + -DMETACG_BUILD_GRAPH_TOOLS=ON + -DMETACG_BUILD_CGPATCH=ON + -DCAGE_USE_METAVIRT=ON + -DCGPATCH_USE_MPI=OFF + -DMETACG_BUILD_PYMETACG=ON + -DMETACG_BUILD_PYMETACG_TESTS=ON + -DMETACG_USE_EXTERNAL_JSON=ON + -DMETACG_USE_EXTERNAL_CXXOPTS=ON + -DMETACG_USE_EXTERNAL_NANOBIND=ON + -DLLVM_EXTERNAL_LIT=$(which lit) + -DCUBE_DIR=$(spack location -i cubelib) + -DMETACG_BUILD_CGFCOLLECTOR=ON + - cmake --build $BUILD + +# test whether a custom in-tree metadata type gets included in the build +configure-custom-md: + stage: configure-build-lint + needs: ["build"] # to prevent this job from interfering with the different builds + script: + - spack load --first cmake + - test -d graph/include/metacg/metadata/custom || exit 1 + - echo "// Empty test header" > graph/include/metacg/metadata/custom/TestHeader.h + - cmake -S . -B build-configure-md -DCMAKE_BUILD_TYPE=Debug -DMETACG_BUILD_UNIT_TESTS=OFF + - grep "TestHeader.h" "build-configure-md/graph/include/metacg/metadata/CustomMD.h" || exit 1 + - rm -f graph/include/metacg/metadata/custom/TestHeader.h + + +# Stage: test +# =========== +test-graphlib: + <<: *job-setup + stage: test + script: + - cd $BUILD/graph/test/unit && ./libtests --gtest_output=xml:${VARIANT}.xml + - sed -i -e "s/Test\"/Test-${VARIANT}\"/g" ${VARIANT}.xml + artifacts: + when: always + paths: + - $BUILD/graph/test/unit/${VARIANT}.xml + reports: + junit: $BUILD/graph/test/unit/${VARIANT}.xml + +test-pymetacg: + <<: *job-setup + stage: test + script: + - cd $BUILD/pymetacg/tests + - ctest . --verbose + +test-cgc: + <<: *job-setup + stage: test + script: + - cd cgcollector/test + - mkdir -p log + - bash run_format_two_test.sh -b $BUILD + +test-cgpatch-lit: + <<: *job-setup + stage: test + script: + - cmake --install $BUILD + - cmake --build $BUILD --target pass-tests + - cmake --build $BUILD --target pass-tests-lto + - cmake --build $BUILD --target pass-tests-optimized + - cmake --build $BUILD --target pass-tests-optimized-lto + +test-cgdiff: + <<: *job-setup + stage: test + script: + - cd $BUILD/tools/cgdiff/test/unit/ + - ./cgdifftests + +check-test-format: + <<: *job-setup + stage: test + script: + - ./checkTests.sh $BUILD/tools/cgformat/cgformat + +test-cgformat: + <<: *job-setup + stage: test + script: + - cd $BUILD/tools/cgformat/test + - ./run_cgformat_tests.sh + +test-cgtodot: + <<: *job-setup + stage: test + script: + - cd $BUILD/tools/cgtodot + - ctest . --verbose + + +# Stage: integration-test +# ======================= +test-cgcollector2: + <<: *job-setup + stage: integration-test + script: + - cd tools/cgcollector2/test/ + - ./testBase.sh -b $BUILD + +test-cgvalidate: + <<: *job-setup + stage: integration-test + script: + - cd cgcollector/test/integration + - ./runner.sh $BUILD + +test-graphlib-merge: + <<: *job-setup + stage: integration-test + script: + - cd graph/test/integration/CallgraphMerge + - ./MergeTestRunner.sh -b $BUILD + +test-target-collector: + <<: *job-setup + stage: integration-test + script: + - cmake --install $BUILD + - cd graph/test/integration/TargetCollector + - ./TestRunner.sh -b $BUILD + +test-cgpatch-integration: + <<: *job-setup + script: + - cd $BUILD/tools/cgpatch/test/integration/ + - ./CGPIntegrationRunner.sh -b $BUILD + +test-cgdiff-integration: + <<: *job-setup + stage: integration-test + script: + - cmake --install $BUILD + - cd tools/cgdiff/test/integration/ + - ./run_tests.sh -b $BUILD + +test-cage: + <<: *job-setup + parallel: + matrix: + - GCC: 11 + LLVM: [16.0.6, 17.0.6, 18.1.8, 19.1.7, 20.1.8, 21.1.4] + _CC: clang + _CXX: clang++ + - GCC: 15 + LLVM: [21.1.4] + _CC: clang + _CXX: clang++ + - GCC: 15 + LLVM: [21.1.4] + _CC: gcc + _CXX: g++ + stage: integration-test + script: + - cd $BUILD/tools/cage/test + - ./runCaGeTests.sh + +test-cgfcollector: + <<: *job-setup + parallel: + matrix: + - GCC: 11 + LLVM: [18.1.8, 19.1.7, 20.1.8, 21.1.4] + _CC: clang + _CXX: clang++ + - GCC: 15 + LLVM: [21.1.4] + _CC: clang + _CXX: clang++ + - GCC: 15 + LLVM: [21.1.4] + _CC: gcc + _CXX: g++ + stage: integration-test + script: + - cd $BUILD/cgfcollector + - ctest . --verbose + + +# Stage: install +# ============== +install: + stage: install + <<: *job-setup + + script: + - cmake --install $BUILD + - stat $INSTALL/lib64/libmetacg.so + - stat $INSTALL/bin/cgcollector + - stat $INSTALL/include/metacg/metadata/CustomMD.h + - stat $INSTALL/lib64/cmake/metacg/metacgConfig.cmake + - stat $INSTALL/bin/cgdiff + - stat $INSTALL/bin/cgquery + - stat $INSTALL/bin/cgformat + - stat $INSTALL/bin/cgconvert + - stat $INSTALL/bin/cgtodot + - export LD_LIBRARY_PATH=$INSTALL/lib64:$INSTALL/lib:$LD_LIBRARY_PATH + - $INSTALL/bin/metacg-config --help + - $INSTALL/bin/cgcollector --help + +# test whether we can build project that uses MetaCG +install-test: + <<: *job-setup + stage: install + needs: ["install"] + script: + - export BUILD=$(realpath $BUILD) + - export INSTALL=$(realpath $INSTALL) + - cd graph/test/install + - CMAKE_PREFIX_PATH=$INSTALL/lib64/cmake/metacg cmake -S . -B $BUILD-install-test + - cmake --build $BUILD-install-test + +CGC2Plugin-test: + <<: *job-setup + stage: install + needs: ["install"] + script: + - export BUILD=$(realpath $BUILD) + - export INSTALL=$(realpath $INSTALL) + - cd tools/cgcollector2/CGC2DemoPlugin + - CMAKE_PREFIX_PATH=$INSTALL/lib64/cmake/metacg cmake -S . -B $BUILD-install-test-cgc2 + - cmake --build $BUILD-install-test-cgc2 + +CaGePlugin-test: + <<: *job-setup + stage: install + needs: ["install"] + script: + - export BUILD=$(realpath $BUILD) + - export INSTALL=$(realpath $INSTALL) + - cd tools/cage/CaGeDemoPlugin + - CMAKE_PREFIX_PATH=$INSTALL/lib64/cmake/metacg cmake -S . -B $BUILD-install-test-cage + - cmake --build $BUILD-install-test-cage \ No newline at end of file diff --git a/.ci/gitlab/README.md b/.ci/gitlab/README.md new file mode 100644 index 00000000..7a46c030 --- /dev/null +++ b/.ci/gitlab/README.md @@ -0,0 +1,3 @@ +# GitLab CI + +This directory contains the scripts and configuration for running the downstream CI on the [Lichtenberg high-performance computer](https://www.hrz.tu-darmstadt.de/hlr/hochleistungsrechnen/index.en.jsp). diff --git a/.ci/gitlab/spack/check_spack_env.py b/.ci/gitlab/spack/check_spack_env.py new file mode 100644 index 00000000..5c549c3e --- /dev/null +++ b/.ci/gitlab/spack/check_spack_env.py @@ -0,0 +1,110 @@ +#!/usr/bin/env spack python +""" +Check if the current Spack environment is fully installed. + +Usage: + spack python check_spack_env.py +""" + +import sys +import spack.environment as ev +import spack.store + + +def check_environment_installed(): + """ + Check if all specs in the active Spack environment are installed. + + Returns: + int: Exit code (0 = success, 1 = incomplete installation) + """ + # Get the active environment + env = ev.active_environment() + + if env is None: + print("Error: No active Spack environment found.") + print("Please activate an environment with 'spack env activate '") + sys.exit(2) + + print(f"Checking environment: {env.name}") + print(f"Environment path: {env.path}") + print("-" * 60) + + # Get all specs in the environment + all_specs = env.all_specs() + root_specs = env.user_specs + + if not all_specs: + print("Environment is empty (no specs defined).") + return 0 + + total_specs = len(all_specs) + installed_specs = [] + external_specs = [] + missing_specs = [] + missing_root_specs = [] + missing_dep_specs = [] + + # Create set of root spec names for quick lookup + root_spec_names = {spec.name for spec in root_specs} + + # Check installation status of each spec + for spec in all_specs: + # Check if the spec is installed + if spec.installed: + installed_specs.append(spec) + # Check if this is an external package + # External packages can be detected via spec.external or spec.external_path + if spec.external or spec.external_path: + external_specs.append(spec) + else: + missing_specs.append(spec) + # Check if this is a root spec or a dependency + if spec.name in root_spec_names: + missing_root_specs.append(spec) + else: + missing_dep_specs.append(spec) + + print() + print(f"Total specs: {total_specs}") + print(f"Installed: {len(installed_specs)}") + print(f"Missing: {len(missing_specs)}") + + is_fully_installed = len(missing_specs) == 0 + + if is_fully_installed: + print("\n✓ Environment is fully installed!") + return 0 + else: + print("\n✗ Environment is NOT fully installed.") + + if missing_root_specs: + print(f"\nMissing root package(s): {len(missing_root_specs)}") + for spec in missing_root_specs: + print(f" - {spec}") + + if missing_dep_specs: + print(f"\nMissing dependencies: {len(missing_dep_specs)}") + for spec in missing_dep_specs: + print(f" - {spec}") + + print("\nTo install missing packages, run:") + print(" spack install") + return 1 + + return is_fully_installed, details + + +def main(): + """Main entry point.""" + try: + exit_code = check_environment_installed() + sys.exit(exit_code) + + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(2) + + +if __name__ == "__main__": + main() diff --git a/.ci/gitlab/spack/prepare.sh b/.ci/gitlab/spack/prepare.sh new file mode 100755 index 00000000..9aca00fd --- /dev/null +++ b/.ci/gitlab/spack/prepare.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +# Concretize Spack environment at CWD and check whether everything is installed + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +if [ "$FORCE_RECONCRETIZE" = "1" ]; then + rm spack.lock +fi + +spack concretize || exit 1 + +spack python ${SCRIPT_DIR}/check_spack_env.py || exit 1 + +spack env view regenerate || exit 1 + +exit 0 diff --git a/.ci/gitlab/spack/replace.sh b/.ci/gitlab/spack/replace.sh new file mode 100755 index 00000000..9fffcb7b --- /dev/null +++ b/.ci/gitlab/spack/replace.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +# +# Multi-Pattern Search and Replace Script +# +# This script reads input from stdin and performs multiple search-and-replace operations +# on the content using sed. It accepts search/replace patterns as command-line argument +# pairs and processes them sequentially. +# +# Usage: +# cat file.txt | $0 [search2] [replace2] ... +# echo "text" | $0 'pattern1' 'replacement1' 'pattern2' 'replacement2' +# +# Arguments: +# - Must be provided in pairs (search pattern followed by replacement string) +# - Each search pattern is treated as a sed regular expression +# - Replacements are applied globally (all occurrences per line) +# - Patterns are processed in the order they are provided +# +# Example: +# cat file.txt | $0 'old' 'new' 'foo' 'bar' +# # This replaces all 'old' with 'new', then all 'foo' with 'bar' +# +# Note: Special regex characters in search patterns should be escaped appropriately +# + +# Check if arguments are provided in pairs +if [ $# -eq 0 ] || [ $(($# % 2)) -ne 0 ]; then + echo "Usage: $0 [search2] [replace2] ..." >&2 + echo "Example: cat file.txt | $0 'old' 'new' 'foo' 'bar'" >&2 + exit 1 +fi + +# Read stdin into a variable +content=$(cat) + +# Process each search/replace pair +while [ $# -gt 0 ]; do + search="$1" + replace="$2" + + # Perform the replacement using sed + content=$(echo "$content" | sed "s/$search/$replace/g") + + shift 2 +done + +# Output the result +echo "$content" diff --git a/.ci/gitlab/spack/spack_env_template.yaml b/.ci/gitlab/spack/spack_env_template.yaml new file mode 100644 index 00000000..815a4866 --- /dev/null +++ b/.ci/gitlab/spack/spack_env_template.yaml @@ -0,0 +1,38 @@ +# This is a Spack Environment file. +# +# It describes a set of packages to be installed, along with +# configuration settings. +spack: + specs: + # Compilers + - 'gcc@' + - 'llvm@ +clang +flang +utils %gcc@' + + # Build tools + - cmake + - ninja + + # Basic dependencies + - 'nlohmann-json@3.12:' + - cxxopts + + # For cgvalidate + - cubelib + + # For pymetacg + - 'python@3.13:' + - py-nanobind + - py-pytest-cmake + + # For cgfcollector + - py-fortdepend + + view: true + concretizer: + unify: true + + # Make sure LD_LIBRARY_PATH is set when activating the environment + modules: + prefix_inspections: + lib: [LD_LIBRARY_PATH] + lib64: [LD_LIBRARY_PATH] diff --git a/.github/workflows/mcg-ci.yml b/.github/workflows/mcg-ci.yml index a325822e..db45e970 100644 --- a/.github/workflows/mcg-ci.yml +++ b/.github/workflows/mcg-ci.yml @@ -1,65 +1,126 @@ name: MetaCG Builds -on: +on: push: paths-ignore: - - 'README.md' - - 'LICENSE.txt' - - 'CONTRIBUTING.md' - - 'CITATION.cff' - - 'AUTHORS' - - 'formatSrc.sh' - - '.gitlab-ci.yml' - - '.gitignore' - - '.ci-defaults-template.yml' - - 'cgcollector/README.md' - - 'cgcollector/formatAllSrc.sh' - - 'cgcollector/.gitignore' - - 'graph/README.md' - - 'pgis/README.md' - - 'tools/README.md' - - 'pymetacg/README.md' - pull_request: + - "README.md" + - "LICENSE.txt" + - "CONTRIBUTING.md" + - "CITATION.cff" + - "AUTHORS" + - "formatSrc.sh" + - ".gitlab-ci.yml" + - ".gitignore" + - ".ci-defaults-template.yml" + - "cgcollector/README.md" + - "cgcollector/formatAllSrc.sh" + - "cgcollector/.gitignore" + - "graph/README.md" + - "pgis/README.md" + - "tools/README.md" + - "pymetacg/README.md" + pull_request: paths-ignore: - - 'README.md' - - 'LICENSE.txt' - - 'CONTRIBUTING.md' - - 'CITATION.cff' - - 'AUTHORS' - - 'formatSrc.sh' - - '.gitlab-ci.yml' - - '.gitignore' - - '.ci-defaults-template.yml' - - 'cgcollector/README.md' - - 'cgcollector/formatAllSrc.sh' - - 'cgcollector/.gitignore' - - 'graph/README.md' - - 'pgis/README.md' - - 'tools/README.md' - - 'pymetacg/README.md' + - "README.md" + - "LICENSE.txt" + - "CONTRIBUTING.md" + - "CITATION.cff" + - "AUTHORS" + - "formatSrc.sh" + - ".gitlab-ci.yml" + - ".gitignore" + - ".ci-defaults-template.yml" + - "cgcollector/README.md" + - "cgcollector/formatAllSrc.sh" + - "cgcollector/.gitignore" + - "graph/README.md" + - "pgis/README.md" + - "tools/README.md" + - "pymetacg/README.md" jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: cmake - run: cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -B build -S . - - name: build - run: cmake --build build --parallel - - name: install - run: | - cmake --install build --prefix install - stat install/lib/libmetacg.so - stat install/include/metadata/CustomMD.h - stat install/lib/cmake/metacg/metacgConfig.cmake - - name: install-test - run: | - CMAKE_PREFIX_PATH=install/lib/cmake/metacg cmake -S graph/test/install -B build-install-test - cmake --build build-install-test - - build-container: + - uses: actions/checkout@v6 + - name: cmake + run: cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_INSTALL_PREFIX="$(pwd)/install" -B build -S . + - name: build + run: cmake --build build --parallel + - name: install + run: | + cmake --install build --prefix install + stat install/lib/libmetacg.so + stat install/include/metacg/metadata/CustomMD.h + stat install/lib/cmake/metacg/metacgConfig.cmake + - name: install-test + run: | + CMAKE_PREFIX_PATH=install/lib/cmake/metacg cmake -S graph/test/install -B build-install-test + cmake --build build-install-test + - name: metacg-config-test + run: | + MCG_CFG=install/bin/metacg-config + stat $MCG_CFG + [[ "$($MCG_CFG --prefix)" == "$(cd install; pwd)" ]] || exit 1 + [[ "$($MCG_CFG --revision)" == "$(git rev-parse HEAD)" ]] || exit 1 + + # TODO: Complete the llvm-20 based CI definition + build-container-llvm-20: + runs-on: ubuntu-latest + steps: + - name: checkout code + uses: actions/checkout@v6 + - uses: docker/setup-buildx-action@v3 + - uses: docker/build-push-action@v6 + with: + context: . + file: "container/full-build-llvm-20" + tags: metacg-llvm-20-devel:latest + load: true + cache-from: type=gha + cache-to: type=gha,mode=max + push: false + - name: Install tests + uses: maus007/docker-run-action-fork@207a4e2a8ebf7e4b985656ba990b1e53715dce2a + with: + image: metacg-llvm-20-devel:latest + run: | + stat /tmp/metacg/lib/libmetacg.so + stat /tmp/metacg/include/metacg/metadata/CustomMD.h + stat /tmp/metacg/lib/cmake/metacg/metacgConfig.cmake + stat /tmp/metacg/bin/metacg-config + stat /tmp/metacg/bin/cgdiff + stat /tmp/metacg/bin/cgquery + stat /tmp/metacg/bin/cgformat + stat /tmp/metacg/bin/cgconvert + stat /tmp/metacg/bin/cgtodot + stat /tmp/metacg/lib/libcgfcollector.so + - name: Check for canonical test graph format + uses: maus007/docker-run-action-fork@207a4e2a8ebf7e4b985656ba990b1e53715dce2a + with: + image: metacg-llvm-20-devel:latest + run: /opt/metacg/checkTests.sh /opt/metacg/build/tools/cgformat/cgformat + - name: Run cgc2 Demo Plugin tests + uses: maus007/docker-run-action-fork@207a4e2a8ebf7e4b985656ba990b1e53715dce2a + with: + image: metacg-llvm-20-devel:latest + run: | + export PATH=/tmp/metacg/bin/:$PATH + export LD_LIBRARY_PATH=/tmp/metacg/lib:$LD_LIBRARY_PATH + export LD_LIBRARY_PATH=/opt/metacg/tools/cgcollector2/CGC2DemoPlugin/build:$LD_LIBRARY_PATH + cgcollector2 --pluginPaths=libCGC2DemoPlugin.so --metacg-format-version=4 /dev/null 2>&1 | grep -q "Successfully loaded Plugin: CGC2 Demo Plugin" + - name: Run CaGe Demo Plugin tests + uses: maus007/docker-run-action-fork@207a4e2a8ebf7e4b985656ba990b1e53715dce2a + with: + image: metacg-llvm-20-devel:latest + run: | + export PATH=/tmp/metacg/bin/:$PATH + export LD_LIBRARY_PATH=/tmp/metacg/lib:$LD_LIBRARY_PATH + export LD_LIBRARY_PATH=/opt/metacg/tools/cage/CaGeDemoPlugin/build:$LD_LIBRARY_PATH + opt --load-pass-plugin=cage-plugin.so --plugin-paths=libCaGeDemoPlugin.so -S --cage-verbose --passes="CaGe" /dev/null | grep -q "Successfully loaded Plugin: Cage Demo Plugin" + + build-container-llvm-18: runs-on: ubuntu-latest steps: - name: checkout code @@ -68,74 +129,103 @@ jobs: - uses: docker/build-push-action@v6 with: context: . - file: "container/full-build" - tags: metacg-devel:latest + file: "container/full-build-llvm-18" + tags: metacg-llvm-18-devel:latest load: true cache-from: type=gha cache-to: type=gha,mode=max push: false + - name: Install tests + uses: maus007/docker-run-action-fork@207a4e2a8ebf7e4b985656ba990b1e53715dce2a + with: + image: metacg-llvm-18-devel:latest + run: | + stat /tmp/metacg/lib/libmetacg.so + stat /tmp/metacg/include/metacg/metadata/CustomMD.h + stat /tmp/metacg/lib/cmake/metacg/metacgConfig.cmake + stat /tmp/metacg/bin/metacg-config + stat /tmp/metacg/bin/cgdiff + stat /tmp/metacg/bin/cgquery + stat /tmp/metacg/bin/cgformat + stat /tmp/metacg/bin/cgconvert + stat /tmp/metacg/bin/cgtodot + stat /tmp/metacg/lib/libcgfcollector.so - name: Check for canonical test graph format - uses: addnab/docker-run-action@v3 + uses: maus007/docker-run-action-fork@207a4e2a8ebf7e4b985656ba990b1e53715dce2a with: - image: metacg-devel:latest + image: metacg-llvm-18-devel:latest run: /opt/metacg/checkTests.sh /opt/metacg/build/tools/cgformat/cgformat - - name: Run MCG library tests - uses: addnab/docker-run-action@v3 - with: - image: metacg-devel:latest + - name: Run MCG library tests + uses: maus007/docker-run-action-fork@207a4e2a8ebf7e4b985656ba990b1e53715dce2a + with: + image: metacg-llvm-18-devel:latest run: /opt/metacg/build/graph/test/unit/libtests - - name: Run PGIS library tests - uses: addnab/docker-run-action@v3 - with: - image: metacg-devel:latest + - name: Run PGIS library tests + uses: maus007/docker-run-action-fork@207a4e2a8ebf7e4b985656ba990b1e53715dce2a + with: + image: metacg-llvm-18-devel:latest run: /opt/metacg/build/pgis/test/unit/pgistests - - name: Run target collector tests - uses: addnab/docker-run-action@v3 - with: - image: metacg-devel:latest + - name: Run target collector tests + uses: maus007/docker-run-action-fork@207a4e2a8ebf7e4b985656ba990b1e53715dce2a + with: + image: metacg-llvm-18-devel:latest run: | cd /opt/metacg/graph/test/integration/TargetCollector ./TestRunner.sh - name: Run call graph merge tests - uses: addnab/docker-run-action@v3 - with: - image: metacg-devel:latest + uses: maus007/docker-run-action-fork@207a4e2a8ebf7e4b985656ba990b1e53715dce2a + with: + image: metacg-llvm-18-devel:latest run: | cd /opt/metacg/graph/test/integration/CallgraphMerge ./MergeTestRunner.sh - name: Run cgcollector tests - uses: addnab/docker-run-action@v3 - with: - image: metacg-devel:latest + uses: maus007/docker-run-action-fork@207a4e2a8ebf7e4b985656ba990b1e53715dce2a + with: + image: metacg-llvm-18-devel:latest run: | cd /opt/metacg/cgcollector/test - bash run_format_one_test.sh - bash run_format_two_test.sh + bash run_format_one_test.sh + bash run_format_two_test.sh + - name: Run cgcollector2 tests + uses: maus007/docker-run-action-fork@207a4e2a8ebf7e4b985656ba990b1e53715dce2a + with: + image: metacg-llvm-18-devel:latest + run: | + cd /opt/metacg/tools/cgcollector2/test + bash testBase.sh + - name: Run CaGe tests + uses: maus007/docker-run-action-fork@207a4e2a8ebf7e4b985656ba990b1e53715dce2a + with: + image: metacg-llvm-18-devel:latest + run: | + cd /opt/metacg/build/tools/cage/test + ./runCaGeTests.sh - name: Run cgvalidate tests - uses: addnab/docker-run-action@v3 - with: - image: metacg-devel:latest + uses: maus007/docker-run-action-fork@207a4e2a8ebf7e4b985656ba990b1e53715dce2a + with: + image: metacg-llvm-18-devel:latest run: | cd /opt/metacg/cgcollector/test/integration ./runner.sh build - name: Run cgformat tests - uses: addnab/docker-run-action@v3 + uses: maus007/docker-run-action-fork@207a4e2a8ebf7e4b985656ba990b1e53715dce2a with: - image: metacg-devel:latest + image: metacg-llvm-18-devel:latest run: | cd /opt/metacg/build/tools/cgformat/test ./run_cgformat_tests.sh - name: Run pymetacg tests - uses: addnab/docker-run-action@v3 - with: - image: metacg-devel:latest + uses: maus007/docker-run-action-fork@207a4e2a8ebf7e4b985656ba990b1e53715dce2a + with: + image: metacg-llvm-18-devel:latest run: | cd /opt/metacg/build/pymetacg/tests ctest . --verbose - name: Run cpatch pass-tests (lit) - uses: addnab/docker-run-action@v3 + uses: maus007/docker-run-action-fork@207a4e2a8ebf7e4b985656ba990b1e53715dce2a with: - image: metacg-devel:latest + image: metacg-llvm-18-devel:latest run: | cd /opt/metacg/ cmake --build build --target pass-tests @@ -143,12 +233,55 @@ jobs: cmake --build build --target pass-tests-optimized cmake --build build --target pass-tests-optimized-lto - name: Run cgpatch integration tests - uses: addnab/docker-run-action@v3 + uses: maus007/docker-run-action-fork@207a4e2a8ebf7e4b985656ba990b1e53715dce2a with: - image: metacg-devel:latest + image: metacg-llvm-18-devel:latest run: | export OMPI_ALLOW_RUN_AS_ROOT=1 export OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 cd /opt/metacg/build/tools/cgpatch/test/integration/ ./CGPIntegrationRunner.sh -d - + - name: Run cgdiff integration tests + uses: maus007/docker-run-action-fork@207a4e2a8ebf7e4b985656ba990b1e53715dce2a + with: + image: metacg-llvm-18-devel:latest + run: | + cd /opt/metacg/tools/cgdiff/test/integration/ + ./run_tests.sh -b build + - name: Run cgdiff unit tests + uses: maus007/docker-run-action-fork@207a4e2a8ebf7e4b985656ba990b1e53715dce2a + with: + image: metacg-llvm-18-devel:latest + run: /opt/metacg/build/tools/cgdiff/test/unit/cgdifftests + - name: Run cgtodot tests + uses: maus007/docker-run-action-fork@207a4e2a8ebf7e4b985656ba990b1e53715dce2a + with: + image: metacg-llvm-18-devel:latest + run: | + cd /opt/metacg/build/tools/cgtodot/ + ctest . --verbose + - name: Run cgc2 Demo Plugin tests + uses: maus007/docker-run-action-fork@207a4e2a8ebf7e4b985656ba990b1e53715dce2a + with: + image: metacg-llvm-18-devel:latest + run: | + export PATH=/tmp/metacg/bin/:$PATH + export LD_LIBRARY_PATH=/tmp/metacg/lib:$LD_LIBRARY_PATH + export LD_LIBRARY_PATH=/opt/metacg/tools/cgcollector2/CGC2DemoPlugin/build:$LD_LIBRARY_PATH + cgcollector2 --pluginPaths=libCGC2DemoPlugin.so --metacg-format-version=4 /dev/null 2>&1 | grep -q "Successfully loaded Plugin: CGC2 Demo Plugin" + - name: Run CaGe Demo Plugin tests + uses: maus007/docker-run-action-fork@207a4e2a8ebf7e4b985656ba990b1e53715dce2a + with: + image: metacg-llvm-18-devel:latest + run: | + export PATH=/tmp/metacg/bin/:$PATH + export LD_LIBRARY_PATH=/tmp/metacg/lib:$LD_LIBRARY_PATH + export LD_LIBRARY_PATH=/opt/metacg/tools/cage/CaGeDemoPlugin/build:$LD_LIBRARY_PATH + opt --load-pass-plugin=cage-plugin.so --plugin-paths=libCaGeDemoPlugin.so -S --cage-verbose --passes="CaGe" /dev/null | grep -q "Successfully loaded Plugin: Cage Demo Plugin" + - name: Run cgfcollector tests + uses: maus007/docker-run-action-fork@207a4e2a8ebf7e4b985656ba990b1e53715dce2a + with: + image: metacg-llvm-18-devel:latest + run: | + cd /opt/metacg/build/cgfcollector + ctest . --verbose diff --git a/.github/workflows/mcg-lint.yml b/.github/workflows/mcg-lint.yml index 280b1fd4..2c1535d7 100644 --- a/.github/workflows/mcg-lint.yml +++ b/.github/workflows/mcg-lint.yml @@ -13,8 +13,37 @@ jobs: - name: Install lint tool in container run: | python3 -m pip install cmakelang - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 + with: + fetch-depth: 2 + + - name: Changed Files + id: changed-files + uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 # v47.0.1 + with: + separator: " " + skip_initial_fetch: true + base_sha: 'HEAD~1' + sha: 'HEAD' + + - name: List changed files + env: + CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }} + run: | + echo "Changed files:" + echo "${CHANGED_FILES}" + - name: CMake Lint + env: + CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }} run: | - for f in $(find {cgcollector,graph,pgis} -name "CMakeLists.txt"); do cmake-format --check $f || exit 1; done ; cmake-format --check CMakeLists.txt - for f in $(find ./cmake -type f); do cmake-format --check $f || exit 1; done + for f in ${CHANGED_FILES}; do + filename=$(basename $f) + if [[ $filename == *.cmake ]]; then + echo "Running on $f" + cmake-format --check $f + elif [[ $filename == "CMakeLists.txt" ]]; then + echo "Running on $f" + cmake-format --check $f + fi + done diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 0e71b0f4..00000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,370 +0,0 @@ -# File: .gitlab-ci.yml -# License: Part of the MetaCG proect. Licensed under BSD 3 clause license. See LICENSE.txt file at https://github.com/jplehr/pira/LICENSE.txt -# Description: File to configure our Gitlab CI -include: .ci-defaults-template.yml - -# Stages -stages: - - download - - lint - - build-deps - - configure-test - - build - - test - - integration-test - - install - -.job-setup: &job-setup - parallel: - matrix: - - GCC: 11 - LLVM: [13.0.1, 14.0.6, 15.0.7, 16.0.6, 17.0.6, 18.1.8] - variables: - MCG_BUILD: build-GCC-$GCC-LLVM-$LLVM - - -# State: Download -mcg-download: - stage: download - variables: - GIT_STRATEGY: clone - GIT_CLONE_PATH: $CI_BUILDS_DIR/$CI_PIPELINE_ID - script: - - echo 'Done.' - - -# Stage: lint -mcg-cmake-lint: - stage: lint - needs: ["mcg-download"] - script: - - for f in $(find {cgcollector,graph,pgis} -name "CMakeLists.txt"); do cmake-format --check $f || exit 1; done ; cmake-format --check CMakeLists.txt - - for f in $(find ./cmake -type f); do cmake-format --check $f || exit 1; done - - -# Stage: build-deps -build-dependencies: - stage: build-deps - needs: ["mcg-download"] - script: - - ./build_submodules.sh - -# Stage: configure-test -configure-custom-md: - stage: configure-test - needs: ["mcg-download"] - script: - - test -d graph/include/metadata/custom || exit 1 - - echo "// Empty test header" > graph/include/metadata/custom/TestHeader.h - - cmake -S . -B build-configure-md -G Ninja -DCMAKE_BUILD_TYPE=Debug - -DMETACG_BUILD_PGIS=OFF - -DMETACG_BUILD_CGCOLLECTOR=OFF - -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON - -DMETACG_BUILD_UNIT_TESTS=OFF - - grep "TestHeader.h" "build-configure-md/graph/include/metadata/CustomMD.h" || exit 1 - - rm -f graph/include/metadata/custom/TestHeader.h - -# Stage: build -build-mcg-no-tests: - <<: *job-setup - stage: build - needs: ["build-dependencies", "configure-custom-md"] - script: - - module load clang/$LLVM - - cmake -S . -B $MCG_BUILD-no-tests -G Ninja -DCMAKE_BUILD_TYPE=Debug - -DCMAKE_INSTALL_PREFIX=/tmp/metacg/$MCG_BUILD-no-tests - -DCUBE_DIR=$(dirname $(which cube_info))/.. - -DEXTRAP_INCLUDE=./extern/install/extrap/include - -DEXTRAP_LIB=./extern/install/extrap/lib - -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON - -DMETACG_BUILD_UNIT_TESTS=OFF - - cmake --build $MCG_BUILD-no-tests --parallel - - module purge - -build-mcg-stripped-all: - <<: *job-setup - stage: build - needs: ["build-dependencies", "configure-custom-md"] - script: - - module load clang/$LLVM - - cmake -S . -B $MCG_BUILD-no-all -G Ninja -DCMAKE_BUILD_TYPE=Debug - -DCMAKE_INSTALL_PREFIX=/tmp/metacg/$MCG_BUILD-dflt - -DCUBE_DIR=$(dirname $(which cube_info))/.. - -DEXTRAP_INCLUDE=./extern/install/extrap/include - -DEXTRAP_LIB=./extern/install/extrap/lib - -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON - - cmake --build $MCG_BUILD-no-all --parallel - - module purge - -build-mcg-stripped-pgis: - <<: *job-setup - stage: build - needs: ["build-dependencies", "configure-custom-md"] - script: - - module load clang/$LLVM - - cmake -S . -B $MCG_BUILD-no-cgcollector -G Ninja -DCMAKE_BUILD_TYPE=Debug - -DCMAKE_INSTALL_PREFIX=/tmp/metacg/$MCG_BUILD-w_pgis - -DCUBE_DIR=$(dirname $(which cube_info))/.. - -DEXTRAP_INCLUDE=./extern/install/extrap/include - -DEXTRAP_LIB=./extern/install/extrap/lib - -DMETACG_BUILD_PGIS=ON - -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON - - cmake --build $MCG_BUILD-no-cgcollector --parallel - - module purge - -build-mcg-stripped-cgcollector: - <<: *job-setup - stage: build - needs: ["build-dependencies", "configure-custom-md"] - script: - - module load clang/$LLVM - - cmake -S . -B $MCG_BUILD-no-pgis -G Ninja -DCMAKE_BUILD_TYPE=Debug - -DCMAKE_INSTALL_PREFIX=/tmp/metacg/$MCG_BUILD-w_cgcollector - -DCUBE_DIR=$(dirname $(which cube_info))/.. - -DEXTRAP_INCLUDE=./extern/install/extrap/include - -DEXTRAP_LIB=./extern/install/extrap/lib - -DMETACG_BUILD_CGCOLLECTOR=ON - -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON - - cmake --build $MCG_BUILD-no-pgis --parallel - - module purge - -build-mcg: - <<: *job-setup - stage: build - needs: ["build-dependencies", "configure-custom-md"] - script: - - module load clang/$LLVM - - source .venv/bin/activate - - cmake -S . -B $MCG_BUILD -G Ninja -DCMAKE_BUILD_TYPE=Debug - -DCMAKE_INSTALL_PREFIX=/tmp/metacg/$MCG_BUILD-w_all - -DLLVM_EXTERNAL_LIT=$(which lit) - -DCUBE_DIR=$(dirname $(which cube_info))/.. - -DEXTRAP_INCLUDE=./extern/install/extrap/include - -DEXTRAP_LIB=./extern/install/extrap/lib - -DMETACG_BUILD_PGIS=ON - -DMETACG_BUILD_CGCOLLECTOR=ON - -DMETACG_BUILD_GRAPH_TOOLS=ON - -DMETACG_BUILD_CGPATCH=ON - -DCGPATCH_USE_MPI=OFF - -DMETACG_BUILD_PYMETACG=ON - -DMETACG_BUILD_PYMETACG_TESTS=ON - -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON - - cmake --build $MCG_BUILD --parallel - - module purge - -check-test-format: - <<: *job-setup - stage: test - needs: ["build-mcg"] - script: - - ./checkTests.sh $MCG_BUILD/tools/cgformat/cgformat - -# Stage: test -test-cgc: - <<: *job-setup - stage: test - needs: ["build-mcg"] - script: - - module load clang/$LLVM - - cd cgcollector/test - - mkdir -p log - - bash run_format_one_test.sh -b $MCG_BUILD - - bash run_format_two_test.sh -b $MCG_BUILD - #- bash run_aa_test.sh -b $MCG_BUILD - -test-graphlib: - <<: *job-setup - stage: test - needs: ["build-mcg"] - script: - - module load clang/$LLVM - - cd $MCG_BUILD/graph/test/unit && ./libtests --gtest_output=xml:gcc-$GCC-llvm-$LLVM.xml - - GCCv=$GCC && LLVMv=$LLVM && sed -i -e "s/Test\"/Test-gcc-$GCCv-llvm-$LLVMv\"/g" gcc-$GCC-llvm-$LLVM.xml - artifacts: - when: always - paths: - - $MCG_BUILD/graph/test/unit/gcc-$GCC-llvm-$LLVM.xml - reports: - junit: $MCG_BUILD/graph/test/unit/gcc-$GCC-llvm-$LLVM.xml - -test-pgis: - <<: *job-setup - stage: test - needs: ["build-mcg"] - script: - - module load clang/$LLVM - - cd $MCG_BUILD/pgis/test/unit && ./pgistests --gtest_output=xml:gcc-$GCC-llvm-$LLVM.xml - - GCCv=$GCC && LLVMv=$LLVM && sed -i -e "s/Test\"/Test-gcc-$GCCv-llvm-$LLVMv\"/g" gcc-$GCC-llvm-$LLVM.xml - artifacts: - when: always - paths: - - $MCG_BUILD/pgis/test/unit/gcc-$GCC-llvm-$LLVM.xml - reports: - junit: $MCG_BUILD/pgis/test/unit/gcc-$GCC-llvm-$LLVM.xml - -test-pymetacg: - <<: *job-setup - stage: test - needs: ["build-mcg"] - script: - - cd $MCG_BUILD/pymetacg/tests - - ctest . --verbose - -test-cgpatch-lit: - <<: *job-setup - stage: test - needs: ["build-mcg"] - parallel: - matrix: - - GCC: 11 - LLVM: ["15.0.7", "16.0.6", "17.0.6", "18.1.8"] - script: - - module load clang/$LLVM - - cmake --install $MCG_BUILD - - cmake --build $MCG_BUILD --target pass-tests - - cmake --build $MCG_BUILD --target pass-tests-lto - - cmake --build $MCG_BUILD --target pass-tests-optimized - - cmake --build $MCG_BUILD --target pass-tests-optimized-lto - -# Stage: integration-test -test-graphlib-merge: - <<: *job-setup - stage: integration-test - needs: ["build-mcg"] - script: - - module load clang/$LLVM - - cd graph/test/integration/CallgraphMerge - - ./MergeTestRunner.sh -b $MCG_BUILD - -test-cgvalidate: - <<: *job-setup - stage: integration-test - needs: ["build-mcg"] - script: - - module load clang/$LLVM - - cd cgcollector/test/integration - - ./runner.sh $MCG_BUILD - -test-cgformat: - <<: *job-setup - stage: test - needs: [ "build-mcg" ] - script: - - cd $MCG_BUILD/tools/cgformat/test - - ./run_cgformat_tests.sh - -test-basic-pgis: - <<: *job-setup - stage: integration-test - needs: ["build-mcg"] - script: - - module load clang/$LLVM - - cd pgis - - export LD_LIBRARY_PATH=$PWD/../extern/install/extrap/lib:$LD_LIBRARY_PATH - - cd test/integration - - ./basicTestRunner.sh -b $MCG_BUILD - -test-static-pgis: - <<: *job-setup - stage: integration-test - needs: ["build-mcg"] - script: - - module load clang/$LLVM - - cd pgis - - export LD_LIBRARY_PATH=$PWD/../extern/install/extrap/lib:$LD_LIBRARY_PATH - - cd test/integration - - ./selectionTestRunner.sh -t static -b $MCG_BUILD - -test-modeling-pgis: - <<: *job-setup - stage: integration-test - needs: ["build-mcg"] - script: - - module load clang/$LLVM - - cd pgis - - export LD_LIBRARY_PATH=$PWD/../extern/install/extrap/lib:$LD_LIBRARY_PATH - - cd test/integration - - ./selectionTestRunner.sh -t modeling -b $MCG_BUILD - -test-dynamic-pgis: - <<: *job-setup - stage: integration-test - needs: ["build-mcg"] - script: - - module load clang/$LLVM - - cd pgis - - export LD_LIBRARY_PATH=$PWD/../extern/install/extrap/lib:$LD_LIBRARY_PATH - - cd test/integration - - ./selectionTestRunner.sh -t dynamic -b $MCG_BUILD - -test-imbalance-pgis: - <<: *job-setup - stage: integration-test - needs: ["build-mcg"] - script: - - module load clang/$LLVM - - cd pgis - - export LD_LIBRARY_PATH=$PWD/../extern/install/extrap/lib:$LD_LIBRARY_PATH - - cd test/integration - - ./selectionTestRunner.sh -t imbalance -b $MCG_BUILD - -test-target-collector: - <<: *job-setup - stage: integration-test - needs: ["build-mcg"] - script: - - module load clang/$LLVM - - cmake --install $MCG_BUILD - - cd graph/test/integration/TargetCollector - - ./TestRunner.sh -b $MCG_BUILD - -test-cgpatch-integration: - <<: *job-setup - parallel: - matrix: - - GCC: 11 - LLVM: [15.0.7, 16.0.6, 17.0.6, 18.1.8] - needs: ["build-mcg"] - script: - - module load clang/$LLVM - - cd $MCG_BUILD/tools/cgpatch/test/integration/ - - ./CGPIntegrationRunner.sh -b $MCG_BUILD - -# Stage: install -install-mcg: - <<: *job-setup - stage: install - needs: ["build-mcg"] - script: - - cmake --install $MCG_BUILD-no-all - - cd graph/test/install - - stat /tmp/metacg/$MCG_BUILD-dflt/lib64/libmetacg.so - - CMAKE_PREFIX_PATH=/tmp/metacg/$MCG_BUILD-dflt/lib64/cmake/metacg cmake -S . -B $MCG_BUILD-no-all/install-test -DCMAKE_INSTALL_PREFIX=/tmp/metacg-user/test - - cmake --build $MCG_BUILD-no-all/install-test --parallel - - rm -r /tmp/metacg/$MCG_BUILD-dflt - -install-pgis: - <<: *job-setup - stage: install - needs: ["build-mcg"] - script: - - module load clang/$LLVM - - export LD_LIBRARY_PATH=$PWD/extern/install/extrap/lib:$LD_LIBRARY_PATH - - cmake --install $MCG_BUILD-no-cgcollector - - stat /tmp/metacg/$MCG_BUILD-w_pgis/bin/pgis_pira - - export LD_LIBRARY_PATH=/tmp/metacg/$MCG_BUILD-w_pgis/lib64:/tmp/metacg/$MCG_BUILD-w_pgis/lib:$LD_LIBRARY_PATH - - /tmp/metacg/$MCG_BUILD-w_pgis/bin/pgis_pira --help - - rm -r /tmp/metacg/$MCG_BUILD-w_pgis - -install-cgcollector: - <<: *job-setup - stage: install - needs: ["build-mcg"] - script: - - module load clang/$LLVM - - export LD_LIBRARY_PATH=$PWD/extern/install/extrap/lib:$LD_LIBRARY_PATH - - cmake --install $MCG_BUILD-no-pgis - - stat /tmp/metacg/$MCG_BUILD-w_cgcollector/bin/cgcollector - - export LD_LIBRARY_PATH=/tmp/metacg/$MCG_BUILD-w_cgcollector/lib64:/tmp/metacg/$MCG_BUILD-w_cgcollector/lib:$LD_LIBRARY_PATH - - /tmp/metacg/$MCG_BUILD-w_cgcollector/bin/cgcollector --help - - rm -r /tmp/metacg/$MCG_BUILD-w_cgcollector diff --git a/CMakeLists.txt b/CMakeLists.txt index e43a59d2..255d7fb9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,7 @@ endif() # End of require out-of-source builds # Project information -set(METACG_VERSION 0.9.0) +set(METACG_VERSION 1.00.0) project( MetaCG VERSION ${METACG_VERSION} @@ -55,6 +55,7 @@ option( if(METACG_BUILD_UNIT_TESTS) enable_testing() + include(GoogleTest) endif() include(ToolchainOptions) @@ -76,12 +77,7 @@ if(NOT endif() # Configure the file that holds version information -configure_file(Config.h.in config.h) - -if(METACG_BUILD_UNIT_TESTS) - # Provide googletest library - include(GoogleTest) -endif() +configure_file(Config.h.in include/metacg/config.h) # Component options MetaCG graph library will always be built. The actual graph implementation add_subdirectory(graph) @@ -95,6 +91,29 @@ option( if(METACG_BUILD_GRAPH_TOOLS) # New MetaCG tools based on the graphlib include(ClangLLVM) + + # Determine if we can build CaGe + if(${LLVM_PACKAGE_VERSION} + GREATER + 14 + ) + set(BUILD_CAGE ON) + + option( + CAGE_USE_METAVIRT + ON + "Rely on metavirt for virtual call handling" + ) + + if(${CAGE_USE_METAVIRT}) + include(metavirt) + endif() + + else() + set(BUILD_CAGE OFF) + message(STATUS "Skipping CaGe: requires LLVM version 16 or higher") + endif() + add_subdirectory(tools) endif() @@ -129,6 +148,7 @@ if(METACG_BUILD_CGPATCH) message(STATUS "Skipping CGPatch: requires LLVM version 15 or higher") endif() endif() + # PIRA analyzer Should PGIS be built option( METACG_BUILD_PGIS @@ -191,3 +211,28 @@ option( if(METACG_BUILD_PYMETACG) add_subdirectory(pymetacg) endif() + +add_subdirectory(utils) + +# Build fortran collector +option( + METACG_BUILD_CGFCOLLECTOR + "On or off" + OFF +) + +if(METACG_BUILD_CGFCOLLECTOR) + if(NOT METACG_BUILD_GRAPH_TOOLS) + message(FATAL_ERROR "CGFCollector requires METACG_BUILD_GRAPH_TOOLS=ON to build") + endif() + + if(${LLVM_PACKAGE_VERSION} + GREATER_EQUAL + 18 + ) + include(FlangLLVM) + add_subdirectory(cgfcollector) + else() + message(STATUS "Skipping CGFCollector: requires LLVM version 18 or higher") + endif() +endif() diff --git a/LICENSE.txt b/LICENSE.txt index 831cca0d..48708431 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2020-2025, Jan-Patrick Lehr +Copyright (c) 2020-2026, Jan-Patrick Lehr All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/README.md b/README.md index 4597d891..3fa4591d 100644 --- a/README.md +++ b/README.md @@ -18,24 +18,15 @@ Refer to the [pymetacg README](pymetacg/README.md) for more information how to b ## Requirements and Building -MetaCG consists of the graph library, a CG construction tool, and an example analysis tool. -The graph library is always built, while the CGCollector and the PGIS tool can be disabled at configure time. - -We test MetaCG internally using GCC 11 for Clang/LLVM 13, 14, 15, 16, 17 and 18. +MetaCG consists of the graph library, CG construction tools, Python bindings, and an example analysis tool. +The graph library is always built, while the other components can be selectively built. +In the GitHub testing, MetaCG is built using GCC 12 and tests against LLVM versions 18 and 20. Other version combinations *may* work. **Build Requirements (for graph lib)** - nlohmann/json library [github](https://github.com/nlohmann/json) - spdlog [github](https://github.com/gabime/spdlog) -**Additional Build Requirements (for full build)** -- Clang/LLVM version 10 (and above) -- Cube 4.5 [scalasca.org](https://www.scalasca.org/software/cube-4.x/download.html) -- Extra-P 3.0 [.tar.gz](http://apps.fz-juelich.de/scalasca/releases/extra-p/extrap-3.0.tar.gz) -- cxxopts [github](https://github.com/jarro2783/cxxopts) -- PyQt5 -- matplotlib - ### Building #### Graph Library Only @@ -57,8 +48,19 @@ $> cmake --install build You can configure MetaCG to also build CGCollector, PGIS and other tools based on the graphlib (cgconvert, cgformat, cgmerge2). This requires additional dependencies. -Clang/LLVM (in a supported version) are assumed to be available on the system. -Extra-P and Cube library can be built using the `build_submodules.sh` script provided in the repository, though the script is not tested outside of our CI system. + +**Additional Build Requirements (for full build)** +- Clang/LLVM version 13 (and above) +- Cube 4.5 [scalasca.org](https://www.scalasca.org/software/cube-4.x/download.html) +- Extra-P 3.0 [.tar.gz](http://apps.fz-juelich.de/scalasca/releases/extra-p/extrap-3.0.tar.gz) +- cxxopts [github](https://github.com/jarro2783/cxxopts) +- PyQt5 +- matplotlib + + +Clang/LLVM (in a supported version) are assumed to be available on the system and is required for the CG construction tools. + +For the PGIS tool, Extra-P and the Cube library can be built using the `build_submodules.sh` script provided in the repository, though the script is not tested outside of our CI system. It builds and installs the dependencies into `./extern`. ```{.sh} diff --git a/cgcollector/lib/include/JSONManager.h b/cgcollector/lib/include/JSONManager.h index 09c7d865..0778fac4 100644 --- a/cgcollector/lib/include/JSONManager.h +++ b/cgcollector/lib/include/JSONManager.h @@ -2,7 +2,7 @@ #define CGCOLLECTOR_JSONMANAGER_H #include "MetaInformation.h" -#include "config.h" +#include "metacg/config.h" #include "AliasAnalysis.h" #include "nlohmann/json.hpp" diff --git a/cgcollector/lib/include/MetaCollector.h b/cgcollector/lib/include/MetaCollector.h index ed61b69e..343bd444 100644 --- a/cgcollector/lib/include/MetaCollector.h +++ b/cgcollector/lib/include/MetaCollector.h @@ -1,3 +1,8 @@ +/** +* File: MetaCollector.h +* License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at +* https://github.com/tudasc/metacg/LICENSE.txt + */ #ifndef CGCOLLECTOR_METACOLLECTOR_H #define CGCOLLECTOR_METACOLLECTOR_H diff --git a/cgcollector/lib/include/helper/ASTHelper.h b/cgcollector/lib/include/helper/ASTHelper.h index 276cef18..7b82808f 100644 --- a/cgcollector/lib/include/helper/ASTHelper.h +++ b/cgcollector/lib/include/helper/ASTHelper.h @@ -1,3 +1,8 @@ +/** +* File: ASTHelper.h +* License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at +* https://github.com/tudasc/metacg/LICENSE.txt + */ #ifndef CGCOLLECTOR_HELPER_ASTHELPER_H #define CGCOLLECTOR_HELPER_ASTHELPER_H diff --git a/cgcollector/lib/include/helper/common.h b/cgcollector/lib/include/helper/common.h index c487205c..75f712e6 100644 --- a/cgcollector/lib/include/helper/common.h +++ b/cgcollector/lib/include/helper/common.h @@ -1,3 +1,8 @@ +/** +* File: common.h +* License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at +* https://github.com/tudasc/metacg/LICENSE.txt + */ #ifndef CGCOLLECTOR_HELPER_COMMON_H #define CGCOLLECTOR_HELPER_COMMON_H diff --git a/cgcollector/lib/src/CallgraphToJSON.cpp b/cgcollector/lib/src/CallgraphToJSON.cpp index 77dd1c33..cd422f69 100644 --- a/cgcollector/lib/src/CallgraphToJSON.cpp +++ b/cgcollector/lib/src/CallgraphToJSON.cpp @@ -1,4 +1,4 @@ -#include "config.h" +#include "metacg/config.h" #include "CallgraphToJSON.h" #include "helper/common.h" diff --git a/cgcollector/test/CGSimpleTester.cpp b/cgcollector/test/CGSimpleTester.cpp deleted file mode 100644 index e95c8bff..00000000 --- a/cgcollector/test/CGSimpleTester.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include "nlohmann/json.hpp" - -#include -#include - -int main(int argc, char** argv) { - if (argc != 3) { - std::cerr << "Usage: " << argv[0] << " groundtruth.json collector-result.ipcg" << std::endl; - return -1; - } - - std::cout << "Running test for " << argv[1] << " == " << argv[2] << std::endl; - - nlohmann::json groundTruth; - { - std::ifstream file(argv[1]); - file >> groundTruth; - } - - nlohmann::json collectorResult; - { - std::ifstream file(argv[2]); - file >> collectorResult; - } - - if (groundTruth == collectorResult) { - std::cout << "Test success" << std::endl; - - return 0; - } else { - std::cerr << "Test failure" << std::endl; - - return 1; - } -} diff --git a/cgcollector/test/CMakeLists.txt b/cgcollector/test/CMakeLists.txt index 2db9678e..0be3a01e 100644 --- a/cgcollector/test/CMakeLists.txt +++ b/cgcollector/test/CMakeLists.txt @@ -1,35 +1,6 @@ set(PROJECT_NAME collector-tests) set(TARGETS_EXPORT_NAME ${PROJECT_NAME}-target) -add_executable(cgsimpletester CGSimpleTester.cpp) - -add_executable(mcgtester MCGTester.cpp) - -add_executable(stdtester STDTester.cpp) - -# register_to_clang_tidy(cgsimpletester) register_to_clang_tidy(mcgtester) - -add_json(cgsimpletester) -default_compile_options(cgsimpletester) - -add_json(mcgtester) -add_collector_include(mcgtester) -add_collector_lib(mcgtester) -default_compile_options(mcgtester) - -add_json(stdtester) -add_collector_include(stdtester) -add_collector_lib(stdtester) -default_compile_options(stdtester) - -install( - TARGETS cgsimpletester - mcgtester - stdtester - EXPORT ${TARGETS_EXPORT_NAME} - RUNTIME DESTINATION bin -) - configure_package_config_file( ${METACG_Directory}/cmake/Config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake INSTALL_DESTINATION lib/cmake diff --git a/cgcollector/test/MCGTester.cpp b/cgcollector/test/MCGTester.cpp deleted file mode 100644 index 1f5451f4..00000000 --- a/cgcollector/test/MCGTester.cpp +++ /dev/null @@ -1,50 +0,0 @@ - -#include "JSONManager.h" - -#include - -int main(int argc, char** argv) { - if (argc < 3) { - std::cerr << "Too few argument.\nUsage: ./" << argv[0] << " groundtruth.mcg result.mcg" << std::endl; - return -1; - } - - nlohmann::json gt; - nlohmann::json gen; - - FuncMapT gtFuncMap; - FuncMapT genFuncMap; - - // Read the ground truth, i.e., expected content - gt = buildFromJSONv2(gtFuncMap, argv[1], nullptr); - // Read the generated result - gen = buildFromJSONv2(genFuncMap, argv[2], nullptr); - - // Test for equality of result and groundtruth - // Test same functions in both [set of keys equal] - std::unordered_set unfound; - for (const auto& [k, v] : gtFuncMap) { - if (genFuncMap.find(k) == genFuncMap.end()) { - unfound.insert(k); - } - } - if (!unfound.empty()) { - std::cerr << "Unfound keys: " << unfound.size() << std::endl; - return -2; - } - // Test that entries for keys are equal w.r.t. structure - for (const auto& [k, v] : gtFuncMap) { - const auto genValue = genFuncMap[k]; - if (!v.compareStructure(genValue)) { - std::cerr << "Structural difference for " << k << std::endl; - return -3; - } - // Test that meta information for the same keys are present and they are equal - if (!v.compareMeta(genValue)) { - std::cerr << "Meta difference for " << k << std::endl; - return -4; - } - } - - return 0; -} diff --git a/cgcollector/test/STDTester.cpp b/cgcollector/test/STDTester.cpp deleted file mode 100644 index 191f67f2..00000000 --- a/cgcollector/test/STDTester.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include "JSONManager.h" - -#include - -int main(int argc, char** argv) { - if (argc < 3) { - std::cerr << "Too few argument.\nUsage: ./" << argv[0] << " gt.json result.mcg" << std::endl; - return -1; - } - - nlohmann::json gt; - - FuncMapT genFuncMap; - - // Read the ground truth, i.e., expected content - readIPCG(argv[1], gt); - if (gt.is_null()) { - std::cerr << "Ground truth is null." << std::endl; - return -1; - } - // std::cout << gt.dump() << std::endl; - // Read the generated result - buildFromJSONv2(genFuncMap, argv[2], nullptr); - for (const auto& element : gt.items()) { - const std::string name = element.key(); - const std::set expected = element.value()["required"]; - const bool strict = element.value()["strict"]; - const std::set blacklist = element.value()["blacklist"]; - const auto it = genFuncMap.find(name); - if (it == genFuncMap.end()) { - std::cerr << "Required function not found: " << name << std::endl; - return -2; - } - if (!it->second.check_callees(expected, blacklist, strict)) { - std::cerr << "Function " << name << " does not call the expected functions" << std::endl; - return -3; - } - } - - return 0; -} diff --git a/cgcollector/test/run_aa_test.sh b/cgcollector/test/run_aa_test.sh deleted file mode 100755 index f066588a..00000000 --- a/cgcollector/test/run_aa_test.sh +++ /dev/null @@ -1,135 +0,0 @@ -#!/usr/bin/env bash - -. ./testBase.sh - -#if [ command -v $testerExe ]; then -if [[ $(type -P $testerExe) ]]; then - echo "The CGSimpleTester binary (cgsimpletester) could not be found in path, testing with relative path." -fi -stat ../../${build_dir}/cgcollector/test/mcgtester >>log/testrun.log 2>&1 -if [ $? -eq 1 ]; then - echo "The file seems also non-present in ../${build_dir}/test. Aborting test. Failure! Please build the tester first." - exit 1 -else - testerExe=../../${build_dir}/cgcollector/test/mcgtester -fi - -if [[ $(type -P $cgcollectorExe) ]]; then - echo "No cgcollector in PATH. Trying relative path ../${build_dir}/tools" -fi -stat ../../${build_dir}/cgcollector/tools/cgcollector >>log/testrun.log 2>&1 -if [ $? -eq 1 ]; then - echo "The file seems also non-present in ../${build_dir}/tools. Aborting test. Failure! Please build the collector first." - exit 1 -else - cgcollectorExe=../../${build_dir}/cgcollector/tools/cgcollector -fi - -if [[ $(type -P $cgmergeExe) ]]; then - echo "No cgcollector in PATH. Trying relative path ../${build_dir}/tools" -fi -stat ../../${build_dir}/cgcollector/tools/cgmerge >>log/testrun.log 2>&1 -if [ $? -eq 1 ]; then - echo "The file seems also non-present in ../${build_dir}/tools. Aborting test. Failure! Please build the collector first." - exit 1 -else - cgmergeExe=../../${build_dir}/cgcollector/tools/cgmerge -fi - -# Multi-file tests -multiTests=(0042 0043 0044 0050 0053 0060) - -# Multi-file aa tests -multiTestsAA=(0070 0071 0072 0214 0240 0241) - - -fails=0 - -# Single File -echo " --- Running single file tests [file format version 2.0 with Alias Analysis]---" -echo " --- Running basic tests ---" -testGlob="./input/singleTU/*.cpp" -for tc in ${testGlob}; do - echo "Running test ${tc}" - applyFileFormatTwoToSingleTUWithAA ${tc} - fail=$? - fails=$((fails + fail)) -done -echo "Single file test failures: $fails" - -# Single File and full Ctor/Dtor coverage -echo -e "\n --- Running single file full ctor/dtor tests ---" -testGlob="./input/allCtorDtor/*.cpp" -for tc in ${testGlob}; do - echo "Running test ${tc}" - applyFileFormatTwoToSingleTUWithAA ${tc} - fail=$? - fails=$((fails + fail)) -done -echo "Single file test failures: $fails" - -# Single File and functionPointers -echo -e "\n --- Running single file functionPointers tests ---" -testGlob="./input/functionPointers/*.cpp" -for tc in ${testGlob}; do - echo "Running test ${tc}" - applyFileFormatTwoToSingleTUWithAA ${tc} - fail=$? - fails=$((fails + fail)) -done -echo "Single file test failures: $fails" - -# Single File metaCollectors -echo -e "\n --- Running single file metaCollectors tests ---" -testGlob="./input/metaCollectors/numStatements/*.cpp" -for tc in ${testGlob}; do - echo "Running test ${tc}" - applyFileFormatTwoToSingleTUWithAA ${tc} - fail=$? - fails=$((fails + fail)) -done -echo "Single file test failures: $fails" - -# Single File virtualCalls -echo -e "\n --- Running single file virtualCalls tests ---" -testGlob="./input/virtualCalls/*.cpp" -for tc in ${testGlob}; do - echo "Running test ${tc}" - applyFileFormatTwoToSingleTUWithAA ${tc} - fail=$? - fails=$((fails + fail)) -done -echo "Single file test failures: $fails" - -# Single File AA -echo -e "\n --- Running single file alias analyis tests ---" -testGlob="./input/singleTUAA/*.cpp" -for tc in ${testGlob}; do - echo "Running test ${tc}" - applyFileFormatTwoToSingleTUWithAA ${tc} - fail=$? - fails=$((fails + fail)) -done -echo "Single file test failures: $fails" - -# Multi File -echo -e "\n --- Running multi file tests with AA ---" -for tc in "${multiTests[@]}"; do - echo "Running test ${tc}" - # Input files - applyFileFormatTwoToMultiTUWithAA ${tc} - fail=$? - fails=$((fails + fail)) -done -echo "Multi file test failures: $fails" -for tc in "${multiTestsAA[@]}"; do - echo "Running test ${tc}" - # Input files - applyFileFormatTwoToMultiTUWithAA ${tc} - fail=$? - fails=$((fails + fail)) -done -echo "Multi file test failures: $fails" - -echo -e "$fails failures occured when running tests" -exit $fails diff --git a/cgcollector/test/run_format_one_test.sh b/cgcollector/test/run_format_one_test.sh deleted file mode 100755 index 74c68388..00000000 --- a/cgcollector/test/run_format_one_test.sh +++ /dev/null @@ -1,124 +0,0 @@ -#!/usr/bin/env bash - -# Holds definitions for default programs and test functions -. ./testBase.sh - -#if [ command -v $testerExe ]; then -if [[ $(type -P $testerExe) ]]; then - echo "The CGSimpleTester binary (cgsimpletester) could not be found in path, testing with relative path." -fi -stat ../../${build_dir}/cgcollector/test/cgsimpletester >>log/testrun.log 2>&1 -if [ $? -eq 1 ]; then - echo "The file seems also non-present in ../${build_dir}/test. Aborting test. Failure! Please build the tester first." - exit 1 -else - testerExe=../../${build_dir}/cgcollector/test/cgsimpletester -fi - -if [[ $(type -P $cgcollectorExe) ]]; then - echo "No cgcollector in PATH. Trying relative path ../${build_dir}/tools" -fi -stat ../../${build_dir}/cgcollector/tools/cgcollector >>log/testrun.log 2>&1 -if [ $? -eq 1 ]; then - echo "The file seems also non-present in ../${build_dir}/tools. Aborting test. Failure! Please build the collector first." - exit 1 -else - cgcollectorExe=../../${build_dir}/cgcollector/tools/cgcollector -fi - -if [[ $(type -P $cgmergeExe) ]]; then - echo "No cgcollector in PATH. Trying relative path ../${build_dir}/tools" -fi -stat ../../${build_dir}/cgcollector/tools/cgmerge >>log/testrun.log 2>&1 -if [ $? -eq 1 ]; then - echo "The file seems also non-present in ../${build_dir}/tools. Aborting test. Failure! Please build the collector first." - exit 1 -else - cgmergeExe=../../${build_dir}/cgcollector/tools/cgmerge -fi - -# Multi-file tests -multiTests=(0042 0043 0044 0050 0053 0060) - -fails=0 - -# Single File -echo " --- Running single file tests [file format version 1.0]---" -echo " --- Running basic tests ---" -testGlob="./input/singleTU/*.cpp" -for tc in ${testGlob}; do - echo "Running test ${tc}" - applyFileFormatOneToSingleTU ${tc} - fail=$? - fails=$((fails + fail)) -done -echo "Single file test failures: $fails" - -# Single File and full Ctor/Dtor coverage -echo -e "\n --- Running single file full ctor/dtor tests ---" -testGlob="./input/allCtorDtor/*.cpp" -for tc in ${testGlob}; do - echo "Running test ${tc}" - applyFileFormatOneToSingleTU ${tc} "--capture-ctors-dtors" - fail=$? - fails=$((fails + fail)) -done -echo "Single file test failures: $fails" - -# Single File and full Ctor/Dtor coverage (--no-infer-ctor-dtor-calls) -echo -e "\n --- Running single file full ctor/dtor tests (--no-infer-ctor-dtor-calls) ---" -testGlob="./input/allCtorDtor/*.cpp" -for tc in ${testGlob}; do - echo "Running test ${tc}" - applyFileFormatOneToSingleTU ${tc} "--capture-ctors-dtors --no-infer-ctor-dtor-calls" "noinfer" - fail=$? - fails=$((fails + fail)) -done -echo "Single file test failures: $fails" - -# Single File functionPointers -echo -e "\n --- Running single file functionPointers tests ---" -testGlob="./input/functionPointers/*.cpp" -for tc in ${testGlob}; do - echo "Running test ${tc}" - applyFileFormatOneToSingleTU ${tc} - fail=$? - fails=$((fails + fail)) -done -echo "Single file test failures: $fails" - -# Single File metaCollectors -echo -e "\n --- Running single file metaCollectors tests ---" -testGlob="./input/metaCollectors/numStatements/*.cpp" -for tc in ${testGlob}; do - echo "Running test ${tc}" - applyFileFormatOneToSingleTU ${tc} - fail=$? - fails=$((fails + fail)) -done -echo "Single file test failures: $fails" - -# Single File virtual calls -echo -e "\n --- Running single file virtualCalls tests ---" -testGlob="./input/virtualCalls/*.cpp" -for tc in ${testGlob}; do - echo "Running test ${tc}" - applyFileFormatOneToSingleTU ${tc} - fail=$? - fails=$((fails + fail)) -done -echo "Single file test failures: $fails" - -# Multi File -echo -e "\n --- Running multi file tests ---" -for tc in "${multiTests[@]}"; do - echo "Running test ${tc}" - # Input files - applyFileFormatOneToMultiTU ${tc} - fail=$? - fails=$((fails + fail)) -done -echo "Multi file test failures: $fails" - -echo -e "$fails failures occurred when running tests" -exit $fails diff --git a/cgcollector/test/run_format_two_test.sh b/cgcollector/test/run_format_two_test.sh index f74c5e47..ac44d750 100755 --- a/cgcollector/test/run_format_two_test.sh +++ b/cgcollector/test/run_format_two_test.sh @@ -2,16 +2,15 @@ . ./testBase.sh -#if [ command -v $testerExe ]; then if [[ $(type -P $testerExe) ]]; then - echo "The CGSimpleTester binary (cgsimpletester) could not be found in path, testing with relative path." + echo "The cgdiff binary (cgdiff) could not be found in path, testing with relative path." fi -stat ../../${build_dir}/cgcollector/test/mcgtester >>log/testrun.log 2>&1 +stat ../../${build_dir}/tools/cgdiff/cgdiff >> log/testrun.log 2>&1 if [ $? -eq 1 ]; then - echo "The file seems also non-present in ../${build_dir}/test. Aborting test. Failure! Please build the tester first." + echo "The file seems also non-present in ../${build_dir}/cgdiff/. Aborting test. Failure! Please build CGDiff first." exit 1 else - testerExe=../../${build_dir}/cgcollector/test/mcgtester + testerExe="../../${build_dir}/tools/cgdiff/cgdiff -o $diffFile" fi if [[ $(type -P $cgcollectorExe) ]]; then diff --git a/cgcollector/test/testBase.sh b/cgcollector/test/testBase.sh index 9e559fd0..845ccd9f 100644 --- a/cgcollector/test/testBase.sh +++ b/cgcollector/test/testBase.sh @@ -1,51 +1,64 @@ cgcollectorExe=cgcollector -testerExe=cgsimpletester cgmergeExe=cgmerge build_dir=build # may be changed with opt 'b' +diffFile=$(mktemp temp.json.XXX) +export diffFile timeStamp=$(date +%s) : ${CI_CONCURRENT_ID:=$timeStamp} mkdir -p log -# Function to invoke the CGCollector to a target source code -# Param 1: The relative path name to the test case. -# Param 2: Parameter to steer certain features on / off -function applyFileFormatOneToSingleTU { - testCaseFile=$1 - addFlags=$2 - gtvariant=$3 - fail=0 - - local infix="" - if [[ -n "$gtvariant" ]]; then - infix="${gtvariant}." - fi - - # Set up the different data files, we need: - # - The test case - # - Tehe groundtruth data for reconciling the CG constructed by MetaCG - tfile=$testCaseFile - gfile=${testCaseFile/cpp/${infix}ipcg}-${CI_CONCURRENT_ID} - tgt=${testCaseFile/cpp/${infix}gtipcg} - - echo "Running ${testerExe} on ${tfile}" - $cgcollectorExe --metacg-format-version=1 ${addFlags} --output ${gfile} $tfile -- >>log/testrun.log 2>&1 - cat $gfile | python3 -m json.tool > ${gfile}_ - mv ${gfile}_ ${gfile} - $testerExe $tgt $gfile >>log/testrun.log 2>&1 - - if [ $? -ne 0 ]; then - echo "Failure for file: $gfile. Keeping generated file for inspection" - fail=$((fail + 1)) - else - rm $gfile - fi - - return $fail +only_metadata_diff() { + local diffFile="$1" + + if [ ! -f "$diffFile" ]; then + echo "Diff file not found: $diffFile" + return 2 + fi + + # Iterate over all nodes + local nodes + nodes=$(jq -r '.diff.nodeDiffs | keys[]' "$diffFile") || return 2 + + for node in $nodes; do + # Extract diffType array + local diffTypes + diffTypes=$(jq -r ".diff.nodeDiffs[\"$node\"].diffType[]" "$diffFile") + + # Skip nodes with other types of differences + for dt in $diffTypes; do + if [ "$dt" != "differentMetadata" ]; then + return 1 + fi + done + + # Extract metadataOnlyInA and metadataOnlyInB arrays + local aJson bJson + aJson=$(jq ".diff.nodeDiffs[\"$node\"].metadataOnlyInA" "$diffFile") + bJson=$(jq ".diff.nodeDiffs[\"$node\"].metadataOnlyInB" "$diffFile") + + if ! echo "$bJson" | jq -e --argjson a "$aJson" 'contains($a)' >/dev/null; then + return 1 # some metadata in A is missing or differs in value + fi + done + + return 0 } +jqExpr='if ._CG | type == "object" then + ._CG |= with_entries( + if (.value.meta.numOperations? // false) | type == "object" then + .value.meta.numOperations.numberOfControlFlowOps = "42" + else + . + end + ) + else + . + end' + # Function to invoke the CGCollector with file format version 2 to a target source code # Param 1: The relative path name to the test case. # Param 2: Parameter to steer certain features on / off @@ -66,44 +79,25 @@ function applyFileFormatTwoToSingleTU { tfile=$testCaseFile gfile=${testCaseFile/cpp/${infix}ipcg}-${CI_CONCURRENT_ID} tgt=${testCaseFile/cpp/${infix}gtmcg} + tgt2=$tgt-${CI_CONCURRENT_ID} - echo "Running tester on ${tfile}" $cgcollectorExe --metacg-format-version=2 ${addFlags} --output ${gfile} $tfile -- >>log/testrun.log 2>&1 cat $gfile | python3 -m json.tool > ${gfile}_ mv ${gfile}_ ${gfile} - $testerExe $tgt $gfile >>log/testrun.log 2>&1 - - if [ $? -ne 0 ]; then - echo "Failure for file: $gfile. Keeping generated file for inspection" - fail=$((fail + 1)) - else - rm $gfile - fi - return $fail -} + jq "$jqExpr" $tgt > $tgt2 + jq "$jqExpr" $gfile > $gfile -function applyFileFormatTwoToSingleTUWithAA { - testCaseFile=$1 - addFlags=$2 - fail=0 - - # Set up the different data files, we need: - # - The test case - # - Tehe groundtruth data for reconciling the CG constructed by MetaCG - tfile=$testCaseFile - gfile=${testCaseFile/cpp/ipcg}-${CI_CONCURRENT_ID} - tgt=${testCaseFile/cpp/gtaacg} - - echo "Running tester on ${tfile}" - $cgcollectorExe --metacg-format-version=2 --capture-ctors-dtors --capture-stack-ctors-dtors --enable-AA ${addFlags} --output ${gfile} $tfile -- >>log/testrun.log 2>&1 - cat $gfile | python3 -m json.tool > ${gfile}_ - mv ${gfile}_ ${gfile} - $testerExe $tgt $gfile >>log/testrun.log 2>&1 + $testerExe $tgt2 $gfile >>log/testrun.log 2>&1 if [ $? -ne 0 ]; then - echo "Failure for file: $gfile. Keeping generated file for inspection" - fail=$((fail + 1)) + if only_metadata_diff $diffFile; then + rm $gfile + rm $tgt2 + else + echo "Failure for file: $gfile. Keeping generated file for inspection" + fail=$((fail + 1)) + fi else rm $gfile fi @@ -111,57 +105,6 @@ function applyFileFormatTwoToSingleTUWithAA { return $fail } -function applyFileFormatOneToMultiTU { - fail=0 - tc=$1 - taFile=${tc}_a.cpp - tbFile=${tc}_b.cpp - - # Result files - ipcgTaFile="${taFile/cpp/ipcg}-${CI_CONCURRENT_ID}" - ipcgTbFile="${tbFile/cpp/ipcg}-${CI_CONCURRENT_ID}" - - # Groundtruth files - gtaFile="${taFile/cpp/gtipcg}" - gtbFile="${tbFile/cpp/gtipcg}" - gtCombFile="${tc}_combined.gtipcg" - - # Translation-unit-local - $cgcollectorExe --output ./input/multiTU/${ipcgTaFile} ./input/multiTU/$taFile -- >>log/testrun.log 2>&1 - $cgcollectorExe --output ./input/multiTU/${ipcgTbFile} ./input/multiTU/$tbFile -- >>log/testrun.log 2>&1 - - cat ./input/multiTU/${ipcgTaFile} | python3 -m json.tool >./input/multiTU/${ipcgTaFile}_ - mv ./input/multiTU/${ipcgTaFile}_ ./input/multiTU/${ipcgTaFile} - cat ./input/multiTU/${ipcgTbFile} | python3 -m json.tool >./input/multiTU/${ipcgTbFile}_ - mv ./input/multiTU/${ipcgTbFile}_ ./input/multiTU/${ipcgTbFile} - - $testerExe ./input/multiTU/${ipcgTaFile} ./input/multiTU/${gtaFile} >>log/testrun.log 2>&1 - aErr=$? - $testerExe ./input/multiTU/${ipcgTbFile} ./input/multiTU/${gtbFile} >>log/testrun.log 2>&1 - bErr=$? - - combFile=${tc}_combined-${CI_CONCURRENT_ID}.ipcg - echo "null" >./input/multiTU/${combFile} - - ${cgmergeExe} ./input/multiTU/${combFile} ./input/multiTU/${ipcgTaFile} ./input/multiTU/${ipcgTbFile} >>log/testrun.log 2>&1 - mErr=$? - - cat ./input/multiTU/${combFile} | python3 -m json.tool >./input/multiTU/${combFile}_ - mv ./input/multiTU/${combFile}_ ./input/multiTU/${combFile} - - ${testerExe} ./input/multiTU/${combFile} ./input/multiTU/${gtCombFile} >>log/testrun.log 2>&1 - cErr=$? - - #echo "$aErr or $bErr or $mErr or $cErr" - - if [[ ${aErr} -ne 0 || ${bErr} -ne 0 || ${mErr} -ne 0 || ${cErr} -ne 0 ]]; then - echo "Failure for file: $combFile. Keeping generated file for inspection" - fail=$((fail + 1)) - else - rm ./input/multiTU/$combFile ./input/multiTU/${ipcgTaFile} ./input/multiTU/${ipcgTbFile} - fi - return $fail -} function applyFileFormatTwoToMultiTU { fail=0 @@ -187,63 +130,26 @@ function applyFileFormatTwoToMultiTU { cat ./input/multiTU/${ipcgTbFile} | python3 -m json.tool >./input/multiTU/${ipcgTbFile}_ mv ./input/multiTU/${ipcgTbFile}_ ./input/multiTU/${ipcgTbFile} - $testerExe ./input/multiTU/${ipcgTaFile} ./input/multiTU/${gtaFile} >>log/testrun.log 2>&1 + $testerExe ./input/multiTU/${gtaFile} ./input/multiTU/${ipcgTaFile} >>log/testrun.log 2>&1 aErr=$? - $testerExe ./input/multiTU/${ipcgTbFile} ./input/multiTU/${gtbFile} >>log/testrun.log 2>&1 - bErr=$? - - combFile=${tc}_combined-${CI_CONCURRENT_ID}.ipcg - echo "null" >./input/multiTU/${combFile} - - ${cgmergeExe} ./input/multiTU/${combFile} ./input/multiTU/${ipcgTaFile} ./input/multiTU/${ipcgTbFile} >>log/testrun.log 2>&1 - mErr=$? - - cat ./input/multiTU/${combFile} | python3 -m json.tool >./input/multiTU/${combFile}_ - mv ./input/multiTU/${combFile}_ ./input/multiTU/${combFile} - - ${testerExe} ./input/multiTU/${combFile} ./input/multiTU/${gtCombFile} >>log/testrun.log 2>&1 - cErr=$? - echo "$aErr or $bErr or $mErr or $cErr" - - if [[ ${aErr} -ne 0 || ${bErr} -ne 0 || ${mErr} -ne 0 || ${cErr} -ne 0 ]]; then - echo "Failure for file: $combFile. Keeping generated file for inspection" - fail=$((fail + 1)) + if only_metadata_diff "$diffFile"; then + aErr=0 else - rm ./input/multiTU/$combFile ./input/multiTU/${ipcgTaFile} ./input/multiTU/${ipcgTbFile} + echo "[Info] aErr metadata diff" + echo "Running $testerExe ./input/multiTU/${ipcgTaFile} ./input/multiTU/${gtaFile}" fi - return $fail -} - -function applyFileFormatTwoToMultiTUWithAA { - fail=0 - tc=$1 - taFile=${tc}_a.cpp - tbFile=${tc}_b.cpp - - # Result files - ipcgTaFile="${taFile/cpp/ipcg}-${CI_CONCURRENT_ID}" - ipcgTbFile="${tbFile/cpp/ipcg}-${CI_CONCURRENT_ID}" - # Groundtruth files - gtaFile="${taFile/cpp/gtaacg}" - gtbFile="${tbFile/cpp/gtaacg}" - gtCombFile="${tc}_combined.gtaacg" - - # Translation-unit-local - $cgcollectorExe --metacg-format-version=2 --capture-ctors-dtors --capture-stack-ctors-dtors --enable-AA --output ./input/multiTU/${ipcgTaFile} ./input/multiTU/$taFile -- >>log/testrun.log 2>&1 - $cgcollectorExe --metacg-format-version=2 --capture-ctors-dtors --capture-stack-ctors-dtors --enable-AA --output ./input/multiTU/${ipcgTbFile} ./input/multiTU/$tbFile -- >>log/testrun.log 2>&1 - - cat ./input/multiTU/${ipcgTaFile} | python3 -m json.tool >./input/multiTU/${ipcgTaFile}_ - mv ./input/multiTU/${ipcgTaFile}_ ./input/multiTU/${ipcgTaFile} - cat ./input/multiTU/${ipcgTbFile} | python3 -m json.tool >./input/multiTU/${ipcgTbFile}_ - mv ./input/multiTU/${ipcgTbFile}_ ./input/multiTU/${ipcgTbFile} - - $testerExe ./input/multiTU/${ipcgTaFile} ./input/multiTU/${gtaFile} >>log/testrun.log 2>&1 - aErr=$? - $testerExe ./input/multiTU/${ipcgTbFile} ./input/multiTU/${gtbFile} >>log/testrun.log 2>&1 + $testerExe ./input/multiTU/${gtbFile} ./input/multiTU/${ipcgTbFile} >>log/testrun.log 2>&1 bErr=$? + if only_metadata_diff "$diffFile"; then + bErr=0 + else + echo "[Info] bErr metadata diff" + echo "Running $testerExe ./input/multiTU/${ipcgTbFile} ./input/multiTU/${gtbFile}" + fi + combFile=${tc}_combined-${CI_CONCURRENT_ID}.ipcg echo "null" >./input/multiTU/${combFile} @@ -253,9 +159,16 @@ function applyFileFormatTwoToMultiTUWithAA { cat ./input/multiTU/${combFile} | python3 -m json.tool >./input/multiTU/${combFile}_ mv ./input/multiTU/${combFile}_ ./input/multiTU/${combFile} - ${testerExe} ./input/multiTU/${combFile} ./input/multiTU/${gtCombFile} >>log/testrun.log 2>&1 + ${testerExe} ./input/multiTU/${gtCombFile} ./input/multiTU/${combFile} >>log/testrun.log 2>&1 cErr=$? + if only_metadata_diff "$diffFile"; then + cErr=0 + else + echo "[Info] cErr metadata diff" + echo "Was running: ${testerExe} ./input/multiTU/${combFile} ./input/multiTU/${gtCombFile} >>log/testrun.log 2>&1" + fi + echo "$aErr or $bErr or $mErr or $cErr" if [[ ${aErr} -ne 0 || ${bErr} -ne 0 || ${mErr} -ne 0 || ${cErr} -ne 0 ]]; then diff --git a/cgcollector/tools/CGCollector.cpp b/cgcollector/tools/CGCollector.cpp index 27dcea88..f415d45c 100644 --- a/cgcollector/tools/CGCollector.cpp +++ b/cgcollector/tools/CGCollector.cpp @@ -1,4 +1,4 @@ -#include "config.h" +#include "metacg/config.h" #include "AliasAnalysis.h" #include "CallGraph.h" @@ -42,7 +42,7 @@ static llvm::cl::opt outputFilenameOption("output", llvm::cl::desc( */ static llvm::cl::opt disableClassicCGConstruction( "disable-classic-cgc", llvm::cl::desc("Disable the \"classic\" call graph construction"), llvm::cl::cat(cgc)); -static llvm::cl::opt enableAA("enable-AA", llvm::cl::desc("Enable Alias Analysis (experimental)"), +static llvm::cl::opt enableAA("enable-AA", llvm::cl::desc("Enable Alias Analysis (DEPRECATED)"), llvm::cl::cat(cgc)); // Configuration for the call count estimation diff --git a/cgcollector/tools/CGMerge.cpp b/cgcollector/tools/CGMerge.cpp index 14a7dcc9..49780f90 100644 --- a/cgcollector/tools/CGMerge.cpp +++ b/cgcollector/tools/CGMerge.cpp @@ -1,4 +1,4 @@ -#include "config.h" +#include "metacg/config.h" #include "AliasAnalysis.h" #include "GlobalCallDepth.h" diff --git a/cgcollector/tools/CGValidate.cpp b/cgcollector/tools/CGValidate.cpp index 3fea7a36..ec210b01 100644 --- a/cgcollector/tools/CGValidate.cpp +++ b/cgcollector/tools/CGValidate.cpp @@ -1,4 +1,4 @@ -#include "config.h" +#include "metacg/config.h" #include "JSONManager.h" #include "MetaInformation.h" diff --git a/cgfcollector/CMakeLists.txt b/cgfcollector/CMakeLists.txt new file mode 100644 index 00000000..d2295fe7 --- /dev/null +++ b/cgfcollector/CMakeLists.txt @@ -0,0 +1,120 @@ +set(PROJECT_NAME cgfcollector) +set(TARGETS_EXPORT_NAME ${PROJECT_NAME}-target) + +file( + GLOB + CGFCOLLECTOR_SOURCES + src/*.cpp +) + +add_library(${PROJECT_NAME} SHARED ${CGFCOLLECTOR_SOURCES}) +add_flang(${PROJECT_NAME}) +add_metacg(${PROJECT_NAME}) +add_spdlog_libraries(${PROJECT_NAME}) + +target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) + +install( + TARGETS ${PROJECT_NAME} + EXPORT ${TARGETS_EXPORT_NAME} + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib +) + +configure_package_config_file( + ${METACG_Directory}/cmake/Config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake + INSTALL_DESTINATION lib/cmake +) + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake DESTINATION lib/cmake) + +# generate wrapper scripts + +function( + configure_file_and_install + TEMPLATE + OUTPUT_BASENAME + DESTINATION +) + set(BUILD_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_BASENAME}") + set(INSTALL_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_BASENAME}.install") + + # build-time values for development + set(CGFCOLLECTOR_FILE_NAME_LIB "${CMAKE_CURRENT_BINARY_DIR}/lib${PROJECT_NAME}.so") + set(CGFCOLLECTOR_CGDIFF_BIN "${CMAKE_BINARY_DIR}/tools/cgdiff/cgdiff") + set(CGFCOLLECTOR_COMP_WRAPPER "${CMAKE_CURRENT_BINARY_DIR}/cgfcollector_comp_wrapper.sh") + set(CGFCOLLECTOR_LINK_WRAPPER "${CMAKE_CURRENT_BINARY_DIR}/cgfcollector_link_wrapper.sh") + set(CGFCOLLECTOR_TEST_CASES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/test") + set(CGMERGE2_BIN "${CMAKE_BINARY_DIR}/tools/cgmerge2/cgmerge2") + + configure_file( + "${TEMPLATE}" + "${BUILD_OUTPUT}" + @ONLY + ) + + if(DESTINATION + STREQUAL + "" + ) + return() + endif() + + # install-time values + set(CGFCOLLECTOR_FILE_NAME_LIB "${CMAKE_INSTALL_PREFIX}/lib/lib${PROJECT_NAME}.so") + + configure_file( + "${TEMPLATE}" + "${INSTALL_OUTPUT}" + @ONLY + ) + + install( + PROGRAMS "${INSTALL_OUTPUT}" + DESTINATION "${DESTINATION}" + RENAME "${OUTPUT_BASENAME}" + ) +endfunction() + +configure_file_and_install( + "${CMAKE_CURRENT_SOURCE_DIR}/tools/cgfcollector_comp_wrapper.sh.in" + "cgfcollector_comp_wrapper.sh" + "bin" +) + +configure_file_and_install( + "${CMAKE_CURRENT_SOURCE_DIR}/tools/cgfcollector_link_wrapper.sh.in" + "cgfcollector_link_wrapper.sh" + "bin" +) + +configure_file_and_install( + "${CMAKE_CURRENT_SOURCE_DIR}/tools/test_runner.sh.in" + "test_runner.sh" + "" +) + +configure_file_and_install( + "${CMAKE_CURRENT_SOURCE_DIR}/test/multi/cmake_base.txt.in" + "cmake_base.txt" + "" +) + +configure_file_and_install( + "${CMAKE_CURRENT_SOURCE_DIR}/test/multi/make_base.in" + "make_base" + "" +) + +# tests + +# dependency for tests +find_program(FORTDEPEND_BIN fortdepend HINTS ${Python_ROOT_DIR}/bin) +if(NOT FORTDEPEND_BIN) + message(FATAL_ERROR "fortdepend not found.") +endif() + +message(STATUS "Found fortdepend: ${FORTDEPEND_BIN}") + +add_test(NAME cgfcollector_tests COMMAND ${CMAKE_CURRENT_BINARY_DIR}/test_runner.sh) +set_tests_properties(cgfcollector_tests PROPERTIES ENVIRONMENT "FORTDEPEND_BIN=${FORTDEPEND_BIN}") diff --git a/cgfcollector/README.md b/cgfcollector/README.md new file mode 100644 index 00000000..864a8955 --- /dev/null +++ b/cgfcollector/README.md @@ -0,0 +1,84 @@ +# CG Fortran Collector + +Fortran call graph generation tool for MetaCG. This tool is implemented as a +Flang plugin and generates a call graph from source-level. + +## Usage + +For single file projects or projects not using modules use: + +```sh +cgfcollector_comp_wrapper.sh [flang options, source file/s, plugin options] +``` + +For any other projects you need a build system. That calls `cgfcollector_comp_wrapper.sh` +and `cgfcollector_link_wrapper.sh` accordingly. For examples [see](#generate-a-call-graph). + +You can also run the plugin directly with Flang: + +```sh +flang -fc1 -load "libcgfcollector.so" -plugin "genCG" [options] +``` + +Available options: + +- `--dot`: Additionally generate a DOT file. This is mostly used for debugging. +- `--verbose`: Print additional information during the generation process. +- `--graph-name`: Set internal graph name. +- `--include-intrinsics`: Include intrinsic procedures in the call graph. + +Additionally these other tools are included: + +- `cgfcollector_comp_wrapper.sh`: Acts like a normal Flang compiler but also generates a call graph. +- `cgfcollector_link_wrapper`: Acts like a normal Flang linker but also merges + the generated call graphs. +- `test_runner.sh`: Run tests. + +## How to build + +To build the cgfcollector the option `METACG_BUILD_CGFCOLLECTOR` must be set to +`ON`. + +## Generate a call graph + +### from a CMake project + +Paste this into your CMakeLists.txt. + +``` +set(CMAKE_Fortran_COMPILER "flang-new") +set(CMAKE_Fortran_COMPILER_LAUNCHER ) +if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.1") + set(CMAKE_Fortran_LINKER_LAUNCHER "" --skip-gen-bin) +else() + set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK " --skip-gen-bin") +endif() +``` + +This will hook into the CMake build process and generate a call graph. + +An example can be found in `test/multi/deps`. + +### from other projects + +Use `cgfcollector_comp_wrapper.sh` and `cgfcollector_link_wrapper.sh` to hook into the +build process of your favorite tool. + +An example can be found in `test/multi/fortdepend_deps`. This example uses +fortdepend to generate a module dependency list. If your build system already +generates such a list and executes the compiler on the files in the correct order +you probably don't need this. + +## Running test + +Run `test_runner.sh` + +NOTE: The test `test/multi/fortdepend_deps` has a dependency on [fortdepend](https://fortdepend.readthedocs.io/en/latest/) + +## Debugging + +### print parse tree + +```sh +flang-new -fc1 -fdebug-dump-parse-tree file.f90 +``` diff --git a/cgfcollector/design.md b/cgfcollector/design.md new file mode 100644 index 00000000..e0f769dc --- /dev/null +++ b/cgfcollector/design.md @@ -0,0 +1,79 @@ +# Design Overview + +This document provides a high-level overview of `cgfcollector`. + +More implementation details can be found in the relevant source/header files. + +## Purpose + +`cgfcollector` builds a call graph for Fortran code by traversing the Flang +parse tree and collecting: + +- Nodes (procedures and related entities) +- Edges (call relations between nodes) + +## Execution Flow + +1. `Main.cpp` initializes the plugin and starts parse tree traversal. +2. `ParseTreeVisitor` walks grammar nodes (based on Flang Fortran 2018 grammar). +3. During traversal, procedures/types/temporary state are collected. +4. This information is used to insert the appropriate nodes and edges into the + call graph. +5. Some edges (for example finalizer-related) can only be resolved at the end of + the parse tree traversal, so they are collected as "potential" edges and + added in a post-processing step. +6. Collected nodes and edges are emitted as the resulting call graph. + +Grammar reference: https://flang.llvm.org/docs/f2018-grammar.html + +## Main Components + +- `Main.cpp` + - Entry point and traversal setup. +- `ParseTreeVisitor` + - Core traversal logic and feature handling. +- `Function` + - Representation for Fortran procedures and related metadata. +- `Type` + - Representation for Fortran type information and inheritance. +- `Edge` and `EdgeSymbol` + - Representation of call-graph edges. +- `EdgeManager` + - Manages edge collection. +- `PotentialFinalizer` + - Deferred finalizer call candidate, resolved at end of traversal. +- `VariableTracking` + - Variable initialization/state tracking (currently used for finalizers). +- `FortranUtil` + - Shared Fortran helper utilities. + +## Currently Covered Features + +### Node Collection + +- Procedures +- Main program +- Entry statements +- Intrinsics +- Statement function + +### Edge Collection + +- Subroutine calls +- Function calls +- Object-oriented calls +- Finalizer calls +- Operator overloading +- Generics and interface grouping +- Constructor calls +- C interoperability +- Statement function calls +- `USE` statement relations +- Nesting relations + +## Current limitations + +- Function pointer calls +- Unlimited polymorphic calls +- Operator overloading +- Variable tracking diff --git a/cgfcollector/include/Edge.h b/cgfcollector/include/Edge.h new file mode 100644 index 00000000..de602a29 --- /dev/null +++ b/cgfcollector/include/Edge.h @@ -0,0 +1,93 @@ +/** + * File: Edge.h + * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt + */ + +#ifndef METACG_CGFCOLLECTOR_EDGE_H +#define METACG_CGFCOLLECTOR_EDGE_H + +#include "FortranUtil.h" +#include "Type.h" + +#include +#include +#include +#include +#include + +namespace metacg::cgfcollector { + +struct PotentialFinalizer; + +struct Edge { + std::string caller; + std::string callee; + + Edge(std::string caller, std::string callee) : caller(std::move(caller)), callee(std::move(callee)) {} + + bool operator==(const Edge& other) const { return caller == other.caller && callee == other.callee; } + bool operator<(const Edge& other) const { + return caller < other.caller || (caller == other.caller && callee < other.callee); + } +}; // namespace metacg::cgfcollector + +struct EdgeSymbol { + const Fortran::semantics::Symbol* caller; + const Fortran::semantics::Symbol* callee; + + EdgeSymbol(const Fortran::semantics::Symbol* caller, const Fortran::semantics::Symbol* callee) + : caller(caller), callee(callee) {} + + bool operator==(const EdgeSymbol& other) const { return caller == other.caller && callee == other.callee; } + bool operator<(const EdgeSymbol& other) const { + return caller < other.caller || (caller == other.caller && callee < other.callee); + } +}; + +struct EdgeManager { + std::vector& edges; + + EdgeManager(std::vector& edges, bool underscoring) : edges(edges), underscoring(underscoring) {} + + void addEdge(const EdgeSymbol& e); + void addEdge(const Fortran::semantics::Symbol* caller, const Fortran::semantics::Symbol* callee); + void addEdge(const Edge& e); + void addEdge(const std::string& caller, const std::string& callee); + void addEdges(const std::vector& newEdges); + void addEdges(const std::vector& newEdges); + + /** + * @brief Adds all finalizers for a given symbol (`symbol`) to the edges vector. This also takes inherited finalizers + * into account by following the type hierarchy defined in `types`. + * + * @param types + * @param currentProcedureSymbol From node + * @param symbol To node + */ + void addEdgesForFinalizers(const std::vector& types, + const std::unordered_map>& finalizers, + const Fortran::semantics::Symbol* currentProcedureSymbol, + const Fortran::semantics::Symbol* symbol); + + /** + * @brief Add all edges from `PotentialFinalizer` `e` to the edges vector. + * + * @param e + */ + void addEdgesForFinalizers(const PotentialFinalizer& e); + + /** + * @brief Remove duplicate edges from the edges vector. This prevents massive amounts of duplication warnings from the + * graph lib. + */ + void uniquifyEdges(); + + private: + bool underscoring; +}; + +} // namespace metacg::cgfcollector + +#endif // METACG_CGFCOLLECTOR_EDGE_H diff --git a/cgfcollector/include/FortranUtil.h b/cgfcollector/include/FortranUtil.h new file mode 100644 index 00000000..e9b3ed89 --- /dev/null +++ b/cgfcollector/include/FortranUtil.h @@ -0,0 +1,213 @@ +/** + * File: FortranUtil.h + * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt + */ + +#ifndef METACG_CGFCOLLECTOR_FORTRANUTIL_H +#define METACG_CGFCOLLECTOR_FORTRANUTIL_H + +#include "Type.h" + +#include +#include +#include +#include +#include +#include +#include + +/** + * @brief Formatter for CharBlock + */ +template <> +struct fmt::formatter { + constexpr auto parse(fmt::format_parse_context& ctx) { return ctx.begin(); } + + template + auto format(const Fortran::parser::CharBlock& cb, FormatContext& ctx) const { + return fmt::format_to(ctx.out(), "{}", std::string_view(cb.begin(), cb.size())); + } +}; + +namespace metacg::cgfcollector { + +/** + * @brief Variant check if it holds any of the given types + * + * @tparam Variant + * @tparam Ts + * @param v + * @return + */ +template +bool holds_any_of(const Variant& v) { + return (std::holds_alternative(v) || ...); +} + +/** + * @brief Get Name from type that has a designator + * + * @tparam T + * @param t + * @return + */ +template +const Fortran::parser::Name* getNameFromClassWithDesignator(const T& t) { + if (const Fortran::common::Indirection* designator = + std::get_if>(&t.u)) { + if (const Fortran::parser::DataRef* dataRef = std::get_if(&designator->value().u)) { + if (const Fortran::parser::Name* name = std::get_if(&dataRef->u)) { + return name; + } + } + } + return nullptr; +} + +/** + * @brief Get the derived type symbol from a symbol that has a type. + * + * @param sym + */ +const Fortran::semantics::Symbol* getTypeAsDerivedTypeSymbol(const Fortran::semantics::Symbol* sym); + +/** + * @brief Get the absolute base symbol for a given type. This is done by following the `extendsFrom` field of the type + * until the `extendsFrom` field is null. This also canonicalizes the resulting symbol. + * + * @param types + * @param type + */ +const Fortran::semantics::Symbol* getAbsoluteBaseSymbol(const std::vector& types, const Type* type); + +enum class CanonicalMode { + Identity, // preserve variables/procedures by symbol + ByType, // normalize variables/procedures by derived type +}; + +/** + * @class CanonicalSymbol + * @brief Struct to hold symbol and its canonical kind. Mainly used for comparing symbols. + * + */ +struct CanonicalSymbol { + enum class Kind { DerivedType, Procedure, Other }; + + const Fortran::semantics::Symbol* symbol; + Kind kind; +}; + +/** + * @brief Canonicalizes a given symbol. This tries to resolve a symbol to its original construct, e.g. if it's defined + * in a modules and therefore is a Use/Host associated symbol, it will be resolved to the original symbol in the module. + * This function also handles generic symbols, and an short coming of older LLVM versions (18 and before). See function + * body for details. + * + * Note: Mainly used for comparing symbols. + * + * @param input + * @param mode (see CanonicalMode definition) + * @return + */ +CanonicalSymbol canonicalizeSymbol(const Fortran::semantics::Symbol* input, + CanonicalMode mode = CanonicalMode::Identity); + +/** + * @brief Compares two symbols for equality also resolves the original construct the symbol comes from. + * This could have been defined in another module/file. + * + * @param a + * @param b + * @param mode (see CanonicalMode definition) + * @return true if both symbols are equal + */ +bool compareSymbols(const Fortran::semantics::Symbol* a, const Fortran::semantics::Symbol* b, + CanonicalMode mode = CanonicalMode::Identity); + +/** + * @brief Generate mangled name from symbol + * + * @param sym + * @param underscoring + * @return + */ +std::string mangleSymbol(const Fortran::semantics::Symbol* sym, bool underscoring); + +/** + * @brief Check if expression is an operator expression + * + * @param e + * @return + */ +bool isOperator(const Fortran::parser::Expr* e); + +/** + * @brief If `expr` is an operator return a string representation. + * + * @param expr + */ +std::string getOperatorStringFromExpr(const Fortran::parser::Expr* expr); + +/** + * @brief String representation for a `DefinedOperator` + * + * @param op + */ +std::string getOperatorStringFromDefinedOperator(const Fortran::parser::DefinedOperator* op); + +/** + * @brief String representation of `GenericDetails` + * + * @param symbol + * @param gen + */ +std::string getOperatorStringFromGenericDetails(const Fortran::semantics::Symbol* symbol, + const Fortran::semantics::GenericKind& gen); + +/** + * @brief Compare if expression match given intrinsic operator + * + * @param expr + * @param op + * @return + */ +bool compareExprIntrinsicOperator(const Fortran::parser::Expr* expr, + Fortran::parser::DefinedOperator::IntrinsicOperator op); + +/** + * @brief Check if binary operator expression + * + * @param e + * @return + */ +bool isBinaryOperator(const Fortran::parser::Expr* e); + +/** + * @brief Check if unary operator expression + * + * @param e + * @return + */ +bool isUnaryOperator(const Fortran::parser::Expr* e); + +/** + * @brief Get intrinsic operator from a variant of several operator types (RelationalOperator, LogicalOperator, + * NumericOperator) + * + * @param gk + * @return + */ +Fortran::parser::DefinedOperator::IntrinsicOperator variantGetIntrinsicOperator( + const Fortran::semantics::GenericKind& gk); + +/** + * @brief String representation of details of a symbols details. + * + * @param symbol + */ +std::string getDetailsName(const Fortran::semantics::Symbol* symbol); + +} // namespace metacg::cgfcollector + +#endif // !METACG_CGFCOLLECTOR_FORTRANUTIL_H diff --git a/cgfcollector/include/ParseTreeVisitor.h b/cgfcollector/include/ParseTreeVisitor.h new file mode 100644 index 00000000..3eae47f5 --- /dev/null +++ b/cgfcollector/include/ParseTreeVisitor.h @@ -0,0 +1,419 @@ +/** + * File: ParseTreeVisitor.h + * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt + */ + +#ifndef METACG_CGFCOLLECTOR_PARSETREEVISITOR_H +#define METACG_CGFCOLLECTOR_PARSETREEVISITOR_H + +#include "Edge.h" +#include "FortranUtil.h" +#include "PotentialFinalizer.h" +#include "Procedure.h" +#include "Type.h" +#include "VariableTracking.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace metacg::cgfcollector { + +struct pair_hash { + template + std::size_t operator()(const std::pair& p) const noexcept { + std::size_t h1 = std::hash{}(p.first); + std::size_t h2 = std::hash{}(p.second); + return h1 ^ (h2 << 1); + } +}; + +/** + * @class ParseTreeVisitor + * @brief Implements visitor methods to traverse parse tree and generate call graph. + * Intended to be used with `Fortran::parser::Walk()` + * + * The parse tree is composed of different node types. Each node type has a corresponding visitor methods in this class. + * The visitor methods are called `Pre` and `Post` methods. The `Pre` method is called before visiting the children of + * the node, and the Post method is called after visiting the children of the node. All possible node types and there + * relations are defined through a grammar. Can be found here: https://flang.llvm.org/docs/f2018-grammar.html + */ +class ParseTreeVisitor { + public: + ParseTreeVisitor(metacg::Callgraph* cg, std::string currentFileName, bool underscoring, bool includeInstrinsics) + : cg(cg), + currentFileName(currentFileName), + underscoring(underscoring), + includeInstrinsics(includeInstrinsics), + edgeM(std::make_unique(edges, underscoring)), + varTracking(std::make_unique(trackedVars, types, procedures, finalizers, underscoring)) {}; + + /** + * @brief Add dummy args to current procedure in `currentProcedures` and `Procedures`. Also initiates variable + * tracking. + * + * @tparam Iterable + * @tparam Extractor + * @param items List of dummy args + * @param extract Procedure to extract dummy arg to `Symbol` + */ + template + void handleDummyArgs(const Iterable& items, Extractor extract); + + /** + * @brief Collects function/subroutine statements (begin) and their dummy args. + * + * This method populates `currentProcedures` and `Procedures` vectors. It also adds a node for the procedure to the + * call graph. Also this method should only be called from a `FunctionStmt` and `SubroutineStmt` visitor methods. It + * is implemented as a template to avoid code duplication. + * + * @tparam T + * @param stmt + */ + template + void handleFuncSubStmt(const T& stmt); + + /** + * @brief Handles function/subroutine end statements. + * + * At the end of a procedure, we handle the tracked variables and maintain `currentProcedures`. + */ + void handleEndFuncSubStmt(); + + /** + * @brief Add uniquefied edges and potential finalizers edges to the call graph. + * + * Needs to be called after the parse tree traversal. + * + * Edges are uniquefied because the graph lib warns about duplicate edges. This leads to a lot of warns being printed + * on screen, as we potentially collect a edge many times. + */ + void postProcess(); + + // visitor methods + + template + bool Pre(const A&) { + return true; + } + template + void Post(const A&) {} + + /** + * @brief Pre visitor of the main procedure of the Fortran program. + * + * @param p + * @return + */ + bool Pre(const Fortran::parser::MainProgram& p); + + /** + * @brief Post visitor of the main procedure. + */ + void Post(const Fortran::parser::MainProgram&); + + /** + * @brief Bookkeeping that we are in a function. + * + * @return + */ + bool Pre(const Fortran::parser::FunctionSubprogram&); + + /** + * @brief Bookkeeping that we are in a function. + */ + void Post(const Fortran::parser::FunctionSubprogram&); + + /** + * @brief Bookkeeping that we are in a subroutine. + * + * @return + */ + bool Pre(const Fortran::parser::SubroutineSubprogram&); + + /** + * @brief Bookkeeping that we are in a subroutine. + */ + void Post(const Fortran::parser::SubroutineSubprogram&); + + /** + * @brief Set `hasBody` field. + * + * @param e + */ + void Post(const Fortran::parser::ExecutionPart& e); + + /** + * @brief Handle the entry statement like a normal procedure. + * + * @param e + */ + void Post(const Fortran::parser::EntryStmt& e); + + /** + * @brief Function entry: Call `handleFuncSubStmt`, and collect procedure arguments for `procedure` vector. + * + * @param f + */ + void Post(const Fortran::parser::FunctionStmt& f); + + /** + * @brief calls `handleEndFuncSubStmt` + */ + void Post(const Fortran::parser::EndFunctionStmt&); + + /** + * @brief Function entry: Call `handleFuncSubStmt`, and collect subroutine arguments for `procedures` vector. + * + * @param f + */ + void Post(const Fortran::parser::SubroutineStmt& s); + + /** + * @brief calls `handleEndFuncSubStmt` + */ + void Post(const Fortran::parser::EndSubroutineStmt&); + + /** + * @brief `ProcedureDesignator`: A procedure being called. Handles both cases: function and subroutine calls. + * + * @param p + */ + void Post(const Fortran::parser::ProcedureDesignator& p); + + /** + * @brief Handle `trackedVar` assignment + * + * @param a + */ + void Post(const Fortran::parser::AssignmentStmt& a); + + /** + * @brief Handle `trackedVar` assignment through allocate statement. + * + * @param a + */ + void Post(const Fortran::parser::AllocateStmt& a); + + /** + * @brief Mostly add potential finalizers for variables that get initialized through procedure arguments. + * + * It iterations through the arguments of the call statement and adds potential finalizers for all arguments that a + * registered as a tracked variable. + * + * It also handles the case where a variable is initialized through the `move_alloc` intrinsic. + * + * @param c + */ + void Post(const Fortran::parser::Call& c); + + /** + * @brief A `TypeDeclarationStmt` is an argument in the procedure definition. This visitor mostly handles finalizers. + * Handles the different ways a variable can be parsed to a procedure and gets initialized. + * + * @param t + */ + void Post(const Fortran::parser::TypeDeclarationStmt& t); + + // The following methods are for collecting types and their procedures. See type struct and vector. + + /** + * @brief Bookkeeping: At the start of a derived type definition. Also start collecting type information and + * store in `types` vector. + * + * @return + */ + bool Pre(const Fortran::parser::DerivedTypeDef&); + + /** + * @brief Bookkeeping: At the end of a derived type definition. Also collect finializers for that derived + * type. + */ + void Post(const Fortran::parser::DerivedTypeDef&); + + /** + * @brief Collect derived type symbol. + * + * @param t + * @return + */ + bool Pre(const Fortran::parser::DerivedTypeStmt& t); + + /** + * @brief Type attributes like extends + * + * @param a + */ + void Post(const Fortran::parser::TypeAttrSpec& a); + + /** + * @brief Collect type-bound procedures in derived type definitions + * + * @param s + */ + void Post(const Fortran::parser::TypeBoundProcedureStmt& s); + + /** + * @brief Collect type-bound operators (operator overloading) + * + * @param s + */ + void Post(const Fortran::parser::TypeBoundGenericStmt& s); + + // The following methods are for collecting defined operators in interface statements + + /** + * @brief Bookkeeping: In interface statement + * + * @return + */ + bool Pre(const Fortran::parser::InterfaceStmt&); + + /** + * @brief Bookkeeping: Leaving interface statement + * + * @return + */ + bool Pre(const Fortran::parser::EndInterfaceStmt&); + + /** + * @brief Collect interface operators and store in `interfaceOperators`. The second element in `interfaceOperators` is + * a list of procedure symbols that are specified for the operator. These get collected in the `ProcedureStmt` + * visitor. + * + * @param op + */ + void Post(const Fortran::parser::DefinedOperator& op); + + /** + * @brief A `ProcedureStmt` references one or more procedures. We use it to collect procedures specified for an + * interface operator. + * + * @param p + */ + void Post(const Fortran::parser::ProcedureStmt& p); + + /** + * @brief Handle operator overloading. Uses `interfaceOperators` and `types` vectors to extract the procedures getting + * called when evaluation an expression with operators. For example, `(.NOT. 324 < 2) .EQV. .true.` has the operators + * `.NOT.`, `<`, and `.EQV.`. If these operators are overloaded, we need to find the procedures that are called for + * this expression. To go this we first go to the lowest operator in the expression (`<` in the example) and then go + * up the expression tree. For each operator we check if it is overloaded through an interface operator or a + * type-bound operator. If it is, we add edges to the procedures that are specified for the operator. For the + * type-bound operators we also need to check for any polymorphic calls. + * + * There are two ways to define operators in Fortran: through interface statements and through type definitions. + * + * @param e + * @return + */ + bool Pre(const Fortran::parser::Expr& e); + + /** + * @brief Maintain `exprStmtWithOps` for `Pre` visitor of `Expr`. + * + * @param e + */ + void Post(const Fortran::parser::Expr& e); + + /** + * @brief Extract additional information from use statements. This includes derived types, interface operators, and + * procedures. + * + * @param u + */ + void Post(const Fortran::parser::UseStmt& u); + + /** + * @brief Handle StmtFunctionStmt like normal functions + * + * @param u + */ + bool Pre(const Fortran::parser::StmtFunctionStmt& s); + + /** + * @brief Handle StmtFunctionStmt like normal functions + * + * @param u + */ + void Post(const Fortran::parser::StmtFunctionStmt& s); + + private: + metacg::Callgraph* cg; + std::string currentFileName; + bool underscoring; + bool includeInstrinsics; + + bool inFunctionOrSubroutineSubProgram = false; + bool inMainProgram = false; + bool inDerivedTypeDef = false; + bool inInterfaceStmt = false; + bool inInterfaceStmtDefinedOperator = false; + bool inInterfaceSpecification = false; + + std::unique_ptr edgeM; + + std::unique_ptr varTracking; + + // added to cg in postProcess step + std::vector edges; + + // all procedures. This vector collects all procedures (+ dummy args) that were discovered during traversal of the + // parse tree. + std::vector procedures; + + // intended as a stack. It holds the current procedure symbol and its dummy + // args when the AST walker is in the respective procedure. + std::vector currentProcedures; + + // This vector collects all types that were discovered during traversal of the parse tree. + std::vector types; + + // Collects all interface operators discovered during traversal. First is either a symbol of a DefinedOpName or + // IntrinsicOperator. Second is a vector procedure symbols, bound to that operator. + std::vector< + std::pair, + std::vector>> + interfaceOperators; + + std::vector exprStmtWithOps; + + // mainly used for destructor handling + std::vector trackedVars; + + std::vector potentialFinalizers; + + // Maps each (type, binding name) pair to the set of procedures that override it, + // allowing lookup of all overriding implementations via polymorphism. + // {type, procBinding name} -> {overriding procedures} + std::unordered_map, + std::vector, pair_hash> + procedureOverwrites; + + // Maps each type to its finalizers + std::unordered_map> finalizers; + + // Maps each (type, type-bound operator name) pair to the set of procedures that implement it. The value string can be + // used with @ref procedureOverwrites to find all overriding implementations of this operator. + std::unordered_map, std::vector, pair_hash> + typeOperators; +}; + +} // namespace metacg::cgfcollector + +#endif // METACG_CGFCOLLECTOR_PARSETREEVISITOR_H diff --git a/cgfcollector/include/PotentialFinalizer.h b/cgfcollector/include/PotentialFinalizer.h new file mode 100644 index 00000000..45800337 --- /dev/null +++ b/cgfcollector/include/PotentialFinalizer.h @@ -0,0 +1,30 @@ +/** + * File: PotentialFinalizer.h + * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt + */ + +#ifndef METACG_CGFCOLLECTOR_POTENTIALFINALIZER_H +#define METACG_CGFCOLLECTOR_POTENTIALFINALIZER_H + +#include "Edge.h" + +#include +#include + +namespace metacg::cgfcollector { + +struct PotentialFinalizer { + std::size_t argPos; + std::string procedureCalled; + std::vector finalizerEdges; + + explicit PotentialFinalizer(std::size_t pos, std::string procCalled) + : argPos(pos), procedureCalled(std::move(procCalled)) {} + + void addFinalizerEdge(const Edge& e) { finalizerEdges.emplace_back(e); } +}; + +} // namespace metacg::cgfcollector + +#endif // METACG_CGFCOLLECTOR_POTENTIALFINALIZER_H diff --git a/cgfcollector/include/Procedure.h b/cgfcollector/include/Procedure.h new file mode 100644 index 00000000..9a07522a --- /dev/null +++ b/cgfcollector/include/Procedure.h @@ -0,0 +1,36 @@ +/** + * File: Procedure.h + * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt + */ + +#ifndef METACG_CGFCOLLECTOR_PROCEDURE_H +#define METACG_CGFCOLLECTOR_PROCEDURE_H + +#include +#include + +namespace metacg::cgfcollector { + +struct Procedure { + struct DummyArg { + const Fortran::semantics::Symbol* symbol; + bool hasBeenInitialized = false; + + explicit DummyArg(const Fortran::semantics::Symbol* sym) : symbol(sym) {} + explicit DummyArg(const Fortran::semantics::Symbol* sym, bool init) : symbol(sym), hasBeenInitialized(init) {} + }; + + const Fortran::semantics::Symbol* symbol; // procedure symbol + std::vector dummyArgs; + + explicit Procedure(const Fortran::semantics::Symbol* sym) : symbol(sym) {} + explicit Procedure(const Fortran::semantics::Symbol* sym, std::vector args) + : symbol(sym), dummyArgs(std::move(args)) {} + + void addDummyArg(const Fortran::semantics::Symbol* sym) { dummyArgs.emplace_back(sym); } +}; + +} // namespace metacg::cgfcollector + +#endif // METACG_CGFCOLLECTOR_PROCEDURE_H diff --git a/cgfcollector/include/Type.h b/cgfcollector/include/Type.h new file mode 100644 index 00000000..69b0971b --- /dev/null +++ b/cgfcollector/include/Type.h @@ -0,0 +1,24 @@ +/** + * File: Type.h + * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt + */ + +#ifndef METACG_CGFCOLLECTOR_TYPE_H +#define METACG_CGFCOLLECTOR_TYPE_H + +#include +#include +#include +// #include + +namespace metacg::cgfcollector { + +struct Type { + const Fortran::semantics::Symbol* typeSymbol; + const Fortran::semantics::Symbol* extendsFrom; +}; + +} // namespace metacg::cgfcollector + +#endif // METACG_CGFCOLLECTOR_TYPE_H diff --git a/cgfcollector/include/VariableTracking.h b/cgfcollector/include/VariableTracking.h new file mode 100644 index 00000000..7490b840 --- /dev/null +++ b/cgfcollector/include/VariableTracking.h @@ -0,0 +1,112 @@ +/** + * File: VariableTracking.h + * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt + */ + +#ifndef METACG_CGFCOLLECTOR_VARIABLETRACKING_H +#define METACG_CGFCOLLECTOR_VARIABLETRACKING_H + +#include "Edge.h" +#include "Procedure.h" +#include "Type.h" + +#include + +namespace metacg::cgfcollector { + +/** + * @class TrackedVar + * @brief Stores information about a tracked variable. + * + */ +struct TrackedVar { + const Fortran::semantics::Symbol* var; + + // procedure in which var was defined + const Fortran::semantics::Symbol* procedure; + + bool hasBeenInitialized = false; + bool addFinalizers = false; + + TrackedVar(const Fortran::semantics::Symbol* var, const Fortran::semantics::Symbol* procedure) + : var(var), procedure(procedure), hasBeenInitialized(false), addFinalizers(false) {} + TrackedVar(const Fortran::semantics::Symbol* var, const Fortran::semantics::Symbol* procedure, bool initialized, + bool addFinalizers) + : var(var), procedure(procedure), hasBeenInitialized(initialized), addFinalizers(addFinalizers) {} +}; + +/** + * @class VariableTracking + * @brief Handle tracking of variable initializations and finalizations at the end of the procedure scope. + * + * NOTE: Variables cannot be reliably tracked across multiple procedure calls. If a variable is passed into one + * procedure and then forwarded to another, it is no longer suffientily tracked and remains uninitialized in + * `trackedVars`. + */ +struct VariableTracking { + public: + VariableTracking( + std::vector& trackedVars, std::vector& types, std::vector& procedures, + std::unordered_map>& finalizers, + bool underscoring) + : trackedVars(trackedVars), + types(types), + procedures(procedures), + finalizers(finalizers), + underscoring(underscoring) {} + + /** + * @brief Search trackedVars for a canditate by source name. Also taking the current procedure scope into account. + * + * @param currentProcedureSymbol + * @param sourceName + * @return trackedVar* or nullptr if not found + */ + [[nodiscard]] TrackedVar* getTrackedVarFromSourceName(const Fortran::semantics::Symbol* currentProcedureSymbol, + Fortran::semantics::SourceName sourceName); + + /** + * @brief Search trackedVars for a canditate and set it as initialized. + * + * @param currentProcedureSymbol + * @param sourceName + */ + void handleTrackedVarAssignment(const Fortran::semantics::Symbol* currentProcedureSymbol, + Fortran::semantics::SourceName sourceName); + + /** + * @brief Is called at the end of a function/subroutine end statement. It checks trackedVars for any initialized + * variables and adds edges. Currently only adds finalizer edges. + * + * @param currentProcedureSymbol + * @param edgeM TODO: remove dep + */ + void handleTrackedVars(const Fortran::semantics::Symbol* currentProcedureSymbol, std::unique_ptr& edgeM); + + /** + * @brief Register a variable for tracking. + * + * @param var + */ + void addTrackedVar(TrackedVar var); + + /** + * @brief Remove all tracked variables that are not needed anymore after the function/subroutine end statement. Should + * be called at the end of function/subroutine processing. + * + * @param procedureSymbol + */ + void removeTrackedVars(const Fortran::semantics::Symbol* procedureSymbol); + + private: + std::vector& trackedVars; + std::vector& types; + std::vector& procedures; + std::unordered_map>& finalizers; + bool underscoring; +}; + +} // namespace metacg::cgfcollector + +#endif // METACG_CGFCOLLECTOR_VARIABLETRACKING_H diff --git a/cgfcollector/src/Edge.cpp b/cgfcollector/src/Edge.cpp new file mode 100644 index 00000000..a72773a8 --- /dev/null +++ b/cgfcollector/src/Edge.cpp @@ -0,0 +1,88 @@ +/** + * File: Edge.cpp + * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt + */ + +#include "Edge.h" + +#include "FortranUtil.h" +#include "PotentialFinalizer.h" + +using namespace Fortran::semantics; +using namespace Fortran::parser; +using namespace metacg; + +namespace metacg::cgfcollector { + +void EdgeManager::addEdgesForFinalizers( + const std::vector& types, + const std::unordered_map>& + finalizers, + const Symbol* currentProcedureSymbol, const Symbol* symbol) { + auto baseTypeIt = std::find_if(types.begin(), types.end(), [&](const Type& t) { + return compareSymbols(t.typeSymbol, symbol, CanonicalMode::ByType); + }); + if (baseTypeIt == types.end()) + return; + + try { + auto final = finalizers.at(getAbsoluteBaseSymbol(types, &(*baseTypeIt))); + for (const Symbol* f : final) { + addEdge(currentProcedureSymbol, f); + } + } catch (const std::out_of_range& e) { + // no finalizer for this type, do nothing + } +} + +void EdgeManager::addEdgesForFinalizers(const PotentialFinalizer& e) { + for (const Edge& edge : e.finalizerEdges) { + addEdge(edge); + MCGLogger::logDebug("Add edge for finalizer: {} -> {}", edge.caller, edge.callee); + } +} + +void EdgeManager::addEdge(const EdgeSymbol& e) { + edges.emplace_back(mangleSymbol(e.caller, underscoring), mangleSymbol(e.callee, underscoring)); + MCGLogger::logDebug("Add edge: {} ({}) ({}) -> {} ({}) ({})", mangleSymbol(e.caller, underscoring), + getDetailsName(e.caller), fmt::ptr(e.caller), mangleSymbol(e.callee, underscoring), + getDetailsName(e.callee), fmt::ptr(e.callee)); +} + +void EdgeManager::addEdge(const Symbol* caller, const Symbol* callee) { + edges.emplace_back(mangleSymbol(caller, underscoring), mangleSymbol(callee, underscoring)); + MCGLogger::logDebug("Add edge: {} ({}) ({}) -> {} ({}) ({})", mangleSymbol(caller, underscoring), + getDetailsName(caller), fmt::ptr(caller), mangleSymbol(callee, underscoring), + getDetailsName(callee), fmt::ptr(callee)); +} + +void EdgeManager::addEdge(const Edge& e) { + edges.emplace_back(e.caller, e.callee); + MCGLogger::logDebug("Add edge: {} -> {}", e.caller, e.callee); +} + +void EdgeManager::addEdge(const std::string& caller, const std::string& callee) { + edges.emplace_back(caller, callee); + MCGLogger::logDebug("Add edge: {} -> {}", caller, callee); +} + +void EdgeManager::addEdges(const std::vector& newEdges) { + for (const Edge& e : newEdges) { + addEdge(e); + } +} + +void EdgeManager::addEdges(const std::vector& newEdges) { + for (const EdgeSymbol& e : newEdges) { + addEdge(e); + } +} + +void EdgeManager::uniquifyEdges() { + std::sort(edges.begin(), edges.end()); + auto it = std::unique(edges.begin(), edges.end()); + edges.erase(it, edges.end()); +} + +} // namespace metacg::cgfcollector diff --git a/cgfcollector/src/FortranUtil.cpp b/cgfcollector/src/FortranUtil.cpp new file mode 100644 index 00000000..268f4b39 --- /dev/null +++ b/cgfcollector/src/FortranUtil.cpp @@ -0,0 +1,355 @@ +/** + * File: FortranUtil.cpp + * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt + */ + +#include "FortranUtil.h" + +#include + +using namespace Fortran::semantics; +using namespace Fortran::parser; +using namespace Fortran::common; +using namespace metacg; + +namespace metacg::cgfcollector { + +const Symbol* getTypeAsDerivedTypeSymbol(const Symbol* sym) { + if (auto* type = sym->GetType()) { + if (auto* derived = type->AsDerived()) { + return &derived->typeSymbol(); + } + } + return nullptr; +} + +const Symbol* getAbsoluteBaseSymbol(const std::vector& types, const Type* type) { + const Symbol* base = type->typeSymbol; + const Symbol* current = type->extendsFrom; + while (current) { + base = current; + auto it = std::find_if(types.begin(), types.end(), [&](const Type& t) { return t.typeSymbol == current; }); + if (it == types.end()) + break; + current = it->extendsFrom; + } + return canonicalizeSymbol(base).symbol; +} + +/** + * @brief Resolve a symbol by following UseDetails and HostAssocDetails until we reach a symbol that has neither. This + * is used to canonicalize symbols. + * + * @param sym + * @return + */ +static const Symbol* resolveUseHostSymbol(const Symbol* sym) { + while (true) { + if (auto* host = sym->detailsIf()) { + sym = &host->symbol(); + continue; + } + if (auto* use = sym->detailsIf()) { + sym = &use->symbol(); + continue; + } + break; + } + return sym; +} + +CanonicalSymbol canonicalizeSymbol(const Symbol* input, CanonicalMode mode) { + std::unordered_set visited; + const Symbol* sym = input; + + while (sym && !visited.count(sym)) { + visited.insert(sym); + sym = resolveUseHostSymbol(sym); + + // DerivedTypeDetails, is already canonicalized. + if (sym->detailsIf()) { + return {sym, CanonicalSymbol::Kind::DerivedType}; + } + + // GenericDetails, unpack to specific procedure if it has one. This is forwarded to the next check for + // SubprogramDetails. TODO: here we ignore possibly multiple specific procedure definitions. For the comparison step + // these need to be also considered. That's why this falls short when using generics. + if (auto* gen = sym->detailsIf()) { + if (!gen->specificProcs().empty()) { + const Symbol* proc = &gen->specificProcs().front().get(); + sym = proc; + continue; + } + } + + // SubprogramDetails, is already canonicalized. + if (auto* sub = sym->detailsIf()) { + // If it's a function, it was likely unpacked from the GenericDetails. We then extract the derived type symbol + // from the result type of the function, if it exists. This is mainly to handle cases where the definition of a + // constructor (defined through an interface block) with the same name as the derived type is defined. In LLVM + // versions 18 and before, the constructor would shadow the derived type symbol, leading that type beging lost in + // the process. In LLVM 19, this limitation is fixed, meaning constructors and derived types can better + // differentiated. We keep this for backwards compatibility. + if (sub->isFunction()) { + const Symbol& resultSym = sub->result(); + if (const auto* typeSym = getTypeAsDerivedTypeSymbol(&resultSym)) { + sym = typeSym; + continue; + } + } + return {sym, CanonicalSymbol::Kind::Procedure}; + } + + // If in ByType mode, unpack symbol type to the derived type symbol T, if it exists. This is handled in an extra + // mode because if we compare variables with the derived type definition we need to get to the type with GetType, to + // compare the actual type. Trying and comparing variables with the derived type definition would not work and makes + // no sense. But other times we don't want to unpack to the type but keep the symbol as is, and compare the derived + // type symbols directly. + if (mode == CanonicalMode::ByType) { + if (const auto* typeSym = getTypeAsDerivedTypeSymbol(sym)) { + sym = typeSym; + continue; + } + } + + return {sym, CanonicalSymbol::Kind::Other}; + } + + return {sym, CanonicalSymbol::Kind::Other}; +} + +bool compareSymbols(const Symbol* a, const Symbol* b, CanonicalMode mode) { + auto ca = canonicalizeSymbol(a, mode); + auto cb = canonicalizeSymbol(b, mode); + + MCGLogger::logDebug("Comparing symbols: {} ({}) ({}) and {} ({}) ({}), canon: {} ({}) ({}) {} ({}) ({})", + a ? a->name() : "nullptr", a ? getDetailsName(a) : "nullptr", fmt::ptr(a), + b ? b->name() : "nullptr", b ? getDetailsName(b) : "nullptr", fmt::ptr(b), + ca.symbol ? ca.symbol->name() : "nullptr", ca.symbol ? getDetailsName(ca.symbol) : "nullptr", + fmt::ptr(ca.symbol), cb.symbol ? cb.symbol->name() : "nullptr", + cb.symbol ? getDetailsName(cb.symbol) : "nullptr", fmt::ptr(cb.symbol)); + + return ca.kind == cb.kind && ca.symbol == cb.symbol; +} + +std::string mangleSymbol(const Symbol* sym, bool underscoring) { + assert(sym && "mangleSymbol called with nullptr"); + + std::string mangledName = Fortran::lower::mangle::mangleName(*sym); + + // Legacy Fortran - C interoperability before BIND(C) existed. + // We have to do this manually because normally it would run as + // a pass (ExternalNameConversionPass). + // + // NOTE: underscoring can be disabled with `-fno-underscoring` + auto result = fir::NameUniquer::deconstruct(mangledName); + if (fir::NameUniquer::isExternalFacingUniquedName(result)) { + if (result.first == fir::NameUniquer::NameKind::COMMON && result.second.name.empty()) + mangledName = blankCommonObjectName; + mangledName = GetExternalAssemblyName(result.second.name, underscoring); + } + + return mangledName; +} + +bool isOperator(const Expr* e) { + /* Operators: see 15.4.3.4.2, 10.1.6.1, 6.2.4 (https://j3-fortran.org/doc/year/23/23-007r1.pdf) + Negate, NOT, Power, Multiply, Divide, Add, Subtract, Concat, + LT, LE, EQ, NE, GE, GT, AND, OR, EQV, NEQV, + DefinedUnary, DefinedBinary + */ + + return holds_any_ofu), Expr::UnaryPlus, Expr::Negate, Expr::NOT, Expr::Power, Expr::Multiply, + Expr::Divide, Expr::Add, Expr::Subtract, Expr::Concat, Expr::LT, Expr::LE, Expr::EQ, Expr::NE, + Expr::GE, Expr::GT, Expr::AND, Expr::OR, Expr::EQV, Expr::NEQV, Expr::DefinedUnary, + Expr::DefinedBinary>(e->u); +} + +std::string getOperatorStringFromExpr(const Expr* expr) { + if (!isOperator(expr)) { + return ""; + } + + return std::visit(visitors{[](const Expr::UnaryPlus&) { return std::string("ADD"); }, + [](const Expr::Negate&) { return std::string("SUBTRACT"); }, + [](const Expr::NOT&) { return std::string("NOT"); }, + [](const Expr::Power&) { return std::string("POWER"); }, + [](const Expr::Multiply&) { return std::string("MULTIPLY"); }, + [](const Expr::Divide&) { return std::string("DIVIDE"); }, + [](const Expr::Add&) { return std::string("ADD"); }, + [](const Expr::Subtract&) { return std::string("SUBTRACT"); }, + [](const Expr::Concat&) { return std::string("CONCAT"); }, + [](const Expr::LT&) { return std::string("LT"); }, + [](const Expr::LE&) { return std::string("LE"); }, + [](const Expr::EQ&) { return std::string("EQ"); }, + [](const Expr::NE&) { return std::string("NE"); }, + [](const Expr::GE&) { return std::string("GE"); }, + [](const Expr::GT&) { return std::string("GT"); }, + [](const Expr::AND&) { return std::string("AND"); }, + [](const Expr::OR&) { return std::string("OR"); }, + [](const Expr::EQV&) { return std::string("EQV"); }, + [](const Expr::NEQV&) { return std::string("NEQV"); }, + + [](const Expr::DefinedUnary& op) { return std::get(op.t).v.ToString(); }, + [](const Expr::DefinedBinary& op) { return std::get(op.t).v.ToString(); }, + + [](const auto&) -> std::string { return std::string("UNKNOWN_OPERATOR"); }}, + expr->u); +} + +std::string getOperatorStringFromDefinedOperator(const DefinedOperator* op) { + std::string out = std::visit(visitors{[](const DefinedOperator::IntrinsicOperator& v) { + return std::string(DefinedOperator::EnumToString(v)); + }, + [](const DefinedOpName& v) { return v.v.ToString(); }, + [](const auto&) { return std::string("UNKNOWN_DEFINED_OPERATOR"); }}, + op->u); + + std::transform(out.begin(), out.end(), out.begin(), ::toupper); + return out; +} + +std::string getOperatorStringFromGenericDetails(const Symbol* symbol, const GenericKind& gk) { + std::string out = "UNKNOWN_GENERIC_KIND"; + + if (gk.IsIntrinsicOperator()) { + out = DefinedOperator::EnumToString(variantGetIntrinsicOperator(gk)); + } + + if (gk.IsDefinedOperator()) { + out = symbol->name().ToString(); + } + + std::transform(out.begin(), out.end(), out.begin(), ::toupper); + return out; +} + +bool compareExprIntrinsicOperator(const Expr* expr, DefinedOperator::IntrinsicOperator op) { + if (!expr) + return false; + + return std::visit( + visitors{[&](const Expr::UnaryPlus& e) { return op == DefinedOperator::IntrinsicOperator::Add; }, + [&](const Expr::Negate& e) { return op == DefinedOperator::IntrinsicOperator::Subtract; }, + [&](const Expr::NOT& e) { return op == DefinedOperator::IntrinsicOperator::NOT; }, + [&](const Expr::Power& e) { return op == DefinedOperator::IntrinsicOperator::Power; }, + [&](const Expr::Multiply& e) { return op == DefinedOperator::IntrinsicOperator::Multiply; }, + [&](const Expr::Divide& e) { return op == DefinedOperator::IntrinsicOperator::Divide; }, + [&](const Expr::Add& e) { return op == DefinedOperator::IntrinsicOperator::Add; }, + [&](const Expr::Subtract& e) { return op == DefinedOperator::IntrinsicOperator::Subtract; }, + [&](const Expr::Concat& e) { return op == DefinedOperator::IntrinsicOperator::Concat; }, + [&](const Expr::LT& e) { return op == DefinedOperator::IntrinsicOperator::LT; }, + [&](const Expr::LE& e) { return op == DefinedOperator::IntrinsicOperator::LE; }, + [&](const Expr::EQ& e) { return op == DefinedOperator::IntrinsicOperator::EQ; }, + [&](const Expr::NE& e) { return op == DefinedOperator::IntrinsicOperator::NE; }, + [&](const Expr::GE& e) { return op == DefinedOperator::IntrinsicOperator::GE; }, + [&](const Expr::GT& e) { return op == DefinedOperator::IntrinsicOperator::GT; }, + [&](const Expr::AND& e) { return op == DefinedOperator::IntrinsicOperator::AND; }, + [&](const Expr::OR& e) { return op == DefinedOperator::IntrinsicOperator::OR; }, + [&](const Expr::EQV& e) { return op == DefinedOperator::IntrinsicOperator::EQV; }, + [&](const Expr::NEQV& e) { return op == DefinedOperator::IntrinsicOperator::NEQV; }, + + [](const auto&) { return false; }}, + expr->u); +} + +bool isBinaryOperator(const Expr* e) { + if (!e) + return false; + + return holds_any_ofu), Expr::Power, Expr::Multiply, Expr::Divide, Expr::Add, Expr::Subtract, + Expr::Concat, Expr::LT, Expr::LE, Expr::EQ, Expr::NE, Expr::GE, Expr::GT, Expr::AND, Expr::OR, + Expr::EQV, Expr::NEQV, Expr::DefinedBinary>(e->u); +} + +bool isUnaryOperator(const Expr* e) { + if (!e) + return false; + + return holds_any_ofu), Expr::UnaryPlus, Expr::Negate, Expr::NOT, Expr::DefinedUnary>(e->u); +} + +DefinedOperator::IntrinsicOperator variantGetIntrinsicOperator(const GenericKind& gk) { + return std::visit(visitors{[](const RelationalOperator& op) { + using RO = RelationalOperator; + using IO = DefinedOperator::IntrinsicOperator; + + switch (op) { + case RO::LT: + return IO::LT; + case RO::LE: + return IO::LE; + case RO::EQ: + return IO::EQ; + case RO::NE: + return IO::NE; + case RO::GE: + return IO::GE; + case RO::GT: + return IO::GT; + default: + MCGLogger::logDebug("Error: Unknown RelationalOperator in mapToIntrinsicOperator"); + return IO::LT; // avoid warning + } + }, + [](const LogicalOperator& op) { + using LO = LogicalOperator; + using IO = DefinedOperator::IntrinsicOperator; + + switch (op) { + case LO::And: + return IO::AND; + case LO::Or: + return IO::OR; + case LO::Eqv: + return IO::EQV; + case LO::Neqv: + return IO::NEQV; + case LO::Not: + return IO::NOT; + default: + MCGLogger::logDebug("Error: Unknown LogicalOperator in mapToIntrinsicOperator"); + return IO::AND; // avoid warning + } + }, + [](const NumericOperator& op) { + using NO = NumericOperator; + using IO = DefinedOperator::IntrinsicOperator; + + switch (op) { + case NO::Power: + return IO::Power; + case NO::Multiply: + return IO::Multiply; + case NO::Divide: + return IO::Divide; + case NO::Add: + return IO::Add; + case NO::Subtract: + return IO::Subtract; + default: + MCGLogger::logDebug("Error: Unknown NumericOperator in mapToIntrinsicOperator"); + return IO::Add; // avoid warning + } + }, + [](const auto& op) { + MCGLogger::logDebug("Error: Unknown operator type in mapToIntrinsicOperator"); + return DefinedOperator::IntrinsicOperator::Add; // avoid warning + }}, + gk.u); +} + +std::string getDetailsName(const Symbol* symbol) { + assert(symbol && "getDetailsName called with nullptr"); + + const char* names[] = {"UnknownDetails", "MainProgramDetails", "ModuleDetails", "SubprogramDetails", + "SubprogramNameDetails", "EntityDetails", "ObjectEntityDetails", "ProcEntityDetails", + "AssocEntityDetails", "DerivedTypeDetails", "UseDetails", "UseErrorDetails", + "HostAssocDetails", "GenericDetails", "ProcBindingDetails", "NamelistDetails", + "CommonBlockDetails", "TypeParamDetails", "MiscDetails", "UserReductionDetails", + "MapperDetails"}; + return names[symbol->details().index()]; +} + +} // namespace metacg::cgfcollector diff --git a/cgfcollector/src/Main.cpp b/cgfcollector/src/Main.cpp new file mode 100644 index 00000000..5d109ade --- /dev/null +++ b/cgfcollector/src/Main.cpp @@ -0,0 +1,116 @@ +/** + * File: Main.cpp + * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt + */ + +#include "ParseTreeVisitor.h" + +#include +#include + +using namespace metacg; +using namespace metacg::graph; +using namespace metacg::io; +using namespace metacg::cgfcollector; +using namespace Fortran::parser; + +static MCGManager& mcgManager = MCGManager::get(); + +static llvm::cl::OptionCategory CGCategory("Callgraph Plugin Options"); + +static llvm::cl::opt Dot("dot", llvm::cl::desc("Generate DOT output"), llvm::cl::cat(CGCategory), + llvm::cl::init(false)); +static llvm::cl::opt Verbose("verbose", llvm::cl::desc("Enable verbose logging"), llvm::cl::cat(CGCategory), + llvm::cl::init(false)); +static llvm::cl::opt GraphName("graph-name", + llvm::cl::desc("Name of the generated graph (default: \"cg\")"), + llvm::cl::cat(CGCategory), llvm::cl::init("cg")); +static llvm::cl::opt IncludeIntrinsics("include-intrinsics", + llvm::cl::desc("Include intrinsic procedures in the callgraph"), + llvm::cl::cat(CGCategory), llvm::cl::init(false)); + +/** + * @brief Replace the file extension of filePath with newExtension. If filePath does not have an extension, append + * newExtension. + * + * @param filePath + * @param newExtension + */ +void replaceExtension(std::string& filePath, const std::string& newExtension) { + size_t lastDotPos = filePath.find_last_of('.'); + if (lastDotPos != std::string::npos) { + filePath = filePath.substr(0, lastDotPos) + newExtension; + } else { + filePath += newExtension; + } +} + +/** + * @class CollectCG + * @brief Plugin action to collect callgraph and dump it as JSON file + * + */ +class CollectCG : public Fortran::frontend::PluginParseTreeAction { + public: + void executeAction() override { + if (Verbose) { + metacg::MCGLogger::instance().getConsole()->set_level(spdlog::level::debug); + metacg::MCGLogger::instance().getConsole()->set_pattern("%v"); + } + + // create and register callgraph + mcgManager.addToManagedGraphs(GraphName, std::make_unique(), true); + + Callgraph* cg = mcgManager.getCallgraph(GraphName); + if (!cg) { + MCGLogger::logError("Failed to create callgraph"); + return; + } + + // traverse parse tree and generate callgraph + std::string currentFile = getCurrentFile().str(); + bool underscoring = getInstance().getInvocation().getLoweringOpts().getUnderscoring(); + + ParseTreeVisitor visitor(cg, currentFile, underscoring, IncludeIntrinsics); + Fortran::parser::Walk(getParsing().parseTree(), visitor); + visitor.postProcess(); + + // create writer + auto mcgWriter = io::createWriter(4); + if (!mcgWriter) { + MCGLogger::logError("Unable to create a writer for format version {}", 4); + return; + } + + io::JsonSink jsonSink; + mcgWriter->write(cg, jsonSink); + + // determine output file name. Honors `-o` option. + std::string outputFile = getInstance().getFrontendOpts().outputFile; + + if (outputFile.empty()) { + outputFile = getCurrentFile().str(); + replaceExtension(outputFile, ".mcg"); + } else { + outputFile.append(".mcg"); + } + + std::ofstream os(outputFile); + os << jsonSink.getJson() << std::endl; + + // generate dot output + if (Dot) { + dot::DotGenerator dotGen(cg); + dotGen.generate(); + + auto dotOutputFile = outputFile; + replaceExtension(dotOutputFile, ".dot"); + + std::ofstream dotOs(dotOutputFile); + dotOs << dotGen.getDotString() << std::endl; + } + } +}; + +static Fortran::frontend::FrontendPluginRegistry::Add X("genCG", "Generate Callgraph"); diff --git a/cgfcollector/src/ParseTreeVisitor.cpp b/cgfcollector/src/ParseTreeVisitor.cpp new file mode 100644 index 00000000..1863308a --- /dev/null +++ b/cgfcollector/src/ParseTreeVisitor.cpp @@ -0,0 +1,847 @@ +/** + * File: ParseTreeVisitor.cpp + * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt + */ + +#include "ParseTreeVisitor.h" +#include "FortranUtil.h" +#include +#include + +using namespace Fortran::parser; +using namespace Fortran::semantics; +using namespace Fortran::common; +using namespace metacg; + +namespace metacg::cgfcollector { + +template +void ParseTreeVisitor::handleDummyArgs(const Iterable& items, Extractor extract) { + const Symbol* currentProcedureSymbol = currentProcedures.back().symbol; + + auto it = std::find_if(procedures.begin(), procedures.end(), + [&](const Procedure& func) { return func.symbol == currentProcedureSymbol; }); + if (it == procedures.end()) + return; + + for (const auto& item : items) { + if (const Symbol* symbol = extract(item)) { + currentProcedures.back().addDummyArg(symbol); + it->addDummyArg(symbol); + varTracking->addTrackedVar({symbol, currentProcedureSymbol}); + } + } +} + +template +void ParseTreeVisitor::handleFuncSubStmt(const T& stmt) { + if (const Symbol* sym = std::get(stmt.t).symbol) { + currentProcedures.emplace_back(sym, std::vector()); + procedures.emplace_back(sym, std::vector()); + cg->getOrInsertNode(mangleSymbol(sym, underscoring), currentFileName, false, false); + + MCGLogger::logDebug("Add node: {} ({}) ({})", mangleSymbol(sym, underscoring), getDetailsName(sym), fmt::ptr(sym)); + } +} + +void ParseTreeVisitor::handleEndFuncSubStmt() { + varTracking->handleTrackedVars(currentProcedures.back().symbol, edgeM); + + if (!currentProcedures.empty()) { + currentProcedures.pop_back(); + } +} + +void ParseTreeVisitor::postProcess() { + // handle potential finalizers from procedure calls + for (PotentialFinalizer pf : potentialFinalizers) { + auto calledIt = std::find_if(procedures.begin(), procedures.end(), [&](const Procedure& f) { + return mangleSymbol(f.symbol, underscoring) == pf.procedureCalled; + }); + if (calledIt == procedures.end()) + continue; + + auto arg = calledIt->dummyArgs.begin() + pf.argPos; + + if (!arg->hasBeenInitialized) + continue; + + edgeM->addEdgesForFinalizers(pf); + } + + edgeM->uniquifyEdges(); + + // add edges + for (const Edge& edge : edges) { + const CgNode& callerNode = cg->getOrInsertNode(edge.caller); + const CgNode& calleeNode = cg->getOrInsertNode(edge.callee); + + cg->addEdge(callerNode, calleeNode); + } + + // debug + + MCGLogger::logDebug("procedureOverwrites Map:"); + for (const auto& [key, values] : procedureOverwrites) { + MCGLogger::logDebug(" Type: {} ({}) ({}), Name: {}", key.first ? key.first->name().ToString() : "nullptr", + getDetailsName(key.first), fmt::ptr(key.first), key.second); + for (const Symbol* value : values) { + MCGLogger::logDebug(" Overwrites: {} ({}) ({})", value->name(), getDetailsName(value), fmt::ptr(value)); + } + } + + MCGLogger::logDebug("finalizers Map:"); + for (const auto& [key, values] : finalizers) { + MCGLogger::logDebug(" Type: {} ({}) ({})", key->name(), getDetailsName(key), fmt::ptr(key)); + for (const Symbol* value : values) { + MCGLogger::logDebug(" Finalizer: {} ({}) ({})", value->name(), getDetailsName(value), fmt::ptr(value)); + } + } + + MCGLogger::logDebug("typeOperators Map:"); + for (const auto& [key, values] : typeOperators) { + MCGLogger::logDebug(" Type: {} ({}) ({}), Operator: {}", key.first ? key.first->name().ToString() : "nullptr", + getDetailsName(key.first), fmt::ptr(key.first), key.second); + for (const std::string& value : values) { + MCGLogger::logDebug(" Binding name: {}", value); + } + } + + MCGLogger::logDebug("Type vector:"); + for (const auto& type : types) { + MCGLogger::logDebug(" Type: {} ({}) ({})", type.typeSymbol->name(), getDetailsName(type.typeSymbol), + fmt::ptr(type.typeSymbol)); + if (type.extendsFrom) { + MCGLogger::logDebug(" Extends from: {} ({}) ({})", type.extendsFrom->name(), getDetailsName(type.extendsFrom), + fmt::ptr(type.extendsFrom)); + } + } +} + +// Visitor implementations + +bool ParseTreeVisitor::Pre(const MainProgram& p) { + inMainProgram = true; + + if (const auto& optionalProgramStmt = std::get<0>(p.t)) { + Symbol* mainProgramSymbol = optionalProgramStmt->statement.v.symbol; + if (!mainProgramSymbol) + return true; + + const Symbol* currentProcedureSymbol = + currentProcedures.emplace_back(mainProgramSymbol, std::vector()).symbol; + cg->getOrInsertNode(mangleSymbol(currentProcedureSymbol, underscoring), currentFileName, false, false); + + MCGLogger::logDebug("\nIn main program: {} ({}) ({})", mangleSymbol(currentProcedureSymbol, underscoring), + getDetailsName(currentProcedureSymbol), fmt::ptr(currentProcedureSymbol)); + } + return true; +} + +void ParseTreeVisitor::Post(const MainProgram&) { + if (!currentProcedures.empty()) { + const Symbol* currentProcedureSymbol = currentProcedures.back().symbol; + MCGLogger::logDebug("End main program: {} ({}) ({})", mangleSymbol(currentProcedureSymbol, underscoring), + getDetailsName(currentProcedureSymbol), fmt::ptr(currentProcedureSymbol)); + } + + handleEndFuncSubStmt(); + + inMainProgram = false; +} + +bool ParseTreeVisitor::Pre(const FunctionSubprogram&) { + inFunctionOrSubroutineSubProgram = true; + return true; +} + +void ParseTreeVisitor::Post(const FunctionSubprogram&) { inFunctionOrSubroutineSubProgram = false; } + +bool ParseTreeVisitor::Pre(const SubroutineSubprogram&) { + inFunctionOrSubroutineSubProgram = true; + return true; +} + +void ParseTreeVisitor::Post(const SubroutineSubprogram&) { inFunctionOrSubroutineSubProgram = false; } + +void ParseTreeVisitor::Post(const ExecutionPart& e) { + if (!inFunctionOrSubroutineSubProgram && !inMainProgram) + return; + + CgNode* node = cg->getFirstNode(mangleSymbol(currentProcedures.back().symbol, underscoring)); + if (!node) { + return; + } + + node->setHasBody(true); +} + +void ParseTreeVisitor::Post(const EntryStmt& e) { + const Name* name = &std::get(e.t); + if (!name->symbol) + return; + + MCGLogger::logDebug("Add Entry point: {} ({}) ({})", mangleSymbol(name->symbol, underscoring), + getDetailsName(name->symbol), fmt::ptr(name->symbol)); + + // handle entry statement as normal procedure. + cg->getOrInsertNode(mangleSymbol(name->symbol, underscoring), currentFileName, false, true); +} + +void ParseTreeVisitor::Post(const FunctionStmt& f) { + MCGLogger::logDebug("\nIn function: {} ({}) ({})", mangleSymbol(std::get(f.t).symbol, underscoring), + getDetailsName(std::get(f.t).symbol), fmt::ptr(std::get(f.t).symbol)); + + handleFuncSubStmt(f); + + handleDummyArgs(std::get>(f.t), [](const Name& name) { return name.symbol; }); +} + +void ParseTreeVisitor::Post(const EndFunctionStmt&) { + if (!currentProcedures.empty()) { + const Symbol* currentProcedureSymbol = currentProcedures.back().symbol; + MCGLogger::logDebug("End function: {} ({}) ({})", mangleSymbol(currentProcedureSymbol, underscoring), + getDetailsName(currentProcedureSymbol), fmt::ptr(currentProcedureSymbol)); + } + + handleEndFuncSubStmt(); +} + +void ParseTreeVisitor::Post(const SubroutineStmt& s) { + MCGLogger::logDebug("\nIn subroutine: {} ({}) ({})", mangleSymbol(std::get(s.t).symbol, underscoring), + getDetailsName(std::get(s.t).symbol), fmt::ptr(std::get(s.t).symbol)); + + handleFuncSubStmt(s); + + handleDummyArgs(std::get>(s.t), [](const DummyArg& name) { + if (const Name* n = std::get_if(&name.u)) { + return n->symbol; + } + return static_cast(nullptr); + }); +} + +void ParseTreeVisitor::Post(const EndSubroutineStmt&) { + if (!currentProcedures.empty()) { + const Symbol* currentProcedureSymbol = currentProcedures.back().symbol; + MCGLogger::logDebug("End subroutine: {} ({}) ({})", mangleSymbol(currentProcedureSymbol, underscoring), + getDetailsName(currentProcedureSymbol), fmt::ptr(currentProcedureSymbol)); + } + + handleEndFuncSubStmt(); +} + +void ParseTreeVisitor::Post(const ProcedureDesignator& p) { + if (currentProcedures.empty()) + return; + + const Symbol* currentProcedureSymbol = currentProcedures.back().symbol; + + // if just the name is called. (as subroutine with call or as function without call) + if (const Name* name = std::get_if(&p.u)) { + if (!name->symbol) + return; + + // ignore intrinsic functions + if (!includeInstrinsics && name->symbol->attrs().test(Attr::INTRINSIC)) { + return; + } + + edgeM->addEdge(currentProcedureSymbol, name->symbol); + + // if called from a object with %. (base % component) + } else if (const ProcComponentRef* procCompRef = std::get_if(&p.u)) { + const Symbol* symbolComp = procCompRef->v.thing.component.symbol; + if (!symbolComp) + return; + + edgeM->addEdge(currentProcedureSymbol, symbolComp); + + const Name* baseName = std::get_if(&procCompRef->v.thing.base.u); + if (!baseName || !baseName->symbol) + return; + const Symbol* symbolBase = baseName->symbol; + + // handle derived types edges + + auto baseTypeIt = std::find_if(types.begin(), types.end(), [&](const Type& t) { + return compareSymbols(t.typeSymbol, symbolBase, CanonicalMode::ByType); + }); + if (baseTypeIt == types.end()) + return; + + try { + auto proc = procedureOverwrites.at({getAbsoluteBaseSymbol(types, &(*baseTypeIt)), symbolComp->name().ToString()}); + for (const Symbol* p : proc) { + edgeM->addEdge(currentProcedureSymbol, p); + } + } catch (const std::out_of_range& e) { + // no overwrites for this procedure, do nothing + } + } +} + +void ParseTreeVisitor::Post(const AssignmentStmt& a) { + const Variable* var = &std::get(a.t); + + const Name* name = getNameFromClassWithDesignator(*var); + if (!name || !name->symbol) + return; + + varTracking->handleTrackedVarAssignment(currentProcedures.back().symbol, name->symbol->name()); +} + +void ParseTreeVisitor::Post(const AllocateStmt& a) { + const std::list* allocs = &std::get>(a.t); + + for (const Allocation& alloc : *allocs) { + const AllocateObject* allocObj = &std::get(alloc.t); + const Name* name = std::get_if(&allocObj->u); + if (!name || !name->symbol) { + continue; + } + + varTracking->handleTrackedVarAssignment(currentProcedures.back().symbol, name->symbol->name()); + } +} + +void ParseTreeVisitor::Post(const Call& c) { + const ProcedureDesignator* designator = &std::get(c.t); + const std::list* args = &std::get>(c.t); + const Symbol* currentProcedureSymbol = currentProcedures.back().symbol; + + const Name* procName = std::get_if(&designator->u); + if (!procName || !procName->symbol) + return; + + std::size_t argPos = 0; + for (const ActualArgSpec& arg : *args) { + const ActualArg* actualArg = &std::get(arg.t); + const Indirection* expr = std::get_if>(&actualArg->u); + if (!expr) + return; + const Name* name = getNameFromClassWithDesignator(expr->value()); + if (!name || !name->symbol) + return; + + // handle move_alloc intrinsic for allocatable vars + if (procName->symbol->attrs().test(Attr::INTRINSIC) && procName->symbol->name() == "move_alloc") { + varTracking->handleTrackedVarAssignment(currentProcedureSymbol, name->symbol->name()); + } else { + // handle finalizers for allocatable vars. + // This collects info from variables that are parse as arguments to procedures. Procedures are defined below the + // execution part, so this need to be handled at the end of the parse tree traversal. + TrackedVar* trackedVar = varTracking->getTrackedVarFromSourceName(currentProcedureSymbol, name->symbol->name()); + if (!trackedVar) + continue; + + MCGLogger::logDebug("Add potential finalizers for var: {} ({}) ({})", name->symbol->name(), + getDetailsName(name->symbol), fmt::ptr(name->symbol)); + PotentialFinalizer& pf = potentialFinalizers.emplace_back(argPos, mangleSymbol(procName->symbol, underscoring)); + auto baseTypeIt = std::find_if(types.begin(), types.end(), [&](const Type& t) { + return compareSymbols(t.typeSymbol, name->symbol, CanonicalMode::ByType); + }); + if (baseTypeIt == types.end()) + continue; + + try { + auto final = finalizers.at(getAbsoluteBaseSymbol(types, &(*baseTypeIt))); + for (const Symbol* f : final) { + pf.addFinalizerEdge({mangleSymbol(currentProcedureSymbol, underscoring), mangleSymbol(f, underscoring)}); + MCGLogger::logDebug(" Potential finalizer edge: {} ({}) -> {} ({})", + mangleSymbol(currentProcedureSymbol, underscoring), + getDetailsName(currentProcedureSymbol), mangleSymbol(f, underscoring), getDetailsName(f)); + } + } catch (const std::out_of_range& e) { + // no finalizer for this type, do nothing + } + } + } +} + +void ParseTreeVisitor::Post(const TypeDeclarationStmt& t) { + if (currentProcedures.empty()) { + return; + } + + for (const EntityDecl& entity : std::get>(t.t)) { + const Name& name = std::get(entity.t); + if (!name.symbol) + continue; + + // skip if name is an argument to a function or subroutine + bool isProcedureArg = false; + std::vector& currentDummyArgs = currentProcedures.back().dummyArgs; + if (!currentDummyArgs.empty()) { + auto it = std::find_if(currentDummyArgs.begin(), currentDummyArgs.end(), + [&name](const Procedure::DummyArg& dummyArg) { return dummyArg.symbol == name.symbol; }); + + if (it != currentDummyArgs.end()) + isProcedureArg = true; + } + + bool holds_allocatable = false; + const IntentSpec* holds_intent = nullptr; + bool holds_save = false; + for (const AttrSpec& attr : std::get>(t.t)) { + if (std::holds_alternative(attr.u)) + holds_allocatable = true; + else if (std::holds_alternative(attr.u)) + holds_intent = &std::get(attr.u); + else if (std::holds_alternative(attr.u)) + holds_save = true; + } + + // vars with save attr are not destructed + if (holds_save) { + continue; + } + + const Symbol* currentProcedureSymbol = currentProcedures.back().symbol; + + if (isProcedureArg) { + if (!holds_allocatable) { + if (!holds_intent) { + // no intent attr, if not set does not call finalizer. + MCGLogger::logDebug("Add tracking for procedure argument: {} ({}) ({})", name.symbol->name(), + getDetailsName(name.symbol), fmt::ptr(name.symbol)); + varTracking->addTrackedVar({name.symbol, currentProcedureSymbol, false, true}); + } else { + if (holds_intent->v == IntentSpec::Intent::Out) { + // intent out, calls finalizer because (7.5.6.3 line 21 and onwards) + edgeM->addEdgesForFinalizers(types, finalizers, currentProcedureSymbol, name.symbol); + } else if (holds_intent->v == IntentSpec::Intent::InOut) { + // intent inout, calls finalizer when set. + MCGLogger::logDebug("Add tracking for inout argument: {} ({}) ({})", name.symbol->name(), + getDetailsName(name.symbol), fmt::ptr(name.symbol)); + varTracking->addTrackedVar({name.symbol, currentProcedureSymbol, false, true}); + } + } + } + } else { + if (holds_allocatable) { + MCGLogger::logDebug("Add tracking for allocatable variable: {} ({}) ({})", name.symbol->name(), + getDetailsName(name.symbol), fmt::ptr(name.symbol)); + varTracking->addTrackedVar({name.symbol, currentProcedureSymbol, false, true}); + // skip var with allocatable attr. + // Add to trackedVars because it needs to be assigned at least once before calling a finalizers make sense. + } else { + edgeM->addEdgesForFinalizers(types, finalizers, currentProcedureSymbol, name.symbol); + } + } + } +} + +bool ParseTreeVisitor::Pre(const DerivedTypeDef&) { + inDerivedTypeDef = true; + + // initialize with empty `Type` + types.emplace_back(); + + return true; +} + +void ParseTreeVisitor::Post(const DerivedTypeDef&) { + inDerivedTypeDef = false; + + // finalizers (must be after extends discovery) + Type& currentType = types.back(); + const Symbol* canon = canonicalizeSymbol(currentType.typeSymbol).symbol; + if (const DerivedTypeDetails* details = canon->detailsIf()) { + for (const auto& final : details->finals()) { + finalizers[getAbsoluteBaseSymbol(types, ¤tType)].emplace_back(&final.second.get()); + } + } + + MCGLogger::logDebug("End derived type: {} ({}) ({})", types.back().typeSymbol->name(), + getDetailsName(types.back().typeSymbol), fmt::ptr(types.back().typeSymbol)); +} + +bool ParseTreeVisitor::Pre(const DerivedTypeStmt& t) { + if (!inDerivedTypeDef) + return true; + + Type& currentType = types.back(); + const Name& name = std::get(t.t); + currentType.typeSymbol = name.symbol; + + MCGLogger::logDebug("\nIn derived type: {} ({}) ({})", currentType.typeSymbol->name(), + getDetailsName(currentType.typeSymbol), fmt::ptr(currentType.typeSymbol)); + + return true; +} + +void ParseTreeVisitor::Post(const TypeAttrSpec& a) { + if (!inDerivedTypeDef) + return; + + Type& currentType = types.back(); + if (std::holds_alternative(a.u)) { + const TypeAttrSpec::Extends& extends = std::get(a.u); + currentType.extendsFrom = extends.v.symbol; + + MCGLogger::logDebug("Extends from: {} ({}) ({})", currentType.extendsFrom->name(), + getDetailsName(currentType.extendsFrom), fmt::ptr(currentType.extendsFrom)); + } +} + +void ParseTreeVisitor::Post(const TypeBoundProcedureStmt& s) { + if (!inDerivedTypeDef) + return; + + // basically normal type-bound procedure statement + if (const TypeBoundProcedureStmt::WithoutInterface* withoutInterface = + std::get_if(&s.u)) { + for (const TypeBoundProcDecl& d : withoutInterface->declarations) { + const Name& name = std::get(d.t); + if (!name.symbol) + return; + + Type& currentType = types.back(); + + const std::optional& opt = std::get>(d.t); + const Symbol* value = opt ? opt->symbol : name.symbol; + + procedureOverwrites[{getAbsoluteBaseSymbol(types, ¤tType), name.symbol->name().ToString()}].emplace_back( + value); + + MCGLogger::logDebug("Add procedure: {} ({}) ({}) -> {} ({}) ({})", name.symbol->name(), + getDetailsName(name.symbol), fmt::ptr(name.symbol), value->name(), getDetailsName(value), + fmt::ptr(value)); + } + + // For abstract types. This is eqivalent to an abstract class in C++. In Fortran, you provide the signature of a + // procedure for an abstract type in an extra interface block. And Flang wraps such definitions in a `WithInterface` + // struct. + } else if (const TypeBoundProcedureStmt::WithInterface* withInterface = + std::get_if(&s.u)) { + for (const Name& n : withInterface->bindingNames) { + if (!n.symbol) + return; + + if (const ProcBindingDetails* bindingDetails = n.symbol->detailsIf()) { + Type& currentType = types.back(); + + procedureOverwrites[{getAbsoluteBaseSymbol(types, ¤tType), n.symbol->name().ToString()}].emplace_back( + &bindingDetails->symbol()); + + MCGLogger::logDebug("Add procedure: {} ({}) ({}) -> {} ({}) ({})", n.symbol->name(), fmt::ptr(n.symbol), + getDetailsName(n.symbol), n.symbol->name(), getDetailsName(n.symbol), fmt::ptr(n.symbol)); + } + } + } +} + +void ParseTreeVisitor::Post(const TypeBoundGenericStmt& s) { + if (!inDerivedTypeDef) + return; + + Type& currentType = types.back(); + + // type-bound operators are defined as type-bound generic statements. Here we unpack them and add them + // to the current type. + const Indirection& genericSpec = std::get>(s.t); + if (const DefinedOperator* definedOperator = std::get_if(&genericSpec.value().u)) { + for (const Name n : std::get>(s.t)) { + typeOperators[{getAbsoluteBaseSymbol(types, ¤tType), getOperatorStringFromDefinedOperator(definedOperator)}] + .emplace_back(n.symbol->name().ToString()); + } + } +} + +bool ParseTreeVisitor::Pre(const InterfaceStmt&) { + inInterfaceStmt = true; + return true; +} + +bool ParseTreeVisitor::Pre(const EndInterfaceStmt&) { + inInterfaceStmt = false; + inInterfaceStmtDefinedOperator = false; + return true; +} + +void ParseTreeVisitor::Post(const DefinedOperator& op) { + if (!inInterfaceStmt) + return; + + inInterfaceStmtDefinedOperator = true; + + // intrinsic operators with a predefined name, like +, *, etc. + if (std::holds_alternative(op.u)) { + DefinedOperator::IntrinsicOperator intrinsicOp = std::get(op.u); + interfaceOperators.emplace_back(intrinsicOp, std::vector()); + // custom operators with a name + } else if (std::holds_alternative(op.u)) { + const DefinedOpName& opName = std::get(op.u); + if (!opName.v.symbol) + return; + + interfaceOperators.emplace_back(opName.v.symbol, std::vector()); + } +} + +void ParseTreeVisitor::Post(const ProcedureStmt& p) { + if (!inInterfaceStmtDefinedOperator) + return; + + const std::list& name = std::get>(p.t); + for (const Name& n : name) { + if (!n.symbol) + continue; + + if (interfaceOperators.empty()) { + MCGLogger::logError("This should not happen. Likely there is a bug with parsing DefinedOperator's"); + continue; + } + + interfaceOperators.back().second.push_back(n.symbol); + } +} + +bool ParseTreeVisitor::Pre(const Expr& e) { + const Name* name = getNameFromClassWithDesignator(e); + // true if not in a designator expr + if (!name || !name->symbol || exprStmtWithOps.empty()) { + if (isOperator(&e)) { + exprStmtWithOps.emplace_back(&e); + } + + return true; + } + + // not in a function. So no overloaded operator is called. + if (currentProcedures.empty()) + return true; + + for (const Expr* e : exprStmtWithOps) { + // search in interfaceOperators first before search in derived types + auto interfaceOp = std::find_if(interfaceOperators.begin(), interfaceOperators.end(), [&](const auto& op) { + if (std::holds_alternative(op.first)) { + DefinedOperator::IntrinsicOperator intrinsicOp = std::get(op.first); + return compareExprIntrinsicOperator(e, intrinsicOp); + } else if (std::holds_alternative(op.first)) { + const Symbol* definedOpNameSym = std::get(op.first); + if (const Expr::DefinedUnary* definedUnary = std::get_if(&e->u)) { + const DefinedOpName& exprOpName = std::get<0>(definedUnary->t); + return definedOpNameSym->name() == exprOpName.v.symbol->name(); + } else if (const Expr::DefinedBinary* definedBinary = std::get_if(&e->u)) { + const DefinedOpName& exprOpName = std::get<0>(definedBinary->t); + return definedOpNameSym->name() == exprOpName.v.symbol->name(); + } + } + return false; + }); + if (interfaceOp != interfaceOperators.end()) { + bool isUnaryOp = isUnaryOperator(e); + bool isBinaryOp = isBinaryOperator(e); + + for (const Symbol* sym : interfaceOp->second) { + const Symbol* currentProcedureSymbol = currentProcedures.back().symbol; + + // skip self calls + if (mangleSymbol(sym, underscoring) == mangleSymbol(currentProcedureSymbol, underscoring)) + continue; + + // if unary, add potential unary operators. Same for binary operators. + auto procedureIt = + std::find_if(procedures.begin(), procedures.end(), [&](const Procedure& f) { return f.symbol == sym; }); + if (procedureIt != procedures.end()) { + if ((!isUnaryOp || procedureIt->dummyArgs.size() != 1) && + (!isBinaryOp || procedureIt->dummyArgs.size() != 2)) { + continue; + } + } + + edgeM->addEdge(currentProcedureSymbol, sym); + } + } + + // search in derived types. Handle polymorphic calls by adding edges for all derived types that have a procedure for + // the operator. + + auto typeIt = std::find_if(types.begin(), types.end(), [&](const Type& t) { + return compareSymbols(t.typeSymbol, name->symbol, CanonicalMode::ByType); + }); + if (typeIt == types.end()) + continue; + + auto op = typeOperators.find({getAbsoluteBaseSymbol(types, &(*typeIt)), getOperatorStringFromExpr(e)}); + if (op == typeOperators.end()) + continue; + + for (const std::string& opBindingName : op->second) { + // skip self calls + if (opBindingName == currentProcedures.back().symbol->name().ToString()) { + continue; + } + + auto procOverwriteIt = procedureOverwrites.find({getAbsoluteBaseSymbol(types, &(*typeIt)), opBindingName}); + if (procOverwriteIt == procedureOverwrites.end()) + continue; + + // skip self call + bool isSelfCall = false; + for (const Symbol* overwriteSym : procOverwriteIt->second) { + if (overwriteSym->name() == currentProcedures.back().symbol->name()) { + isSelfCall = true; + break; + } + } + if (isSelfCall) + continue; + + for (const Symbol* overwriteSym : procOverwriteIt->second) { + edgeM->addEdge(currentProcedures.back().symbol, overwriteSym); + } + } + } + + return true; +} + +void ParseTreeVisitor::Post(const Expr& e) { + if (!isOperator(&e)) { + return; + } + + if (!exprStmtWithOps.empty()) { + exprStmtWithOps.pop_back(); + } +} + +void ParseTreeVisitor::Post(const UseStmt& u) { + const Symbol* useSymbol = u.moduleName.symbol; + + MCGLogger::logDebug("\nUse module: {} ({}) ({})", useSymbol->name(), getDetailsName(useSymbol), fmt::ptr(useSymbol)); + + if (const Scope* modScope = useSymbol->scope()) { + for (const auto& pair : *modScope) { + const Symbol* symbol = &*pair.second; + + // extract derived types from module and populate types var + if (const DerivedTypeDetails* details = symbol->detailsIf()) { + MCGLogger::logDebug("Found derived type in module: {} ({}) ({})", symbol->name(), getDetailsName(symbol), + fmt::ptr(symbol)); + + Type& currentType = types.emplace_back(); + currentType.typeSymbol = symbol; + + // extends + if (const Symbol* extendsSymbol = details->GetParentComponent(*symbol->scope())) { + if (const ObjectEntityDetails* objectDetails = extendsSymbol->detailsIf()) { + if (const Symbol* typeSym = getTypeAsDerivedTypeSymbol(extendsSymbol)) { + currentType.extendsFrom = typeSym; + + MCGLogger::logDebug("Found extends in module derived type: {} ({}) ({}) -> {} ({}) ({})", symbol->name(), + getDetailsName(symbol), fmt::ptr(symbol), currentType.extendsFrom->name(), + getDetailsName(currentType.extendsFrom), fmt::ptr(currentType.extendsFrom)); + } + } + } + + // finalizers (must be after extends discovery) + const Symbol* canon = canonicalizeSymbol(symbol).symbol; + if (const DerivedTypeDetails* details = canon->detailsIf()) { + for (const auto& final : details->finals()) { + finalizers[getAbsoluteBaseSymbol(types, ¤tType)].emplace_back(&final.second.get()); + } + } + + for (auto pair : *symbol->scope()) { + const Symbol* component = &*pair.second; + + // type-bound procedures + if (const ProcBindingDetails* procDetails = component->detailsIf()) { + procedureOverwrites[{getAbsoluteBaseSymbol(types, ¤tType), component->name().ToString()}] + .emplace_back(&procDetails->symbol()); + + MCGLogger::logDebug("Found procedure in module derived type: {} ({}) ({})", component->name(), + getDetailsName(component), fmt::ptr(&component)); + } + + // type-bound generic operators + if (const GenericDetails* gen = component->detailsIf()) { + for (const auto& p : gen->specificProcs()) { + typeOperators[{getAbsoluteBaseSymbol(types, ¤tType), + getOperatorStringFromGenericDetails(component, gen->kind())}] + .emplace_back(p.get().name().ToString()); + } + } + } + } + + // same but with interface operators + if (const GenericDetails* gen = symbol->detailsIf()) { + std::variant interfaceOp; + std::vector procs; + + if (gen->kind().IsIntrinsicOperator()) { + interfaceOp = variantGetIntrinsicOperator(gen->kind()); + MCGLogger::logDebug("Found interface operator in module: {}", + DefinedOperator::EnumToString(std::get(interfaceOp))); + } else if (gen->kind().IsDefinedOperator()) { + interfaceOp = symbol; + MCGLogger::logDebug("Found interface operator in module: {} ({})", symbol->name(), getDetailsName(symbol)); + } else { + continue; + } + + for (const auto& p : gen->specificProcs()) { + procs.push_back(&p.get()); + MCGLogger::logDebug(" with procedure: {} ({}) ({})", p.get().name(), getDetailsName(&p.get()), + fmt::ptr(&p.get())); + } + + interfaceOperators.push_back({interfaceOp, procs}); + } + + // same but with procedures + if (const SubprogramDetails* details = symbol->detailsIf()) { + // function and function dummy definition in interface + if (!details->isFunction() && !details->isInterface()) + continue; + + std::vector dummyArgs; + for (const Symbol* arg : details->dummyArgs()) { + dummyArgs.emplace_back(arg, false); + } + + procedures.emplace_back(symbol, dummyArgs); + MCGLogger::logDebug("Found procedure in module: {} ({}) ({})", symbol->name(), getDetailsName(symbol), + fmt::ptr(symbol)); + } + } + } + + MCGLogger::logDebug("Finished Use module: {} ({}) ({})", useSymbol->name(), getDetailsName(useSymbol), + fmt::ptr(useSymbol)); +} + +bool ParseTreeVisitor::Pre(const StmtFunctionStmt& s) { + MCGLogger::logDebug("\nIn statement function: {} ({}) ({})", mangleSymbol(std::get(s.t).symbol, underscoring), + getDetailsName(std::get(s.t).symbol), fmt::ptr(std::get(s.t).symbol)); + + handleFuncSubStmt(s); + + // hasBody flag + CgNode* node = cg->getFirstNode(mangleSymbol(currentProcedures.back().symbol, underscoring)); + if (!node) { + return true; + } + node->setHasBody(true); + + return true; +} + +void ParseTreeVisitor::Post(const StmtFunctionStmt& s) { + if (!currentProcedures.empty()) { + const Symbol* currentProcedureSymbol = currentProcedures.back().symbol; + MCGLogger::logDebug("End statement function: {} ({}) ({})", mangleSymbol(currentProcedureSymbol, underscoring), + getDetailsName(currentProcedureSymbol), fmt::ptr(currentProcedureSymbol)); + } + + handleEndFuncSubStmt(); +} + +} // namespace metacg::cgfcollector diff --git a/cgfcollector/src/VariableTracking.cpp b/cgfcollector/src/VariableTracking.cpp new file mode 100644 index 00000000..d0d081ef --- /dev/null +++ b/cgfcollector/src/VariableTracking.cpp @@ -0,0 +1,118 @@ +/** + * File: VariableTracking.cpp + * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt + */ + +#include "VariableTracking.h" +#include "FortranUtil.h" + +using namespace Fortran::semantics; +using namespace metacg; + +namespace metacg::cgfcollector { + +TrackedVar* VariableTracking::getTrackedVarFromSourceName(const Symbol* currentProcedureSymbol, SourceName sourceName) { + auto anyTrackedVarIt = std::find_if(trackedVars.begin(), trackedVars.end(), + [&](const TrackedVar& t) { return t.var->name() == sourceName; }); + if (anyTrackedVarIt == trackedVars.end()) + return nullptr; + + // find local variable with the same name in the current procedure scope (shadowed) + auto localVarIt = std::find_if(trackedVars.begin(), trackedVars.end(), [&](const TrackedVar& t) { + return t.var->name() == sourceName && compareSymbols(t.procedure, currentProcedureSymbol); + }); + + // prefer local var if found + return (localVarIt != trackedVars.end()) ? &(*localVarIt) : &(*anyTrackedVarIt); +} + +void VariableTracking::handleTrackedVarAssignment(const Symbol* currentProcedureSymbol, SourceName sourceName) { + TrackedVar* trackedVar = getTrackedVarFromSourceName(currentProcedureSymbol, sourceName); + if (!trackedVar) + return; + + trackedVar->hasBeenInitialized = true; + + MCGLogger::logDebug("Tracked var assigned: {} ({}) ({})", trackedVar->var->name(), getDetailsName(trackedVar->var), + fmt::ptr(trackedVar->var)); +} + +void VariableTracking::handleTrackedVars(const Symbol* currentProcedureSymbol, std::unique_ptr& edgeM) { + // the fortran standard does not require finalizers for variables in the main program. So we skip it. + // NOTE: Flang does call them. + if (mangleSymbol(currentProcedureSymbol, underscoring) == "_QQmain") { + return; + } + + if (!trackedVars.empty()) { + MCGLogger::logDebug("Handle tracked vars for procedure {} ({}) ({})", + mangleSymbol(currentProcedureSymbol, underscoring), getDetailsName(currentProcedureSymbol), + fmt::ptr(currentProcedureSymbol)); + } + + for (TrackedVar& trackedVar : trackedVars) { + MCGLogger::logDebug(" Tracked var: {} ({}) ({}) - initialized: {}, addFinalizers: {}", trackedVar.var->name(), + getDetailsName(trackedVar.var), fmt::ptr(trackedVar.var), trackedVar.hasBeenInitialized, + trackedVar.addFinalizers); + + if (!trackedVar.hasBeenInitialized) + continue; + if (trackedVar.procedure != currentProcedureSymbol) + continue; + + // add edge for deconstruction (finalizer) + if (trackedVar.addFinalizers) { + edgeM->addEdgesForFinalizers(types, finalizers, currentProcedureSymbol, trackedVar.var); + } + + // set init on dummy procedure args + auto procedureIt = std::find_if(procedures.begin(), procedures.end(), [&](const Procedure& f) { + return compareSymbols(f.symbol, currentProcedureSymbol); + }); + if (procedureIt != procedures.end()) { + auto dummyArgIt = + std::find_if(procedureIt->dummyArgs.begin(), procedureIt->dummyArgs.end(), + [&](const Procedure::DummyArg& d) { return compareSymbols(d.symbol, trackedVar.var); }); + if (dummyArgIt != procedureIt->dummyArgs.end()) { + dummyArgIt->hasBeenInitialized = true; + } + } + } + + // cleanup trackedVars + removeTrackedVars(currentProcedureSymbol); +} + +void VariableTracking::addTrackedVar(TrackedVar var) { + auto it = std::find_if(trackedVars.begin(), trackedVars.end(), + [&](const TrackedVar& t) { return compareSymbols(t.var, var.var); }); + if (it != trackedVars.end()) { + // update info + it->addFinalizers = var.addFinalizers; + it->hasBeenInitialized = var.hasBeenInitialized; + MCGLogger::logDebug("Update tracked variable: {} ({}) ({})", var.var->name(), getDetailsName(var.var), + fmt::ptr(var.var)); + return; + } + + trackedVars.push_back(var); + MCGLogger::logDebug("Add tracking for variable: {} ({}) ({})", var.var->name(), getDetailsName(var.var), + fmt::ptr(var.var)); +} + +void VariableTracking::removeTrackedVars(const Symbol* procedureSymbol) { + auto newEnd = std::remove_if(trackedVars.begin(), trackedVars.end(), [&](const TrackedVar& t) { + if (compareSymbols(t.procedure, procedureSymbol)) { + MCGLogger::logDebug("Removing tracked variable: {} ({}) ({}) for procedure: {} ({}) ({})", t.var->name(), + getDetailsName(t.var), fmt::ptr(t.var), mangleSymbol(procedureSymbol, underscoring), + getDetailsName(procedureSymbol), fmt::ptr(procedureSymbol)); + return true; + } + return false; + }); + + trackedVars.erase(newEnd, trackedVars.end()); +} + +} // namespace metacg::cgfcollector diff --git a/cgfcollector/test/multi/cmake_base.txt.in b/cgfcollector/test/multi/cmake_base.txt.in new file mode 100644 index 00000000..335af8f0 --- /dev/null +++ b/cgfcollector/test/multi/cmake_base.txt.in @@ -0,0 +1,7 @@ +set(CMAKE_Fortran_COMPILER "flang-new") +set(CMAKE_Fortran_COMPILER_LAUNCHER "@CGFCOLLECTOR_COMP_WRAPPER@") +if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.1") + set(CMAKE_Fortran_LINKER_LAUNCHER "@CGFCOLLECTOR_LINK_WRAPPER@" --skip-gen-bin) +else() + set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "@CGFCOLLECTOR_LINK_WRAPPER@ --skip-gen-bin") +endif() diff --git a/cgfcollector/test/multi/deps/CMakeLists.txt b/cgfcollector/test/multi/deps/CMakeLists.txt new file mode 100644 index 00000000..743824bd --- /dev/null +++ b/cgfcollector/test/multi/deps/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.20) + +include(${CGFCOLLECTOR_TEST_CMAKE_BASE}) + +project(deps LANGUAGES Fortran) + +file( + GLOB + SOURCES + "*.f90" +) + +add_executable(${CMAKE_PROJECT_NAME} ${SOURCES}) diff --git a/cgfcollector/test/multi/deps/main.f90 b/cgfcollector/test/multi/deps/main.f90 new file mode 100644 index 00000000..def16537 --- /dev/null +++ b/cgfcollector/test/multi/deps/main.f90 @@ -0,0 +1,10 @@ +program main + use my_module, only: my_subroutine + use module3 + + implicit none + + call my_subroutine(5) + +end program main + diff --git a/cgfcollector/test/multi/deps/module.f90 b/cgfcollector/test/multi/deps/module.f90 new file mode 100644 index 00000000..2a1b0134 --- /dev/null +++ b/cgfcollector/test/multi/deps/module.f90 @@ -0,0 +1,20 @@ +module my_module + use module0_5 + + implicit none + +contains + subroutine subsub() + integer :: n + end subroutine subsub + + subroutine my_subroutine(n) + integer, intent(in) :: n + + write (*, *) n + + call func() + + end subroutine my_subroutine +end module my_module + diff --git a/cgfcollector/test/multi/deps/module0_5.f90 b/cgfcollector/test/multi/deps/module0_5.f90 new file mode 100644 index 00000000..0ff134db --- /dev/null +++ b/cgfcollector/test/multi/deps/module0_5.f90 @@ -0,0 +1,13 @@ +module module0_5 + use module2, only: func2 => func + + implicit none + +contains + subroutine func() + call func2() + print *, "This is module2" + end subroutine func + +end module module0_5 + diff --git a/cgfcollector/test/multi/deps/module2.f90 b/cgfcollector/test/multi/deps/module2.f90 new file mode 100644 index 00000000..9c7af856 --- /dev/null +++ b/cgfcollector/test/multi/deps/module2.f90 @@ -0,0 +1,10 @@ +module module2 + + implicit none +contains + subroutine func() + print *, "This is module2" + end subroutine func + +end module module2 + diff --git a/cgfcollector/test/multi/deps/module3.f90 b/cgfcollector/test/multi/deps/module3.f90 new file mode 100644 index 00000000..1abff39f --- /dev/null +++ b/cgfcollector/test/multi/deps/module3.f90 @@ -0,0 +1,13 @@ +module module3 + use module2, only: func2 => func + + implicit none + +contains + subroutine func() + call func2() + print *, "This is module3" + end subroutine func + +end module module3 + diff --git a/cgfcollector/test/multi/deps/output.mcg b/cgfcollector/test/multi/deps/output.mcg new file mode 100644 index 00000000..1e0e91a5 --- /dev/null +++ b/cgfcollector/test/multi/deps/output.mcg @@ -0,0 +1,57 @@ +{ + "_CG": { + "meta": {}, + "nodes": { + "0": { + "callees": { "1": {} }, + "functionName": "_QMmodule0_5Pfunc", + "hasBody": true, + "meta": {}, + "origin": "module0_5.f90" + }, + "1": { + "callees": {}, + "functionName": "_QMmodule2Pfunc", + "hasBody": true, + "meta": {}, + "origin": null + }, + "2": { + "callees": { "1": {} }, + "functionName": "_QMmodule3Pfunc", + "hasBody": true, + "meta": {}, + "origin": "module3.f90" + }, + "3": { + "callees": { "0": {} }, + "functionName": "_QMmy_modulePmy_subroutine", + "hasBody": true, + "meta": {}, + "origin": null + }, + "4": { + "callees": {}, + "functionName": "_QMmy_modulePsubsub", + "hasBody": true, + "meta": {}, + "origin": "module.f90" + }, + "5": { + "callees": { "3": {} }, + "functionName": "_QQmain", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + } + } + }, + "_MetaCG": { + "generator": { + "name": "MetaCG", + "sha": "68fb73aebcc0af419653b36a6b5e3e9668408d10", + "version": "0.9" + }, + "version": "4.0" + } +} diff --git a/cgfcollector/test/multi/fortdepend_deps/Makefile b/cgfcollector/test/multi/fortdepend_deps/Makefile new file mode 100644 index 00000000..d297d49b --- /dev/null +++ b/cgfcollector/test/multi/fortdepend_deps/Makefile @@ -0,0 +1,35 @@ +include $(CGFCOLLECTOR_TEST_MAKE_BASE) + +BUILD_DIR = build +DEP_FILE = $(BUILD_DIR)/Makefile.dep +TARGET = $(BUILD_DIR)/output + +SRCS := $(wildcard *.f90) +OBJS := $(SRCS:.f90=.o) +OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) +OBJS_JSON := $(SRCS:.f90=.o) +OBJS_JSON := $(addprefix $(BUILD_DIR)/, $(OBJS_JSON)) + +all: $(TARGET) $(DEP_FILE) + +$(TARGET): $(OBJS) + $(LD) -o $(TARGET) $(OBJS_JSON) + +$(BUILD_DIR)/%.o: %.f90 | $(BUILD_DIR) + $(FC) -c -module-dir $(BUILD_DIR) $(FCFLAGS) -o $@ $< + +$(BUILD_DIR): + mkdir -p $(BUILD_DIR) + +.PHONY: depend +depend: $(DEP_FILE) + +$(DEP_FILE): $(SRCS) | $(BUILD_DIR) + @echo "Making dependencies!" + $(FORTDEPEND_BIN) --build $(BUILD_DIR) -w -o $(DEP_FILE) -f $(SRCS) + +.PHONY: clean +clean: + rm -rf $(BUILD_DIR) + +include $(DEP_FILE) diff --git a/cgfcollector/test/multi/fortdepend_deps/main.f90 b/cgfcollector/test/multi/fortdepend_deps/main.f90 new file mode 100644 index 00000000..def16537 --- /dev/null +++ b/cgfcollector/test/multi/fortdepend_deps/main.f90 @@ -0,0 +1,10 @@ +program main + use my_module, only: my_subroutine + use module3 + + implicit none + + call my_subroutine(5) + +end program main + diff --git a/cgfcollector/test/multi/fortdepend_deps/module.f90 b/cgfcollector/test/multi/fortdepend_deps/module.f90 new file mode 100644 index 00000000..2a1b0134 --- /dev/null +++ b/cgfcollector/test/multi/fortdepend_deps/module.f90 @@ -0,0 +1,20 @@ +module my_module + use module0_5 + + implicit none + +contains + subroutine subsub() + integer :: n + end subroutine subsub + + subroutine my_subroutine(n) + integer, intent(in) :: n + + write (*, *) n + + call func() + + end subroutine my_subroutine +end module my_module + diff --git a/cgfcollector/test/multi/fortdepend_deps/module0_5.f90 b/cgfcollector/test/multi/fortdepend_deps/module0_5.f90 new file mode 100644 index 00000000..0ff134db --- /dev/null +++ b/cgfcollector/test/multi/fortdepend_deps/module0_5.f90 @@ -0,0 +1,13 @@ +module module0_5 + use module2, only: func2 => func + + implicit none + +contains + subroutine func() + call func2() + print *, "This is module2" + end subroutine func + +end module module0_5 + diff --git a/cgfcollector/test/multi/fortdepend_deps/module2.f90 b/cgfcollector/test/multi/fortdepend_deps/module2.f90 new file mode 100644 index 00000000..9c7af856 --- /dev/null +++ b/cgfcollector/test/multi/fortdepend_deps/module2.f90 @@ -0,0 +1,10 @@ +module module2 + + implicit none +contains + subroutine func() + print *, "This is module2" + end subroutine func + +end module module2 + diff --git a/cgfcollector/test/multi/fortdepend_deps/module3.f90 b/cgfcollector/test/multi/fortdepend_deps/module3.f90 new file mode 100644 index 00000000..1abff39f --- /dev/null +++ b/cgfcollector/test/multi/fortdepend_deps/module3.f90 @@ -0,0 +1,13 @@ +module module3 + use module2, only: func2 => func + + implicit none + +contains + subroutine func() + call func2() + print *, "This is module3" + end subroutine func + +end module module3 + diff --git a/cgfcollector/test/multi/fortdepend_deps/output.mcg b/cgfcollector/test/multi/fortdepend_deps/output.mcg new file mode 100644 index 00000000..1e0e91a5 --- /dev/null +++ b/cgfcollector/test/multi/fortdepend_deps/output.mcg @@ -0,0 +1,57 @@ +{ + "_CG": { + "meta": {}, + "nodes": { + "0": { + "callees": { "1": {} }, + "functionName": "_QMmodule0_5Pfunc", + "hasBody": true, + "meta": {}, + "origin": "module0_5.f90" + }, + "1": { + "callees": {}, + "functionName": "_QMmodule2Pfunc", + "hasBody": true, + "meta": {}, + "origin": null + }, + "2": { + "callees": { "1": {} }, + "functionName": "_QMmodule3Pfunc", + "hasBody": true, + "meta": {}, + "origin": "module3.f90" + }, + "3": { + "callees": { "0": {} }, + "functionName": "_QMmy_modulePmy_subroutine", + "hasBody": true, + "meta": {}, + "origin": null + }, + "4": { + "callees": {}, + "functionName": "_QMmy_modulePsubsub", + "hasBody": true, + "meta": {}, + "origin": "module.f90" + }, + "5": { + "callees": { "3": {} }, + "functionName": "_QQmain", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + } + } + }, + "_MetaCG": { + "generator": { + "name": "MetaCG", + "sha": "68fb73aebcc0af419653b36a6b5e3e9668408d10", + "version": "0.9" + }, + "version": "4.0" + } +} diff --git a/cgfcollector/test/multi/make_base.in b/cgfcollector/test/multi/make_base.in new file mode 100644 index 00000000..b6eba9ac --- /dev/null +++ b/cgfcollector/test/multi/make_base.in @@ -0,0 +1,2 @@ +FC = @CGFCOLLECTOR_COMP_WRAPPER@ +LD = @CGFCOLLECTOR_LINK_WRAPPER@ diff --git a/cgfcollector/test/multi/module_inherit/CMakeLists.txt b/cgfcollector/test/multi/module_inherit/CMakeLists.txt new file mode 100644 index 00000000..776353c7 --- /dev/null +++ b/cgfcollector/test/multi/module_inherit/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.20) + +include(${CGFCOLLECTOR_TEST_CMAKE_BASE}) + +project(module_inherit LANGUAGES Fortran) + +file( + GLOB + SOURCES + "*.f90" +) + +add_executable(${CMAKE_PROJECT_NAME} ${SOURCES}) diff --git a/cgfcollector/test/multi/module_inherit/main.f90 b/cgfcollector/test/multi/module_inherit/main.f90 new file mode 100644 index 00000000..24caf86e --- /dev/null +++ b/cgfcollector/test/multi/module_inherit/main.f90 @@ -0,0 +1,67 @@ +module mod2 + use mod + implicit none + + type, extends(base) :: derived + contains + procedure :: set_var => set_var_derived + end type derived + + type, extends(derived) :: more_derived + contains + procedure :: set_var => set_var_more_derived + end type more_derived + +contains + + subroutine set_var_derived(this, a) + class(derived), intent(inout) :: this + real, intent(in) :: a + print *, 'Setting var from derived' + this%var = a + end subroutine set_var_derived + + subroutine set_var_more_derived(this, a) + class(more_derived), intent(inout) :: this + real, intent(in) :: a + print *, 'Setting var from more_derived' + this%var = a + end subroutine set_var_more_derived + + subroutine set_from_item(item, a) + class(derived), intent(inout) :: item + real, intent(in) :: a + + print *, 'Setting var from item' + call item%set_var(a) + end subroutine set_from_item + +end module mod2 + +program main + use mod2 + + implicit none + + call case1() + call case2() + +contains + subroutine case1() + type(derived) :: b + + call b%set_var(3.14) + + call set_from_item(b, 2.71) + end subroutine case1 + + subroutine case2() + class(derived), allocatable :: b + + allocate (more_derived :: b) + + call b%set_var(3.14) + end subroutine case2 + +end program main + diff --git a/cgfcollector/test/multi/module_inherit/mod.f90 b/cgfcollector/test/multi/module_inherit/mod.f90 new file mode 100644 index 00000000..417aeaa7 --- /dev/null +++ b/cgfcollector/test/multi/module_inherit/mod.f90 @@ -0,0 +1,21 @@ +module mod + + implicit none + + type :: base + real ::var + contains + procedure :: set_var => set_var_base + end type base + +contains + + subroutine set_var_base(this, a) + class(base), intent(inout) :: this + real, intent(in) :: a + print *, 'Setting var from base' + this%var = a + end subroutine set_var_base + +end module mod + diff --git a/cgfcollector/test/multi/module_inherit/output.mcg b/cgfcollector/test/multi/module_inherit/output.mcg new file mode 100644 index 00000000..ee3c8afb --- /dev/null +++ b/cgfcollector/test/multi/module_inherit/output.mcg @@ -0,0 +1,64 @@ +{ + "_CG": { + "meta": {}, + "nodes": { + "0": { + "callees": {}, + "functionName": "_QMmodPset_var_base", + "hasBody": true, + "meta": {}, + "origin": "mod.f90" + }, + "1": { + "callees": {}, + "functionName": "_QMmod2Pset_var_derived", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "2": { + "callees": { "0": {}, "1": {}, "3": {} }, + "functionName": "_QMmod2Pset_from_item", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "3": { + "callees": {}, + "functionName": "_QMmod2Pset_var_more_derived", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "4": { + "callees": { "5": {}, "6": {} }, + "functionName": "_QQmain", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "5": { + "callees": { "0": {}, "1": {}, "2": {}, "3": {} }, + "functionName": "_QFPcase1", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "6": { + "callees": { "0": {}, "1": {}, "3": {} }, + "functionName": "_QFPcase2", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + } + } + }, + "_MetaCG": { + "generator": { + "name": "MetaCG", + "sha": "5b45b5518fe1d09584c1518ceeb337c7389df48a", + "version": "0.9" + }, + "version": "4.0" + } +} diff --git a/cgfcollector/test/multi/operator/CMakeLists.txt b/cgfcollector/test/multi/operator/CMakeLists.txt new file mode 100644 index 00000000..ad965fbe --- /dev/null +++ b/cgfcollector/test/multi/operator/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.20) + +include(${CGFCOLLECTOR_TEST_CMAKE_BASE}) + +project(operator LANGUAGES Fortran) + +file( + GLOB + SOURCES + "*.f90" +) + +add_executable(${CMAKE_PROJECT_NAME} ${SOURCES}) diff --git a/cgfcollector/test/multi/operator/main.f90 b/cgfcollector/test/multi/operator/main.f90 new file mode 100644 index 00000000..a8766598 --- /dev/null +++ b/cgfcollector/test/multi/operator/main.f90 @@ -0,0 +1,27 @@ +program main + use mod + + implicit none + + call test_compare() + +contains + subroutine test_compare() + class(sortable), allocatable :: a, b + type(integer_sortable) :: c, d + + c%value = 5 + d%value = 10 + + allocate (a, source=c) + allocate (b, source=d) + + if (a < b) then + print *, "a is less then b" + else + print *, "a is nat less then b" + end if + end subroutine test_compare + +end program main + diff --git a/cgfcollector/test/multi/operator/mod.f90 b/cgfcollector/test/multi/operator/mod.f90 new file mode 100644 index 00000000..2b8a8629 --- /dev/null +++ b/cgfcollector/test/multi/operator/mod.f90 @@ -0,0 +1,39 @@ +module mod + + implicit none + + type, abstract :: sortable + contains + procedure(compare), deferred :: less_then + generic :: operator(<) => less_then + end type sortable + + interface + logical function compare(this, other) + import :: sortable + implicit none + class(sortable), intent(in) :: this, other + end function compare + end interface + + type, extends(sortable) :: integer_sortable + integer :: value + contains + procedure :: less_then => less_then_integer + end type integer_sortable + +contains + + logical function less_then_integer(this, other) + class(integer_sortable), intent(in) :: this + class(sortable), intent(in) :: other + + select type (other) + type is (integer_sortable) + less_then_integer = this%value < other%value + print *, "Comparing integer_sortable: ", this%value, " < ", other%value + class default + error stop "Type mismatch in comparison" + end select + end function less_then_integer +end module mod diff --git a/cgfcollector/test/multi/operator/output.mcg b/cgfcollector/test/multi/operator/output.mcg new file mode 100644 index 00000000..58a91784 --- /dev/null +++ b/cgfcollector/test/multi/operator/output.mcg @@ -0,0 +1,43 @@ +{ + "_CG": { + "meta": {}, + "nodes": { + "0": { + "callees": {}, + "functionName": "compare_", + "hasBody": false, + "meta": {}, + "origin": "mod.f90" + }, + "1": { + "callees": {}, + "functionName": "_QMmodPless_then_integer", + "hasBody": true, + "meta": {}, + "origin": "mod.f90" + }, + "2": { + "callees": { "3": {} }, + "functionName": "_QQmain", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "3": { + "callees": { "0": {}, "1": {} }, + "functionName": "_QFPtest_compare", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + } + } + }, + "_MetaCG": { + "generator": { + "name": "MetaCG", + "sha": "b2cf8d25b16310481bf5ba0fda1c99fbe2e69267", + "version": "0.9" + }, + "version": "4.0" + } +} diff --git a/cgfcollector/test/multi/operator_interface/CMakeLists.txt b/cgfcollector/test/multi/operator_interface/CMakeLists.txt new file mode 100644 index 00000000..4652c2df --- /dev/null +++ b/cgfcollector/test/multi/operator_interface/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.20) + +include(${CGFCOLLECTOR_TEST_CMAKE_BASE}) + +project(operator_interface LANGUAGES Fortran) + +file( + GLOB + SOURCES + "*.f90" +) + +add_executable(${CMAKE_PROJECT_NAME} ${SOURCES}) diff --git a/cgfcollector/test/multi/operator_interface/main.f90 b/cgfcollector/test/multi/operator_interface/main.f90 new file mode 100644 index 00000000..f3e08d8b --- /dev/null +++ b/cgfcollector/test/multi/operator_interface/main.f90 @@ -0,0 +1,20 @@ +program main + use mod + + implicit none + + integer :: x, y, res + real :: e = 5.0, f + + x = 5 + y = 10 + + res = x + y + + print *, "Result of x + y = ", res + + f = .NEGX.e + + print *, "Negated value: ", f +end program main + diff --git a/cgfcollector/test/multi/operator_interface/mod.f90 b/cgfcollector/test/multi/operator_interface/mod.f90 new file mode 100644 index 00000000..013fc4e8 --- /dev/null +++ b/cgfcollector/test/multi/operator_interface/mod.f90 @@ -0,0 +1,42 @@ +module mod + + implicit none + + type :: integer_wrapper + integer :: value + end type integer_wrapper + + interface operator(+) + procedure add_stuff + module procedure unary_plus + end interface + + interface operator(.NEGX.) + module procedure negx + end interface + +contains + + function add_stuff(x, y) result(res) + type(integer_wrapper), intent(in) :: x, y + type(integer_wrapper) :: res + res%value = x%value + y%value + print *, "Hello from add_stuff" + end function add_stuff + + function unary_plus(x) result(res) + type(integer_wrapper), intent(in) :: x + type(integer_wrapper) :: res + res%value = x%value + print *, "Hello from unary_plus" + end function unary_plus + + function negx(x) result(res) + real, intent(in) :: x + real :: res + res = -x + print *, "Hello from negx" + end function negx + +end module mod + diff --git a/cgfcollector/test/multi/operator_interface/output.mcg b/cgfcollector/test/multi/operator_interface/output.mcg new file mode 100644 index 00000000..037ac23c --- /dev/null +++ b/cgfcollector/test/multi/operator_interface/output.mcg @@ -0,0 +1,43 @@ +{ + "_CG": { + "meta": {}, + "nodes": { + "0": { + "callees": {}, + "functionName": "_QMmodPadd_stuff", + "hasBody": true, + "meta": {}, + "origin": "mod.f90" + }, + "1": { + "callees": {}, + "functionName": "_QMmodPunary_plus", + "hasBody": true, + "meta": {}, + "origin": "mod.f90" + }, + "2": { + "callees": {}, + "functionName": "_QMmodPnegx", + "hasBody": true, + "meta": {}, + "origin": "mod.f90" + }, + "3": { + "callees": { "0": {}, "2": {} }, + "functionName": "_QQmain", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + } + } + }, + "_MetaCG": { + "generator": { + "name": "MetaCG", + "sha": "37c33e071f7bc6b1dba2e01ff2e7305aaeb3ccee", + "version": "0.9" + }, + "version": "4.0" + } +} diff --git a/cgfcollector/test/multi/use/CMakeLists.txt b/cgfcollector/test/multi/use/CMakeLists.txt new file mode 100644 index 00000000..dc39bf6d --- /dev/null +++ b/cgfcollector/test/multi/use/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.20) + +include(${CGFCOLLECTOR_TEST_CMAKE_BASE}) + +project(use LANGUAGES Fortran) + +file( + GLOB + SOURCES + "*.f90" +) + +add_executable(${CMAKE_PROJECT_NAME} ${SOURCES}) diff --git a/cgfcollector/test/multi/use/main.f90 b/cgfcollector/test/multi/use/main.f90 new file mode 100644 index 00000000..4faf339b --- /dev/null +++ b/cgfcollector/test/multi/use/main.f90 @@ -0,0 +1,11 @@ +program main + use mod + implicit none + + class(base), allocatable :: b + allocate (derived :: b) + + call b%set_var(3.14) + +end program main + diff --git a/cgfcollector/test/multi/use/mod.f90 b/cgfcollector/test/multi/use/mod.f90 new file mode 100644 index 00000000..e60d4155 --- /dev/null +++ b/cgfcollector/test/multi/use/mod.f90 @@ -0,0 +1,34 @@ +module mod + + implicit none + + type :: base + private + real ::var + contains + procedure :: set_var => set_var_base + end type base + + type, extends(base) :: derived + contains + procedure :: set_var => set_var_derived + end type derived + +contains + + subroutine set_var_base(this, a) + class(base), intent(inout) :: this + real, intent(in) :: a + print *, 'Setting var from base' + this%var = a + end subroutine set_var_base + + subroutine set_var_derived(this, a) + class(derived), intent(inout) :: this + real, intent(in) :: a + print *, 'Setting var from derived' + this%var = a + end subroutine set_var_derived + +end module mod + diff --git a/cgfcollector/test/multi/use/output.mcg b/cgfcollector/test/multi/use/output.mcg new file mode 100644 index 00000000..2c7c7b15 --- /dev/null +++ b/cgfcollector/test/multi/use/output.mcg @@ -0,0 +1,36 @@ +{ + "_CG": { + "meta": {}, + "nodes": { + "0": { + "callees": {}, + "functionName": "_QMmodPset_var_base", + "hasBody": true, + "meta": {}, + "origin": "mod.f90" + }, + "1": { + "callees": {}, + "functionName": "_QMmodPset_var_derived", + "hasBody": true, + "meta": {}, + "origin": "mod.f90" + }, + "2": { + "callees": { "0": {}, "1": {} }, + "functionName": "_QQmain", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + } + } + }, + "_MetaCG": { + "generator": { + "name": "MetaCG", + "sha": "b2cf8d25b16310481bf5ba0fda1c99fbe2e69267", + "version": "0.9" + }, + "version": "4.0" + } +} diff --git a/cgfcollector/test/simple/empty_main/main.f90 b/cgfcollector/test/simple/empty_main/main.f90 new file mode 100644 index 00000000..83b66288 --- /dev/null +++ b/cgfcollector/test/simple/empty_main/main.f90 @@ -0,0 +1,6 @@ +program main + + implicit none + +end program main + diff --git a/cgfcollector/test/simple/empty_main/output.mcg b/cgfcollector/test/simple/empty_main/output.mcg new file mode 100644 index 00000000..cf4669c2 --- /dev/null +++ b/cgfcollector/test/simple/empty_main/output.mcg @@ -0,0 +1,22 @@ +{ + "_CG": { + "meta": {}, + "nodes": { + "0": { + "callees": {}, + "functionName": "_QQmain", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + } + } + }, + "_MetaCG": { + "generator": { + "name": "MetaCG", + "sha": "1cd843384a952a5a8ece2d8f7f3d6726964583f8", + "version": "0.9" + }, + "version": "4.0" + } +} diff --git a/cgfcollector/test/simple/empty_module/mod.f90 b/cgfcollector/test/simple/empty_module/mod.f90 new file mode 100644 index 00000000..2caa6d54 --- /dev/null +++ b/cgfcollector/test/simple/empty_module/mod.f90 @@ -0,0 +1,5 @@ +module mod + + implicit none + +end module mod diff --git a/cgfcollector/test/simple/empty_module/output.mcg b/cgfcollector/test/simple/empty_module/output.mcg new file mode 100644 index 00000000..df47c1cb --- /dev/null +++ b/cgfcollector/test/simple/empty_module/output.mcg @@ -0,0 +1,11 @@ +{ + "_CG": { "meta": {}, "nodes": {} }, + "_MetaCG": { + "generator": { + "name": "MetaCG", + "sha": "1cd843384a952a5a8ece2d8f7f3d6726964583f8", + "version": "0.9" + }, + "version": "4.0" + } +} diff --git a/cgfcollector/test/simple/entry/main.f90 b/cgfcollector/test/simple/entry/main.f90 new file mode 100644 index 00000000..46027443 --- /dev/null +++ b/cgfcollector/test/simple/entry/main.f90 @@ -0,0 +1,31 @@ +module mod + + implicit none + +contains + subroutine func(a, b, c) + integer :: a, b + character(4) :: c + print *, "entry func" + return + + entry entry1(a, b, c) + print *, "entry entry1" + return + + entry entry2 + print *, "entry entry2" + return + end +end module mod + +program main + use mod + implicit none + + call func(1, 2, "1234") + call entry1(1, 2, "1234") + call entry2() + +end program main + diff --git a/cgfcollector/test/simple/entry/output.mcg b/cgfcollector/test/simple/entry/output.mcg new file mode 100644 index 00000000..0fae7506 --- /dev/null +++ b/cgfcollector/test/simple/entry/output.mcg @@ -0,0 +1,43 @@ +{ + "_CG": { + "meta": {}, + "nodes": { + "0": { + "callees": {}, + "functionName": "_QMmodPentry1", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "1": { + "callees": {}, + "functionName": "_QMmodPentry2", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "2": { + "callees": {}, + "functionName": "_QMmodPfunc", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "3": { + "callees": { "0": {}, "1": {}, "2": {} }, + "functionName": "_QQmain", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + } + } + }, + "_MetaCG": { + "generator": { + "name": "MetaCG", + "sha": "68fb73aebcc0af419653b36a6b5e3e9668408d10", + "version": "0.9" + }, + "version": "4.0" + } +} diff --git a/cgfcollector/test/simple/final/input.f90 b/cgfcollector/test/simple/final/input.f90 new file mode 100644 index 00000000..31d82da9 --- /dev/null +++ b/cgfcollector/test/simple/final/input.f90 @@ -0,0 +1,123 @@ +module mod + implicit none + + type :: polynomial + private + real, allocatable :: a(:) + contains + procedure :: print_polynomial + final :: finalize_polynomial + end type polynomial + + interface polynomial + module procedure create_polynomial + end interface + +contains + + type(polynomial) function create_polynomial(a) + real, intent(in) :: a(0:) + integer :: degree(1) + + degree = findloc(a /= 0.0, value=.true., back=.true.) - 1 + allocate (create_polynomial%a(0:degree(1))) + create_polynomial%a(0:) = a(0:degree(1)) + end function create_polynomial + + subroutine print_polynomial(this) + class(polynomial), intent(in) :: this + write (*, *) 'Polynomial:', this%a + end subroutine print_polynomial + + subroutine finalize_polynomial(this) + type(polynomial), intent(inout) :: this + if (allocated(this%a)) then + deallocate (this%a) + end if + write (*, *) 'Finalizing polynomial' + end subroutine finalize_polynomial + +end module mod + +module mod_use + use mod + + implicit none + +contains + + subroutine func_calls_final() + type(polynomial), allocatable :: q + + q = polynomial([2., 3., 1., 0., 0.]) + call q%print_polynomial() + end subroutine func_calls_final + + subroutine func_calls_final2() + type(polynomial) :: q + end subroutine func_calls_final2 + + subroutine func_calls_final3() + type(polynomial), allocatable :: q + + call set_q() + ! call set_q_alloc() + ! call set_q_alloc2() + ! call set_q_move_alloc() + + contains + subroutine set_q() + q = polynomial([2., 3., 1., 0., 0.]) + end subroutine set_q + subroutine set_q_alloc() + type(polynomial), allocatable :: p + p = polynomial([2., 3., 1., 0., 0.]) + + allocate (q, source=p) + end subroutine set_q_alloc + subroutine set_q_alloc2() + type(polynomial), allocatable :: q + allocate (q) + end subroutine set_q_alloc2 + subroutine set_nothing() + end subroutine set_nothing + subroutine set_q_move_alloc() + type(polynomial), allocatable :: p + p = polynomial([2., 3., 1., 0., 0.]) + + call move_alloc(p, q) + end subroutine set_q_move_alloc + end subroutine func_calls_final3 + + subroutine func_does_not_call_final() + type(polynomial), allocatable :: q + end subroutine func_does_not_call_final + +end module mod_use + +program main + use mod + use mod_use + implicit none + + call func() + +contains + subroutine func() + work: block + type(polynomial), allocatable :: q + + q = polynomial([2., 3., 1., 0., 0.]) + call q%print_polynomial() + end block work + + print *, 'Calling func_calls_final' + call func_calls_final() + print *, 'Calling func_calls_final2' + call func_calls_final2() + print *, 'Calling func_does_not_call_final' + call func_does_not_call_final() + print *, 'Calling func_calls_final3' + call func_calls_final3() + end subroutine func +end program main diff --git a/cgfcollector/test/simple/final/output.mcg b/cgfcollector/test/simple/final/output.mcg new file mode 100644 index 00000000..66fe22a7 --- /dev/null +++ b/cgfcollector/test/simple/final/output.mcg @@ -0,0 +1,121 @@ +{ + "_CG": { + "meta": {}, + "nodes": { + "0": { + "callees": { + "1": {}, + "10": {}, + "11": {}, + "12": {}, + "2": {}, + "3": {}, + "9": {} + }, + "functionName": "_QFPfunc", + "hasBody": true, + "meta": {}, + "origin": "input.f90" + }, + "1": { + "callees": {}, + "functionName": "_QMmodPcreate_polynomial", + "hasBody": true, + "meta": {}, + "origin": "input.f90" + }, + "10": { + "callees": { "2": {} }, + "functionName": "_QMmod_usePfunc_calls_final2", + "hasBody": true, + "meta": {}, + "origin": "input.f90" + }, + "11": { + "callees": { "2": {}, "5": {} }, + "functionName": "_QMmod_usePfunc_calls_final3", + "hasBody": true, + "meta": {}, + "origin": "input.f90" + }, + "12": { + "callees": {}, + "functionName": "_QMmod_usePfunc_does_not_call_final", + "hasBody": true, + "meta": {}, + "origin": "input.f90" + }, + "13": { + "callees": { "0": {} }, + "functionName": "_QQmain", + "hasBody": true, + "meta": {}, + "origin": "input.f90" + }, + "2": { + "callees": {}, + "functionName": "_QMmodPfinalize_polynomial", + "hasBody": true, + "meta": {}, + "origin": "input.f90" + }, + "3": { + "callees": {}, + "functionName": "_QMmodPprint_polynomial", + "hasBody": true, + "meta": {}, + "origin": "input.f90" + }, + "4": { + "callees": {}, + "functionName": "_QMmod_useFfunc_calls_final3Pset_nothing", + "hasBody": true, + "meta": {}, + "origin": "input.f90" + }, + "5": { + "callees": { "1": {} }, + "functionName": "_QMmod_useFfunc_calls_final3Pset_q", + "hasBody": true, + "meta": {}, + "origin": "input.f90" + }, + "6": { + "callees": { "1": {}, "2": {} }, + "functionName": "_QMmod_useFfunc_calls_final3Pset_q_alloc", + "hasBody": true, + "meta": {}, + "origin": "input.f90" + }, + "7": { + "callees": { "2": {} }, + "functionName": "_QMmod_useFfunc_calls_final3Pset_q_alloc2", + "hasBody": true, + "meta": {}, + "origin": "input.f90" + }, + "8": { + "callees": { "1": {}, "2": {} }, + "functionName": "_QMmod_useFfunc_calls_final3Pset_q_move_alloc", + "hasBody": true, + "meta": {}, + "origin": "input.f90" + }, + "9": { + "callees": { "1": {}, "2": {}, "3": {} }, + "functionName": "_QMmod_usePfunc_calls_final", + "hasBody": true, + "meta": {}, + "origin": "input.f90" + } + } + }, + "_MetaCG": { + "generator": { + "name": "MetaCG", + "sha": "68fb73aebcc0af419653b36a6b5e3e9668408d10", + "version": "0.9" + }, + "version": "4.0" + } +} diff --git a/cgfcollector/test/simple/final2/main.f90 b/cgfcollector/test/simple/final2/main.f90 new file mode 100644 index 00000000..745761a1 --- /dev/null +++ b/cgfcollector/test/simple/final2/main.f90 @@ -0,0 +1,94 @@ +module mod + + implicit none + + type :: base + integer :: a + contains + final :: finalize_base + end type base + + type, extends(base) :: derived + integer :: b + contains + final :: finalize_derived + end type derived + +contains + + subroutine finalize_base(this) + type(base), intent(inout) :: this + print *, "Finalizing base" + end subroutine finalize_base + + subroutine finalize_derived(this) + type(derived), intent(inout) :: this + print *, "Finalizing derived" + end subroutine finalize_derived + + subroutine func_arg(this) + ! this does not call a finalize + type(derived) :: this + print *, "In func_arg" + end subroutine func_arg + + subroutine func_arg2(this) + ! this calls a finalize + type(derived) :: this + print *, "In func_arg2" + this = derived(1, 2) + end subroutine func_arg2 + + subroutine func_arg_out(this) + ! this calls a finalize (7.5.6.3 line 21 and onwards) + type(derived), intent(out) :: this + print *, "In func_arg_out" + end subroutine func_arg_out + + subroutine func_arg_inout(this) + ! does not call a finalize + type(derived), intent(inout) :: this + print *, "In func_arg_inout" + end subroutine func_arg_inout + + subroutine func_arg_inout2(this) + ! this calls a finalize + type(derived), intent(inout) :: this + print *, "In func_arg_inout2" + this = derived(1, 2) + end subroutine func_arg_inout2 + +end module mod + +program main + use mod + + implicit none + + call func() + +contains + + subroutine func() + type(derived) :: obj + + print *, "Before func_arg" + call func_arg(obj) + print *, "After func_arg" + print *, "Before func_arg2" + call func_arg2(obj) + print *, "After func_arg2" + print *, "Before func_arg_out" + call func_arg_out(obj) + print *, "After func_arg_out" + print *, "Before func_arg_inout" + call func_arg_inout(obj) + print *, "After func_arg_inout" + print *, "Before func_arg_inout2" + call func_arg_inout2(obj) + print *, "After func_arg_inout2" + + end subroutine func + +end program main + diff --git a/cgfcollector/test/simple/final2/output.mcg b/cgfcollector/test/simple/final2/output.mcg new file mode 100644 index 00000000..139e208a --- /dev/null +++ b/cgfcollector/test/simple/final2/output.mcg @@ -0,0 +1,86 @@ +{ + "_CG": { + "meta": {}, + "nodes": { + "0": { + "callees": { + "1": {}, + "2": {}, + "3": {}, + "4": {}, + "5": {}, + "6": {}, + "7": {} + }, + "functionName": "_QFPfunc", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "1": { + "callees": {}, + "functionName": "_QMmodPfinalize_base", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "2": { + "callees": {}, + "functionName": "_QMmodPfinalize_derived", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "3": { + "callees": {}, + "functionName": "_QMmodPfunc_arg", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "4": { + "callees": { "1": {}, "2": {} }, + "functionName": "_QMmodPfunc_arg2", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "5": { + "callees": {}, + "functionName": "_QMmodPfunc_arg_inout", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "6": { + "callees": { "1": {}, "2": {} }, + "functionName": "_QMmodPfunc_arg_inout2", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "7": { + "callees": { "1": {}, "2": {} }, + "functionName": "_QMmodPfunc_arg_out", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "8": { + "callees": { "0": {} }, + "functionName": "_QQmain", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + } + } + }, + "_MetaCG": { + "generator": { + "name": "MetaCG", + "sha": "68fb73aebcc0af419653b36a6b5e3e9668408d10", + "version": "0.9" + }, + "version": "4.0" + } +} diff --git a/cgfcollector/test/simple/final3/main.f90 b/cgfcollector/test/simple/final3/main.f90 new file mode 100644 index 00000000..bcb7dae2 --- /dev/null +++ b/cgfcollector/test/simple/final3/main.f90 @@ -0,0 +1,126 @@ +module mod + + implicit none + + type :: base + integer :: a + contains + final :: finalize_base + end type base + + type, extends(base) :: derived + integer :: b + contains + final :: finalize_derived + end type derived + +contains + + subroutine finalize_base(this) + type(base), intent(inout) :: this + print *, "Finalizing base" + end subroutine finalize_base + + subroutine finalize_derived(this) + type(derived), intent(inout) :: this + print *, "Finalizing derived" + end subroutine finalize_derived + + subroutine func_arg(this) + type(derived), allocatable :: this + print *, "In func_arg" + allocate (this) + end subroutine func_arg + + subroutine func_arg_out(this) + type(derived), allocatable, intent(out) :: this + print *, "In func_arg_out" + allocate (this) + end subroutine func_arg_out + + subroutine func_arg_inout(this) + type(derived), allocatable, intent(inout) :: this + print *, "In func_arg_inout" + allocate (this) + end subroutine func_arg_inout + + subroutine func_arg2(this) + type(derived), allocatable :: this + print *, "In func_arg2" + end subroutine func_arg2 + + subroutine func_arg_out2(this) + type(derived), allocatable, intent(out) :: this + print *, "In func_arg_out2" + end subroutine func_arg_out2 + + subroutine func_arg_inout2(this) + type(derived), allocatable, intent(inout) :: this + print *, "In func_arg_inout2" + end subroutine func_arg_inout2 + +end module mod + +program main + use mod + + implicit none + + print *, "Calling func" + call func() + print *, "Calling func_no_finalize" + call func_no_finalize() + +contains + + subroutine func() + type(derived), allocatable :: obj + type(derived), allocatable :: obj2 + type(derived), allocatable :: obj3 + + print *, "Before func_arg" + call func_arg(obj) + print *, "After func_arg" + print *, "Before func_arg_out" + call func_arg_out(obj2) + print *, "After func_arg_out" + print *, "Before func_arg_inout" + call func_arg_inout(obj3) + print *, "After func_arg_inout" + + end subroutine func + + subroutine func_no_finalize() + type(derived), allocatable :: obj + type(derived), allocatable :: obj2 + type(derived), allocatable :: obj3 + + print *, "Before func_arg2" + call func_arg2(obj) + print *, "After func_arg2" + print *, "Before func_arg_out2" + call func_arg_out2(obj2) + print *, "After func_arg_out2" + print *, "Before func_arg_inout2" + call func_arg_inout2(obj3) + print *, "After func_arg_inout2" + + end subroutine func_no_finalize + + ! TODO: + ! subroutine func_more() + ! type(derived), allocatable :: obj + + ! call func_more_internal(obj) + + ! contains + ! subroutine func_more_internal(obj_internal) + ! type(derived), intent(out), allocatable :: obj_internal + + ! call func_arg_out(obj_internal) + + ! end subroutine func_more_internal + ! end subroutine func_more + +end program main + diff --git a/cgfcollector/test/simple/final3/output.mcg b/cgfcollector/test/simple/final3/output.mcg new file mode 100644 index 00000000..548c165d --- /dev/null +++ b/cgfcollector/test/simple/final3/output.mcg @@ -0,0 +1,92 @@ +{ + "_CG": { + "meta": {}, + "nodes": { + "0": { + "callees": { "2": {}, "3": {}, "4": {}, "6": {}, "8": {} }, + "functionName": "_QFPfunc", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "1": { + "callees": { "5": {}, "7": {}, "9": {} }, + "functionName": "_QFPfunc_no_finalize", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "10": { + "callees": { "0": {}, "1": {} }, + "functionName": "_QQmain", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "2": { + "callees": {}, + "functionName": "_QMmodPfinalize_base", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "3": { + "callees": {}, + "functionName": "_QMmodPfinalize_derived", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "4": { + "callees": {}, + "functionName": "_QMmodPfunc_arg", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "5": { + "callees": {}, + "functionName": "_QMmodPfunc_arg2", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "6": { + "callees": {}, + "functionName": "_QMmodPfunc_arg_inout", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "7": { + "callees": {}, + "functionName": "_QMmodPfunc_arg_inout2", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "8": { + "callees": {}, + "functionName": "_QMmodPfunc_arg_out", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "9": { + "callees": {}, + "functionName": "_QMmodPfunc_arg_out2", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + } + } + }, + "_MetaCG": { + "generator": { + "name": "MetaCG", + "sha": "68fb73aebcc0af419653b36a6b5e3e9668408d10", + "version": "0.9" + }, + "version": "4.0" + } +} diff --git a/cgfcollector/test/simple/final4/main.f90 b/cgfcollector/test/simple/final4/main.f90 new file mode 100644 index 00000000..0d7e2fbb --- /dev/null +++ b/cgfcollector/test/simple/final4/main.f90 @@ -0,0 +1,36 @@ +module mod + implicit none + + type dummy_type + integer :: i + contains + final :: finalize_dummy + end type dummy_type + + interface dummy_type + module procedure create_dummy_type + end interface dummy_type +contains + subroutine finalize_dummy(this) + type(dummy_type), intent(inout) :: this + write (*, *) 'Finalizing dummy_type' + end subroutine finalize_dummy + + type(dummy_type) function create_dummy_type(i) + integer, intent(in) :: i + create_dummy_type%i = i + write (*, *) 'Creating dummy_type with i = ', create_dummy_type%i + end function create_dummy_type +end module mod + +program main + use mod + + implicit none + + type(dummy_type), allocatable :: dummy + dummy = dummy_type(1) + + ! dummy finalizer is called here but this is not confrom with the fortran specification (7.5.6.4) +end program main + diff --git a/cgfcollector/test/simple/final4/output.mcg b/cgfcollector/test/simple/final4/output.mcg new file mode 100644 index 00000000..f633ac20 --- /dev/null +++ b/cgfcollector/test/simple/final4/output.mcg @@ -0,0 +1,36 @@ +{ + "_CG": { + "meta": {}, + "nodes": { + "0": { + "callees": {}, + "functionName": "_QMmodPcreate_dummy_type", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "1": { + "callees": {}, + "functionName": "_QMmodPfinalize_dummy", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "2": { + "callees": { "0": {} }, + "functionName": "_QQmain", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + } + } + }, + "_MetaCG": { + "generator": { + "name": "MetaCG", + "sha": "68fb73aebcc0af419653b36a6b5e3e9668408d10", + "version": "0.9" + }, + "version": "4.0" + } +} diff --git a/cgfcollector/test/simple/final5/main.f90 b/cgfcollector/test/simple/final5/main.f90 new file mode 100644 index 00000000..c8ea710a --- /dev/null +++ b/cgfcollector/test/simple/final5/main.f90 @@ -0,0 +1,46 @@ +module mod + + implicit none + + type :: base + integer :: a + contains + final :: finalize_base + end type base + + type, extends(base) :: derived + integer :: b + contains + final :: finalize_derived + end type derived + + type(derived) :: derivedInModule ! not destructed because static lifetime + +contains + + subroutine finalize_base(this) + type(base), intent(inout) :: this + print *, "Finalizing base" + end subroutine finalize_base + + subroutine finalize_derived(this) + type(derived), intent(inout) :: this + print *, "Finalizing derived" + end subroutine finalize_derived + +end module mod + +program main + + implicit none + +contains + subroutine func() + use mod + type(derived), save :: obj ! save = static lifetime + + obj = derived(1, 2) + end subroutine func + +end program main + diff --git a/cgfcollector/test/simple/final5/output.mcg b/cgfcollector/test/simple/final5/output.mcg new file mode 100644 index 00000000..c54c2e59 --- /dev/null +++ b/cgfcollector/test/simple/final5/output.mcg @@ -0,0 +1,43 @@ +{ + "_CG": { + "meta": {}, + "nodes": { + "0": { + "callees": {}, + "functionName": "_QFPfunc", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "1": { + "callees": {}, + "functionName": "_QMmodPfinalize_base", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "2": { + "callees": {}, + "functionName": "_QMmodPfinalize_derived", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "3": { + "callees": {}, + "functionName": "_QQmain", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + } + } + }, + "_MetaCG": { + "generator": { + "name": "MetaCG", + "sha": "68fb73aebcc0af419653b36a6b5e3e9668408d10", + "version": "0.9" + }, + "version": "4.0" + } +} diff --git a/cgfcollector/test/simple/function_pointer/main.f90 b/cgfcollector/test/simple/function_pointer/main.f90 new file mode 100644 index 00000000..a08975b0 --- /dev/null +++ b/cgfcollector/test/simple/function_pointer/main.f90 @@ -0,0 +1,36 @@ +program main + implicit none + + abstract interface + function func(x) result(y) + real, intent(in) :: x + end function func + end interface + + procedure(func), pointer :: func_ptr => null() + real :: result + + result = 0.0 + func_ptr => square + result = func_ptr(2.0) + print *, "Square of 2.0 is: ", result + + func_ptr => cube + result = func_ptr(2.0) + print *, "Cube of 2.0 is: ", result + +contains + + function square(x) result(y) + real, intent(in) :: x + real :: y + y = x*x + end function square + + function cube(x) result(y) + real, intent(in) :: x + real :: y + y = x*x*x + end function cube + +end program main diff --git a/cgfcollector/test/simple/function_test/main.f90 b/cgfcollector/test/simple/function_test/main.f90 new file mode 100644 index 00000000..e9485175 --- /dev/null +++ b/cgfcollector/test/simple/function_test/main.f90 @@ -0,0 +1,92 @@ +module vector_operations +contains + function vector_add(a, b) result(result) + implicit none + real, dimension(:), intent(in) :: a, b + real, dimension(size(a)) :: result + integer :: i + + if (size(a) /= size(b)) then + print *, "Error: Vectors must be of the same size." + stop + end if + + do i = 1, size(a) + result(i) = a(i) + b(i) + end do + end function vector_add + function vector_norm(n, vec) result(norm) + implicit none + integer, intent(in) :: n + real, intent(in) :: vec(n) + real :: norm + + norm = sqrt(sum(vec**2)) + + end function vector_norm +end module vector_operations + +function vector_norm2(n, vec) result(norm) + implicit none + integer, intent(in) :: n + real, intent(in) :: vec(n) + real :: norm + + norm = sqrt(sum(vec**2)) + +end function vector_norm2 + +function size(arr) result(s) + implicit none + real, dimension(:), intent(in) :: arr + integer :: s + + s = 10 + +contains + function func1(arr) result(s) + implicit none + real, dimension(:), intent(in) :: arr + integer :: s + + s = size(arr) + end function func1 + subroutine func2(arr) + implicit none + real, dimension(:), intent(in) :: arr + integer :: s + + s = size(arr) + end subroutine func2 +end function size + +program main + use vector_operations, only: vector_add, vector_norm2 => vector_norm + implicit none + + real, dimension(3) :: a, b, result + integer :: i + + interface + function size(arr) result(s) + implicit none + real, dimension(:), intent(in) :: arr + integer :: s + end function size + end interface + + a = [1.0, 2.0, 3.0] + b = [4.0, 5.0, 6.0] + result = vector_add(a, b) + + print *, "Result of vector addition:" + do i = 1, size(result) + print *, result(i) + end do + + i = size(a) + print *, "i: ", i + + print *, "Norm of vector a:" + print *, vector_norm2(size(a), a) +end program main diff --git a/cgfcollector/test/simple/function_test/output.mcg b/cgfcollector/test/simple/function_test/output.mcg new file mode 100644 index 00000000..7b5bcd12 --- /dev/null +++ b/cgfcollector/test/simple/function_test/output.mcg @@ -0,0 +1,64 @@ +{ + "_CG": { + "meta": {}, + "nodes": { + "0": { + "callees": {}, + "functionName": "_QMvector_operationsPvector_add", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "1": { + "callees": {}, + "functionName": "_QMvector_operationsPvector_norm", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "2": { + "callees": {}, + "functionName": "vector_norm2_", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "3": { + "callees": {}, + "functionName": "size_", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "4": { + "callees": { "3": {} }, + "functionName": "_QFsizePfunc1", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "5": { + "callees": { "3": {} }, + "functionName": "_QFsizePfunc2", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "6": { + "callees": { "0": {}, "1": {}, "3": {} }, + "functionName": "_QQmain", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + } + } + }, + "_MetaCG": { + "generator": { + "name": "MetaCG", + "sha": "96b749864d4e9ffe279252205b6ef755a6a86056", + "version": "0.9" + }, + "version": "4.0" + } +} diff --git a/cgfcollector/test/simple/generic_interface/main.f90 b/cgfcollector/test/simple/generic_interface/main.f90 new file mode 100644 index 00000000..325deb15 --- /dev/null +++ b/cgfcollector/test/simple/generic_interface/main.f90 @@ -0,0 +1,44 @@ +module mod + + implicit none + + interface add + module procedure add_int, add_real + end interface add + +contains + + subroutine add_int(x, y) + implicit none + integer, intent(inout) :: x, y + x = x + y + end subroutine add_int + + subroutine add_real(x, y) + implicit none + real, intent(inout) :: x, y + x = x + y + end subroutine add_real + +end module mod + +program main + use mod + + implicit none + + integer :: x, y + real :: r1, r2 + x = 1 + y = 2 + r1 = 3.3 + r2 = 4.4 + + call add(x, y) + call add(r1, r2) + + print *, "Integer addition result: ", x + print *, "Real addition result: ", r1 + +end program main + diff --git a/cgfcollector/test/simple/generic_interface/output.mcg b/cgfcollector/test/simple/generic_interface/output.mcg new file mode 100644 index 00000000..48e756a3 --- /dev/null +++ b/cgfcollector/test/simple/generic_interface/output.mcg @@ -0,0 +1,36 @@ +{ + "_CG": { + "meta": {}, + "nodes": { + "0": { + "callees": {}, + "functionName": "_QMmodPadd_int", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "1": { + "callees": {}, + "functionName": "_QMmodPadd_real", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "2": { + "callees": { "0": {}, "1": {} }, + "functionName": "_QQmain", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + } + } + }, + "_MetaCG": { + "generator": { + "name": "MetaCG", + "sha": "68fb73aebcc0af419653b36a6b5e3e9668408d10", + "version": "0.9" + }, + "version": "4.0" + } +} diff --git a/cgfcollector/test/simple/interop/func.c b/cgfcollector/test/simple/interop/func.c new file mode 100644 index 00000000..1f764483 --- /dev/null +++ b/cgfcollector/test/simple/interop/func.c @@ -0,0 +1 @@ +int add(int a, int b) { return a + b; } diff --git a/cgfcollector/test/simple/interop/main.f90 b/cgfcollector/test/simple/interop/main.f90 new file mode 100644 index 00000000..d8ecb4b9 --- /dev/null +++ b/cgfcollector/test/simple/interop/main.f90 @@ -0,0 +1,24 @@ +program main + use iso_c_binding + implicit none + + interface + function add(a, b) bind(C) result(res) + import :: C_INT + implicit none + integer(C_INT), value :: a, b + integer(C_INT) :: res + end function add + end interface + + integer(C_INT) :: result + integer(C_INT) :: var1 + integer(C_INT) :: var2 + var1 = 3 + var2 = 23 + + result = add(var1, var2) + print *, "Result from C add function:", result + +end program main + diff --git a/cgfcollector/test/simple/interop/output.mcg b/cgfcollector/test/simple/interop/output.mcg new file mode 100644 index 00000000..5fda0b43 --- /dev/null +++ b/cgfcollector/test/simple/interop/output.mcg @@ -0,0 +1,29 @@ +{ + "_CG": { + "meta": {}, + "nodes": { + "0": { + "callees": { "1": {} }, + "functionName": "_QQmain", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "1": { + "callees": {}, + "functionName": "add", + "hasBody": false, + "meta": {}, + "origin": "main.f90" + } + } + }, + "_MetaCG": { + "generator": { + "name": "MetaCG", + "sha": "68fb73aebcc0af419653b36a6b5e3e9668408d10", + "version": "0.9" + }, + "version": "4.0" + } +} diff --git a/cgfcollector/test/simple/interop_external/func.c b/cgfcollector/test/simple/interop_external/func.c new file mode 100644 index 00000000..ba2771bf --- /dev/null +++ b/cgfcollector/test/simple/interop_external/func.c @@ -0,0 +1 @@ +int add_(int* a, int* b) { return *a + *b; } diff --git a/cgfcollector/test/simple/interop_external/main.f90 b/cgfcollector/test/simple/interop_external/main.f90 new file mode 100644 index 00000000..1c34e2ed --- /dev/null +++ b/cgfcollector/test/simple/interop_external/main.f90 @@ -0,0 +1,15 @@ +program main + use iso_c_binding + implicit none + + integer(C_INT), external :: add + + integer(C_INT) :: result + integer(C_INT) :: var1 + integer(C_INT) :: var2 + var1 = 3 + var2 = 23 + + result = add(var1, var2) + print *, "Result from C add function:", result +end program main diff --git a/cgfcollector/test/simple/interop_external/output.mcg b/cgfcollector/test/simple/interop_external/output.mcg new file mode 100644 index 00000000..9e49af94 --- /dev/null +++ b/cgfcollector/test/simple/interop_external/output.mcg @@ -0,0 +1,29 @@ +{ + "_CG": { + "meta": {}, + "nodes": { + "0": { + "callees": {}, + "functionName": "add_", + "hasBody": false, + "meta": {}, + "origin": null + }, + "1": { + "callees": { "0": {} }, + "functionName": "_QQmain", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + } + } + }, + "_MetaCG": { + "generator": { + "name": "MetaCG", + "sha": "68fb73aebcc0af419653b36a6b5e3e9668408d10", + "version": "0.9" + }, + "version": "4.0" + } +} diff --git a/cgfcollector/test/simple/math_demo/main.f90 b/cgfcollector/test/simple/math_demo/main.f90 new file mode 100644 index 00000000..e73ba738 --- /dev/null +++ b/cgfcollector/test/simple/math_demo/main.f90 @@ -0,0 +1,73 @@ +module math_utils + implicit none + private + public :: factorial, array_stats, dot_product_custom, normalize_vector + +contains + recursive function factorial(n) result(fact) + integer, intent(in) :: n + integer :: fact + if (n <= 1) then + fact = 1 + else + fact = n*factorial(n - 1) + end if + end function factorial + + subroutine array_stats(arr, mean, std_dev) + real, intent(in) :: arr(:) + real, intent(out) :: mean, std_dev + real :: sum_arr, variance + integer :: n + n = size(arr) + sum_arr = sum(arr) + mean = sum_arr/n + variance = sum((arr - mean)**2)/n + std_dev = sqrt(variance) + end subroutine array_stats + + function dot_product_custom(a, b) result(dp) + real, intent(in) :: a(:), b(:) + real :: dp + dp = sum(a*b) + end function dot_product_custom + + subroutine normalize_vector(vec, norm_vec) + real, intent(in) :: vec(:) + real, intent(out) :: norm_vec(size(vec)) + real :: magnitude + magnitude = sqrt(sum(vec**2)) + if (magnitude /= 0.0) then + norm_vec = vec/magnitude + else + norm_vec = 0.0 + end if + end subroutine normalize_vector + +end module math_utils + +program complex_demo + use math_utils, only: factorial, array_stats, dot_product_custom, normalize_vector + implicit none + + real :: x(5), mean, std_dev, dp + real :: vec1(3), vec2(3), norm_vec(3) + integer :: i, fact + + x = [1.0, 2.0, 3.0, 4.0, 5.0] + call array_stats(x, mean, std_dev) + print *, 'Mean:', mean, 'Std Dev:', std_dev + + vec1 = [1.0, 0.0, 0.0] + vec2 = [0.0, 1.0, 0.0] + dp = dot_product_custom(vec1, vec2) + print *, 'Dot Product:', dp + + call normalize_vector(vec1, norm_vec) + print *, 'Normalized Vector:', norm_vec + + do i = 1, 5 + fact = factorial(i) + print *, 'Factorial(', i, ') = ', fact + end do +end program complex_demo diff --git a/cgfcollector/test/simple/math_demo/output.mcg b/cgfcollector/test/simple/math_demo/output.mcg new file mode 100644 index 00000000..01155e90 --- /dev/null +++ b/cgfcollector/test/simple/math_demo/output.mcg @@ -0,0 +1,50 @@ +{ + "_CG": { + "meta": {}, + "nodes": { + "0": { + "callees": {}, + "functionName": "_QMmath_utilsParray_stats", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "1": { + "callees": {}, + "functionName": "_QMmath_utilsPdot_product_custom", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "2": { + "callees": { "2": {} }, + "functionName": "_QMmath_utilsPfactorial", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "3": { + "callees": {}, + "functionName": "_QMmath_utilsPnormalize_vector", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "4": { + "callees": { "0": {}, "1": {}, "2": {}, "3": {} }, + "functionName": "_QQmain", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + } + } + }, + "_MetaCG": { + "generator": { + "name": "MetaCG", + "sha": "68fb73aebcc0af419653b36a6b5e3e9668408d10", + "version": "0.9" + }, + "version": "4.0" + } +} diff --git a/cgfcollector/test/simple/nesting/main.f90 b/cgfcollector/test/simple/nesting/main.f90 new file mode 100644 index 00000000..85b73c69 --- /dev/null +++ b/cgfcollector/test/simple/nesting/main.f90 @@ -0,0 +1,29 @@ +module m + implicit none + + type :: inner + contains + procedure :: say + end type inner + + type :: outer + type(inner) :: b + end type outer + +contains + + subroutine say(this) + class(inner), intent(in) :: this + print *, "Hello from inner" + end subroutine say + +end module m + +program main + use m + implicit none + + type(outer) :: a + + call a%b%say() +end program main diff --git a/cgfcollector/test/simple/nesting/output.mcg b/cgfcollector/test/simple/nesting/output.mcg new file mode 100644 index 00000000..e827c325 --- /dev/null +++ b/cgfcollector/test/simple/nesting/output.mcg @@ -0,0 +1,29 @@ +{ + "_CG": { + "meta": {}, + "nodes": { + "0": { + "callees": {}, + "functionName": "_QMmPsay", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "1": { + "callees": { "0": {} }, + "functionName": "_QQmain", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + } + } + }, + "_MetaCG": { + "generator": { + "name": "MetaCG", + "sha": "68fb73aebcc0af419653b36a6b5e3e9668408d10", + "version": "0.9" + }, + "version": "4.0" + } +} diff --git a/cgfcollector/test/simple/nesting_calls/main.f90 b/cgfcollector/test/simple/nesting_calls/main.f90 new file mode 100644 index 00000000..5d715a89 --- /dev/null +++ b/cgfcollector/test/simple/nesting_calls/main.f90 @@ -0,0 +1,38 @@ +module m + implicit none + + type :: T + contains + procedure :: func => return_ptr + procedure :: say + end type T + +contains + + function return_ptr(this) result(p) + class(T), intent(in) :: this + class(T), pointer :: p + allocate (T :: p) + select type (p) + type is (T) + p = this + end select + end function return_ptr + + subroutine say(this) + class(T), intent(in) :: this + print *, 'Hello from say' + end subroutine say + +end module m + +program main + use m + implicit none + + type(T) :: a + class(T), pointer :: tmp + + tmp => a%func() + call tmp%say() +end program main diff --git a/cgfcollector/test/simple/nesting_calls/output.mcg b/cgfcollector/test/simple/nesting_calls/output.mcg new file mode 100644 index 00000000..2f3a3626 --- /dev/null +++ b/cgfcollector/test/simple/nesting_calls/output.mcg @@ -0,0 +1,36 @@ +{ + "_CG": { + "meta": {}, + "nodes": { + "0": { + "callees": {}, + "functionName": "_QMmPreturn_ptr", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "1": { + "callees": {}, + "functionName": "_QMmPsay", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "2": { + "callees": { "0": {}, "1": {} }, + "functionName": "_QQmain", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + } + } + }, + "_MetaCG": { + "generator": { + "name": "MetaCG", + "sha": "68fb73aebcc0af419653b36a6b5e3e9668408d10", + "version": "0.9" + }, + "version": "4.0" + } +} diff --git a/cgfcollector/test/simple/no_body/main.f90 b/cgfcollector/test/simple/no_body/main.f90 new file mode 100644 index 00000000..ea37b6b0 --- /dev/null +++ b/cgfcollector/test/simple/no_body/main.f90 @@ -0,0 +1,39 @@ +program main + implicit none + + interface + subroutine print_stuff(n) + implicit none + integer, intent(in)::n + end subroutine print_stuff + end interface + + call print_stuff(1) + + call print_stars(5) +contains + subroutine print_stars(n) + implicit none + integer, intent(in) :: n + integer :: i + + interface + subroutine print_stuff2(f) + implicit none + integer, intent(in)::f + end subroutine print_stuff2 + end interface + + call print_stuff(1) + + do i = 1, n + write (*, *) '*' + end do + + contains + subroutine subsub(d) + integer, intent(in) :: d + end subroutine subsub + + end subroutine print_stars +end program main diff --git a/cgfcollector/test/simple/no_body/output.mcg b/cgfcollector/test/simple/no_body/output.mcg new file mode 100644 index 00000000..45d85e95 --- /dev/null +++ b/cgfcollector/test/simple/no_body/output.mcg @@ -0,0 +1,50 @@ +{ + "_CG": { + "meta": {}, + "nodes": { + "0": { + "callees": { "1": {}, "2": {} }, + "functionName": "_QQmain", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "1": { + "callees": {}, + "functionName": "print_stuff_", + "hasBody": false, + "meta": {}, + "origin": "main.f90" + }, + "2": { + "callees": { "1": {} }, + "functionName": "_QFPprint_stars", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "3": { + "callees": {}, + "functionName": "print_stuff2_", + "hasBody": false, + "meta": {}, + "origin": "main.f90" + }, + "4": { + "callees": {}, + "functionName": "_QFFprint_starsPsubsub", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + } + } + }, + "_MetaCG": { + "generator": { + "name": "MetaCG", + "sha": "96b749864d4e9ffe279252205b6ef755a6a86056", + "version": "0.9" + }, + "version": "4.0" + } +} diff --git a/cgfcollector/test/simple/operator/main.f90 b/cgfcollector/test/simple/operator/main.f90 new file mode 100644 index 00000000..d034daf3 --- /dev/null +++ b/cgfcollector/test/simple/operator/main.f90 @@ -0,0 +1,196 @@ +module mod + + implicit none + + type, abstract :: sortable + contains + procedure(compare), deferred :: less_then + procedure(not), deferred :: not_impl + generic :: operator(<) => less_then + generic :: operator(.NOT.) => not_impl + end type sortable + + interface + logical function compare(this, other) + import :: sortable + implicit none + class(sortable), intent(in) :: this, other + end function compare + logical function not(this) + import :: sortable + implicit none + class(sortable), intent(in) :: this + end function not + end interface + + type, extends(sortable) :: integer_sortable + integer :: value + contains + procedure :: less_then => less_then_integer + procedure :: not_impl => not_impl_integer + end type integer_sortable + + type :: integer_wrapper + integer :: value + end type integer_wrapper + + interface operator(.NEGX.) + module procedure negx + end interface + + interface operator(+) + procedure add_stuff, add_stuff2 + module procedure unary_plus + end interface + +contains + logical function less_then_integer(this, other) + class(integer_sortable), intent(in) :: this + class(sortable), intent(in) :: other + + select type (other) + type is (integer_sortable) + less_then_integer = this%value < other%value + print *, "Comparing integer_sortable: ", this%value, " < ", other%value + class default + error stop "Type mismatch in comparison" + end select + end function less_then_integer + + logical function not_impl(this) + class(sortable), intent(in) :: this + not_impl = .NOT. this%less_then(this) + print *, "Hello from not_impl" + end function not_impl + + logical function not_impl_integer(this) + class(integer_sortable), intent(in) :: this + not_impl_integer = .NOT. this < this + print *, "Hello from not_impl_integer" + end function not_impl_integer + + function negx(x) result(res) + real, intent(in) :: x + real :: res + res = -x + print *, "Hello from negx" + end function negx + + function add_stuff(x, y) result(res) + type(integer_sortable), intent(in) :: x, y + type(integer_sortable) :: res + res%value = x%value + y%value + print *, "Hello from add_stuff" + end function add_stuff + + function add_stuff2(x, y) result(res) + type(integer_wrapper), intent(in) :: x, y + type(integer_wrapper) :: res + res%value = x%value + y%value + print *, "Hello from add_stuff2" + end function add_stuff2 + + function unary_plus(x) result(res) + class(integer_sortable), intent(in) :: x + type(integer_sortable) :: res + + res%value = x%value + print *, "Hello from unary_plus" + end function unary_plus +end module mod + +program main + use mod + + implicit none + + call test_compare() + call test_compare2() + call test_not() + call test_negx() + call test_add() + call test_add2() + call test_unary_plus() + call test_expr() + +contains + subroutine test_compare() + class(sortable), allocatable :: a, b + type(integer_sortable) :: c, d + type(integer_sortable) :: res + + c%value = 5 + d%value = 10 + allocate (a, source=c) + allocate (b, source=d) + + if (a < b) then + print *, "a is less then b" + else + print *, "a is nat less then b" + end if + end subroutine test_compare + + subroutine test_compare2() + class(sortable), allocatable :: a, b + type(integer_sortable) :: c, d + type(integer_sortable) :: res + + c%value = 5 + d%value = 10 + allocate (a, source=c) + allocate (b, source=d) + + if (c < d) then + print *, "c is less then d" + else + print *, "c is not less then d" + end if + end subroutine test_compare2 + + subroutine test_not() + type(integer_sortable) :: c + + if (.NOT. c) then + print *, "c is not true" + else + print *, "c is true" + end if + end subroutine test_not + + subroutine test_negx() + real :: e = 5.0, f + f = .NEGX.e + print *, "Negated value: ", f + end subroutine test_negx + + subroutine test_add() + type(integer_sortable) :: c, d, res + c%value = 5 + d%value = 10 + res = c + d + print *, "Result of addition: ", res%value + end subroutine test_add + + subroutine test_add2() + type(integer_wrapper) :: c, d, res + c%value = 5 + d%value = 10 + res = c + d + c + print *, "Result of addition: ", res%value + end subroutine test_add2 + + subroutine test_unary_plus() + type(integer_sortable) :: c, res + c%value = 5 + res = +c + print *, "Result of unary plus: ", res%value + end subroutine test_unary_plus + + subroutine test_expr() + logical :: ad + + ad = (.NOT. 324 < 2) .EQV. .true. + end subroutine test_expr + +end program main diff --git a/cgfcollector/test/simple/operator/output.mcg b/cgfcollector/test/simple/operator/output.mcg new file mode 100644 index 00000000..39629dbf --- /dev/null +++ b/cgfcollector/test/simple/operator/output.mcg @@ -0,0 +1,150 @@ +{ + "_CG": { + "meta": {}, + "nodes": { + "0": { + "callees": {}, + "functionName": "compare_", + "hasBody": false, + "meta": {}, + "origin": "main.f90" + }, + "1": { + "callees": {}, + "functionName": "not_", + "hasBody": false, + "meta": {}, + "origin": "main.f90" + }, + "10": { + "callees": { "0": {}, "2": {} }, + "functionName": "_QFPtest_compare", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "11": { + "callees": { "0": {}, "2": {} }, + "functionName": "_QFPtest_compare2", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "12": { + "callees": { "1": {}, "4": {} }, + "functionName": "_QFPtest_not", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "13": { + "callees": { "5": {} }, + "functionName": "_QFPtest_negx", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "14": { + "callees": { "6": {}, "7": {} }, + "functionName": "_QFPtest_add", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "15": { + "callees": { "6": {}, "7": {} }, + "functionName": "_QFPtest_add2", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "16": { + "callees": { "8": {} }, + "functionName": "_QFPtest_unary_plus", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "17": { + "callees": {}, + "functionName": "_QFPtest_expr", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "2": { + "callees": {}, + "functionName": "_QMmodPless_then_integer", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "3": { + "callees": { "0": {}, "2": {} }, + "functionName": "_QMmodPnot_impl", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "4": { + "callees": { "0": {}, "2": {} }, + "functionName": "_QMmodPnot_impl_integer", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "5": { + "callees": {}, + "functionName": "_QMmodPnegx", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "6": { + "callees": {}, + "functionName": "_QMmodPadd_stuff", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "7": { + "callees": {}, + "functionName": "_QMmodPadd_stuff2", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "8": { + "callees": {}, + "functionName": "_QMmodPunary_plus", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "9": { + "callees": { + "10": {}, + "11": {}, + "12": {}, + "13": {}, + "14": {}, + "15": {}, + "16": {}, + "17": {} + }, + "functionName": "_QQmain", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + } + } + }, + "_MetaCG": { + "generator": { + "name": "MetaCG", + "sha": "96b749864d4e9ffe279252205b6ef755a6a86056", + "version": "0.9" + }, + "version": "4.0" + } +} diff --git a/cgfcollector/test/simple/operator2/main.f90 b/cgfcollector/test/simple/operator2/main.f90 new file mode 100644 index 00000000..9ea4245d --- /dev/null +++ b/cgfcollector/test/simple/operator2/main.f90 @@ -0,0 +1,72 @@ +module mod + + implicit none + + type, abstract :: base + integer :: value + contains + procedure(fbase), deferred :: function_base + end type base + + abstract interface + logical function fbase(this) + import :: base + implicit none + class(base), intent(in) :: this + end function fbase + end interface + + type, extends(base) :: derived + integer :: extra_value + contains + procedure :: function_base => function_base2 + end type derived + + interface operator(.NOT.) + module procedure not_base + end interface + +contains + + logical function not_base(this) + class(base), intent(in) :: this + + select type (this) + type is (derived) + print *, "Derived NOT called with value: ", this%value, " and extra value: ", this%extra_value + not_base = .true. + class default + print *, "Base NOT called with value: ", this%value + not_base = .false. + end select + end function not_base + + logical function function_base2(this) + class(derived), intent(in) :: this + print *, "Derived function called with value: ", this%value, " and extra value: ", this%extra_value + function_base2 = .true. + end function function_base2 +end module mod + +program main + use mod + implicit none + + class(base), allocatable :: obj1, obj2, result + + class(base), allocatable :: obj3 + logical :: obj3_result, obj3_result2 + + allocate (derived :: obj3) + obj3%value = 20 + select type (d => obj3) + type is (derived) + d%extra_value = 30 + end select + obj3_result = (.NOT. obj3) + obj3_result2 = obj3%function_base() + print *, "Base object NOT: ", obj3_result + print *, "Base function: ", obj3_result2 + +end program main + diff --git a/cgfcollector/test/simple/operator2/output.mcg b/cgfcollector/test/simple/operator2/output.mcg new file mode 100644 index 00000000..a5663baa --- /dev/null +++ b/cgfcollector/test/simple/operator2/output.mcg @@ -0,0 +1,43 @@ +{ + "_CG": { + "meta": {}, + "nodes": { + "0": { + "callees": {}, + "functionName": "_QMmodPfbase", + "hasBody": false, + "meta": {}, + "origin": "main.f90" + }, + "1": { + "callees": {}, + "functionName": "_QMmodPfunction_base2", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "2": { + "callees": {}, + "functionName": "_QMmodPnot_base", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "3": { + "callees": { "0": {}, "1": {}, "2": {} }, + "functionName": "_QQmain", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + } + } + }, + "_MetaCG": { + "generator": { + "name": "MetaCG", + "sha": "68fb73aebcc0af419653b36a6b5e3e9668408d10", + "version": "0.9" + }, + "version": "4.0" + } +} diff --git a/cgfcollector/test/simple/operator3/main.f90 b/cgfcollector/test/simple/operator3/main.f90 new file mode 100644 index 00000000..31475fec --- /dev/null +++ b/cgfcollector/test/simple/operator3/main.f90 @@ -0,0 +1,102 @@ +module mod + + implicit none + + type, abstract :: base + integer :: value + contains + procedure(s), deferred :: show + end type base + + abstract interface + subroutine s(this) + import :: base + implicit none + class(base), intent(in) :: this + end subroutine s + end interface + + type, extends(base) :: derived1 + contains + procedure :: show => show_derived1 + end type derived1 + + type, extends(base) :: derived2 + contains + procedure :: show => show_derived2 + end type derived2 + + interface operator(+) + module procedure add_base + end interface + + interface operator(-) + module procedure sub_derived1 + end interface + +contains + + function add_base(a, b) result(c) + class(base), intent(in) :: a, b + class(base), allocatable :: c + + select type (a) + type is (derived1) + allocate (derived1 :: c) + c%value = a%value + b%value + type is (derived2) + allocate (derived2 :: c) + c%value = a%value + b%value + class default + allocate (derived1 :: c) + c%value = a%value + b%value + end select + end function add_base + + function sub_derived1(a, b) result(c) + class(base), intent(in) :: a, b + class(base), allocatable :: c + + allocate (derived1 :: c) + c%value = a%value - b%value + end function sub_derived1 + + subroutine show_derived1(this) + class(derived1), intent(in) :: this + print *, "Derived1 with value: ", this%value + end subroutine show_derived1 + + subroutine show_derived2(this) + class(derived2), intent(in) :: this + print *, "Derived2 with value: ", this%value + end subroutine show_derived2 + +end module mod + +program main + use mod + + implicit none + + class(base), allocatable :: obj1, obj2, result + + class(derived1), allocatable :: obj_d1 + class(base), allocatable :: obj_d2 + + allocate (derived1 :: obj1) + obj1%value = 5 + allocate (derived2 :: obj2) + obj2%value = 7 + allocate (derived1 :: obj_d1) + obj_d1%value = 10 + allocate (derived1 :: obj_d2) + obj_d2%value = 10 + + result = (obj1 + obj2) - obj1 + call result%show() + + result = obj_d1 + obj_d2 + call result%show() + +end program main + diff --git a/cgfcollector/test/simple/operator3/output.mcg b/cgfcollector/test/simple/operator3/output.mcg new file mode 100644 index 00000000..f1c5b0f2 --- /dev/null +++ b/cgfcollector/test/simple/operator3/output.mcg @@ -0,0 +1,57 @@ +{ + "_CG": { + "meta": {}, + "nodes": { + "0": { + "callees": {}, + "functionName": "_QMmodPadd_base", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "1": { + "callees": {}, + "functionName": "_QMmodPs", + "hasBody": false, + "meta": {}, + "origin": "main.f90" + }, + "2": { + "callees": {}, + "functionName": "_QMmodPshow_derived1", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "3": { + "callees": {}, + "functionName": "_QMmodPshow_derived2", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "4": { + "callees": {}, + "functionName": "_QMmodPsub_derived1", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "5": { + "callees": { "0": {}, "1": {}, "2": {}, "3": {}, "4": {} }, + "functionName": "_QQmain", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + } + } + }, + "_MetaCG": { + "generator": { + "name": "MetaCG", + "sha": "68fb73aebcc0af419653b36a6b5e3e9668408d10", + "version": "0.9" + }, + "version": "4.0" + } +} diff --git a/cgfcollector/test/simple/operator4/main.f90 b/cgfcollector/test/simple/operator4/main.f90 new file mode 100644 index 00000000..4b6e1a40 --- /dev/null +++ b/cgfcollector/test/simple/operator4/main.f90 @@ -0,0 +1,34 @@ +module mod + implicit none + + type :: base + integer :: value + end type base + + interface operator(*) + module procedure multiply_base + end interface + + real, parameter :: num = 2*3.1415926535898 + real, parameter :: num2 = num*0.5, num3 = num2*0.5 + + type(base), parameter :: pi = base(3.1415926535898) + type(base), parameter :: half = base(0.5) + ! type(base) :: half_pi = pi*half not possible becasue no constant + +contains + + function multiply_base(a, b) result(res) + type(base), intent(in) :: a, b + type(base) :: res + res%value = a%value*b%value + print *, "Multiplying base values: ", a%value, " * ", b%value, " = ", res%value + end function multiply_base + +end module mod + +program main + use mod + implicit none + +end program main diff --git a/cgfcollector/test/simple/operator4/output.mcg b/cgfcollector/test/simple/operator4/output.mcg new file mode 100644 index 00000000..20660d4d --- /dev/null +++ b/cgfcollector/test/simple/operator4/output.mcg @@ -0,0 +1,29 @@ +{ + "_CG": { + "meta": {}, + "nodes": { + "0": { + "callees": {}, + "functionName": "_QMmodPmultiply_base", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "1": { + "callees": {}, + "functionName": "_QQmain", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + } + } + }, + "_MetaCG": { + "generator": { + "name": "MetaCG", + "sha": "4cc8210c90cb04678c623259ac8d2665c6474f45", + "version": "0.9" + }, + "version": "4.0" + } +} diff --git a/cgfcollector/test/simple/polymorphism/main.f90 b/cgfcollector/test/simple/polymorphism/main.f90 new file mode 100644 index 00000000..ee02c0ff --- /dev/null +++ b/cgfcollector/test/simple/polymorphism/main.f90 @@ -0,0 +1,66 @@ +module mod + + implicit none + + type :: body + private + real :: mass + real :: pos(3), vel(3) + contains + procedure :: set_mass => set_mass_body + end type body + + type, extends(body) :: charged_body + real :: charge + contains + procedure :: set_mass => set_mass_charged_body + end type charged_body + + type, extends(charged_body) :: very_charged_body + real :: charge2 + contains + procedure :: set_mass + end type very_charged_body + + class(body), allocatable :: polymorphic_body + +contains + subroutine set_mass_body(this, a) + class(body), intent(inout) :: this + real, intent(in) :: a + + write (*, *) 'Setting mass in body' + + this%mass = a + end subroutine set_mass_body + + subroutine set_mass_charged_body(this, a) + class(charged_body), intent(inout) :: this + real, intent(in) :: a + + write (*, *) 'Setting mass in charged body' + + this%mass = a + end subroutine set_mass_charged_body + + subroutine set_mass(this, a) + class(very_charged_body), intent(inout) :: this + real, intent(in) :: a + + write (*, *) 'Setting mass in very charged body' + + this%mass = a + end subroutine set_mass + +end module mod + +program main + use mod + + implicit none + + allocate (charged_body :: polymorphic_body) + call polymorphic_body%set_mass(5.0) + +end program main + diff --git a/cgfcollector/test/simple/polymorphism/output.mcg b/cgfcollector/test/simple/polymorphism/output.mcg new file mode 100644 index 00000000..1fe968ab --- /dev/null +++ b/cgfcollector/test/simple/polymorphism/output.mcg @@ -0,0 +1,43 @@ +{ + "_CG": { + "meta": {}, + "nodes": { + "0": { + "callees": {}, + "functionName": "_QMmodPset_mass_body", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "1": { + "callees": {}, + "functionName": "_QMmodPset_mass_charged_body", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "2": { + "callees": {}, + "functionName": "_QMmodPset_mass", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "3": { + "callees": { "0": {}, "1": {}, "2": {} }, + "functionName": "_QQmain", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + } + } + }, + "_MetaCG": { + "generator": { + "name": "MetaCG", + "sha": "ee64da05acb38bcac03c622a74e3bac1d7f8da5b", + "version": "1.0" + }, + "version": "4.0" + } +} diff --git a/cgfcollector/test/simple/polymorphism2/main.f90 b/cgfcollector/test/simple/polymorphism2/main.f90 new file mode 100644 index 00000000..6f2dc900 --- /dev/null +++ b/cgfcollector/test/simple/polymorphism2/main.f90 @@ -0,0 +1,59 @@ +module shapes + implicit none + + type, abstract :: Shape + contains + procedure(draw), deferred :: draw_shape + end type Shape + + abstract interface + subroutine draw(self) + import :: Shape + class(Shape), intent(in) :: self + end subroutine draw + end interface + + type, extends(Shape) :: Circle + real :: radius + contains + procedure :: draw_shape => draw_circle + end type Circle + + type, extends(Shape) :: Rectangle + real :: width, height + contains + procedure :: draw_shape => draw_rectangle + end type Rectangle + +contains + + subroutine draw_circle(self) + class(Circle), intent(in) :: self + print *, "Drawing a circle with radius:", self%radius + end subroutine draw_circle + + subroutine draw_rectangle(self) + class(Rectangle), intent(in) :: self + print *, "Drawing a rectangle with width:", self%width, "and height:", self%height + end subroutine draw_rectangle + +end module shapes + +program main + use shapes + implicit none + + class(Shape), allocatable :: s + type(Circle) :: c + type(Rectangle) :: r + + c%radius = 5.0 + s = c + call s%draw_shape() + + r%width = 10.0 + r%height = 20.0 + s = r + call s%draw_shape() + +end program main diff --git a/cgfcollector/test/simple/polymorphism2/output.mcg b/cgfcollector/test/simple/polymorphism2/output.mcg new file mode 100644 index 00000000..8b6fcd7a --- /dev/null +++ b/cgfcollector/test/simple/polymorphism2/output.mcg @@ -0,0 +1,43 @@ +{ + "_CG": { + "meta": {}, + "nodes": { + "0": { + "callees": {}, + "functionName": "_QMshapesPdraw", + "hasBody": false, + "meta": {}, + "origin": "main.f90" + }, + "1": { + "callees": {}, + "functionName": "_QMshapesPdraw_circle", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "2": { + "callees": {}, + "functionName": "_QMshapesPdraw_rectangle", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "3": { + "callees": { "0": {}, "1": {}, "2": {} }, + "functionName": "_QQmain", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + } + } + }, + "_MetaCG": { + "generator": { + "name": "MetaCG", + "sha": "68fb73aebcc0af419653b36a6b5e3e9668408d10", + "version": "0.9" + }, + "version": "4.0" + } +} diff --git a/cgfcollector/test/simple/simple/input.f90 b/cgfcollector/test/simple/simple/input.f90 new file mode 100644 index 00000000..8d049bea --- /dev/null +++ b/cgfcollector/test/simple/simple/input.f90 @@ -0,0 +1,15 @@ +program main + implicit none + + call print_stars(5) +contains + subroutine print_stars(n) + implicit none + integer, intent(in) :: n + integer :: i + + do i = 1, n + write (*, *) '*' + end do + end subroutine print_stars +end program main diff --git a/cgfcollector/test/simple/simple/output.mcg b/cgfcollector/test/simple/simple/output.mcg new file mode 100644 index 00000000..2821fc4a --- /dev/null +++ b/cgfcollector/test/simple/simple/output.mcg @@ -0,0 +1,29 @@ +{ + "_CG": { + "meta": {}, + "nodes": { + "0": { + "callees": {}, + "functionName": "_QFPprint_stars", + "hasBody": true, + "meta": {}, + "origin": "input.f90" + }, + "1": { + "callees": { "0": {} }, + "functionName": "_QQmain", + "hasBody": true, + "meta": {}, + "origin": "input.f90" + } + } + }, + "_MetaCG": { + "generator": { + "name": "MetaCG", + "sha": "68fb73aebcc0af419653b36a6b5e3e9668408d10", + "version": "0.9" + }, + "version": "4.0" + } +} diff --git a/cgfcollector/test/simple/statement_function/input.f90 b/cgfcollector/test/simple/statement_function/input.f90 new file mode 100644 index 00000000..f8a71fd7 --- /dev/null +++ b/cgfcollector/test/simple/statement_function/input.f90 @@ -0,0 +1,51 @@ +program main + implicit none + + integer :: a, b, c + integer :: res1, res2, res3 + integer :: mul, add, mix + + mul(a,b) = a * b + add(a,b) = a + b + mix(a,b,c) = mul(a,b) + add(b,c) + + res1 = mul(5,2) + res2 = add(3,4) + + res3 = mix(2,3,4) + + print *, "mul:", res1 + print *, "add:", res2 + print *, "mix:", res3 + + call inner_scope() + +contains + + subroutine inner_scope() + implicit none + + integer :: a, b, res, mul + + mul(a,b) = a * b + 1 + + res = mul(2,3) + + print *, "inner mul:", res + + call use_arg(res) + end subroutine inner_scope + + + subroutine use_arg(x) + implicit none + + integer, intent(in) :: x + integer :: a, b, subaddx + + subaddx(a,b) = a - b + x + + print *, "use_arg:", subaddx(10,3) + end subroutine use_arg + +end program main diff --git a/cgfcollector/test/simple/statement_function/output.mcg b/cgfcollector/test/simple/statement_function/output.mcg new file mode 100644 index 00000000..728ee4ec --- /dev/null +++ b/cgfcollector/test/simple/statement_function/output.mcg @@ -0,0 +1,71 @@ +{ + "_CG": { + "meta": {}, + "nodes": { + "0": { + "callees": { "1": {}, "2": {}, "3": {}, "4": {} }, + "functionName": "_QQmain", + "hasBody": true, + "meta": {}, + "origin": "input.f90" + }, + "1": { + "callees": {}, + "functionName": "_QFPmul", + "hasBody": true, + "meta": {}, + "origin": "input.f90" + }, + "2": { + "callees": {}, + "functionName": "_QFPadd", + "hasBody": true, + "meta": {}, + "origin": "input.f90" + }, + "3": { + "callees": { "1": {}, "2": {} }, + "functionName": "_QFPmix", + "hasBody": true, + "meta": {}, + "origin": "input.f90" + }, + "4": { + "callees": { "5": {}, "6": {} }, + "functionName": "_QFPinner_scope", + "hasBody": true, + "meta": {}, + "origin": "input.f90" + }, + "5": { + "callees": {}, + "functionName": "_QFFinner_scopePmul", + "hasBody": true, + "meta": {}, + "origin": "input.f90" + }, + "6": { + "callees": { "7": {} }, + "functionName": "_QFPuse_arg", + "hasBody": true, + "meta": {}, + "origin": "input.f90" + }, + "7": { + "callees": {}, + "functionName": "_QFFuse_argPsubaddx", + "hasBody": true, + "meta": {}, + "origin": "input.f90" + } + } + }, + "_MetaCG": { + "generator": { + "name": "MetaCG", + "sha": "f3f7dfc1f17f17b5897e815199038017f355f95f", + "version": "1.0" + }, + "version": "4.0" + } +} diff --git a/cgfcollector/test/simple/unlimited_polymorphism/main.f90 b/cgfcollector/test/simple/unlimited_polymorphism/main.f90 new file mode 100644 index 00000000..58eaea4d --- /dev/null +++ b/cgfcollector/test/simple/unlimited_polymorphism/main.f90 @@ -0,0 +1,71 @@ +module mod + + implicit none + + type :: my_type + integer :: a + real :: b + contains + final :: finalize_my_type + procedure :: print_stuff + end type my_type + + type :: my_type2 + contains + procedure :: print_stuff => print_stuff2 + final :: finalize_my_type2 + end type my_type2 + +contains + + subroutine finalize_my_type(this) + type(my_type), intent(inout) :: this + print *, "Finalizing my_type" + end subroutine finalize_my_type + + subroutine finalize_my_type2(this) + type(my_type2), intent(inout) :: this + print *, "Finalizing my_type2" + end subroutine finalize_my_type2 + + subroutine print_stuff(this) + class(my_type), intent(in) :: this + print *, "stuff" + end subroutine print_stuff + + subroutine print_stuff2(this) + class(my_type2), intent(in) :: this + print *, "stuff" + end subroutine print_stuff2 +end module mod + +program main + use mod + + implicit none + + call func() + +contains + subroutine func() + class(*), allocatable :: obj + + ! allocate (obj, mold=my_type2()) + ! deallocate (obj) + ! allocate (my_type2 :: obj) + ! deallocate (obj) + ! allocate (obj, source=my_type2()) + + ! can be assigned with allocate, move_alloc, = operator and in function arguments + obj = my_type(123, 12) + + select type (obj) + type is (my_type) + call obj%print_stuff() + print *, 'Object is of type my_type2' + class default + print *, 'Object is of an unknown type' + end select + end subroutine func +end program main + diff --git a/cgfcollector/test/simple/use/main.f90 b/cgfcollector/test/simple/use/main.f90 new file mode 100644 index 00000000..0a298573 --- /dev/null +++ b/cgfcollector/test/simple/use/main.f90 @@ -0,0 +1,48 @@ +module mod + + implicit none + + type :: base + private + real ::var + contains + procedure :: set_var => set_var_base + end type base + + type, extends(base) :: derived + contains + procedure :: set_var => set_var_derived + end type derived + +contains + subroutine set_var_base(this, a) + class(base), intent(inout) :: this + real, intent(in) :: a + + print *, 'Setting var from base' + + this%var = a + end subroutine set_var_base + + subroutine set_var_derived(this, a) + class(derived), intent(inout) :: this + real, intent(in) :: a + + print *, 'Setting var from derived' + + this%var = a + end subroutine set_var_derived +end module mod + +program main + use mod, only: base_rename => base, derived_rename => derived + + implicit none + + type(base_rename), allocatable :: b + + allocate (base_rename :: b) + call b%set_var(3.14) + +end program main + diff --git a/cgfcollector/test/simple/use/output.mcg b/cgfcollector/test/simple/use/output.mcg new file mode 100644 index 00000000..90fa13f9 --- /dev/null +++ b/cgfcollector/test/simple/use/output.mcg @@ -0,0 +1,36 @@ +{ + "_CG": { + "meta": {}, + "nodes": { + "0": { + "callees": {}, + "functionName": "_QMmodPset_var_base", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "1": { + "callees": {}, + "functionName": "_QMmodPset_var_derived", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + }, + "2": { + "callees": { "0": {}, "1": {} }, + "functionName": "_QQmain", + "hasBody": true, + "meta": {}, + "origin": "main.f90" + } + } + }, + "_MetaCG": { + "generator": { + "name": "MetaCG", + "sha": "68fb73aebcc0af419653b36a6b5e3e9668408d10", + "version": "0.9" + }, + "version": "4.0" + } +} diff --git a/cgfcollector/tools/cgfcollector_comp_wrapper.sh.in b/cgfcollector/tools/cgfcollector_comp_wrapper.sh.in new file mode 100755 index 00000000..62efeccb --- /dev/null +++ b/cgfcollector/tools/cgfcollector_comp_wrapper.sh.in @@ -0,0 +1,118 @@ +#!/usr/bin/env bash + +# Wrapper script for running cgfcollector plugin with flang. +# This script acts as a drop-in replacement for the flang compiler +# while addtionally generating call graphs for each source file. +# +# Note: The plugin requires the `-fc1` flag to be passed to flang. +# However, flang invokations with and without `-fc1` accept different +# sets of options. This script filters the original compiler options, +# keeping only those compatible with `-fc1`, and invokes flang with +# the plugin to generate call graphs. Finally, it invokes flang again +# with the original arguments to perform the actual compilation. +# +# Usage: +# cgfcollector_comp_wrapper.sh [flang options, source files, plugin options] +# +# Plugin-specific options: +# -d, --dot # Additionally generate a DOT file. This is mostly used for debugging. +# --skip-gen-bin # Skip the final compilation step. Only generate the call graph. +# -v, --verbose # Print additional information during the generation process. +# --graph-name # Set internal graph name +# --include-intrinsics # Include intrinsic calls in the generated call graph. + +flang_bin=${CGFCOLLECTOR_FLANG_BIN:-"flang-new"} + +# plugin-specific options wrapped in -mllvm plugin flags +declare -A OPTION_MAP=( + ["-d"]="--dot" + ["--dot"]="--dot" + ["--skip-gen-bin"]="--skip-gen-bin" + ["-v"]="--verbose" + ["--verbose"]="--verbose" + ["--graph-name"]="--graph-name" + ["--include-intrinsics"]="--include-intrinsics" +) + +if [[ $# -gt 0 && "$1" != -* ]] && command -v "$1" &>/dev/null; then + flang_bin="$1" + shift +fi + +if ! command -v "$flang_bin" &>/dev/null; then + echo "Error: $flang_bin not found in PATH." + exit 1 +fi + +source_files=() +options=() +all_args=("$@") +forward_args=() +skip_gen_bin=false + +# If you're using the `mpif90` wrapper, we are extracting the underlying compiler arguments using `mpif90 --show`. +# +# Important: Ensure that `flang_fc1_bin` is set to the actual Flang binary (e.g. `flang-new`, which it is by default), +# not the `mpif90` wrapper itself. The wrapper adds MPI-specific flags that are incompatible with `-fc1`. +if [[ "$flang_bin" == *mpif90 ]]; then + read -r -a mpi_show_args <<<"$(mpif90 --show)" + all_args=("${mpi_show_args[@]}" "${all_args[@]}") +fi + +# Extract all flags that are compatible with `-fc1`. +option_o=false +option_module_dir=false +for arg in "${all_args[@]}"; do + if $option_o; then + options+=("$arg") + forward_args+=("$arg") + option_o=false + continue + fi + if $option_module_dir; then + options+=("$arg") + forward_args+=("$arg") + option_module_dir=false + continue + fi + + # plugin-specific options (mapped to -mllvm flags) + if [[ -n "${OPTION_MAP[$arg]}" ]]; then + if [[ "$arg" == "--skip-gen-bin" ]]; then + skip_gen_bin=true + continue + fi + options+=("-mllvm" "${OPTION_MAP[$arg]}") + continue + fi + + forward_args+=("$arg") + + case "$arg" in + -fPIC) + # skip + ;; + -I* | -J* | -std=* | -O* | -D* | -f* | -cpp) + options+=("$arg") + ;; + -o) + options+=("$arg") + option_o=true + ;; + -module-dir) + options+=("$arg") + option_module_dir=true + ;; + *.f90 | *.F90) + source_files+=("$arg") + ;; + esac +done + +if [ ${#source_files[@]} -gt 0 ]; then + $flang_bin -fc1 -load "@CGFCOLLECTOR_FILE_NAME_LIB@" -plugin "genCG" "${options[@]}" "${source_files[@]}" +fi + +if [ ! $skip_gen_bin ]; then + exec "$flang_bin" "${forward_args[@]}" +fi diff --git a/cgfcollector/tools/cgfcollector_link_wrapper.sh.in b/cgfcollector/tools/cgfcollector_link_wrapper.sh.in new file mode 100755 index 00000000..1d702ad1 --- /dev/null +++ b/cgfcollector/tools/cgfcollector_link_wrapper.sh.in @@ -0,0 +1,61 @@ +#!/usr/bin/env bash + +# Wrapper script for merging call graphs at link time. +# +# Usage: +# cgfcollector_link_wrapper.sh [flang options and object files] + +flang_bin=${CGFCOLLECTOR_FLANG_BIN:-"flang-new"} +cgmerge2_bin=${CGFCOLLECTOR_CGMERGE2_BIN:-"@CGMERGE2_BIN@"} + +if [[ $# -gt 0 && "$1" != -* ]] && command -v "$1" &>/dev/null; then + flang_bin="$1" + shift +fi + +if ! command -v "$flang_bin" &>/dev/null; then + echo "Error: $flang_bin not found in PATH." + exit 1 +fi + +if ! command -v "$cgmerge2_bin" &>/dev/null; then + echo "Error: $cgmerge2_bin not found in PATH." + exit 1 +fi + +object_files=() +output_file="" + +# Extract object files +option_o=false +skip_gen_bin=false +for arg in "$@"; do + if $option_o; then + output_file="$arg" + option_o=false + continue + fi + + case "$arg" in + --skip-gen-bin) + skip_gen_bin=true + ;; + -o) + option_o=true + ;; + *.o) + object_files+=("$arg") + ;; + esac +done + +if [ ${#object_files[@]} -gt 0 ] && [ -n "$output_file" ]; then + for i in "${!object_files[@]}"; do + object_files[i]="${object_files[$i]}.mcg" + done + "$cgmerge2_bin" "${output_file}.mcg" "${object_files[@]}" +fi + +if [ ! $skip_gen_bin ]; then + exec "$flang_bin" "$@" +fi diff --git a/cgfcollector/tools/test_runner.sh.in b/cgfcollector/tools/test_runner.sh.in new file mode 100755 index 00000000..1eaf2b8c --- /dev/null +++ b/cgfcollector/tools/test_runner.sh.in @@ -0,0 +1,166 @@ +#!/usr/bin/env bash + +# Test runner for cgfcollector +# +# Usage: +# test_runner.sh # run all tests +# test_runner.sh [test_name] # run specific test cases +# +# Note: Options starting with '-' are passed directly to the cgfcollector +# plugin. For a list see README.md. + +: "${CI_CONCURRENT_ID:=$(date +%s)}" + +test_case_dir="@CGFCOLLECTOR_TEST_CASES_DIR@" +scriptdir="$(cd "$(dirname "$0")" && pwd -P)" +out_dir="$scriptdir/out-$CI_CONCURRENT_ID" + +mkdir -p "$out_dir" + +if ! [ -d "$test_case_dir" ]; then + echo "Error: Test case directory '$test_case_dir' does not exist." + exit 1 +fi + +cgfcollector_options=() +test_cases_to_run=() + +while [[ $# -gt 0 ]]; do + arg="$1" + if [[ "$arg" =~ ^-+ ]]; then + cgfcollector_options+=("$arg") + else + test_cases_to_run+=("$arg") + fi + shift +done + +function find_test_dir() +{ + local test_name="${1#*_}" + local test_category="${1%%_*}" + local test_dir="$test_case_dir/$test_category/$test_name" + + if [ -d "$test_dir" ] && [ -f "$test_dir/output.mcg" ]; then + echo "$test_dir" + return 0 + fi + return 1 +} + +function run_single_test() +{ + local test_dir="$1" + local test_name="$2" + local expected_output="$test_dir/output.mcg" + local bin_output="$out_dir/$test_name" + local actual_output="$bin_output.mcg" + + if [ -f "$test_dir/CMakeLists.txt" ]; then + # cmake managed test + local tmp_dir="$(mktemp -d)" + trap 'rm -rf '"$tmp_dir"'' RETURN + + cmake -S "$test_dir" -B "$tmp_dir" -DCMAKE_Fortran_FLAGS="${cgfcollector_options[*]}" -DCGFCOLLECTOR_TEST_CMAKE_BASE="$scriptdir/cmake_base.txt" || { + echo "Failed: cmake config failed" + return 1 + } + cmake --build "$tmp_dir" || { + echo "Failed: cmake build failed" + return 1 + } + find "$tmp_dir" -maxdepth 1 -name '*.mcg' -exec cp {} "$actual_output" \; || { + echo "Failed: could not generate CG" + return 1 + } + elif [ -f "$test_dir/Makefile" ]; then + # make managed with fortdepend test + local tmp_dir="$(mktemp -d)" + trap 'rm -rf '"$tmp_dir"'' RETURN + + make -C "$test_dir" BUILD_DIR="$tmp_dir" FCFLAGS="${cgfcollector_options[*]}" CGFCOLLECTOR_TEST_MAKE_BASE="$scriptdir/make_base" || { + echo "Failed: make build failed" + return 1 + } + find "$tmp_dir" -maxdepth 1 -name 'output.mcg' -exec cp {} "$actual_output" \; || { + echo "Failed: could not generate CG" + return 1 + } + else + # not make/cmake managed test + local input_files=("$test_dir"/*.f90) + if ! [ -f "${input_files[0]}" ]; then + echo "Failed: no .f90 files found" + return 1 + fi + + @CGFCOLLECTOR_COMP_WRAPPER@ "${cgfcollector_options[@]}" -o "$bin_output" --skip-gen-bin "${input_files[@]}" || { + echo "Failed: could not generate CG" + return 1 + } + fi + + if @CGFCOLLECTOR_CGDIFF_BIN@ "$expected_output" "$actual_output"; then + echo "Passed" + return 0 + else + echo "Failed: Output mismatch" + return 1 + fi +} + +if [ ${#test_cases_to_run[@]} -gt 0 ]; then + # specific test cases + test_cases=() + for test_name in "${test_cases_to_run[@]}"; do + test_dir=$(find_test_dir "$test_name") + if [ -n "$test_dir" ]; then + test_cases+=("$test_dir:$test_name") + else + echo "Warning: Test '$test_name' not found" + fi + done +else + # all test cases + test_cases=() + while read -r output_file; do + test_dir="$(dirname "$output_file")" + test_name="$(basename "$(dirname "$test_dir")")_$(basename "$test_dir")" + test_cases+=("$test_dir:$test_name") + done < <(find "$test_case_dir" -mindepth 1 -type d -exec find {} -maxdepth 1 -name "output.mcg" \;) +fi + +total_tests=${#test_cases[@]} +passed=0 +failed=0 +failed_tests=() + +# run tests +for i in "${!test_cases[@]}"; do + IFS=':' read -r test_dir test_name <<<"${test_cases[i]}" + + printf "Test %d/%d %s\n" "$((i + 1))" "$total_tests" "$test_name" + + if run_single_test "$test_dir" "$test_name"; then + ((passed++)) + else + ((failed++)) + failed_tests+=("$test_name") + fi +done + +# summary +if [ "$total_tests" -gt 0 ]; then + printf "%d%% tests passed, %d tests failed out of %d\n" \ + "$((passed * 100 / total_tests))" "$failed" "$total_tests" +else + echo "No tests found" +fi + +if [ ${#failed_tests[@]} -gt 0 ]; then + echo "Failed tests:" + for test in "${failed_tests[@]}"; do + echo " $test" + done + exit 1 +fi diff --git a/cmake/ClangLLVM.cmake b/cmake/ClangLLVM.cmake index 36ef0850..1dfe7eea 100644 --- a/cmake/ClangLLVM.cmake +++ b/cmake/ClangLLVM.cmake @@ -71,11 +71,39 @@ function(add_clang target) ) endif() + # clang internally relies on llvm to be linked so we also link some llvm libs when we need clang if(LLVM_LINK_LLVM_DYLIB) target_link_libraries(${target} PUBLIC LLVM) else() llvm_map_components_to_libnames(llvm_libs support) target_link_libraries(${target} PUBLIC ${llvm_libs}) endif() +endfunction() + +function( + add_llvm + target + scope +) + target_include_directories( + ${target} + SYSTEM + ${scope} + ${LLVM_INCLUDE_DIRS} + ) + if(LLVM_LINK_LLVM_DYLIB) + target_link_libraries( + ${target} + ${scope} + LLVM + ) + else() + llvm_map_components_to_libnames(llvm_libs support) + target_link_libraries( + ${target} + ${scope} + ${llvm_libs} + ) + endif() endfunction() diff --git a/cmake/FlangLLVM.cmake b/cmake/FlangLLVM.cmake new file mode 100644 index 00000000..02b4088f --- /dev/null +++ b/cmake/FlangLLVM.cmake @@ -0,0 +1,92 @@ +find_program( + LLVM_CONFIG_EXECUTABLE + llvm-config + REQUIRED +) + +execute_process( + COMMAND ${LLVM_CONFIG_EXECUTABLE} --prefix + OUTPUT_VARIABLE LLVM_PREFIX + OUTPUT_STRIP_TRAILING_WHITESPACE +) + +# Add the MLIR and Flang CMake modules to the search path. Otherwise, CMake won't find them. +list( + APPEND + CMAKE_PREFIX_PATH + "${LLVM_PREFIX}/lib/cmake/mlir" + "${LLVM_PREFIX}/lib/cmake/flang" +) + +find_package( + MLIR + REQUIRED + CONFIG +) + +find_package( + Flang + REQUIRED + CONFIG +) + +# Test if the LLVM major version is one of the allowed ones +function(test_llvm_major_version version_string) + # List of all supported major LLVM versions + set(supported_llvm_versions + 18 + 19 + 20 + 21 + ) + # Set to indicate after loop if supported version was found + set(valid_version FALSE) + if(${version_string} + IN_LIST + supported_llvm_versions + ) + message(STATUS "Supported LLVM Found! Version ${version_string}") + set(valid_version TRUE) + endif() + + if(NOT ${valid_version}) + message(WARNING "Support for LLVM Version ${version_string} is not tested! Proceed with care.") + message(WARNING "LLVM/MLIR/Flang version 18 19 20 21 are supported and tested") + endif() +endfunction() + +message(STATUS "Found FlangConfig.cmake in: ${Flang_DIR}") +message(STATUS "Using Flang version: ${Flang_VERSION}") +message(STATUS "Found MLIRConfig.cmake in: ${MLIR_DIR}") +message(STATUS "Using MLIR version: ${MLIR_VERSION}") + +test_llvm_major_version(${LLVM_VERSION_MAJOR}) + +function(add_flang target) + # Before LLVM 21, this is set through CMake's TestBigEndian (deperated in CMake 3.20). As a result this doesn't seem + # to work anymore. We now set it manually the new way. This must to be set or the compilation will fail. Note: This + # check is not required with LLVM 21. + if(CMAKE_CXX_BYTE_ORDER + STREQUAL + "BIG_ENDIAN" + ) + target_compile_definitions(${target} PRIVATE FLANG_BIG_ENDIAN) + elseif( + CMAKE_CXX_BYTE_ORDER + STREQUAL + "LITTLE_ENDIAN" + ) + target_compile_definitions(${target} PRIVATE FLANG_LITTLE_ENDIAN) + else() + message(WARNING "Endianness could not be determined.") + endif() + + target_include_directories(${target} SYSTEM PUBLIC ${FLANG_INCLUDE_DIRS}) + + find_library( + FLANG_FRONTEND_TOOL flangFrontendTool + PATHS ${LLVM_LIBRARY_DIR} + NO_DEFAULT_PATH + ) + target_link_libraries(${target} PUBLIC ${FLANG_FRONTEND_TOOL}) +endfunction() diff --git a/cmake/ToolchainOptions.cmake b/cmake/ToolchainOptions.cmake index a6226108..d1471927 100644 --- a/cmake/ToolchainOptions.cmake +++ b/cmake/ToolchainOptions.cmake @@ -1,12 +1,6 @@ include(json) include(spdlog) - -if(METACG_BUILD_GRAPH_TOOLS - OR METACG_BUILD_CGCOLLECTOR - OR METACG_BUILD_PGIS -) - include(cxxopts-lib) -endif() +include(cxxopts-lib) # Internal dependencies function(add_metacg target) @@ -20,7 +14,7 @@ function(add_pgis target) endfunction() function(add_config_include target) - target_include_directories(${target} PUBLIC $) + target_include_directories(${target} PUBLIC $) endfunction() function(add_pgis_includes target) diff --git a/cmake/installRules.cmake b/cmake/installRules.cmake index fa9d5a60..2798df14 100644 --- a/cmake/installRules.cmake +++ b/cmake/installRules.cmake @@ -72,8 +72,8 @@ install( # Install config.h install( - FILES "${PROJECT_BINARY_DIR}/config.h" - DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" + FILES "${PROJECT_BINARY_DIR}/include/metacg/config.h" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/metacg/" COMPONENT metacg_Development ) @@ -94,8 +94,8 @@ endif() # Install the generated CustomMD.h header install( - FILES ${PROJECT_BINARY_DIR}/graph/include/metadata/CustomMD.h - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/metadata + FILES ${PROJECT_BINARY_DIR}/graph/include/metacg/metadata/CustomMD.h + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/metacg/metadata COMPONENT metacg_Development ) diff --git a/cmake/metavirt.cmake b/cmake/metavirt.cmake new file mode 100644 index 00000000..cc60f666 --- /dev/null +++ b/cmake/metavirt.cmake @@ -0,0 +1,14 @@ +set(METAVIRT_LOG_LEVEL + "0" + CACHE STRING "MetaVirt log level from 0 (least verbose) to 4 (most verbose)" +) + +FetchContent_Declare( + metavirt + GIT_REPOSITORY https://github.com/ahueck/llvm-metavirt.git + GIT_TAG devel + GIT_SHALLOW 1 + FIND_PACKAGE_ARGS +) + +FetchContent_MakeAvailable(metavirt) diff --git a/cmake/spdlog.cmake b/cmake/spdlog.cmake index 235300ec..9b49c264 100644 --- a/cmake/spdlog.cmake +++ b/cmake/spdlog.cmake @@ -12,6 +12,11 @@ set(SPDLOG_INSTALL CACHE INTERNAL "" ) +set(SPDLOG_NO_EXCEPTIONS + ON + CACHE BOOL "Disable exceptions in spdlog" +) + FetchContent_Declare(spdlog URL https://github.com/gabime/spdlog/archive/refs/tags/v1.15.3.tar.gz) FetchContent_MakeAvailable(spdlog) diff --git a/container/base b/container/base deleted file mode 100644 index eb52c9c3..00000000 --- a/container/base +++ /dev/null @@ -1,13 +0,0 @@ -FROM ubuntu:20.04 - -WORKDIR /opt/metacg - -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update -y && apt-get upgrade -y && apt-get install -y gcc g++ gdb cmake python3 apt-utils wget gnupg qt5-default git autoconf automake libtool zlib1g-dev zlib1g vim unzip python3-pip python3-pytest python3-pytest-cov python3-venv openmpi-bin openmpi-common bison flex python2 bear - -RUN pip3 install -U pip - -RUN wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && apt-get install -y libllvm-10-ocaml-dev libllvm10 llvm-10 llvm-10-dev llvm-10-doc llvm-10-examples llvm-10-runtime clang-10 clang-tools-10 clang-10-doc libclang-common-10-dev libclang-10-dev libclang1-10 clang-format-10 python3-clang-10 clangd-10 clang-tidy-10 - -RUN ln -s /usr/bin/clang-10 /usr/bin/clang && ln -s /usr/bin/clang++-10 /usr/bin/clang++ diff --git a/container/build_base_image.sh b/container/build_base_image.sh deleted file mode 100644 index 74321a05..00000000 --- a/container/build_base_image.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash - -# Building the container base image is not possible on Lichtenberg due to a permission issue -# with Podman. Thus, we build a base image on another machine, which is then downloaded and used -# by the CI - -docker login registry.git.rwth-aachen.de -docker build -t registry.git.rwth-aachen.de/tuda-sc/projects/metacg/base:latest -f container/base . -docker push registry.git.rwth-aachen.de/tuda-sc/projects/metacg/base:latest diff --git a/container/devel b/container/devel deleted file mode 100644 index 20056770..00000000 --- a/container/devel +++ /dev/null @@ -1,22 +0,0 @@ -FROM metacg:base - -WORKDIR /opt/metacg - -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update && apt-get install -y ssh rsync tar - -RUN ( \ - echo 'LogLevel DEBUG2'; \ - echo 'PermitRootLogin yes'; \ - echo 'PasswordAuthentication yes'; \ - echo 'Subsystem sftp /usr/lib/openssh/sftp-server'; \ - ) > /etc/ssh/sshd_config_test_clion \ - && mkdir /run/sshd - -RUN useradd -m user \ - && yes password | passwd user - -RUN usermod -s /bin/bash user - -CMD ["/usr/sbin/sshd", "-D", "-e", "-f", "/etc/ssh/sshd_config_test_clion"] diff --git a/container/full-build b/container/full-build-llvm-18 similarity index 71% rename from container/full-build rename to container/full-build-llvm-18 index 7f55ea1e..396fbe0f 100644 --- a/container/full-build +++ b/container/full-build-llvm-18 @@ -14,7 +14,8 @@ RUN apt-get update -y && \ openmpi-bin openmpi-common bison flex bear \ lsb-release wget software-properties-common \ python3-matplotlib python3-pyqt5 ninja-build \ - libopenmpi-dev libhwloc-dev libevent-dev + libopenmpi-dev libhwloc-dev libevent-dev \ + jq RUN wget -qO- 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/bookworm/ llvm-toolchain-bookworm-18 main' && \ @@ -22,10 +23,12 @@ RUN wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | tee /etc/apt/trusted. apt-get update && \ apt-get install -y libllvm18 llvm-18 llvm-18-dev llvm-18-runtime \ clang-18 clang-tools-18 libclang-common-18-dev libclang-18-dev libclang1-18 \ - lld-18 + lld-18 libmlir-18-dev mlir-18-tools flang-18 RUN bash -c "ln -s $(which clang-18) /usr/bin/clang" && \ bash -c "ln -s $(which clang++-18) /usr/bin/clang++" && \ - bash -c "ln -s $(which llvm-config-18) /usr/bin/llvm-config" + bash -c "ln -s $(which opt-18) /usr/bin/opt" && \ + bash -c "ln -s $(which llvm-config-18) /usr/bin/llvm-config" && \ + bash -c "ln -s $(which flang-new-18) /usr/bin/flang-new" ARG extinstalldir=/opt/metacg/extern/install ENV EXTINSTALLDIR=$extinstalldir @@ -34,6 +37,9 @@ COPY ./build_submodules.sh /opt/metacg RUN ./build_submodules.sh $(nproc) +RUN python3 -m venv .venv && \ + .venv/bin/pip install fortdepend + COPY . /opt/metacg RUN cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=Debug \ @@ -45,15 +51,25 @@ RUN cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=Debug \ -DMETACG_BUILD_CGCOLLECTOR=ON \ -DMETACG_BUILD_GRAPH_TOOLS=ON \ -DMETACG_BUILD_CGPATCH=ON \ + -DMETACG_BUILD_CGFCOLLECTOR=ON \ + -DCAGE_USE_METAVIRT=ON \ -DMETACG_BUILD_PYMETACG=ON \ -DPython_ROOT_DIR=/opt/metacg/.venv \ -DPYTEST_EXECUTABLE=/opt/metacg/.venv/bin/pytest \ -DCMAKE_MODULE_PATH=/opt/metacg/.venv/share/Pytest/cmake \ -DMETACG_BUILD_PYMETACG_TESTS=ON \ -DCMAKE_VERBOSE_MAKEFILE=ON \ - -DLLVM_EXTERNAL_LIT=/usr/lib/llvm-18/build/utils/lit/lit.py + -DLLVM_EXTERNAL_LIT=/usr/lib/llvm-18/build/utils/lit/lit.py RUN cmake --build build --parallel RUN cmake --install build +RUN cmake -Dmetacg_ROOT=/tmp/metacg -S graph/test/install -B graph/test/build +RUN cmake --build graph/test/build --parallel + +RUN cmake -Dmetacg_ROOT=/tmp/metacg -S tools/cgcollector2/CGC2DemoPlugin -B tools/cgcollector2/CGC2DemoPlugin/build +RUN cmake --build tools/cgcollector2/CGC2DemoPlugin/build --parallel + +RUN cmake -Dmetacg_ROOT=/tmp/metacg -S tools/cage/CaGeDemoPlugin -B tools/cage/CaGeDemoPlugin/build +RUN cmake --build tools/cage/CaGeDemoPlugin/build --parallel diff --git a/container/full-build-llvm-20 b/container/full-build-llvm-20 new file mode 100644 index 00000000..fa77721f --- /dev/null +++ b/container/full-build-llvm-20 @@ -0,0 +1,75 @@ +FROM debian:bookworm-slim + +WORKDIR /opt/metacg + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update -y && \ + apt-get upgrade -y && \ + apt-get install -y gcc g++ gdb \ + cmake python3 apt-utils wget gnupg \ + qtbase5-dev git autoconf automake \ + libtool zlib1g-dev zlib1g vim unzip \ + python3-pip python3-venv \ + openmpi-bin openmpi-common bison flex bear \ + lsb-release wget software-properties-common \ + python3-matplotlib python3-pyqt5 ninja-build \ + libopenmpi-dev libhwloc-dev libevent-dev \ + jq + +RUN wget -qO- 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/bookworm/ llvm-toolchain-bookworm-20 main' && \ + add-apt-repository -y 'deb http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-20 main' && \ + apt-get update && \ + apt-get install -y libllvm20 llvm-20 llvm-20-dev llvm-20-runtime \ + clang-20 clang-tools-20 libclang-common-20-dev libclang-20-dev libclang1-20 \ + lld-20 libmlir-20-dev mlir-20-tools flang-20 +RUN bash -c "ln -s $(which clang-20) /usr/bin/clang" && \ + bash -c "ln -s $(which clang++-20) /usr/bin/clang++" && \ + bash -c "ln -s $(which opt-20) /usr/bin/opt" && \ + bash -c "ln -s $(which llvm-config-20) /usr/bin/llvm-config" && \ + bash -c "ln -s $(which flang-new-20) /usr/bin/flang-new" + +ARG extinstalldir=/opt/metacg/extern/install +ENV EXTINSTALLDIR=$extinstalldir + +COPY ./build_submodules.sh /opt/metacg + +RUN ./build_submodules.sh $(nproc) + +RUN python3 -m venv .venv && \ + .venv/bin/pip install fortdepend + +COPY . /opt/metacg + +RUN cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_INSTALL_PREFIX=/tmp/metacg \ + -DCUBE_DIR=$EXTINSTALLDIR/cubelib \ + -DEXTRAP_INCLUDE=$EXTINSTALLDIR/extrap/include \ + -DEXTRAP_LIB=$EXTINSTALLDIR/extrap/lib \ + -DMETACG_BUILD_PGIS=ON \ + -DMETACG_BUILD_CGCOLLECTOR=ON \ + -DMETACG_BUILD_GRAPH_TOOLS=ON \ + -DMETACG_BUILD_CGPATCH=ON \ + -DMETACG_BUILD_CGFCOLLECTOR=ON \ + -DCAGE_USE_METAVIRT=ON \ + -DMETACG_BUILD_PYMETACG=ON \ + -DPython_ROOT_DIR=/opt/metacg/.venv \ + -DPYTEST_EXECUTABLE=/opt/metacg/.venv/bin/pytest \ + -DCMAKE_MODULE_PATH=/opt/metacg/.venv/share/Pytest/cmake \ + -DMETACG_BUILD_PYMETACG_TESTS=ON \ + -DCMAKE_VERBOSE_MAKEFILE=ON \ + -DLLVM_EXTERNAL_LIT=/usr/lib/llvm-20/build/utils/lit/lit.py + +RUN cmake --build build --parallel + +RUN cmake --install build + +RUN cmake -Dmetacg_ROOT=/tmp/metacg -S graph/test/install -B graph/test/build +RUN cmake --build graph/test/build --parallel + +RUN cmake -Dmetacg_ROOT=/tmp/metacg -S tools/cgcollector2/CGC2DemoPlugin -B tools/cgcollector2/CGC2DemoPlugin/build +RUN cmake --build tools/cgcollector2/CGC2DemoPlugin/build --parallel + +RUN cmake -Dmetacg_ROOT=/tmp/metacg -S tools/cage/CaGeDemoPlugin -B tools/cage/CaGeDemoPlugin/build +RUN cmake --build tools/cage/CaGeDemoPlugin/build --parallel \ No newline at end of file diff --git a/graph/CMakeLists.txt b/graph/CMakeLists.txt index 7b8f3ba5..b397d72a 100644 --- a/graph/CMakeLists.txt +++ b/graph/CMakeLists.txt @@ -16,27 +16,27 @@ set(MetacgGraphLibSources src/MCGBaseInfo.cpp src/ReachabilityAnalysis.cpp src/MergePolicy.cpp - include/io/MCGReader.h - include/CgNode.h - include/Callgraph.h - include/metadata/MetaData.h - include/metadata/MetadataMixin.h - include/MCGManager.h - include/io/MCGWriter.h - include/Util.h - include/MCGBaseInfo.h - include/LoggerUtil.h - include/DotIO.h - include/Timing.h - include/ReachabilityAnalysis.h - include/MergePolicy.h + include/metacg/io/MCGReader.h + include/metacg/CgNode.h + include/metacg/Callgraph.h + include/metacg/metadata/MetaData.h + include/metacg/metadata/MetadataMixin.h + include/metacg/MCGManager.h + include/metacg/io/MCGWriter.h + include/metacg/Util.h + include/metacg/MCGBaseInfo.h + include/metacg/LoggerUtil.h + include/metacg/DotIO.h + include/metacg/Timing.h + include/metacg/ReachabilityAnalysis.h + include/metacg/MergePolicy.h ) # Custom in-tree metadata file( GLOB CUSTOM_METADATA - "include/metadata/custom/*.h" + "include/metacg/metadata/custom/*.h" ) if(NOT CUSTOM_METADATA @@ -70,8 +70,8 @@ string( ) configure_file( - include/metadata/CustomMD.h.in - ${CMAKE_CURRENT_BINARY_DIR}/include/metadata/CustomMD.h + include/metacg/metadata/CustomMD.h.in + ${CMAKE_CURRENT_BINARY_DIR}/include/metacg/metadata/CustomMD.h @ONLY ) diff --git a/graph/include/DominatorAnalysis.h b/graph/include/DominatorAnalysis.h new file mode 100644 index 00000000..1c835067 --- /dev/null +++ b/graph/include/DominatorAnalysis.h @@ -0,0 +1,129 @@ +/** + * File: DominatorAnalysis.h + * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt + */ +#ifndef METACG_DOMINATORANALYSIS_H +#define METACG_DOMINATORANALYSIS_H + +#include +#include +#include +#include +#include + +namespace metacg::analysis { + +enum class TraverseDir { Forward, Backward }; + +template +struct DomData { + using NodeSet = std::unordered_set; + const NodeT* node{nullptr}; + NodeSet Doms{}; + bool initialized{false}; +}; + +template +using DomAnalysisResult = std::unordered_map>; + +template +DomAnalysisResult computeDoms(const GraphT& graph, const NodeT& exitNode); + +template +Container unorderedIntersection(const Container& a, const Container& b) { + Container result; + for (auto& item : a) { + if (std::find(b.begin(), b.end(), item) != b.end()) { + result.insert(item); + } + } + return result; +} + +// Adopted from https://stackoverflow.com/questions/25505868/the-intersection-of-multiple-sorted-arrays +template +typename DomData::NodeSet intersection(const std::vector& nodes, + const DomAnalysisResult& DomMap) { + if (nodes.empty()) + return {}; + + auto last_intersection = DomMap.at(nodes[0]).Doms; + + typename DomData::NodeSet curr_intersection; + + for (std::size_t i = 1; i < nodes.size(); ++i) { + auto& currSet = DomMap.at(nodes[i]).Doms; + + // Note: std::set_intersection does not work with unordered_set. + curr_intersection = unorderedIntersection(last_intersection, currSet); + std::swap(last_intersection, curr_intersection); + curr_intersection.clear(); + } + return last_intersection; +} + +template +DomAnalysisResult computeDominators(const GraphT& graph, const NodeT& exitNode) { + using DomDataT = DomData; + + auto incoming = [&](const NodeT* n) { + if constexpr (dir == TraverseDir::Forward) + return graph.getCallers(*n); + else + return graph.getCallees(*n); + }; + + auto outgoing = [&](const NodeT* n) { + if constexpr (dir == TraverseDir::Backward) + return graph.getCallers(*n); + else + return graph.getCallees(*n); + }; + + + DomAnalysisResult DomMap{{&exitNode, {&exitNode, {}, false}}}; + + std::deque workQueue{&DomMap[&exitNode]}; + + auto addToQueue = [&workQueue](DomDataT* const data) { + if (std::find(workQueue.begin(), workQueue.end(), data) == workQueue.end()) { + workQueue.push_back(data); + } + }; + + do { + auto& nodeData = *workQueue.front(); + workQueue.pop_front(); + + std::vector initializedCallees{}; + for (auto* calleePtr : incoming(nodeData.node)) { + if (DomMap[calleePtr].initialized) { + initializedCallees.push_back(calleePtr); + } + } + + typename DomDataT::NodeSet DomNew = intersection(initializedCallees, DomMap); + DomNew.insert(nodeData.node); + + if (!nodeData.initialized || nodeData.Doms != DomNew) { + nodeData.Doms = std::move(DomNew); + nodeData.initialized = true; + + auto outs = outgoing(nodeData.node); + for (auto* callerPtr : outs) { + auto& data = DomMap[callerPtr]; + if (!data.node) { + data.node = callerPtr; + } + addToQueue(&data); + } + } + } while (!workQueue.empty()); + + return DomMap; +} + +} // namespace metacg::analysis + +#endif diff --git a/graph/include/Callgraph.h b/graph/include/metacg/Callgraph.h similarity index 99% rename from graph/include/Callgraph.h rename to graph/include/metacg/Callgraph.h index bce7d3c0..e954b4cd 100644 --- a/graph/include/Callgraph.h +++ b/graph/include/metacg/Callgraph.h @@ -6,10 +6,9 @@ #ifndef METACG_GRAPH_CALLGRAPH_H #define METACG_GRAPH_CALLGRAPH_H -#include "CgNode.h" -#include "MergePolicy.h" -#include "Util.h" -#include "metadata/MetadataMixin.h" +#include "metacg/CgNode.h" +#include "metacg/MergePolicy.h" +#include "metacg/metadata/MetadataMixin.h" template <> struct std::hash> { diff --git a/graph/include/CgNode.h b/graph/include/metacg/CgNode.h similarity index 95% rename from graph/include/CgNode.h rename to graph/include/metacg/CgNode.h index c4607ae1..d71107d9 100644 --- a/graph/include/CgNode.h +++ b/graph/include/metacg/CgNode.h @@ -9,16 +9,13 @@ // clang-format off // Graph library -#include "CgTypes.h" -#include "metadata/MetaData.h" -#include "metadata/MetadataMixin.h" +#include "metacg/CgTypes.h" +#include "metacg/metadata/MetadataMixin.h" // System library -#include +#include #include #include -#include -#include // clang-format on namespace metacg { @@ -105,10 +102,8 @@ class CgNode : public MetadataMixin { friend std::ostream& operator<<(std::ostream& stream, const CgNode& n); - public: - const NodeId id; - private: + const NodeId id; std::string functionName; std::optional origin; bool hasBody; diff --git a/graph/include/CgTypes.h b/graph/include/metacg/CgTypes.h similarity index 100% rename from graph/include/CgTypes.h rename to graph/include/metacg/CgTypes.h diff --git a/graph/include/DotIO.h b/graph/include/metacg/DotIO.h similarity index 99% rename from graph/include/DotIO.h rename to graph/include/metacg/DotIO.h index b6b87c57..afe98273 100644 --- a/graph/include/DotIO.h +++ b/graph/include/metacg/DotIO.h @@ -7,7 +7,7 @@ #ifndef METACG_DOTIO_H #define METACG_DOTIO_H -#include +#include "metacg/Callgraph.h" #include #include #include diff --git a/graph/include/LoggerUtil.h b/graph/include/metacg/LoggerUtil.h similarity index 96% rename from graph/include/LoggerUtil.h rename to graph/include/metacg/LoggerUtil.h index 11d8dbda..1cbd8b71 100644 --- a/graph/include/LoggerUtil.h +++ b/graph/include/metacg/LoggerUtil.h @@ -10,7 +10,6 @@ #include "spdlog/sinks/stdout_color_sinks.h" #include "spdlog/spdlog.h" #include -#include namespace metacg { /** @@ -151,7 +150,7 @@ class MCGLogger { } else if constexpr (outPutType == Output::ErrConsole) { getErrConsole()->critical(formattedMessage); } - }; + } /** * Formats and prints the message as log-level trace @@ -254,6 +253,16 @@ class MCGLogger { metacg::MCGLogger::instance().warn(msg, std::forward(args)...); } + template + static void logDebug(const MSG_t msg, Args&&... args) { + metacg::MCGLogger::instance().debug(msg, std::forward(args)...); + } + + template + static void logDebugUnique(const MSG_t msg, Args&&... args) { + metacg::MCGLogger::instance().debug(msg, std::forward(args)...); + } + /** * Resets the uniqueness-property for all messages. * Any message that has been previously logged as unique can now appear again diff --git a/graph/include/MCGBaseInfo.h b/graph/include/metacg/MCGBaseInfo.h similarity index 100% rename from graph/include/MCGBaseInfo.h rename to graph/include/metacg/MCGBaseInfo.h diff --git a/graph/include/MCGManager.h b/graph/include/metacg/MCGManager.h similarity index 98% rename from graph/include/MCGManager.h rename to graph/include/metacg/MCGManager.h index 34270818..8e036816 100644 --- a/graph/include/MCGManager.h +++ b/graph/include/metacg/MCGManager.h @@ -6,7 +6,7 @@ #ifndef METACG_GRAPH_MCGMANAGER_H #define METACG_GRAPH_MCGMANAGER_H -#include "Callgraph.h" +#include "metacg/Callgraph.h" namespace metacg { // This is part of the core graph library diff --git a/graph/include/MergePolicy.h b/graph/include/metacg/MergePolicy.h similarity index 98% rename from graph/include/MergePolicy.h rename to graph/include/metacg/MergePolicy.h index 52b968da..016bfae8 100644 --- a/graph/include/MergePolicy.h +++ b/graph/include/metacg/MergePolicy.h @@ -8,7 +8,7 @@ #include -#include "CgTypes.h" +#include "metacg/CgTypes.h" #include diff --git a/graph/include/ReachabilityAnalysis.h b/graph/include/metacg/ReachabilityAnalysis.h similarity index 86% rename from graph/include/ReachabilityAnalysis.h rename to graph/include/metacg/ReachabilityAnalysis.h index aad4dade..bf04730e 100644 --- a/graph/include/ReachabilityAnalysis.h +++ b/graph/include/metacg/ReachabilityAnalysis.h @@ -7,7 +7,7 @@ #ifndef METACG_REACHABILITYANALYSIS_H #define METACG_REACHABILITYANALYSIS_H -#include "Callgraph.h" +#include "metacg/Callgraph.h" #include #include @@ -29,6 +29,9 @@ class ReachabilityAnalysis { /** Compute if path exists between any two nodes in graph */ bool existsPathBetween(const CgNode* const src, const CgNode* const dest, bool forceUpdate = false); + /** Retrieve reachable nodes from any node **/ + const std::unordered_set& getReachableNodesFrom(const CgNode* const node, bool forceUpdate = false); + private: void runForNode(const CgNode* const n); diff --git a/graph/include/Timing.h b/graph/include/metacg/Timing.h similarity index 97% rename from graph/include/Timing.h rename to graph/include/metacg/Timing.h index db26cf18..1f5a899c 100644 --- a/graph/include/Timing.h +++ b/graph/include/metacg/Timing.h @@ -7,7 +7,7 @@ #ifndef METACG_PGIS_TIMING_H #define METACG_PGIS_TIMING_H -#include "LoggerUtil.h" +#include "metacg/LoggerUtil.h" #include #include diff --git a/graph/include/Util.h b/graph/include/metacg/Util.h similarity index 97% rename from graph/include/Util.h rename to graph/include/metacg/Util.h index 2900229d..31b89820 100644 --- a/graph/include/Util.h +++ b/graph/include/metacg/Util.h @@ -7,8 +7,8 @@ #ifndef METACG_UTIL_H #define METACG_UTIL_H -#include "CgNode.h" -#include "LoggerUtil.h" +#include "metacg/CgNode.h" +#include "metacg/LoggerUtil.h" #include #include #include diff --git a/graph/include/io/IdMapping.h b/graph/include/metacg/io/IdMapping.h similarity index 95% rename from graph/include/io/IdMapping.h rename to graph/include/metacg/io/IdMapping.h index feea3429..2e7ec16e 100644 --- a/graph/include/io/IdMapping.h +++ b/graph/include/metacg/io/IdMapping.h @@ -6,12 +6,12 @@ #ifndef METACG_GRAPH_IDMAPPING_H #define METACG_GRAPH_IDMAPPING_H -#include "CgTypes.h" +#include "metacg/CgTypes.h" #include namespace metacg { -struct CgNode; +class CgNode; /** * Maps string identifiers used in the json file to the respective nodes in the internal call graph representation. diff --git a/graph/include/io/MCGReader.h b/graph/include/metacg/io/MCGReader.h similarity index 97% rename from graph/include/io/MCGReader.h rename to graph/include/metacg/io/MCGReader.h index accf1335..ec3601a9 100644 --- a/graph/include/io/MCGReader.h +++ b/graph/include/metacg/io/MCGReader.h @@ -6,17 +6,14 @@ #ifndef METACG_GRAPH_MCGREADER_H #define METACG_GRAPH_MCGREADER_H -#include "IdMapping.h" -#include "LoggerUtil.h" -#include "MCGManager.h" +#include "metacg/Callgraph.h" +#include "metacg/LoggerUtil.h" #include "nlohmann/json.hpp" #include #include #include -#include -#include namespace metacg::io { diff --git a/graph/include/io/MCGWriter.h b/graph/include/metacg/io/MCGWriter.h similarity index 96% rename from graph/include/io/MCGWriter.h rename to graph/include/metacg/io/MCGWriter.h index e175f227..5151fab5 100644 --- a/graph/include/io/MCGWriter.h +++ b/graph/include/metacg/io/MCGWriter.h @@ -7,14 +7,12 @@ #ifndef METACG_MCGWRITER_H #define METACG_MCGWRITER_H -#include "Callgraph.h" -#include "IdMapping.h" -#include "MCGBaseInfo.h" +#include "metacg/Callgraph.h" +#include "metacg/MCGBaseInfo.h" +#include "metacg/io/IdMapping.h" #include "nlohmann/json.hpp" -#include - namespace metacg::io { /** diff --git a/graph/include/metacg/io/NameMapping.h b/graph/include/metacg/io/NameMapping.h new file mode 100644 index 00000000..1f13f3f9 --- /dev/null +++ b/graph/include/metacg/io/NameMapping.h @@ -0,0 +1,24 @@ +/** + * File: NameMapping.h + * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt + */ +#ifndef METACG_GRAPH_NAMEMAPPING_H +#define METACG_GRAPH_NAMEMAPPING_H + +#include "metacg/Callgraph.h" +#include "metacg/io/IdMapping.h" + +namespace metacg { +struct NameMapping : metacg::NodeToStrMapping { + public: + explicit NameMapping(const metacg::Callgraph& graph) : graph(graph) {} + + virtual std::string getStrFromNode(metacg::NodeId id) override { return graph.getNode(id)->getFunctionName(); } + + private: + const metacg::Callgraph& graph; +}; +} // namespace metacg + +#endif diff --git a/graph/include/io/VersionFourMCGReader.h b/graph/include/metacg/io/VersionFourMCGReader.h similarity index 94% rename from graph/include/io/VersionFourMCGReader.h rename to graph/include/metacg/io/VersionFourMCGReader.h index 61c7299f..65a05b99 100644 --- a/graph/include/io/VersionFourMCGReader.h +++ b/graph/include/metacg/io/VersionFourMCGReader.h @@ -7,7 +7,7 @@ #ifndef METACG_VERSIONFOURMCGREADER_H #define METACG_VERSIONFOURMCGREADER_H -#include "MCGReader.h" +#include "metacg/io/MCGReader.h" namespace metacg::io { diff --git a/graph/include/io/VersionFourMCGWriter.h b/graph/include/metacg/io/VersionFourMCGWriter.h similarity index 94% rename from graph/include/io/VersionFourMCGWriter.h rename to graph/include/metacg/io/VersionFourMCGWriter.h index 20e8246b..0e840a24 100644 --- a/graph/include/io/VersionFourMCGWriter.h +++ b/graph/include/metacg/io/VersionFourMCGWriter.h @@ -7,8 +7,8 @@ #ifndef METACG_VERSIONFOURMCGWRITER_H #define METACG_VERSIONFOURMCGWRITER_H -#include "MCGWriter.h" -#include "config.h" +#include "metacg/config.h" +#include "metacg/io/MCGWriter.h" namespace metacg::io { class VersionFourMCGWriter : public MCGWriter { diff --git a/graph/include/io/VersionTwoMCGReader.h b/graph/include/metacg/io/VersionTwoMCGReader.h similarity index 95% rename from graph/include/io/VersionTwoMCGReader.h rename to graph/include/metacg/io/VersionTwoMCGReader.h index 0ecd5324..19699b09 100644 --- a/graph/include/io/VersionTwoMCGReader.h +++ b/graph/include/metacg/io/VersionTwoMCGReader.h @@ -7,7 +7,7 @@ #ifndef METACG_VERSIONTWOMCGREADER_H #define METACG_VERSIONTWOMCGREADER_H -#include "MCGReader.h" +#include "metacg/io/MCGReader.h" /** * Class to read metacg files in file format v2.0. diff --git a/graph/include/io/VersionTwoMCGWriter.h b/graph/include/metacg/io/VersionTwoMCGWriter.h similarity index 87% rename from graph/include/io/VersionTwoMCGWriter.h rename to graph/include/metacg/io/VersionTwoMCGWriter.h index 058394f7..f7ff10f0 100644 --- a/graph/include/io/VersionTwoMCGWriter.h +++ b/graph/include/metacg/io/VersionTwoMCGWriter.h @@ -7,8 +7,8 @@ #ifndef METACG_VERSIONTWOMCGWRITER_H #define METACG_VERSIONTWOMCGWRITER_H -#include "MCGWriter.h" -#include "config.h" +#include "metacg/config.h" +#include "metacg/io/MCGWriter.h" namespace metacg::io { @@ -18,7 +18,7 @@ class VersionTwoMCGWriter : public MCGWriter { metacg::MCGFileInfo fileInfo = metacg::getVersionTwoFileInfo({std::string("CGCollector"), MetaCG_VERSION_MAJOR, MetaCG_VERSION_MINOR, MetaCG_GIT_SHA}), bool exportSorted = false) - : exportSorted(exportSorted), MCGWriter(std::move(fileInfo)) {} + : MCGWriter(std::move(fileInfo)), exportSorted(exportSorted) {} void write(const Callgraph* graph, JsonSink& js) override; diff --git a/graph/include/metacg/metadata/BuiltinMD.h b/graph/include/metacg/metadata/BuiltinMD.h new file mode 100644 index 00000000..9d249089 --- /dev/null +++ b/graph/include/metacg/metadata/BuiltinMD.h @@ -0,0 +1,23 @@ +/** + * File: BuiltinMD.h + * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt + */ +#ifndef METACG_BUILTINMD_H +#define METACG_BUILTINMD_H + +#include "metacg/metadata/CustomMD.h" + +#include "metacg/metadata/CodeStatisticsMD.h" +#include "metacg/metadata/EntryFunctionMD.h" +#include "metacg/metadata/FilePropertiesMD.h" +#include "metacg/metadata/InlineMD.h" +#include "metacg/metadata/LoopMD.h" +#include "metacg/metadata/MallocVariableMD.h" +#include "metacg/metadata/NumConditionalBranchMD.h" +#include "metacg/metadata/NumOperationsMD.h" +#include "metacg/metadata/NumStatementsMD.h" +#include "metacg/metadata/OverrideMD.h" +#include "metacg/metadata/UniqueTypeMD.h" + +#endif // METACG_BUILTINMD_H \ No newline at end of file diff --git a/graph/include/metadata/CodeStatisticsMD.h b/graph/include/metacg/metadata/CodeStatisticsMD.h similarity index 85% rename from graph/include/metadata/CodeStatisticsMD.h rename to graph/include/metacg/metadata/CodeStatisticsMD.h index 75047982..5d487a1a 100644 --- a/graph/include/metadata/CodeStatisticsMD.h +++ b/graph/include/metacg/metadata/CodeStatisticsMD.h @@ -1,12 +1,12 @@ /** -* File: CodeStatisticsMD.h -* License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at -* https://github.com/tudasc/metacg/LICENSE.txt -*/ + * File: CodeStatisticsMD.h + * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt + */ #ifndef CGCOLLECTOR2_CODESTATISTICSMD_H #define CGCOLLECTOR2_CODESTATISTICSMD_H -#include "metadata/MetaData.h" +#include "metacg/metadata/MetaData.h" namespace metacg { @@ -27,7 +27,7 @@ class CodeStatisticsMD : public metacg::MetaData::Registrar { CodeStatisticsMD(const CodeStatisticsMD& other) : numVars(other.numVars) {} public: - nlohmann::json toJson(NodeToStrMapping& nodeToStr) const final { + nlohmann::json toJson(NodeToStrMapping&) const final { nlohmann::json j; j["numVars"] = numVars; return j; diff --git a/graph/include/metadata/CustomMD.h.in b/graph/include/metacg/metadata/CustomMD.h.in similarity index 100% rename from graph/include/metadata/CustomMD.h.in rename to graph/include/metacg/metadata/CustomMD.h.in diff --git a/graph/include/metadata/EntryFunctionMD.h b/graph/include/metacg/metadata/EntryFunctionMD.h similarity index 97% rename from graph/include/metadata/EntryFunctionMD.h rename to graph/include/metacg/metadata/EntryFunctionMD.h index 894f8ca8..65180f02 100644 --- a/graph/include/metadata/EntryFunctionMD.h +++ b/graph/include/metacg/metadata/EntryFunctionMD.h @@ -6,8 +6,8 @@ #ifndef METACG_ENTRYFUNCTIONMD_H #define METACG_ENTRYFUNCTIONMD_H -#include "CgNode.h" -#include "metadata/MetaData.h" +#include "metacg/CgNode.h" +#include "metacg/metadata/MetaData.h" namespace metacg { diff --git a/graph/include/metacg/metadata/FileInfoMD.h b/graph/include/metacg/metadata/FileInfoMD.h new file mode 100644 index 00000000..914c59b4 --- /dev/null +++ b/graph/include/metacg/metadata/FileInfoMD.h @@ -0,0 +1,52 @@ +/** + * File: FileInfoMD.h + * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt + */ + +#ifndef CGCOLLECTOR2_FILEINFOMETADATA_H +#define CGCOLLECTOR2_FILEINFOMETADATA_H + +#include "metacg/metadata/MetaData.h" + +/** + * This is the same metadata that is used inside the MetaCG library + * The same restrictions apply: + * Implement a static key, and the three virtual functions, and register your metadata via the Registrar + */ + +class FileInfoMetadata : public metacg::MetaData::Registrar { + public: + static constexpr const char* key = "FilePropertiesMetaData"; + FileInfoMetadata() : origin("INVALID"), fromSystemInclude(false), lineNumber(0) {} + explicit FileInfoMetadata(const nlohmann::json& j, metacg::StrToNodeMapping& strToNode) { + if (j.is_null()) { + metacg::MCGLogger::instance().getConsole()->trace("Could not retrieve meta data for fileProperties"); + return; + } + origin = j["origin"].get(); + fromSystemInclude = j["systemInclude"].get(); + } + + FileInfoMetadata(const FileInfoMetadata& other) + : origin(other.origin), fromSystemInclude(other.fromSystemInclude), lineNumber(other.lineNumber) {} + + nlohmann::json toJson(metacg::NodeToStrMapping&) const final { + nlohmann::json j; + j["origin"] = origin; + j["systemInclude"] = fromSystemInclude; + return j; + } + + void applyMapping(const metacg::GraphMapping&) final {} + + void merge(const MetaData&, std::optional, const metacg::GraphMapping&) final {} + const char* getKey() const final { return key; } + + std::unique_ptr clone() const final { return std::make_unique(*this); } + + std::string origin; + bool fromSystemInclude; + int lineNumber; +}; +#endif // CGCOLLECTOR2_FILEINFOMETADATA_H diff --git a/graph/include/metadata/FilePropertiesMD.h b/graph/include/metacg/metadata/FilePropertiesMD.h similarity index 94% rename from graph/include/metadata/FilePropertiesMD.h rename to graph/include/metacg/metadata/FilePropertiesMD.h index 93e2ab4d..3baab049 100644 --- a/graph/include/metadata/FilePropertiesMD.h +++ b/graph/include/metacg/metadata/FilePropertiesMD.h @@ -7,7 +7,7 @@ #ifndef METACG_FILEPROPERTIESMD_H #define METACG_FILEPROPERTIESMD_H -#include "metadata/MetaData.h" +#include "metacg/metadata/MetaData.h" namespace metacg { @@ -27,7 +27,7 @@ class FilePropertiesMD : public metacg::MetaData::Registrar { FilePropertiesMD(const FilePropertiesMD& other) : fromSystemInclude(other.fromSystemInclude) {} public: - nlohmann::json toJson(NodeToStrMapping& nodeToStr) const final { + nlohmann::json toJson(NodeToStrMapping&) const final { nlohmann::json j; j["systemInclude"] = fromSystemInclude; return j; diff --git a/graph/include/metadata/InlineMD.h b/graph/include/metacg/metadata/InlineMD.h similarity index 96% rename from graph/include/metadata/InlineMD.h rename to graph/include/metacg/metadata/InlineMD.h index dc1a630d..98e3981a 100644 --- a/graph/include/metadata/InlineMD.h +++ b/graph/include/metacg/metadata/InlineMD.h @@ -6,7 +6,7 @@ #ifndef METACG_INLINEMD_H #define METACG_INLINEMD_H -#include "metadata/MetaData.h" +#include "metacg/metadata/MetaData.h" namespace metacg { @@ -30,7 +30,7 @@ class InlineMD : public metacg::MetaData::Registrar { InlineMD(const InlineMD& other) = default; public: - nlohmann::json toJson(NodeToStrMapping& nodeToStr) const final { + nlohmann::json toJson(NodeToStrMapping&) const final { nlohmann::json j; j["markedInline"] = markedInline; j["likelyInline"] = likelyInline; diff --git a/graph/include/metadata/LoopMD.h b/graph/include/metacg/metadata/LoopMD.h similarity index 90% rename from graph/include/metadata/LoopMD.h rename to graph/include/metacg/metadata/LoopMD.h index cabf7290..a5ea55da 100644 --- a/graph/include/metadata/LoopMD.h +++ b/graph/include/metacg/metadata/LoopMD.h @@ -1,13 +1,13 @@ /** -* File: LoopMD.h -* License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at -* https://github.com/tudasc/metacg/LICENSE.txt + * File: LoopMD.h + * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt */ #ifndef LOOPMD_H #define LOOPMD_H -#include "metadata/MetaData.h" +#include "metacg/metadata/MetaData.h" namespace metacg { @@ -27,7 +27,7 @@ class LoopDepthMD : public metacg::MetaData::Registrar { LoopDepthMD(const LoopDepthMD& other) : loopDepth(other.loopDepth) {} public: - nlohmann::json toJson(NodeToStrMapping& nodeToStr) const final { return loopDepth; } + nlohmann::json toJson(NodeToStrMapping&) const final { return loopDepth; } const char* getKey() const final { return key; } @@ -63,7 +63,7 @@ class GlobalLoopDepthMD : public metacg::MetaData::Registrar GlobalLoopDepthMD(const GlobalLoopDepthMD& other) : globalLoopDepth(other.globalLoopDepth) {} public: - nlohmann::json toJson(NodeToStrMapping& nodeToStr) const final { return globalLoopDepth; } + nlohmann::json toJson(NodeToStrMapping&) const final { return globalLoopDepth; } const char* getKey() const final { return key; } @@ -106,7 +106,7 @@ class LoopCallDepthMD : public metacg::MetaData::Registrar { LoopCallDepthMD(const LoopCallDepthMD& other) : loopFunctionMap(other.loopFunctionMap) {} public: - nlohmann::json toJson(NodeToStrMapping& nodeToStr) const final { return loopFunctionMap; } + nlohmann::json toJson(NodeToStrMapping&) const final { return loopFunctionMap; } const char* getKey() const final { return key; } @@ -140,4 +140,4 @@ class LoopCallDepthMD : public metacg::MetaData::Registrar { } // namespace metacg -#endif //LOOPMD_H \ No newline at end of file +#endif // LOOPMD_H \ No newline at end of file diff --git a/graph/include/metadata/MallocVariableMD.h b/graph/include/metacg/metadata/MallocVariableMD.h similarity index 81% rename from graph/include/metadata/MallocVariableMD.h rename to graph/include/metacg/metadata/MallocVariableMD.h index 62d785e9..be43b7c7 100644 --- a/graph/include/metadata/MallocVariableMD.h +++ b/graph/include/metacg/metadata/MallocVariableMD.h @@ -1,12 +1,12 @@ /** -* File: MallocVariableMD.h -* License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at -* https://github.com/tudasc/metacg/LICENSE.txt + * File: MallocVariableMD.h + * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt */ #ifndef CGCOLLECTOR2_MALLOCVARIABLEMD_H #define CGCOLLECTOR2_MALLOCVARIABLEMD_H -#include "metadata/MetaData.h" +#include "metacg/metadata/MetaData.h" namespace metacg { @@ -33,7 +33,7 @@ class MallocVariableMD : public metacg::MetaData::Registrar { MallocVariableMD(const MallocVariableMD& other) : allocs(other.allocs) {} public: - nlohmann::json toJson(NodeToStrMapping& nodeToStr) const final { + nlohmann::json toJson(NodeToStrMapping&) const final { std::vector jArray; jArray.reserve(allocs.size()); for (const auto& [k, v] : allocs) { @@ -47,9 +47,6 @@ class MallocVariableMD : public metacg::MetaData::Registrar { void merge(const MetaData& toMerge, std::optional, const GraphMapping&) final { assert(toMerge.getKey() == getKey() && "Trying to merge MallocVariableMD with meta data of different types"); - - const MallocVariableMD* toMergeDerived = static_cast(&toMerge); - // TODO: Merge not implemented as of now } diff --git a/graph/include/metadata/MetaData.h b/graph/include/metacg/metadata/MetaData.h similarity index 88% rename from graph/include/metadata/MetaData.h rename to graph/include/metacg/metadata/MetaData.h index 60444db4..de499d17 100644 --- a/graph/include/metadata/MetaData.h +++ b/graph/include/metacg/metadata/MetaData.h @@ -6,9 +6,9 @@ #ifndef METACG_GRAPH_METADATA_H #define METACG_GRAPH_METADATA_H -#include "LoggerUtil.h" -#include "MergePolicy.h" -#include "io/IdMapping.h" +#include "metacg/LoggerUtil.h" +#include "metacg/MergePolicy.h" +#include "metacg/io/IdMapping.h" #include "nlohmann/json.hpp" // Instance counter to protect meta-data registry against ABI incompatibilities @@ -30,7 +30,7 @@ class MCGManager; * A class *must* implement a static constexpr const char *key that contains the class * name as a string. This is used for registration in the MetaData field of the CgNode. */ -template +template class MetaDataFactory { public: template @@ -64,6 +64,22 @@ class MetaDataFactory { friend CRTPBase; + /** + * Checks whether a metadatas key is registered + * + * @param metadataKey the key of the metadat to search for + * @return true if the key is known + */ + static bool isRegistered(const std::string& metadataKey) { + for (const auto& [registeredMetadataName, _] : data()) { + if (registeredMetadataName == metadataKey) + return true; + } + return false; + } + + + private: class Key { Key() = default; diff --git a/graph/include/metadata/MetadataMixin.h b/graph/include/metacg/metadata/MetadataMixin.h similarity index 99% rename from graph/include/metadata/MetadataMixin.h rename to graph/include/metacg/metadata/MetadataMixin.h index 8666c86c..de0cab3d 100644 --- a/graph/include/metadata/MetadataMixin.h +++ b/graph/include/metacg/metadata/MetadataMixin.h @@ -6,7 +6,7 @@ #ifndef METACG_METADATAMIXIN_H #define METACG_METADATAMIXIN_H -#include "metadata/MetaData.h" +#include "metacg/metadata/MetaData.h" #include #include diff --git a/graph/include/metadata/NumConditionalBranchMD.h b/graph/include/metacg/metadata/NumConditionalBranchMD.h similarity index 85% rename from graph/include/metadata/NumConditionalBranchMD.h rename to graph/include/metacg/metadata/NumConditionalBranchMD.h index e816bf15..46a1999d 100644 --- a/graph/include/metadata/NumConditionalBranchMD.h +++ b/graph/include/metacg/metadata/NumConditionalBranchMD.h @@ -1,12 +1,12 @@ /** -* File: NumConditionalBranchMD.h -* License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at -* https://github.com/tudasc/metacg/LICENSE.txt + * File: NumConditionalBranchMD.h + * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt */ #ifndef CGCOLLECTOR2_NUMCONDITIONALBRANCHMD_H #define CGCOLLECTOR2_NUMCONDITIONALBRANCHMD_H -#include "metadata/MetaData.h" +#include "metacg/metadata/MetaData.h" namespace metacg { @@ -27,7 +27,7 @@ class NumConditionalBranchMD : public metacg::MetaData::Registrar { numberOfMemoryAccesses(other.numberOfMemoryAccesses) {} public: - nlohmann::json toJson(NodeToStrMapping& nodeToStr) const final { + nlohmann::json toJson(NodeToStrMapping&) const final { nlohmann::json j; j["numberOfIntOps"] = numberOfIntOps; j["numberOfFloatOps"] = numberOfFloatOps; diff --git a/graph/include/metadata/NumStatementsMD.h b/graph/include/metacg/metadata/NumStatementsMD.h similarity index 94% rename from graph/include/metadata/NumStatementsMD.h rename to graph/include/metacg/metadata/NumStatementsMD.h index 55c4b475..7cdb6536 100644 --- a/graph/include/metadata/NumStatementsMD.h +++ b/graph/include/metacg/metadata/NumStatementsMD.h @@ -6,7 +6,7 @@ #ifndef CGCOLLECTOR2_NUMSTATEMENTSMD_H #define CGCOLLECTOR2_NUMSTATEMENTSMD_H -#include "metadata/MetaData.h" +#include "metacg/metadata/MetaData.h" namespace metacg { @@ -29,7 +29,7 @@ class NumStatementsMD : public metacg::MetaData::Registrar { NumStatementsMD(const NumStatementsMD& other) : numStmts(other.numStmts) {} public: - nlohmann::json toJson(NodeToStrMapping& nodeToStr) const final { return getNumberOfStatements(); } + nlohmann::json toJson(NodeToStrMapping&) const final { return getNumberOfStatements(); } const char* getKey() const override { return key; } diff --git a/graph/include/metadata/OverrideMD.h b/graph/include/metacg/metadata/OverrideMD.h similarity index 97% rename from graph/include/metadata/OverrideMD.h rename to graph/include/metacg/metadata/OverrideMD.h index 56d004b8..6e2eadbe 100644 --- a/graph/include/metadata/OverrideMD.h +++ b/graph/include/metacg/metadata/OverrideMD.h @@ -7,9 +7,9 @@ #ifndef METACG_OVERRIDEMD_H #define METACG_OVERRIDEMD_H -#include "CgNode.h" -#include "LoggerUtil.h" -#include "metadata/MetaData.h" +#include "metacg/CgNode.h" +#include "metacg/LoggerUtil.h" +#include "metacg/metadata/MetaData.h" #include namespace metacg { diff --git a/graph/include/metadata/UniqueTypeMD.h b/graph/include/metacg/metadata/UniqueTypeMD.h similarity index 84% rename from graph/include/metadata/UniqueTypeMD.h rename to graph/include/metacg/metadata/UniqueTypeMD.h index 4039b932..15083945 100644 --- a/graph/include/metadata/UniqueTypeMD.h +++ b/graph/include/metacg/metadata/UniqueTypeMD.h @@ -1,12 +1,12 @@ /** -* File: UniqueTypeMD.h -* License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at -* https://github.com/tudasc/metacg/LICENSE.txt + * File: UniqueTypeMD.h + * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt */ #ifndef CGCOLLECTOR2_UNIQUETYPEMD_H #define CGCOLLECTOR2_UNIQUETYPEMD_H -#include "metadata/MetaData.h" +#include "metacg/metadata/MetaData.h" namespace metacg { @@ -28,7 +28,7 @@ class UniqueTypeMD : public metacg::MetaData::Registrar { UniqueTypeMD(const UniqueTypeMD& other) : numTypes(other.numTypes) {} public: - nlohmann::json toJson(NodeToStrMapping& nodeToStr) const final { return numTypes; } + nlohmann::json toJson(NodeToStrMapping&) const final { return numTypes; } const char* getKey() const override { return key; } diff --git a/graph/include/metadata/custom/.gitignore b/graph/include/metacg/metadata/custom/.gitignore similarity index 100% rename from graph/include/metadata/custom/.gitignore rename to graph/include/metacg/metadata/custom/.gitignore diff --git a/graph/include/metadata/BuiltinMD.h b/graph/include/metadata/BuiltinMD.h deleted file mode 100644 index 1b3f1b70..00000000 --- a/graph/include/metadata/BuiltinMD.h +++ /dev/null @@ -1,23 +0,0 @@ -/** -* File: BuiltinMD.h -* License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at -* https://github.com/tudasc/metacg/LICENSE.txt -*/ -#ifndef METACG_BUILTINMD_H -#define METACG_BUILTINMD_H - -#include "metadata/CustomMD.h" - -#include "metadata/CodeStatisticsMD.h" -#include "metadata/EntryFunctionMD.h" -#include "metadata/FilePropertiesMD.h" -#include "metadata/InlineMD.h" -#include "metadata/LoopMD.h" -#include "metadata/MallocVariableMD.h" -#include "metadata/NumConditionalBranchMD.h" -#include "metadata/NumOperationsMD.h" -#include "metadata/NumStatementsMD.h" -#include "metadata/OverrideMD.h" -#include "metadata/UniqueTypeMD.h" - -#endif // METACG_BUILTINMD_H \ No newline at end of file diff --git a/graph/src/Callgraph.cpp b/graph/src/Callgraph.cpp index dd1498cc..37ee7a25 100644 --- a/graph/src/Callgraph.cpp +++ b/graph/src/Callgraph.cpp @@ -3,12 +3,12 @@ * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at * https://github.com/tudasc/metacg/LICENSE.txt */ -#include "Callgraph.h" +#include "metacg/Callgraph.h" -#include "LoggerUtil.h" -#include "metadata/EntryFunctionMD.h" -#include "metadata/OverrideMD.h" +#include "metacg/LoggerUtil.h" +#include "metacg/metadata/EntryFunctionMD.h" +#include #include int metacg_RegistryInstanceCounter{0}; @@ -32,7 +32,7 @@ CgNode* Callgraph::getMain(bool forceRecompute) const { // Otherwise, try to find by name. if ((mainNode = getFirstNode("main")) || (mainNode = getFirstNode("_Z4main")) || - (mainNode = getFirstNode("_ZSt4mainiPPc"))) { + (mainNode = getFirstNode("_ZSt4mainiPPc")) || (mainNode = getFirstNode("_QQmain"))) { return mainNode; } @@ -41,6 +41,7 @@ CgNode* Callgraph::getMain(bool forceRecompute) const { CgNode& Callgraph::insert(const std::string& function, std::optional origin, bool isVirtual, bool hasBody) { + assert(!function.empty() && "Function name must not be empty"); NodeId id = nodes.size(); // Note: Can't use make_unique here because make_unqiue is not (and should not be) a friend of the CgNode constructor. nodes.emplace_back(new CgNode(id, function, std::move(origin), isVirtual, hasBody)); @@ -59,10 +60,14 @@ bool Callgraph::erase(NodeId id) { // Remove edges for (auto& calleeId : calleeList[id]) { edges.erase({id, calleeId}); + auto& childCallerList = callerList.at(calleeId); + childCallerList.erase(std::find(childCallerList.begin(), childCallerList.end(), id)); } calleeList.erase(id); for (auto& callerId : callerList[id]) { edges.erase({callerId, id}); + auto& parentCalleeList = calleeList.at(callerId); + parentCalleeList.erase(std::find(parentCalleeList.begin(), parentCalleeList.end(), id)); } callerList.erase(id); // Destroy the node @@ -148,8 +153,7 @@ bool Callgraph::addEdge(const std::string& callerName, const std::string& callee if (callerMatches.size() != 1 || calleeMatches.size() != 1) { return false; } - addEdge(callerMatches.front(), calleeMatches.front()); - return true; + return addEdge(callerMatches.front(), calleeMatches.front()); } bool Callgraph::removeEdge(NodeId parentID, NodeId childID) { @@ -200,7 +204,14 @@ bool Callgraph::hasNode(const CgNode& node) const { return nodeAtId && nodeAtId.get() == &node; } -unsigned Callgraph::countNodes(const std::string& name) const { return nameIdMap.count(name); } +unsigned Callgraph::countNodes(const std::string& name) const { + auto it = nameIdMap.find(name); + if (it == nameIdMap.end()) { + return 0; + } + + return it->second.size(); +} CgNode* Callgraph::getFirstNode(const std::string& name) const { if (auto it = nameIdMap.find(name); it != nameIdMap.end()) { diff --git a/graph/src/CgNode.cpp b/graph/src/CgNode.cpp index 5fa5340e..261129df 100644 --- a/graph/src/CgNode.cpp +++ b/graph/src/CgNode.cpp @@ -4,8 +4,8 @@ * https://github.com/tudasc/metacg/LICENSE.txt */ -#include "CgNode.h" -#include "metadata/OverrideMD.h" +#include "metacg/CgNode.h" +#include "metacg/metadata/OverrideMD.h" #include using namespace metacg; diff --git a/graph/src/DotIO.cpp b/graph/src/DotIO.cpp index 915d0211..ab23f0e8 100644 --- a/graph/src/DotIO.cpp +++ b/graph/src/DotIO.cpp @@ -4,14 +4,14 @@ * https://github.com/tudasc/metacg/LICENSE.txt */ -#include "DotIO.h" -#include "LoggerUtil.h" -#include "MCGManager.h" -#include "Util.h" +#include "metacg/DotIO.h" +#include "metacg/LoggerUtil.h" +#include "metacg/MCGManager.h" #include // for std:isspace #include #include +#include #include namespace metacg::io::dot { diff --git a/graph/src/MCGBaseInfo.cpp b/graph/src/MCGBaseInfo.cpp index 7c67dcbe..79c352a4 100644 --- a/graph/src/MCGBaseInfo.cpp +++ b/graph/src/MCGBaseInfo.cpp @@ -4,8 +4,8 @@ * https://github.com/tudasc/metacg/LICENSE.txt */ -#include "MCGBaseInfo.h" -#include "config.h" +#include "metacg/MCGBaseInfo.h" +#include "metacg/config.h" namespace metacg { // This must be in its own file as to not expose config.h header to linking programs diff --git a/graph/src/MCGManager.cpp b/graph/src/MCGManager.cpp index 5c7e447b..8ec7a362 100644 --- a/graph/src/MCGManager.cpp +++ b/graph/src/MCGManager.cpp @@ -4,8 +4,7 @@ * https://github.com/tudasc/metacg/LICENSE.txt */ -#include "MCGManager.h" -#include "LoggerUtil.h" +#include "metacg/MCGManager.h" using namespace metacg::graph; diff --git a/graph/src/MergePolicy.cpp b/graph/src/MergePolicy.cpp index bf42158b..7b831550 100644 --- a/graph/src/MergePolicy.cpp +++ b/graph/src/MergePolicy.cpp @@ -3,10 +3,10 @@ * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at * https://github.com/tudasc/metacg/LICENSE.txt */ -#include "MergePolicy.h" +#include "metacg/MergePolicy.h" -#include "Callgraph.h" -#include "LoggerUtil.h" +#include "metacg/Callgraph.h" +#include "metacg/LoggerUtil.h" namespace metacg { diff --git a/graph/src/ReachabilityAnalysis.cpp b/graph/src/ReachabilityAnalysis.cpp index 688f529b..bb8dc9b6 100644 --- a/graph/src/ReachabilityAnalysis.cpp +++ b/graph/src/ReachabilityAnalysis.cpp @@ -4,7 +4,7 @@ * https://github.com/tudasc/metacg/LICENSE.txt */ -#include "ReachabilityAnalysis.h" +#include "metacg/ReachabilityAnalysis.h" #include @@ -65,4 +65,15 @@ bool ReachabilityAnalysis::existsPathBetween(const CgNode* const src, const CgNo return reachableSet.find(dest) != reachableSet.end(); } -} // namespace metacg::analysis \ No newline at end of file + +const std::unordered_set& ReachabilityAnalysis::getReachableNodesFrom(const CgNode* const node, bool forceUpdate) { + if (forceUpdate || computedFor.count(node) == 0) { + runForNode(node); + } + + auto it = reachableNodes.find(node); + assert(it != reachableNodes.end()); + return it->second; +} + +} // namespace metacg::analysis diff --git a/graph/src/io/MCGReader.cpp b/graph/src/io/MCGReader.cpp index 95c204dd..ceea2735 100644 --- a/graph/src/io/MCGReader.cpp +++ b/graph/src/io/MCGReader.cpp @@ -5,10 +5,10 @@ * */ -#include "io/MCGReader.h" -#include "LoggerUtil.h" -#include "io/VersionFourMCGReader.h" -#include "io/VersionTwoMCGReader.h" +#include "metacg/io/MCGReader.h" +#include "metacg/LoggerUtil.h" +#include "metacg/io/VersionFourMCGReader.h" +#include "metacg/io/VersionTwoMCGReader.h" namespace metacg::io { diff --git a/graph/src/io/MCGWriter.cpp b/graph/src/io/MCGWriter.cpp index 54a6485b..fcdb886d 100644 --- a/graph/src/io/MCGWriter.cpp +++ b/graph/src/io/MCGWriter.cpp @@ -4,11 +4,11 @@ * https://github.com/tudasc/metacg/LICENSE.txt */ -#include "io/MCGWriter.h" -#include "LoggerUtil.h" -#include "MCGManager.h" -#include "io/VersionFourMCGWriter.h" -#include "io/VersionTwoMCGWriter.h" +#include "metacg/io/MCGWriter.h" +#include "metacg/LoggerUtil.h" +#include "metacg/MCGManager.h" +#include "metacg/io/VersionFourMCGWriter.h" +#include "metacg/io/VersionTwoMCGWriter.h" std::string metacg::NodeToStrMapping::getStrFromNode(const CgNode& node) { return getStrFromNode(node.getId()); } diff --git a/graph/src/io/VersionFourMCGReader.cpp b/graph/src/io/VersionFourMCGReader.cpp index 5fc4104c..ecbd48c7 100644 --- a/graph/src/io/VersionFourMCGReader.cpp +++ b/graph/src/io/VersionFourMCGReader.cpp @@ -4,12 +4,11 @@ * https://github.com/tudasc/metacg/LICENSE.txt */ -#include "io/VersionFourMCGReader.h" -#include "MCGBaseInfo.h" -#include "Timing.h" -#include "Util.h" -#include "metadata/BuiltinMD.h" -#include +#include "metacg/io/VersionFourMCGReader.h" +#include "metacg/MCGBaseInfo.h" +#include "metacg/Timing.h" +#include "metacg/Util.h" +#include "metacg/metadata/BuiltinMD.h" using namespace metacg; @@ -103,7 +102,6 @@ std::unique_ptr metacg::io::VersionFourMCGReader::read() { const MCGFileFormatInfo ffInfo{4, 0}; auto console = metacg::MCGLogger::instance().getConsole(); auto errConsole = metacg::MCGLogger::instance().getErrConsole(); - auto j = source.get(); auto mcgInfo = j[ffInfo.metaInfoFieldName]; if (mcgInfo.is_null()) { @@ -179,8 +177,14 @@ std::unique_ptr metacg::io::VersionFourMCGReader::read() { auto& mdValJ = mdElem.value(); if (auto md = metacg::MetaData::create<>(mdKey, mdValJ, strToNode); md) { cg->addEdgeMetaData({nodeData.nodeId, calleeNode->getId()}, std::move(md)); - } else if (failedMetadataCb) { - (*failedMetadataCb)(nodeData.nodeId, mdKey, mdValJ); + } else { + if (MetaDataFactory<>::isRegistered(mdKey)) { + errConsole->warn("Could not create edge metadata of type {} for edge {} to {}", mdKey, nodeData.nodeId, + calleeNode->getId()); + } + if (failedMetadataCb) { + (*failedMetadataCb)(nodeData.nodeId, mdKey, mdValJ); + } } } } @@ -192,7 +196,9 @@ std::unique_ptr metacg::io::VersionFourMCGReader::read() { if (auto md = metacg::MetaData::create<>(mdKey, mdVal, strToNode); md) { node->addMetaData(std::move(md)); } else { - errConsole->warn("Could not create metadata of type {} for node {}", mdKey, node->getFunctionName()); + if (MetaDataFactory<>::isRegistered(mdKey)) { + errConsole->warn("Could not create metadata of type {} for node {}", mdKey, node->getFunctionName()); + } if (failedMetadataCb) { (*failedMetadataCb)(node->getId(), mdKey, mdVal); } @@ -208,7 +214,9 @@ std::unique_ptr metacg::io::VersionFourMCGReader::read() { if (auto md = metacg::MetaData::create<>(mdKey, mdValJ, strToNode); md) { cg->addMetaData(std::move(md)); } else { - errConsole->warn("Could not create global metadata of type {}", mdKey); + if (MetaDataFactory<>::isRegistered(mdKey)) { + errConsole->warn("Could not create global metadata of type {}", mdKey); + } if (failedMetadataCb) { (*failedMetadataCb)(std::nullopt, mdKey, mdValJ); } diff --git a/graph/src/io/VersionFourMCGWriter.cpp b/graph/src/io/VersionFourMCGWriter.cpp index a121989c..1e30de33 100644 --- a/graph/src/io/VersionFourMCGWriter.cpp +++ b/graph/src/io/VersionFourMCGWriter.cpp @@ -4,10 +4,10 @@ * https://github.com/tudasc/metacg/LICENSE.txt */ -#include "io/VersionFourMCGWriter.h" -#include "MCGManager.h" -#include "config.h" -#include "metadata/BuiltinMD.h" +#include "metacg/io/VersionFourMCGWriter.h" +#include "metacg/MCGManager.h" +#include "metacg/config.h" +#include "metacg/metadata/BuiltinMD.h" #include using namespace metacg; diff --git a/graph/src/io/VersionTwoMCGReader.cpp b/graph/src/io/VersionTwoMCGReader.cpp index bef5523a..3e75b570 100644 --- a/graph/src/io/VersionTwoMCGReader.cpp +++ b/graph/src/io/VersionTwoMCGReader.cpp @@ -4,11 +4,11 @@ * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at * https://github.com/tudasc/metacg/LICENSE.txt */ -#include "io/VersionTwoMCGReader.h" -#include "MCGBaseInfo.h" -#include "Timing.h" -#include "Util.h" -#include "io/VersionFourMCGReader.h" +#include "metacg/io/VersionTwoMCGReader.h" +#include "metacg/MCGBaseInfo.h" +#include "metacg/Timing.h" +#include "metacg/Util.h" +#include "metacg/io/VersionFourMCGReader.h" std::unique_ptr metacg::io::VersionTwoMCGReader::read() { const metacg::RuntimeTimer rtt("VersionTwoMCGReader::read"); @@ -50,7 +50,13 @@ std::unique_ptr metacg::io::VersionTwoMCGReader::read() { generatorVersion); MCGFileInfo fileInfo{ffInfo, genVersionInfo}; - if (!j.contains(ffInfo.cgFieldName) || j.at(ffInfo.cgFieldName).is_null()) { + if (j.at(ffInfo.cgFieldName).is_null()) { + j[ffInfo.cgFieldName] = nlohmann::json::object(); + const std::string warningMsg = "Detected null call graph in metacg file; proceeding with an empty call graph."; + MCGLogger::logWarn(warningMsg); + } + + if (!j.contains(ffInfo.cgFieldName)) { const std::string errorMsg = "The call graph in the metacg file was not found or null."; errConsole->error(errorMsg); throw std::runtime_error(errorMsg); @@ -97,8 +103,12 @@ void metacg::io::VersionTwoMCGReader::upgradeV2FormatToV4Format(nlohmann::json& jNode["origin"] = nullptr; } + bool definesOverrideData = !jNode.at("overrides").empty() || !jNode.at("overriddenBy").empty(); + if (!jNode.at("isVirtual").get() && definesOverrideData) { + MCGLogger::logWarn("Node {} is marked as non-virtual but defines overrides/overriddenBy entries", it.key()); + } // Create OverrideMD if the function is virtual - if (jNode.at("isVirtual")) { + if (jNode.at("isVirtual").get() || definesOverrideData) { auto jOverrides = json::array(); for (const auto& overrideNode : jNode.at("overrides")) { jOverrides.push_back(overrideNode); diff --git a/graph/src/io/VersionTwoMCGWriter.cpp b/graph/src/io/VersionTwoMCGWriter.cpp index af3af910..b329e040 100644 --- a/graph/src/io/VersionTwoMCGWriter.cpp +++ b/graph/src/io/VersionTwoMCGWriter.cpp @@ -4,10 +4,10 @@ * https://github.com/tudasc/metacg/LICENSE.txt */ -#include "io/VersionTwoMCGWriter.h" -#include "MCGManager.h" -#include "io/VersionFourMCGWriter.h" -#include "metadata/OverrideMD.h" +#include "metacg/io/VersionTwoMCGWriter.h" +#include "metacg/MCGManager.h" +#include "metacg/io/VersionFourMCGWriter.h" +#include "metacg/metadata/OverrideMD.h" #include using namespace metacg; diff --git a/graph/test/install/main.cpp b/graph/test/install/main.cpp index 14430cd4..1e96b719 100644 --- a/graph/test/install/main.cpp +++ b/graph/test/install/main.cpp @@ -4,10 +4,10 @@ * https://github.com/tudasc/metacg/LICENSE.txt */ -#include "MCGManager.h" -#include "io/VersionTwoMCGReader.h" -#include "io/VersionTwoMCGWriter.h" -#include "LoggerUtil.h" +#include "metacg/LoggerUtil.h" +#include "metacg/MCGManager.h" +#include "metacg/io/VersionTwoMCGReader.h" +#include "metacg/io/VersionTwoMCGWriter.h" #include #include diff --git a/graph/test/integration/CallgraphMerge/CMakeLists.txt b/graph/test/integration/CallgraphMerge/CMakeLists.txt index 753bec18..87a7c636 100644 --- a/graph/test/integration/CallgraphMerge/CMakeLists.txt +++ b/graph/test/integration/CallgraphMerge/CMakeLists.txt @@ -10,4 +10,8 @@ target_link_libraries(mergetester PUBLIC metacg::metacg) add_config_include(mergetester) -add_test(NAME mergeTests COMMAND mergetester) +add_test( + NAME mergeTests + COMMAND MergeTestRunner.sh + WORKING_DIRECTORY $ +) diff --git a/graph/test/integration/CallgraphMerge/MergeCountMD.h b/graph/test/integration/CallgraphMerge/MergeCountMD.h index 29e99e81..fbd44bd1 100644 --- a/graph/test/integration/CallgraphMerge/MergeCountMD.h +++ b/graph/test/integration/CallgraphMerge/MergeCountMD.h @@ -6,7 +6,7 @@ #ifndef METACG_MERGECOUNTMD_H #define METACG_MERGECOUNTMD_H -#include "metadata/MetaData.h" +#include "metacg/metadata/MetaData.h" class MergeCountMD : public metacg::MetaData::Registrar { public: diff --git a/graph/test/integration/CallgraphMerge/MergeTester.cpp b/graph/test/integration/CallgraphMerge/MergeTester.cpp index 4c498719..18d22303 100644 --- a/graph/test/integration/CallgraphMerge/MergeTester.cpp +++ b/graph/test/integration/CallgraphMerge/MergeTester.cpp @@ -1,9 +1,9 @@ -#include "MCGManager.h" -#include "io/VersionTwoMCGReader.h" -#include "io/VersionTwoMCGWriter.h" +#include "metacg/MCGManager.h" +#include "metacg/io/VersionTwoMCGReader.h" +#include "metacg/io/VersionTwoMCGWriter.h" -#include "metadata/BuiltinMD.h" +#include "metacg/metadata/BuiltinMD.h" #include #include diff --git a/graph/test/integration/Performance/PerfTester.cpp b/graph/test/integration/Performance/PerfTester.cpp index 940c4f4d..b0049da6 100644 --- a/graph/test/integration/Performance/PerfTester.cpp +++ b/graph/test/integration/Performance/PerfTester.cpp @@ -4,12 +4,13 @@ * https://github.com/tudasc/metacg/LICENSE.txt */ -#include "Callgraph.h" -#include "io/MCGReader.h" -#include "io/MCGWriter.h" +#include "metacg/Callgraph.h" +#include "metacg/io/MCGReader.h" +#include "metacg/io/MCGWriter.h" #include #include +#include #include using namespace metacg; @@ -176,4 +177,4 @@ int main(int argc, const char** argv) { return 0; -} \ No newline at end of file +} diff --git a/graph/test/integration/TargetCollector/TestRunner.sh b/graph/test/integration/TargetCollector/TestRunner.sh index 89aee4cb..9150b825 100755 --- a/graph/test/integration/TargetCollector/TestRunner.sh +++ b/graph/test/integration/TargetCollector/TestRunner.sh @@ -27,7 +27,7 @@ done echo "Running integration test for TargetCollector script" echo "{}" > src/wholeProgramCG-${CI_CONCURRENT_ID}.ipcg -test_command=(python3 ../../../../TargetCollector.py \ +test_command=(python3 ../../../../utils/TargetCollector.py \ -a=".." \ -g=both \ -b=build-${CI_CONCURRENT_ID} \ diff --git a/graph/test/unit/CGNodeTests.cpp b/graph/test/unit/CGNodeTests.cpp index 4721fe42..c2ff5b56 100644 --- a/graph/test/unit/CGNodeTests.cpp +++ b/graph/test/unit/CGNodeTests.cpp @@ -4,9 +4,9 @@ * https://github.com/tudasc/metacg/LICENSE.txt */ -#include "Callgraph.h" -#include "CgNode.h" -#include "metadata/OverrideMD.h" +#include "metacg/Callgraph.h" +#include "metacg/CgNode.h" +#include "metacg/metadata/OverrideMD.h" #include "gtest/gtest.h" TEST(CgNode, CreateNodeOnlyName) { diff --git a/graph/test/unit/CMakeLists.txt b/graph/test/unit/CMakeLists.txt index cc18a02b..118c9ab5 100644 --- a/graph/test/unit/CMakeLists.txt +++ b/graph/test/unit/CMakeLists.txt @@ -1,7 +1,9 @@ # Now simply link against gtest or gtest_main as needed.Eg add_executable( libtests + CallgraphTest.cpp CGNodeTests.cpp + DominatorAnalysisTest.cpp DotIOTest.cpp GlobalMDTest.cpp LoggingTest.cpp @@ -23,4 +25,4 @@ add_metacg(libtests) add_config_include(libtests) -add_test(NAME libtests COMMAND $) +add_test(NAME graphlib_unittests COMMAND $) diff --git a/graph/test/unit/CallgraphTest.cpp b/graph/test/unit/CallgraphTest.cpp new file mode 100644 index 00000000..38428e5d --- /dev/null +++ b/graph/test/unit/CallgraphTest.cpp @@ -0,0 +1,216 @@ +/** + * File: CallgraphTest.cpp + * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt + */ + +#include "metacg/Callgraph.h" +#include "metacg/CgNode.h" +#include "metacg/metadata/OverrideMD.h" +#include "gtest/gtest.h" + +namespace { +TEST(Callgraph, EmpytCG) { + metacg::Callgraph cg; + + EXPECT_EQ(cg.getMain(), nullptr); + EXPECT_EQ(cg.hasNode("foo"), false); + EXPECT_EQ(cg.hasDuplicateNames(), false); + EXPECT_EQ(cg.getFirstNode("foo"), nullptr); + EXPECT_EQ(cg.size(), 0); + EXPECT_EQ(cg.getNodeCount(), 0); + + // Query some random "node ID" + { + auto callees = cg.getCallees(0); + EXPECT_EQ(callees.size(), 0); + } + { + auto callers = cg.getCallers(0); + EXPECT_EQ(callers.size(), 0); + } +} + +TEST(Callgraph, getOrInsertOneNodeNotMain) { + metacg::Callgraph cg; + + EXPECT_EQ(cg.size(), 0); + EXPECT_EQ(cg.getNodeCount(), 0); + + std::string n1Name("n1"); + cg.getOrInsertNode(n1Name); + + EXPECT_EQ(cg.size(), 1); + EXPECT_EQ(cg.getNodeCount(), 1); + + // We did not insert a main node + EXPECT_EQ(cg.getMain(), nullptr); + + auto n1Node = cg.getFirstNode(n1Name); + EXPECT_EQ(n1Node->getFunctionName(), n1Name); +} + +TEST(Callgraph, getOrInsertMultiIdentNoOrigin) { + metacg::Callgraph cg; + + std::string n1Name("foo"); + cg.getOrInsertNode(n1Name); + // This does not insert a new node and does not change the existing one + cg.getOrInsertNode(n1Name, {}, true); + cg.getOrInsertNode(n1Name, {}, true, true); + EXPECT_EQ(cg.getNodeCount(), 1); + + auto n1Node = cg.getFirstNode(n1Name); + EXPECT_EQ(n1Node->has(), false); +} + +TEST(Callgraph, getOrInsertMultiIdentDiffOrigin) { + metacg::Callgraph cg; + + std::string n1Name("foo"); + std::string n1OrigA("Orig::A"); + std::string n1OrigB("Orig::B"); + cg.getOrInsertNode(n1Name, n1OrigA); + // getOrInsertNode returns the first node if already exists with that name + cg.getOrInsertNode(n1Name, n1OrigB, true); + cg.getOrInsertNode(n1Name, n1OrigB, true, true); + EXPECT_EQ(cg.getNodeCount(), 1); + + // getOrInsertNode returned an existing node + EXPECT_FALSE(cg.hasDuplicateNames()); + + auto n1Node = cg.getFirstNode(n1Name); + EXPECT_EQ(n1Node->has(), false); + EXPECT_EQ(cg.getMain(), nullptr); +} + +TEST(Callgraph, insertGetOrInsertMultiIdentNoOrigin) { + metacg::Callgraph cg; + + std::string n1Name("foo"); + cg.insert(n1Name); + // This does not insert a new node and does not change the existing one + cg.getOrInsertNode(n1Name, {}, true); + cg.getOrInsertNode(n1Name, {}, true, true); + EXPECT_EQ(cg.getNodeCount(), 1); + + // getOrInsertNode returned an existing node + EXPECT_FALSE(cg.hasDuplicateNames()); + + auto n1Node = cg.getFirstNode(n1Name); + EXPECT_EQ(n1Node->has(), false); + EXPECT_EQ(cg.getMain(), nullptr); +} + +TEST(Callgraph, insertGetOrInsertMultiIdentDiffOrigin) { + metacg::Callgraph cg; + + std::string n1Name("foo"); + std::string n1OrigA("Orig::A"); + std::string n1OrigB("Orig::B"); + // insert always inserts a new node + cg.insert(n1Name, n1OrigA); + cg.insert(n1Name, n1OrigA, true); + // getOrInsertNode returns the first node if already exists with that name + cg.getOrInsertNode(n1Name, n1OrigB, true, true); + EXPECT_EQ(cg.getNodeCount(), 2); + + // insert does add a new node with the same name + EXPECT_TRUE(cg.hasDuplicateNames()); + + auto n1Node = cg.getFirstNode(n1Name); + EXPECT_EQ(n1Node->has(), false); + EXPECT_EQ(cg.getMain(), nullptr); +} + +TEST(Callgraph, insertMultiIdentDiffOrigin) { + metacg::Callgraph cg; + + std::string n1Name("foo"); + std::string n1OrigA("Orig::A"); + std::string n1OrigB("Orig::B"); + // insert always inserts a new node + cg.insert(n1Name, n1OrigA); + cg.insert(n1Name, n1OrigB); + EXPECT_EQ(cg.getNodeCount(), 2); + EXPECT_EQ(cg.countNodes(n1Name), 2); + + // insert does add a new node with the same name + EXPECT_TRUE(cg.hasDuplicateNames()); + + auto n1Node = cg.getFirstNode(n1Name); + EXPECT_EQ(n1Node->getOrigin(), n1OrigA); +} + +TEST(Callgraph, singleEdge) { + metacg::Callgraph cg; + + std::string n1Name("foo"); + std::string n1OrigA("Orig::A"); + cg.insert(n1Name); + + // No edge exists by default + EXPECT_FALSE(cg.existsEdge(cg.getSingleNode(n1Name), cg.getSingleNode(n1Name))); + + // Insert single edge (by Node&) + EXPECT_TRUE(cg.addEdge(cg.getSingleNode(n1Name), cg.getSingleNode(n1Name))); + EXPECT_TRUE(cg.existsEdge(cg.getSingleNode(n1Name), cg.getSingleNode(n1Name))); + EXPECT_FALSE(cg.addEdge(cg.getSingleNode(n1Name), cg.getSingleNode(n1Name))); + + // Remove single edge (by Node&) + EXPECT_TRUE(cg.removeEdge(cg.getSingleNode(n1Name), cg.getSingleNode(n1Name))); + EXPECT_FALSE(cg.existsEdge(cg.getSingleNode(n1Name), cg.getSingleNode(n1Name))); + EXPECT_FALSE(cg.removeEdge(cg.getSingleNode(n1Name), cg.getSingleNode(n1Name))); + + // Insert single edge (by name) + EXPECT_TRUE(cg.addEdge(n1Name, n1Name)); + EXPECT_TRUE(cg.existsEdge(cg.getSingleNode(n1Name), cg.getSingleNode(n1Name))); + EXPECT_FALSE(cg.addEdge(n1Name, n1Name)); + + // Remove single edge (by name) + EXPECT_TRUE(cg.removeEdge(n1Name, n1Name)); + EXPECT_FALSE(cg.existsEdge(cg.getSingleNode(n1Name), cg.getSingleNode(n1Name))); + EXPECT_FALSE(cg.removeEdge(n1Name, n1Name)); +} + +TEST(Callgraph, singleEdgeMultiNode) { + metacg::Callgraph cg; + + std::string n1Name("foo"); + std::string n1OrigA("Orig::A"); + std::string n1OrigB("Orig::B"); + // insert always inserts a new node + auto& n1a = cg.insert(n1Name, n1OrigA); + auto& n1b = cg.insert(n1Name, n1OrigB); + + // Graph holds two nodes with the same name, no insertion + EXPECT_FALSE(cg.addEdge(n1Name, n1Name)); + + auto nodes = cg.getNodes(n1Name); + // For individual nodes with name n1Name, add an edge, and remove it again + // to see if the behavior matches + for (const auto n : nodes) { + EXPECT_TRUE(cg.addEdge(n, n)); + EXPECT_FALSE(cg.addEdge(n, n)); + + EXPECT_TRUE(cg.existsEdge(n, n)); + + EXPECT_TRUE(cg.removeEdge(n, n)); + EXPECT_FALSE(cg.removeEdge(n, n)); + + EXPECT_FALSE(cg.existsEdge(n, n)); + } + + EXPECT_TRUE(cg.addEdge(n1a, n1a)); + EXPECT_FALSE(cg.addEdge(n1a, n1a)); + EXPECT_TRUE(cg.existsAnyEdge(n1Name, n1Name)); + { + // Only that particular edge exists. + auto callees = cg.getCallees(n1a); + EXPECT_EQ(&n1a, *(callees.begin())); + } + EXPECT_FALSE(cg.removeEdge(n1Name, n1Name)); + EXPECT_TRUE(cg.removeEdge(n1a, n1a)); + EXPECT_FALSE(cg.removeEdge(n1a, n1a)); +} +} // namespace diff --git a/graph/test/unit/DominatorAnalysisTest.cpp b/graph/test/unit/DominatorAnalysisTest.cpp new file mode 100644 index 00000000..f799933e --- /dev/null +++ b/graph/test/unit/DominatorAnalysisTest.cpp @@ -0,0 +1,414 @@ +/** + * File: DominatorAnalysisTest.cpp + * License: Part of the metacg project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt + */ + +#include "gtest/gtest.h" + +#include "DominatorAnalysis.h" +#include "metacg/MCGManager.h" + +namespace { +using namespace metacg::analysis; + +static const char* const mainS = "main"; +static const char* const C1 = "child1"; +static const char* const C2 = "child2"; +static const char* const C3 = "child3"; +static const char* const C4 = "child4"; +static const char* const C5 = "child5"; +static const char* const C6 = "child6"; +static const char* const EXIT = "exit"; + +class DominatorAnalysisTest : public ::testing::Test { + protected: + void SetUp() override { + metacg::loggerutil::getLogger(); + auto& mcgm = metacg::graph::MCGManager::get(); + mcgm.resetManager(); + mcgm.addToManagedGraphs("testgraph", std::make_unique()); + } + + metacg::Callgraph* getGraph() { + auto& mcgm = metacg::graph::MCGManager::get(); + return mcgm.getCallgraph(); + } + void fillGraph(metacg::Callgraph* graph) { + graph->insert(mainS); + graph->insert(C1); + graph->insert(C2); + graph->insert(C3); + graph->insert(C4); + graph->insert(C5); + graph->insert(C6); + graph->insert(EXIT); + } +}; + +TEST_F(DominatorAnalysisTest, Dom_SingleNode) { + auto cg = getGraph(); + ASSERT_TRUE(cg != nullptr); + fillGraph(cg); + + auto main = cg->getFirstNode(mainS); + + auto doms = computeDominators(*cg, *main); + + auto& doms_main = doms[main].Doms; + ASSERT_TRUE(doms_main.size() == 1); + ASSERT_TRUE(doms_main.count(main) == 1); +} +TEST_F(DominatorAnalysisTest, Dom_LinearCallChain) { + auto cg = getGraph(); + ASSERT_TRUE(cg != nullptr); + fillGraph(cg); + ASSERT_TRUE(cg->addEdge(mainS, C1)); + ASSERT_TRUE(cg->addEdge(C1, C2)); + ASSERT_TRUE(cg->addEdge(C2, EXIT)); + + auto doms = computeDominators( + *cg, *cg->getFirstNode(mainS)); + + auto main = cg->getFirstNode(mainS); + auto node1 = cg->getFirstNode(C1); + auto node2 = cg->getFirstNode(C2); + auto exit = cg->getFirstNode(EXIT); + + auto& doms_main = doms[main].Doms; + ASSERT_TRUE(doms_main.size() == 1); + ASSERT_TRUE(doms_main.count(main) == 1); + + auto& doms_node1 = doms[node1].Doms; + ASSERT_TRUE(doms_node1.size() == 2); + ASSERT_TRUE(doms_node1.count(main) == 1); + ASSERT_TRUE(doms_node1.count(node1) == 1); + + auto& doms_node2 = doms[node2].Doms; + ASSERT_TRUE(doms_node2.size() == 3); + ASSERT_TRUE(doms_node2.count(main) == 1); + ASSERT_TRUE(doms_node2.count(node1) == 1); + ASSERT_TRUE(doms_node2.count(node2) == 1); + + auto& doms_exit = doms[exit].Doms; + ASSERT_TRUE(doms_exit.size() == 4); + ASSERT_TRUE(doms_exit.count(main) == 1); + ASSERT_TRUE(doms_exit.count(node1) == 1); + ASSERT_TRUE(doms_exit.count(node2) == 1); + ASSERT_TRUE(doms_exit.count(exit) == 1); +} + +TEST_F(DominatorAnalysisTest, Dom_BranchAndJoin) { + auto cg = getGraph(); + ASSERT_TRUE(cg != nullptr); + fillGraph(cg); + ASSERT_TRUE(cg->addEdge(mainS, C1)); + ASSERT_TRUE(cg->addEdge(mainS, C3)); + ASSERT_TRUE(cg->addEdge(C1, C2)); + ASSERT_TRUE(cg->addEdge(C2, EXIT)); + ASSERT_TRUE(cg->addEdge(C3, C2)); + + auto doms = computeDominators( + *cg, *cg->getFirstNode(mainS)); + + auto main = cg->getFirstNode(mainS); + auto node1 = cg->getFirstNode(C1); + auto node2 = cg->getFirstNode(C2); + auto node3 = cg->getFirstNode(C3); + auto exit = cg->getFirstNode(EXIT); + + auto& doms_main = doms[main].Doms; + ASSERT_TRUE(doms_main.size() == 1); + ASSERT_TRUE(doms_main.count(main) == 1); + + auto& doms_node1 = doms[node1].Doms; + ASSERT_TRUE(doms_node1.size() == 2); + ASSERT_TRUE(doms_node1.count(main) == 1); + ASSERT_TRUE(doms_node1.count(node1) == 1); + + auto& doms_node2 = doms[node2].Doms; + ASSERT_TRUE(doms_node2.size() == 2); + ASSERT_TRUE(doms_node2.count(main) == 1); + ASSERT_FALSE(doms_node2.count(node1) == 1); + ASSERT_FALSE(doms_node2.count(node3) == 1); + ASSERT_TRUE(doms_node2.count(node2) == 1); + + auto& doms_node3 = doms[node3].Doms; + ASSERT_TRUE(doms_node3.size() == 2); + ASSERT_TRUE(doms_node3.count(main) == 1); + ASSERT_TRUE(doms_node3.count(node3) == 1); + + auto& doms_exit = doms[exit].Doms; + ASSERT_TRUE(doms_exit.size() == 3); + ASSERT_TRUE(doms_exit.count(main) == 1); + ASSERT_FALSE(doms_exit.count(node1) == 1); + ASSERT_TRUE(doms_exit.count(node2) == 1); + ASSERT_FALSE(doms_exit.count(node3) == 1); + ASSERT_TRUE(doms_exit.count(exit) == 1); +} + +TEST_F(DominatorAnalysisTest, Dom_UnreachableNodes) { + auto cg = getGraph(); + ASSERT_TRUE(cg != nullptr); + fillGraph(cg); + ASSERT_TRUE(cg->addEdge(mainS, C1)); + ASSERT_TRUE(cg->addEdge(C2, C3)); + + auto doms = computeDominators( + *cg, *cg->getFirstNode(mainS)); + + ASSERT_TRUE(doms.find(cg->getFirstNode(C2)) == doms.end()); +} + +TEST_F(DominatorAnalysisTest, Dom_Recursion) { + auto cg = getGraph(); + ASSERT_TRUE(cg != nullptr); + fillGraph(cg); + ASSERT_TRUE(cg->addEdge(mainS, C1)); + ASSERT_TRUE(cg->addEdge(C1, C2)); + ASSERT_TRUE(cg->addEdge(C2, C1)); + ASSERT_TRUE(cg->addEdge(C2, EXIT)); + + auto doms = computeDominators( + *cg, *cg->getFirstNode(mainS)); + + auto main = cg->getFirstNode(mainS); + auto node1 = cg->getFirstNode(C1); + auto node2 = cg->getFirstNode(C2); + auto exit = cg->getFirstNode(EXIT); + + auto& doms_main = doms[main].Doms; + ASSERT_TRUE(doms_main.size() == 1); + ASSERT_TRUE(doms_main.count(main) == 1); + + auto& doms_node1 = doms[node1].Doms; + ASSERT_TRUE(doms_node1.size() == 2); + ASSERT_TRUE(doms_node1.count(main) == 1); + ASSERT_TRUE(doms_node1.count(node1) == 1); + + auto& doms_node2 = doms[node2].Doms; + ASSERT_TRUE(doms_node2.size() == 3); + ASSERT_TRUE(doms_node2.count(main) == 1); + ASSERT_TRUE(doms_node2.count(node1) == 1); + ASSERT_TRUE(doms_node2.count(node2) == 1); + + auto& doms_exit = doms[exit].Doms; + ASSERT_TRUE(doms_exit.size() == 4); + ASSERT_TRUE(doms_exit.count(main) == 1); + ASSERT_TRUE(doms_exit.count(node1) == 1); + ASSERT_TRUE(doms_exit.count(node2) == 1); + ASSERT_TRUE(doms_exit.count(exit) == 1); +} + +TEST_F(DominatorAnalysisTest, Dom_MultiNodeCycle) { + auto cg = getGraph(); + ASSERT_TRUE(cg != nullptr); + fillGraph(cg); + + ASSERT_TRUE(cg->addEdge(mainS, C1)); + ASSERT_TRUE(cg->addEdge(C1, C2)); + ASSERT_TRUE(cg->addEdge(C2, C3)); + ASSERT_TRUE(cg->addEdge(C3, C1)); + ASSERT_TRUE(cg->addEdge(C3, EXIT)); + + auto doms = computeDominators( + *cg, *cg->getFirstNode(mainS)); + + auto main = cg->getFirstNode(mainS); + auto node1 = cg->getFirstNode(C1); + auto node2 = cg->getFirstNode(C2); + auto node3 = cg->getFirstNode(C3); + auto exit = cg->getFirstNode(EXIT); + + ASSERT_TRUE(doms[main].Doms.count(main) == 1); + + ASSERT_TRUE(doms[node1].Doms.count(main) == 1); + ASSERT_TRUE(doms[node1].Doms.count(node1) == 1); + + ASSERT_TRUE(doms[node2].Doms.count(main) == 1); + ASSERT_TRUE(doms[node2].Doms.count(node1) == 1); + ASSERT_TRUE(doms[node2].Doms.count(node2) == 1); + + ASSERT_TRUE(doms[node3].Doms.count(main) == 1); + ASSERT_TRUE(doms[node3].Doms.count(node1) == 1); + ASSERT_TRUE(doms[node3].Doms.count(node2) == 1); + ASSERT_TRUE(doms[node3].Doms.count(node3) == 1); + + ASSERT_TRUE(doms[exit].Doms.count(main) == 1); + ASSERT_TRUE(doms[exit].Doms.count(node1) == 1); + ASSERT_TRUE(doms[exit].Doms.count(node2) == 1); + ASSERT_TRUE(doms[exit].Doms.count(node3) == 1); + ASSERT_TRUE(doms[exit].Doms.count(exit) == 1); +} + +TEST_F(DominatorAnalysisTest, PostDom_SingleNode) { + auto cg = getGraph(); + ASSERT_TRUE(cg != nullptr); + fillGraph(cg); + + auto exitNode = cg->getFirstNode(EXIT); + + auto pdoms = computeDominators(*cg, *exitNode); + + auto& pdoms_exit = pdoms[exitNode].Doms; + ASSERT_TRUE(pdoms_exit.size() == 1); + ASSERT_TRUE(pdoms_exit.count(exitNode)); +} + +TEST_F(DominatorAnalysisTest, PostDom_LinearCallChain) { + auto cg = getGraph(); + ASSERT_TRUE(cg != nullptr); + fillGraph(cg); + ASSERT_TRUE(cg->addEdge(mainS, C1)); + ASSERT_TRUE(cg->addEdge(C1, C2)); + ASSERT_TRUE(cg->addEdge(C2, EXIT)); + + auto pdoms = computeDominators( + *cg, *cg->getFirstNode(EXIT)); + + auto main = cg->getFirstNode(mainS); + auto node1 = cg->getFirstNode(C1); + auto node2 = cg->getFirstNode(C2); + auto exit = cg->getFirstNode(EXIT); + + ASSERT_TRUE(pdoms[exit].Doms.count(exit) == 1); + + ASSERT_TRUE(pdoms[node2].Doms.count(node2) == 1); + ASSERT_TRUE(pdoms[node2].Doms.count(exit) == 1); + + ASSERT_TRUE(pdoms[node1].Doms.count(node1) == 1); + ASSERT_TRUE(pdoms[node1].Doms.count(node2) == 1); + ASSERT_TRUE(pdoms[node1].Doms.count(exit) == 1); + + ASSERT_TRUE(pdoms[main].Doms.count(main) == 1); + ASSERT_TRUE(pdoms[main].Doms.count(node1) == 1); + ASSERT_TRUE(pdoms[main].Doms.count(node2) == 1); + ASSERT_TRUE(pdoms[main].Doms.count(exit) == 1); +} + +TEST_F(DominatorAnalysisTest, PostDom_BranchAndJoin) { + auto cg = getGraph(); + ASSERT_TRUE(cg != nullptr); + fillGraph(cg); + ASSERT_TRUE(cg->addEdge(mainS, C1)); + ASSERT_TRUE(cg->addEdge(mainS, C3)); + ASSERT_TRUE(cg->addEdge(C1, C2)); + ASSERT_TRUE(cg->addEdge(C2, EXIT)); + ASSERT_TRUE(cg->addEdge(C3, C2)); + + auto pdoms = computeDominators( + *cg, *cg->getFirstNode(EXIT)); + + auto main = cg->getFirstNode(mainS); + auto node1 = cg->getFirstNode(C1); + auto node2 = cg->getFirstNode(C2); + auto node3 = cg->getFirstNode(C3); + auto exit = cg->getFirstNode(EXIT); + + ASSERT_TRUE(pdoms[exit].Doms.count(exit) == 1); + + ASSERT_TRUE(pdoms[node2].Doms.count(node2) == 1); + ASSERT_TRUE(pdoms[node2].Doms.count(exit) == 1); + + ASSERT_TRUE(pdoms[node1].Doms.count(node1) == 1); + ASSERT_TRUE(pdoms[node1].Doms.count(node2) == 1); + ASSERT_TRUE(pdoms[node1].Doms.count(exit) == 1); + + ASSERT_TRUE(pdoms[node3].Doms.count(node3) == 1); + ASSERT_TRUE(pdoms[node3].Doms.count(node2) == 1); + ASSERT_TRUE(pdoms[node3].Doms.count(exit) == 1); + + ASSERT_TRUE(pdoms[main].Doms.count(main) == 1); + ASSERT_FALSE(pdoms[main].Doms.count(node1) == 1); + ASSERT_TRUE(pdoms[main].Doms.count(node2) == 1); + ASSERT_FALSE(pdoms[main].Doms.count(node3) == 1); + ASSERT_TRUE(pdoms[main].Doms.count(exit) == 1); +} + +TEST_F(DominatorAnalysisTest, PostDom_UnreachableNodes) { + auto cg = getGraph(); + ASSERT_TRUE(cg != nullptr); + fillGraph(cg); + ASSERT_TRUE(cg->addEdge(mainS, C1)); + ASSERT_TRUE(cg->addEdge(C2, C3)); + + auto pdoms = computeDominators( + *cg, *cg->getFirstNode(EXIT)); + + ASSERT_TRUE(pdoms.find(cg->getFirstNode(C2)) == pdoms.end()); +} + +TEST_F(DominatorAnalysisTest, PostDom_Recursion) { + auto cg = getGraph(); + ASSERT_TRUE(cg != nullptr); + fillGraph(cg); + ASSERT_TRUE(cg->addEdge(mainS, C1)); + ASSERT_TRUE(cg->addEdge(C1, C2)); + ASSERT_TRUE(cg->addEdge(C2, C1)); + ASSERT_TRUE(cg->addEdge(C2, EXIT)); + + auto pdoms = computeDominators( + *cg, *cg->getFirstNode(EXIT)); + + auto main = cg->getFirstNode(mainS); + auto node1 = cg->getFirstNode(C1); + auto node2 = cg->getFirstNode(C2); + auto exit = cg->getFirstNode(EXIT); + + ASSERT_TRUE(pdoms[node2].Doms.count(node2) == 1); + ASSERT_TRUE(pdoms[node2].Doms.count(exit) == 1); + + ASSERT_TRUE(pdoms[node1].Doms.count(node1) == 1); + ASSERT_TRUE(pdoms[node1].Doms.count(node2) == 1); + ASSERT_TRUE(pdoms[node1].Doms.count(exit) == 1); + + ASSERT_TRUE(pdoms[main].Doms.count(main) == 1); + ASSERT_TRUE(pdoms[main].Doms.count(node1) == 1); + ASSERT_TRUE(pdoms[main].Doms.count(node2) == 1); + ASSERT_TRUE(pdoms[main].Doms.count(exit) == 1); + + ASSERT_TRUE(pdoms[exit].Doms.count(exit)); +} + +TEST_F(DominatorAnalysisTest, PostDom_MultiNodeCycle) { + auto cg = getGraph(); + ASSERT_TRUE(cg != nullptr); + fillGraph(cg); + + ASSERT_TRUE(cg->addEdge(mainS, C1)); + ASSERT_TRUE(cg->addEdge(C1, C2)); + ASSERT_TRUE(cg->addEdge(C2, C3)); + ASSERT_TRUE(cg->addEdge(C3, C1)); + ASSERT_TRUE(cg->addEdge(C3, EXIT)); + + auto pdoms = computeDominators( + *cg, *cg->getFirstNode(EXIT)); + + auto main = cg->getFirstNode(mainS); + auto node1 = cg->getFirstNode(C1); + auto node2 = cg->getFirstNode(C2); + auto node3 = cg->getFirstNode(C3); + auto exit = cg->getFirstNode(EXIT); + + ASSERT_TRUE(pdoms[exit].Doms.count(exit) == 1); + + ASSERT_TRUE(pdoms[node3].Doms.count(node3) == 1); + ASSERT_TRUE(pdoms[node3].Doms.count(exit) == 1); + + ASSERT_TRUE(pdoms[node2].Doms.count(node2) == 1); + ASSERT_TRUE(pdoms[node2].Doms.count(node3) == 1); + ASSERT_TRUE(pdoms[node2].Doms.count(exit) == 1); + + ASSERT_TRUE(pdoms[node1].Doms.count(node1) == 1); + ASSERT_TRUE(pdoms[node1].Doms.count(node2) == 1); + ASSERT_TRUE(pdoms[node1].Doms.count(node3) == 1); + ASSERT_TRUE(pdoms[node1].Doms.count(exit) == 1); + + ASSERT_TRUE(pdoms[main].Doms.count(main) == 1); + ASSERT_TRUE(pdoms[main].Doms.count(node1) == 1); + ASSERT_TRUE(pdoms[main].Doms.count(node2) == 1); + ASSERT_TRUE(pdoms[main].Doms.count(node3) == 1); + ASSERT_TRUE(pdoms[main].Doms.count(exit) == 1); +} + +} // namespace diff --git a/graph/test/unit/DotIOTest.cpp b/graph/test/unit/DotIOTest.cpp index c8cc6aec..8bb2a5b2 100644 --- a/graph/test/unit/DotIOTest.cpp +++ b/graph/test/unit/DotIOTest.cpp @@ -6,8 +6,8 @@ #include "gtest/gtest.h" -#include "DotIO.h" -#include "MCGManager.h" +#include "metacg/DotIO.h" +#include "metacg/MCGManager.h" class DotIOTest : public ::testing::Test { protected: diff --git a/graph/test/unit/GlobalMDTest.cpp b/graph/test/unit/GlobalMDTest.cpp index 4bddde57..82dcfada 100644 --- a/graph/test/unit/GlobalMDTest.cpp +++ b/graph/test/unit/GlobalMDTest.cpp @@ -4,7 +4,7 @@ * https://github.com/tudasc/metacg/LICENSE.txt */ -#include "Callgraph.h" +#include "metacg/Callgraph.h" #include "gtest/gtest.h" diff --git a/graph/test/unit/LoggingTest.cpp b/graph/test/unit/LoggingTest.cpp index a7d55342..541050b4 100644 --- a/graph/test/unit/LoggingTest.cpp +++ b/graph/test/unit/LoggingTest.cpp @@ -4,8 +4,8 @@ * https://github.com/tudasc/metacg/LICENSE.txt */ +#include "metacg/LoggerUtil.h" #include "gtest/gtest.h" -#include "LoggerUtil.h" class LoggingTest : public ::testing::Test { protected: diff --git a/graph/test/unit/MCGManagerTest.cpp b/graph/test/unit/MCGManagerTest.cpp index f7ddbff6..f497121a 100644 --- a/graph/test/unit/MCGManagerTest.cpp +++ b/graph/test/unit/MCGManagerTest.cpp @@ -6,10 +6,10 @@ #include "gtest/gtest.h" -#include "MCGManager.h" -#include "metadata/EntryFunctionMD.h" -#include "metadata/MetaData.h" -#include "metadata/OverrideMD.h" +#include "metacg/MCGManager.h" +#include "metacg/metadata/EntryFunctionMD.h" +#include "metacg/metadata/MetaData.h" +#include "metacg/metadata/OverrideMD.h" using json = nlohmann::json; @@ -131,6 +131,7 @@ TEST_F(MCGManagerTest, EraseNodeWithEdge) { int mainNodeId = mainNode.getId(); int childNodeId = childNode.getId(); ASSERT_TRUE(cg.addEdge(mainNodeId, childNodeId)); + ASSERT_EQ(cg.getCallers(childNode).size(), 1); ASSERT_TRUE(cg.erase(mainNode.getId())); ASSERT_FALSE(cg.hasNode("main")); ASSERT_FALSE(cg.hasNode(mainNodeId)); @@ -138,6 +139,7 @@ TEST_F(MCGManagerTest, EraseNodeWithEdge) { ASSERT_EQ(cg.getNodeCount(), 1); ASSERT_FALSE(cg.isEmpty()); ASSERT_FALSE(cg.existsEdge(mainNodeId, childNodeId)); + ASSERT_EQ(cg.getCallers(childNode).size(), 0); } TEST_F(MCGManagerTest, RemoveEdge) { @@ -374,7 +376,7 @@ TEST_F(MCGManagerTest, EraseMainTest) { auto& main = cg->getOrInsertNode("thisIsMain"); cg->getOrCreate(main); ASSERT_EQ(cg->getMain(), &main); - cg->erase(main.id); + cg->erase(main.getId()); ASSERT_EQ(cg->getMain(), nullptr); ASSERT_FALSE(cg->has()); -} \ No newline at end of file +} diff --git a/graph/test/unit/ReachabilityAnalysisTest.cpp b/graph/test/unit/ReachabilityAnalysisTest.cpp index 6988b8b3..ded70043 100644 --- a/graph/test/unit/ReachabilityAnalysisTest.cpp +++ b/graph/test/unit/ReachabilityAnalysisTest.cpp @@ -6,8 +6,8 @@ #include "gtest/gtest.h" -#include "MCGManager.h" -#include "ReachabilityAnalysis.h" +#include "metacg/MCGManager.h" +#include "metacg/ReachabilityAnalysis.h" namespace { using namespace metacg::analysis; diff --git a/graph/test/unit/ReaderFactoryTest.cpp b/graph/test/unit/ReaderFactoryTest.cpp index d0998736..6ddd0992 100644 --- a/graph/test/unit/ReaderFactoryTest.cpp +++ b/graph/test/unit/ReaderFactoryTest.cpp @@ -5,10 +5,10 @@ * https://github.com/tudasc/metacg/LICENSE.txt */ -#include "LoggerUtil.h" -#include "MCGManager.h" -#include "io/VersionFourMCGReader.h" -#include "io/VersionTwoMCGReader.h" +#include "metacg/LoggerUtil.h" +#include "metacg/MCGManager.h" +#include "metacg/io/VersionFourMCGReader.h" +#include "metacg/io/VersionTwoMCGReader.h" #include "nlohmann/json.hpp" #include "gtest/gtest.h" diff --git a/graph/test/unit/TestMD.h b/graph/test/unit/TestMD.h index 7f27d47d..bb20b693 100644 --- a/graph/test/unit/TestMD.h +++ b/graph/test/unit/TestMD.h @@ -6,8 +6,8 @@ #ifndef METACG_TESTMD_H #define METACG_TESTMD_H -#include "io/VersionFourMCGReader.h" -#include "metadata/MetaData.h" +#include "metacg/io/VersionFourMCGReader.h" +#include "metacg/metadata/MetaData.h" struct SimpleTestMD final : metacg::MetaData::Registrar { static constexpr const char* key = "SimpleTestMD"; @@ -59,10 +59,10 @@ struct RefTestMD final : metacg::MetaData::Registrar { explicit RefTestMD(const nlohmann::json& j, metacg::StrToNodeMapping& strToNode) { auto nodeRefStr = j.at("node_ref"); auto* node = strToNode.getNodeFromStr(nodeRefStr); - this->nodeRef = node->id; + this->nodeRef = node->getId(); } - explicit RefTestMD(metacg::CgNode& node) { this->nodeRef = node.id; } + explicit RefTestMD(metacg::CgNode& node) : nodeRef(node.getId()) {} private: RefTestMD(const RefTestMD& other) = default; @@ -106,4 +106,4 @@ struct RefTestMD final : metacg::MetaData::Registrar { metacg::NodeId nodeRef; }; -#endif // METACG_TESTMD_H \ No newline at end of file +#endif // METACG_TESTMD_H diff --git a/graph/test/unit/UtilTest.cpp b/graph/test/unit/UtilTest.cpp index d3aaaab5..e7351baa 100644 --- a/graph/test/unit/UtilTest.cpp +++ b/graph/test/unit/UtilTest.cpp @@ -4,7 +4,7 @@ * https://github.com/tudasc/metacg/LICENSE.txt */ -#include "Util.h" +#include "metacg/Util.h" #include "gtest/gtest.h" TEST(UtilTest, string_split) { diff --git a/graph/test/unit/VersionFourMCGReaderTest.cpp b/graph/test/unit/VersionFourMCGReaderTest.cpp index bf010da3..01f5d51c 100644 --- a/graph/test/unit/VersionFourMCGReaderTest.cpp +++ b/graph/test/unit/VersionFourMCGReaderTest.cpp @@ -5,11 +5,11 @@ * https://github.com/tudasc/metacg/LICENSE.txt */ -#include "io/VersionFourMCGReader.h" -#include "LoggerUtil.h" -#include "MCGManager.h" +#include "metacg/io/VersionFourMCGReader.h" #include "TestMD.h" -#include "metadata/EntryFunctionMD.h" +#include "metacg/LoggerUtil.h" +#include "metacg/MCGManager.h" +#include "metacg/metadata/EntryFunctionMD.h" #include "nlohmann/json.hpp" #include "gtest/gtest.h" @@ -304,6 +304,6 @@ TEST(V4MCGReaderTest, GlobalMetadata) { const Callgraph& graph = *mcgm.getCallgraph(); EXPECT_TRUE(graph.has()); - EXPECT_EQ(graph.get()->getEntryFunctionId(), graph.getSingleNode("thisIsMain").id); + EXPECT_EQ(graph.get()->getEntryFunctionId(), graph.getSingleNode("thisIsMain").getId()); EXPECT_EQ(graph.getMain(), &graph.getSingleNode("thisIsMain")); } diff --git a/graph/test/unit/VersionFourMCGWriterTest.cpp b/graph/test/unit/VersionFourMCGWriterTest.cpp index 2a8245d7..eeceb196 100644 --- a/graph/test/unit/VersionFourMCGWriterTest.cpp +++ b/graph/test/unit/VersionFourMCGWriterTest.cpp @@ -4,14 +4,14 @@ * https://github.com/tudasc/metacg/LICENSE.txt */ -#include "LoggerUtil.h" +#include "metacg/LoggerUtil.h" #include "gtest/gtest.h" -#include "MCGBaseInfo.h" -#include "MCGManager.h" #include "TestMD.h" -#include "io/VersionFourMCGWriter.h" -#include "metadata/EntryFunctionMD.h" +#include "metacg/MCGBaseInfo.h" +#include "metacg/MCGManager.h" +#include "metacg/io/VersionFourMCGWriter.h" +#include "metacg/metadata/EntryFunctionMD.h" class V4MCGWriterTest : public ::testing::Test { protected: diff --git a/graph/test/unit/VersionFourReaderWriterRoundtripTest.cpp b/graph/test/unit/VersionFourReaderWriterRoundtripTest.cpp index e129bb00..c44bea01 100644 --- a/graph/test/unit/VersionFourReaderWriterRoundtripTest.cpp +++ b/graph/test/unit/VersionFourReaderWriterRoundtripTest.cpp @@ -4,12 +4,12 @@ * https://github.com/tudasc/metacg/LICENSE.txt */ -#include "LoggerUtil.h" -#include "MCGManager.h" #include "TestMD.h" -#include "io/MCGWriter.h" -#include "io/VersionFourMCGReader.h" -#include "io/VersionFourMCGWriter.h" +#include "metacg/LoggerUtil.h" +#include "metacg/MCGManager.h" +#include "metacg/io/MCGWriter.h" +#include "metacg/io/VersionFourMCGReader.h" +#include "metacg/io/VersionFourMCGWriter.h" #include "gtest/gtest.h" class V4ReaderWriterRoundtripTest : public ::testing::Test { diff --git a/graph/test/unit/VersionTwoMCGReaderTest.cpp b/graph/test/unit/VersionTwoMCGReaderTest.cpp index 2f2eb8fd..22e27b17 100644 --- a/graph/test/unit/VersionTwoMCGReaderTest.cpp +++ b/graph/test/unit/VersionTwoMCGReaderTest.cpp @@ -4,11 +4,12 @@ * https://github.com/tudasc/metacg/LICENSE.txt */ -#include "LoggerUtil.h" +#include "metacg/LoggerUtil.h" #include "gtest/gtest.h" -#include "MCGManager.h" -#include "io/VersionTwoMCGReader.h" +#include "metacg/MCGManager.h" +#include "metacg/io/VersionTwoMCGReader.h" +#include "metacg/metadata/OverrideMD.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" @@ -65,7 +66,7 @@ class TestMetaData : public metacg::MetaData::Registrar { float metadataFloat = 0.0f; }; -TEST_F(V2MCGReaderTest, NullCG) { +TEST_F(V2MCGReaderTest, NoCG) { nlohmann::json j; auto& mcgm = metacg::graph::MCGManager::get(); metacg::io::JsonSource jsonSource(j); @@ -121,7 +122,7 @@ TEST_F(V2MCGReaderTest, WrongVersionInformation) { } } -TEST_F(V2MCGReaderTest, BrokenCG) { +TEST_F(V2MCGReaderTest, NullCG) { nlohmann::json j = "{\n" " \"_CG\": null,\n" @@ -140,9 +141,8 @@ TEST_F(V2MCGReaderTest, BrokenCG) { auto& mcgm = metacg::graph::MCGManager::get(); try { mcgReader.read(); - EXPECT_TRUE(false); // should not reach here } catch (std::exception& e) { - EXPECT_TRUE(strcmp(e.what(), "The call graph in the metacg file was not found or null.") == 0); + EXPECT_TRUE(false); // should not reach here } } @@ -513,4 +513,58 @@ TEST_F(V2MCGReaderTest, OneNodeWithOriginCGRead) { EXPECT_TRUE(cg->getCallers(*cg->getMain()).empty()); } -#pragma GCC diagnostic pop \ No newline at end of file +TEST_F(V2MCGReaderTest, FixInconsistentIsVirtual) { + nlohmann::json j = + "{\n" + " \"_CG\":{\n" + " \"foo\":{\n" + " \"callees\":[],\n" + " \"callers\":[],\n" + " \"doesOverride\":false,\n" + " \"hasBody\":true,\n" + " \"isVirtual\":false,\n" + " \"meta\":null,\n" + " \"overriddenBy\":[\"bar\"],\n" + " \"overrides\":[]\n" + " },\n" + " \"bar\":{\n" + " \"callees\":[],\n" + " \"callers\":[],\n" + " \"doesOverride\":false,\n" + " \"hasBody\":true,\n" + " \"isVirtual\":false,\n" + " \"meta\":null,\n" + " \"overriddenBy\":[],\n" + " \"overrides\":[\"foo\"]\n" + " }\n" + " },\n" + " \"_MetaCG\":{\n" + " \"generator\":{\n" + " \"name\":\"Test\",\n" + " \"sha\":\"TestSha\",\n" + " \"version\":\"0.1\"\n" + " },\n" + " \"version\":\"2.0\"\n" + " }\n" + "}"_json; + metacg::io::JsonSource jsonSource(j); + metacg::io::VersionTwoMCGReader mcgReader(jsonSource); + auto& mcgm = metacg::graph::MCGManager::get(); + mcgm.addToManagedGraphs("newGraph", mcgReader.read()); + EXPECT_EQ(mcgm.graphs_size(), 1); + const auto& cg = mcgm.getCallgraph(); + EXPECT_EQ(cg->size(), 2); + EXPECT_TRUE(cg->hasNode("foo")); + EXPECT_TRUE(cg->hasNode("bar")); + + auto* foo = cg->getFirstNode("foo"); + auto* bar = cg->getFirstNode("bar"); + EXPECT_TRUE(foo->isVirtual()); + EXPECT_TRUE(bar->isVirtual()); + EXPECT_TRUE(foo->has()); + EXPECT_TRUE(bar->has()); + EXPECT_TRUE(foo->get()->overriddenBy.front() == bar->getId()); + EXPECT_TRUE(bar->get()->overrides.front() == foo->getId()); +} + +#pragma GCC diagnostic pop diff --git a/graph/test/unit/VersionTwoMCGWriterTest.cpp b/graph/test/unit/VersionTwoMCGWriterTest.cpp index 1c36d31a..6e5bd5c4 100644 --- a/graph/test/unit/VersionTwoMCGWriterTest.cpp +++ b/graph/test/unit/VersionTwoMCGWriterTest.cpp @@ -4,12 +4,12 @@ * https://github.com/tudasc/metacg/LICENSE.txt */ -#include "LoggerUtil.h" +#include "metacg/LoggerUtil.h" #include "gtest/gtest.h" -#include "MCGBaseInfo.h" -#include "MCGManager.h" -#include "io/VersionTwoMCGWriter.h" +#include "metacg/MCGBaseInfo.h" +#include "metacg/MCGManager.h" +#include "metacg/io/VersionTwoMCGWriter.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" diff --git a/graph/test/unit/VersionTwoReaderWriterRoundtripTest.cpp b/graph/test/unit/VersionTwoReaderWriterRoundtripTest.cpp index 08003df0..ba241ae6 100644 --- a/graph/test/unit/VersionTwoReaderWriterRoundtripTest.cpp +++ b/graph/test/unit/VersionTwoReaderWriterRoundtripTest.cpp @@ -4,11 +4,11 @@ * https://github.com/tudasc/metacg/LICENSE.txt */ -#include "LoggerUtil.h" -#include "MCGManager.h" -#include "io/MCGWriter.h" -#include "io/VersionTwoMCGReader.h" -#include "io/VersionTwoMCGWriter.h" +#include "metacg/LoggerUtil.h" +#include "metacg/MCGManager.h" +#include "metacg/io/MCGWriter.h" +#include "metacg/io/VersionTwoMCGReader.h" +#include "metacg/io/VersionTwoMCGWriter.h" #include "gtest/gtest.h" class VersionTwoReaderWriterRoundtripTest : public ::testing::Test { diff --git a/pgis/CMakeLists.txt b/pgis/CMakeLists.txt index bd2ae924..14963a9c 100644 --- a/pgis/CMakeLists.txt +++ b/pgis/CMakeLists.txt @@ -1,10 +1,10 @@ set(PROJECT_NAME PGIS) set(TARGETS_EXPORT_NAME ${PROJECT_NAME}-target) -enable_testing() - add_subdirectory(lib) add_subdirectory(tool) + if(METACG_BUILD_UNIT_TESTS) + enable_testing() add_subdirectory(test/unit) endif() diff --git a/pgis/lib/include/CgHelper.h b/pgis/lib/include/CgHelper.h index e872dcc6..b06a4b97 100644 --- a/pgis/lib/include/CgHelper.h +++ b/pgis/lib/include/CgHelper.h @@ -7,10 +7,10 @@ #ifndef CGNODEHELPER_H_ #define CGNODEHELPER_H_ -#include "Callgraph.h" -#include "CgNode.h" -#include "CgTypes.h" -#include "ReachabilityAnalysis.h" +#include "metacg/Callgraph.h" +#include "metacg/CgNode.h" +#include "metacg/CgTypes.h" +#include "metacg/ReachabilityAnalysis.h" #include // std::set_intersection #include diff --git a/pgis/lib/include/CubeReader.h b/pgis/lib/include/CubeReader.h index fd81e021..df19b3f2 100644 --- a/pgis/lib/include/CubeReader.h +++ b/pgis/lib/include/CubeReader.h @@ -7,14 +7,14 @@ #ifndef CUBEREADER_H_ #define CUBEREADER_H_ -#include "LoggerUtil.h" #include "MetaData/CgNodeMetaData.h" #include "PiraMCGProcessor.h" +#include "metacg/LoggerUtil.h" +#include "metacg/MCGManager.h" #include #include -#include #include #include diff --git a/pgis/lib/include/EstimatorPhase.h b/pgis/lib/include/EstimatorPhase.h index 725ff1cf..a253bd7e 100644 --- a/pgis/lib/include/EstimatorPhase.h +++ b/pgis/lib/include/EstimatorPhase.h @@ -7,9 +7,9 @@ #ifndef ESTIMATORPHASE_H_ #define ESTIMATORPHASE_H_ -#include "Callgraph.h" #include "CgHelper.h" -#include "CgNode.h" +#include "metacg/Callgraph.h" +#include "metacg/CgNode.h" #include #include diff --git a/pgis/lib/include/ExtrapConnection.h b/pgis/lib/include/ExtrapConnection.h index 6b9ad119..aa6dcaa7 100644 --- a/pgis/lib/include/ExtrapConnection.h +++ b/pgis/lib/include/ExtrapConnection.h @@ -23,8 +23,8 @@ #include "cxxabi.h" #include "ExtrapAggregatedFunctions.h" -#include "LoggerUtil.h" #include "config/PiraIIConfig.h" +#include "metacg/LoggerUtil.h" #include #include diff --git a/pgis/lib/include/ExtrapEstimatorPhase.h b/pgis/lib/include/ExtrapEstimatorPhase.h index df2abd65..57b14849 100644 --- a/pgis/lib/include/ExtrapEstimatorPhase.h +++ b/pgis/lib/include/ExtrapEstimatorPhase.h @@ -12,8 +12,8 @@ #define PGOE_EXTRAPESTIMATORPHASE_H #include "EstimatorPhase.h" -#include "LoggerUtil.h" #include "MetaData/CgNodeMetaData.h" +#include "metacg/LoggerUtil.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wextra" diff --git a/pgis/lib/include/LegacyMCGReader.h b/pgis/lib/include/LegacyMCGReader.h index 3bb527d6..85de4509 100644 --- a/pgis/lib/include/LegacyMCGReader.h +++ b/pgis/lib/include/LegacyMCGReader.h @@ -7,7 +7,7 @@ #ifndef METACG_PGIS_INCLUDE_LEGACYMCGREADER_H #define METACG_PGIS_INCLUDE_LEGACYMCGREADER_H -#include "io/MCGReader.h" +#include "metacg/io/MCGReader.h" namespace metacg::pgis::io { diff --git a/pgis/lib/include/MetaData/CgNodeMetaData.h b/pgis/lib/include/MetaData/CgNodeMetaData.h index b2f2eb9e..1a166643 100644 --- a/pgis/lib/include/MetaData/CgNodeMetaData.h +++ b/pgis/lib/include/MetaData/CgNodeMetaData.h @@ -9,20 +9,20 @@ // clang-format off // Graph library -#include "CgNode.h" -#include "Utility.h" -#include "metadata/MetaData.h" -#include "metadata/CodeStatisticsMD.h" -#include "metadata/NumOperationsMD.h" -#include "metadata/NumConditionalBranchMD.h" -#include "metadata/LoopMD.h" -#include "LoggerUtil.h" +#include "metacg/CgNode.h" +#include "metacg/metadata/MetaData.h" +#include "metacg/metadata/CodeStatisticsMD.h" +#include "metacg/metadata/NumOperationsMD.h" +#include "metacg/metadata/NumConditionalBranchMD.h" +#include "metacg/metadata/LoopMD.h" +#include "metacg/LoggerUtil.h" // PGIS library #include "ExtrapConnection.h" #include "config/GlobalConfig.h" #include "CgLocation.h" +#include "Utility.h" #include #include diff --git a/pgis/lib/include/MetaData/PGISMetaData.h b/pgis/lib/include/MetaData/PGISMetaData.h index f20b50ce..a69c9577 100644 --- a/pgis/lib/include/MetaData/PGISMetaData.h +++ b/pgis/lib/include/MetaData/PGISMetaData.h @@ -7,9 +7,9 @@ #ifndef METACG_PGISMETADATA_H #define METACG_PGISMETADATA_H -#include "CgNode.h" -#include "LoggerUtil.h" -#include "metadata/MetaData.h" +#include "metacg/CgNode.h" +#include "metacg/LoggerUtil.h" +#include "metacg/metadata/MetaData.h" namespace metacg::pgis { @@ -29,7 +29,7 @@ class InstrumentationMetaData : public metacg::MetaData::Registrartrace("Serializing InstrumentationMetaData to json is not implemented"); return {}; }; diff --git a/pgis/lib/include/PiraMCGProcessor.h b/pgis/lib/include/PiraMCGProcessor.h index f424b39b..dd932932 100644 --- a/pgis/lib/include/PiraMCGProcessor.h +++ b/pgis/lib/include/PiraMCGProcessor.h @@ -9,8 +9,8 @@ // clang-format off // From graph library -#include "Callgraph.h" -#include "CgNode.h" +#include "metacg/Callgraph.h" +#include "metacg/CgNode.h" // From PGIS library #include "EstimatorPhase.h" diff --git a/pgis/lib/include/Utility.h b/pgis/lib/include/Utility.h index 67b1e887..34dc62c5 100644 --- a/pgis/lib/include/Utility.h +++ b/pgis/lib/include/Utility.h @@ -7,9 +7,9 @@ #ifndef PGIS_UTILITY_H #define PGIS_UTILITY_H -#include "Callgraph.h" -#include "CgNode.h" -#include "LoggerUtil.h" +#include "metacg/Callgraph.h" +#include "metacg/CgNode.h" +#include "metacg/LoggerUtil.h" #include "MetaData/PGISMetaData.h" diff --git a/pgis/lib/include/config/GlobalConfig.h b/pgis/lib/include/config/GlobalConfig.h index 2427cd8e..dd8d0ada 100644 --- a/pgis/lib/include/config/GlobalConfig.h +++ b/pgis/lib/include/config/GlobalConfig.h @@ -7,13 +7,13 @@ #ifndef PGIS_CONFIG_GLOBALCONFIG_H #define PGIS_CONFIG_GLOBALCONFIG_H -#include "LoggerUtil.h" +#include "metacg/LoggerUtil.h" +#include #include #include #include #include -#include namespace metacg::pgis { namespace options { diff --git a/pgis/lib/include/loadImbalance/LIConfig.h b/pgis/lib/include/loadImbalance/LIConfig.h index ab7840d7..354cfad6 100644 --- a/pgis/lib/include/loadImbalance/LIConfig.h +++ b/pgis/lib/include/loadImbalance/LIConfig.h @@ -7,8 +7,8 @@ #ifndef LI_CONFIG_H #define LI_CONFIG_H -#include "CgNode.h" #include "MetaData/CgNodeMetaData.h" +#include "metacg/CgNode.h" #include "nlohmann/json.hpp" namespace LoadImbalance { diff --git a/pgis/lib/include/loadImbalance/LIMetaData.h b/pgis/lib/include/loadImbalance/LIMetaData.h index 2baee2ed..9a860c51 100644 --- a/pgis/lib/include/loadImbalance/LIMetaData.h +++ b/pgis/lib/include/loadImbalance/LIMetaData.h @@ -10,7 +10,7 @@ #include #include "MetaData/CgNodeMetaData.h" -#include "metadata/MetaData.h" +#include "metacg/metadata/MetaData.h" #include "nlohmann/json.hpp" namespace LoadImbalance { @@ -55,7 +55,7 @@ class LIMetaData : public metacg::MetaData::Registrar { assessment(other.assessment) {} public: - virtual nlohmann::json toJson(metacg::NodeToStrMapping&) const; + virtual nlohmann::json toJson(metacg::NodeToStrMapping&) const override; [[nodiscard]] const char* getKey() const final { return key; } diff --git a/pgis/lib/include/loadImbalance/metric/AbstractMetric.h b/pgis/lib/include/loadImbalance/metric/AbstractMetric.h index 9ea2906a..948fffff 100644 --- a/pgis/lib/include/loadImbalance/metric/AbstractMetric.h +++ b/pgis/lib/include/loadImbalance/metric/AbstractMetric.h @@ -7,7 +7,7 @@ #ifndef LI_ABSTRACT_METRIC_H #define LI_ABSTRACT_METRIC_H -#include "../../../../../graph/include/CgNode.h" +#include "metacg/CgNode.h" #include namespace LoadImbalance { diff --git a/pgis/lib/src/CubeReader.cpp b/pgis/lib/src/CubeReader.cpp index 4c684b01..b175afa2 100644 --- a/pgis/lib/src/CubeReader.cpp +++ b/pgis/lib/src/CubeReader.cpp @@ -4,8 +4,9 @@ * https://github.com/tudasc/metacg/LICENSE.txt */ +#include "metacg/CgNode.h" + #include "CubeReader.h" -#include "../../../graph/include/CgNode.h" #include "MetaData/CgNodeMetaData.h" #include "PiraMCGProcessor.h" diff --git a/pgis/lib/src/ExtrapEstimatorPhase.cpp b/pgis/lib/src/ExtrapEstimatorPhase.cpp index d7ad8c7f..cefbad0b 100644 --- a/pgis/lib/src/ExtrapEstimatorPhase.cpp +++ b/pgis/lib/src/ExtrapEstimatorPhase.cpp @@ -4,7 +4,7 @@ * https://github.com/tudasc/metacg/LICENSE.txt */ -#include "ReachabilityAnalysis.h" +#include "metacg/ReachabilityAnalysis.h" #include "CgHelper.h" #include "ExtrapEstimatorPhase.h" diff --git a/pgis/lib/src/IPCGEstimatorPhase.cpp b/pgis/lib/src/IPCGEstimatorPhase.cpp index 249d9b4d..a9f0202f 100644 --- a/pgis/lib/src/IPCGEstimatorPhase.cpp +++ b/pgis/lib/src/IPCGEstimatorPhase.cpp @@ -4,7 +4,7 @@ * https://github.com/tudasc/metacg/LICENSE.txt */ -#include "ReachabilityAnalysis.h" +#include "metacg/ReachabilityAnalysis.h" #include "CgHelper.h" #include "IPCGEstimatorPhase.h" diff --git a/pgis/lib/src/LegacyMCGReader.cpp b/pgis/lib/src/LegacyMCGReader.cpp index 35477cde..feaf2063 100644 --- a/pgis/lib/src/LegacyMCGReader.cpp +++ b/pgis/lib/src/LegacyMCGReader.cpp @@ -5,10 +5,11 @@ */ #include "LegacyMCGReader.h" -#include "Callgraph.h" -#include "CgNode.h" -#include "Timing.h" -#include "Util.h" +#include "metacg/Callgraph.h" +#include "metacg/MCGManager.h" +#include "metacg/CgNode.h" +#include "metacg/Timing.h" +#include "metacg/Util.h" #include diff --git a/pgis/lib/src/PiraMCGProcessor.cpp b/pgis/lib/src/PiraMCGProcessor.cpp index e91ad9b8..0049e1b1 100644 --- a/pgis/lib/src/PiraMCGProcessor.cpp +++ b/pgis/lib/src/PiraMCGProcessor.cpp @@ -5,12 +5,12 @@ */ #include "PiraMCGProcessor.h" -#include "DotIO.h" #include "ErrorCodes.h" #include "ExtrapConnection.h" -#include "Timing.h" #include "config/GlobalConfig.h" #include "config/ParameterConfig.h" +#include "metacg/DotIO.h" +#include "metacg/Timing.h" #include "EXTRAP_Model.hpp" diff --git a/pgis/lib/src/loadImbalance/LIEstimatorPhase.cpp b/pgis/lib/src/loadImbalance/LIEstimatorPhase.cpp index c63ba267..5e87f295 100644 --- a/pgis/lib/src/loadImbalance/LIEstimatorPhase.cpp +++ b/pgis/lib/src/loadImbalance/LIEstimatorPhase.cpp @@ -6,8 +6,8 @@ #include "loadImbalance/LIEstimatorPhase.h" #include "CgHelper.h" -#include "LoggerUtil.h" #include "MetaData/PGISMetaData.h" +#include "metacg/LoggerUtil.h" #include #include diff --git a/pgis/test/unit/CMakeLists.txt b/pgis/test/unit/CMakeLists.txt index d54fc088..780fab0b 100644 --- a/pgis/test/unit/CMakeLists.txt +++ b/pgis/test/unit/CMakeLists.txt @@ -17,4 +17,4 @@ add_metacg(pgistests) # add_library(ipcg) target_link_libraries(pgistests) -add_test(NAME cgnode_test COMMAND pgistests) +add_test(NAME pgis_unittests COMMAND pgistests) diff --git a/pgis/test/unit/CallgraphManagerTest.cpp b/pgis/test/unit/CallgraphManagerTest.cpp index 4341299e..b7e57f72 100644 --- a/pgis/test/unit/CallgraphManagerTest.cpp +++ b/pgis/test/unit/CallgraphManagerTest.cpp @@ -6,7 +6,7 @@ #include "gtest/gtest.h" -#include "LoggerUtil.h" +#include "metacg/LoggerUtil.h" #include "PiraMCGProcessor.h" diff --git a/pgis/test/unit/CallgraphTest.cpp b/pgis/test/unit/CallgraphTest.cpp index b11f05e3..2fc6e771 100644 --- a/pgis/test/unit/CallgraphTest.cpp +++ b/pgis/test/unit/CallgraphTest.cpp @@ -6,10 +6,8 @@ #include "gtest/gtest.h" -// #include "LoggerUtil.h" - -#include "../../../graph/include/Callgraph.h" -#include "../../../graph/include/LoggerUtil.h" +#include "metacg/Callgraph.h" +#include "metacg/LoggerUtil.h" using namespace metacg; diff --git a/pgis/test/unit/IPCGEstimatorPhaseTest.cpp b/pgis/test/unit/IPCGEstimatorPhaseTest.cpp index 37f2b900..1358f440 100644 --- a/pgis/test/unit/IPCGEstimatorPhaseTest.cpp +++ b/pgis/test/unit/IPCGEstimatorPhaseTest.cpp @@ -6,9 +6,9 @@ #include "IPCGEstimatorPhase.h" #include "CgHelper.h" -#include "LoggerUtil.h" -#include "MCGManager.h" #include "PiraMCGProcessor.h" +#include "metacg/LoggerUtil.h" +#include "metacg/MCGManager.h" #include "gtest/gtest.h" diff --git a/pgis/test/unit/LegacyMCGReaderTest.cpp b/pgis/test/unit/LegacyMCGReaderTest.cpp index d73aa5fb..23dfdc4d 100644 --- a/pgis/test/unit/LegacyMCGReaderTest.cpp +++ b/pgis/test/unit/LegacyMCGReaderTest.cpp @@ -5,9 +5,9 @@ */ #include "LegacyMCGReader.h" -#include "LoggerUtil.h" -#include "MCGManager.h" #include "loadImbalance/LIMetaData.h" +#include "metacg/LoggerUtil.h" +#include "metacg/MCGManager.h" #include "gtest/gtest.h" #include "nlohmann/json.hpp" diff --git a/pgis/test/unit/PIRAPGISMetadataTest.cpp b/pgis/test/unit/PIRAPGISMetadataTest.cpp index 47b7f1af..487d94d2 100644 --- a/pgis/test/unit/PIRAPGISMetadataTest.cpp +++ b/pgis/test/unit/PIRAPGISMetadataTest.cpp @@ -9,7 +9,7 @@ #include "gtest/gtest.h" #include "MetaData/PGISMetaData.h" -#include "io/IdMapping.h" +#include "metacg/io/IdMapping.h" #include "nlohmann/json.hpp" using namespace metacg; diff --git a/pgis/test/unit/loadImbalance/LIConfigTest.cpp b/pgis/test/unit/loadImbalance/LIConfigTest.cpp index 95b88cb7..e051b515 100644 --- a/pgis/test/unit/loadImbalance/LIConfigTest.cpp +++ b/pgis/test/unit/loadImbalance/LIConfigTest.cpp @@ -4,7 +4,7 @@ * https://github.com/tudasc/metacg/LICENSE.txt */ -#include "LoggerUtil.h" +#include "metacg/LoggerUtil.h" #include "nlohmann/json.hpp" #include diff --git a/pgis/test/unit/loadImbalance/LIEstimatorPhaseTest.cpp b/pgis/test/unit/loadImbalance/LIEstimatorPhaseTest.cpp index 32df39c4..bafd5e6b 100644 --- a/pgis/test/unit/loadImbalance/LIEstimatorPhaseTest.cpp +++ b/pgis/test/unit/loadImbalance/LIEstimatorPhaseTest.cpp @@ -6,12 +6,12 @@ #include "gtest/gtest.h" -#include "LoggerUtil.h" -#include "MCGManager.h" #include "MetaData/PGISMetaData.h" #include "PiraMCGProcessor.h" #include "loadImbalance/LIEstimatorPhase.h" #include "loadImbalance/LIMetaData.h" +#include "metacg/LoggerUtil.h" +#include "metacg/MCGManager.h" #include class LIEstimatorPhaseTest : public ::testing::Test { diff --git a/pgis/test/unit/loadImbalance/LIMetricTest.cpp b/pgis/test/unit/loadImbalance/LIMetricTest.cpp index e57a88f0..14472eb3 100644 --- a/pgis/test/unit/loadImbalance/LIMetricTest.cpp +++ b/pgis/test/unit/loadImbalance/LIMetricTest.cpp @@ -4,8 +4,8 @@ * https://github.com/tudasc/metacg/LICENSE.txt */ -#include "Callgraph.h" #include "MetaData/CgNodeMetaData.h" +#include "metacg/Callgraph.h" #include "gtest/gtest.h" #include #include diff --git a/pgis/tool/PGISMain.cpp b/pgis/tool/PGISMain.cpp index 13968f33..95247d9d 100644 --- a/pgis/tool/PGISMain.cpp +++ b/pgis/tool/PGISMain.cpp @@ -8,17 +8,17 @@ #include "config/ParameterConfig.h" #include "CubeReader.h" -#include "DotIO.h" #include "ErrorCodes.h" #include "ExtrapEstimatorPhase.h" #include "IPCGEstimatorPhase.h" #include "LegacyMCGReader.h" -#include "LoggerUtil.h" #include "MetaData/PGISMetaData.h" #include "PiraMCGProcessor.h" #include "Utility.h" -#include "io/VersionTwoMCGReader.h" -#include "io/VersionTwoMCGWriter.h" +#include "metacg/DotIO.h" +#include "metacg/LoggerUtil.h" +#include "metacg/io/VersionTwoMCGReader.h" +#include "metacg/io/VersionTwoMCGWriter.h" #include #include #include diff --git a/pymetacg/README.md b/pymetacg/README.md index b9af0878..da7be888 100644 --- a/pymetacg/README.md +++ b/pymetacg/README.md @@ -83,6 +83,9 @@ main_has_statement_metadata = "numStatements" in main.meta_data # access JSON representation of node's meta data using `.data` (independent of meta data type) metadata = main.meta_data["numStatements"].data + +# global meta data can be accessed in the same way +global_metadata = cg.meta_data["someGlobalMetaData"] ``` ## Tests diff --git a/pymetacg/pymetacg.cpp b/pymetacg/pymetacg.cpp index ef178f7b..d57a2a6b 100644 --- a/pymetacg/pymetacg.cpp +++ b/pymetacg/pymetacg.cpp @@ -12,25 +12,25 @@ #include #include -#include -#include -#include +#include +#include +#include -#include -#include -#include -#include +#include +#include +#include +#include // BuiltinMD.h is not used directly here, but including this header // ensures that MetaCG's built-in metadata types become part of this // translation unit and are hence included in the pymetacg dynamic library. -#include // IWYU pragma: keep +#include // IWYU pragma: keep #include #include "util.h" -#include +#include namespace nb = nanobind; using namespace metacg::pymetacg; @@ -42,7 +42,7 @@ NB_MODULE(pymetacg, m) { nb::class_(m, "MetaData") .def_prop_ro("key", [](const MetaDataWrapper& self) { return self.md->getKey(); }) .def_prop_ro("data", [](const MetaDataWrapper& self) { - NameMapping mapping(self.graph); + metacg::NameMapping mapping(self.graph); return self.md->toJson(mapping); }); @@ -139,5 +139,7 @@ NB_MODULE(pymetacg, m) { return CgNodeWrapper{self.getNode(nodes[0]), self}; } }) - .def("__contains__", [](const metacg::Callgraph& self, const std::string& key) { return self.hasNode(key); }); + .def("__contains__", [](const metacg::Callgraph& self, const std::string& key) { return self.hasNode(key); }) + .def_prop_ro("meta_data", + [](const metacg::Callgraph& self) { return MetaDataContainer{self.getMetaDataContainer(), self}; }); } diff --git a/pymetacg/tests/DummyMD.cpp b/pymetacg/tests/DummyMD.cpp index 484d8b83..7a1888d3 100644 --- a/pymetacg/tests/DummyMD.cpp +++ b/pymetacg/tests/DummyMD.cpp @@ -5,7 +5,7 @@ */ #include -#include +#include class DummyMD : public metacg::MetaData::Registrar { public: diff --git a/pymetacg/tests/resources/basic_cg_v4.mcg b/pymetacg/tests/resources/basic_cg_v4.mcg index b92942f0..94995df5 100644 --- a/pymetacg/tests/resources/basic_cg_v4.mcg +++ b/pymetacg/tests/resources/basic_cg_v4.mcg @@ -1,6 +1,8 @@ { "_CG": { - "meta": {}, + "meta": { + "dummy_md": {} + }, "nodes": { "0": { "callees": { diff --git a/pymetacg/tests/test_metadata.py b/pymetacg/tests/test_metadata.py index ecba0331..81eaf7c7 100644 --- a/pymetacg/tests/test_metadata.py +++ b/pymetacg/tests/test_metadata.py @@ -23,3 +23,9 @@ def test_custom_metadata(metadata_cg): md = n.meta_data["dummy_md"] assert md.key == "dummy_md" assert md.data == {"key1": "some string", "key2": 42} + +def test_global_metadata(v4_cg): + assert "dummy_md" in v4_cg.meta_data + md = v4_cg.meta_data["dummy_md"] + assert md.key == "dummy_md" + assert md.data == {"key1": "some string", "key2": 42} diff --git a/pymetacg/util.h b/pymetacg/util.h index ce8ec981..733bdda4 100644 --- a/pymetacg/util.h +++ b/pymetacg/util.h @@ -6,11 +6,11 @@ #pragma once -#include "io/IdMapping.h" -#include -#include -#include -#include +#include "metacg/io/NameMapping.h" +#include +#include +#include +#include #include #include @@ -41,16 +41,6 @@ struct MetaDataWrapper { const metacg::Callgraph& graph; }; -struct NameMapping : metacg::NodeToStrMapping { - public: - NameMapping(const metacg::Callgraph& graph) : _graph(graph) {} - - virtual std::string getStrFromNode(metacg::NodeId id) override { return _graph.getNode(id)->getFunctionName(); } - - private: - const metacg::Callgraph& _graph; -}; - /** * Auxiliary iterator to iterate over metacg::Callgraph::NodeContainer and pack CgNode* into CgNodeWrapper */ diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 01f07f11..02ac6fe7 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -1,3 +1,10 @@ +add_subdirectory(cgcollector2) add_subdirectory(cgmerge2) add_subdirectory(cgconvert) add_subdirectory(cgformat) +add_subdirectory(cgdiff) +add_subdirectory(cgquery) +add_subdirectory(cgtodot) +if(BUILD_CAGE) + add_subdirectory(cage) +endif() diff --git a/tools/README.md b/tools/README.md index 9a1bec46..b9788eed 100644 --- a/tools/README.md +++ b/tools/README.md @@ -2,6 +2,49 @@ This folder contains tools for creating and merging call graphs using the MetaCG graph library. +## CaGe + +CaGe is MetaCG's link-time call graph generator. +To use it, the target application needs to be built with (full) LTO using the LLD linker. + +### Basic Usage +Let's consider a project that consists of two source files, `example_a.cpp` and `example_b.cpp`. +A standard build process consists of a compile and a link step: +``` +# Compile step +clang++ example_a.cpp -o example_a.o +clang++ example_b.cpp -o example_b.o + +# Link step +clang++ example_a.o example_b.o -o example +``` + +Modifying this build process to generate a call graph with CaGe is straightforward. +All necessary compile flags can be generated with `metacg-config`: + +``` +# Compile step +clang++ $(metacg-config --cage-cxxflags) example_a.cpp -o example_a.o +clang++ $(metacg-config --cage-cxxflags) example_b.cpp -o example_b.o + +# Link step +clang++ $(metacg-config --cage-ldflags --cage-pass-option -cg-file=example.mcg) example_a.o example_b.o -o example +``` + +### Build system integration +Integrating CaGe into build systems, e.g. Make and CMake, is simple. +Many Make projects already define `CXXFLAGS` and `LDFLAGS` variables, which can be extended with the respective +`metacg-config` output. +For CMake projects, the relevant options are `CMAKE_CXX_FLAGS`, `CMAKE_EXE_LINKER_FLAGS` and `CMAKE_SHARED_LINKER_FLAGS`. + +### Pass options +Pass options can be set by passing `--cage-pass-option