Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
7155b2d
Add modified version of CaGe
sebastiankreutzer Aug 22, 2025
d47f258
[WIP] CaGe tests
sebastiankreutzer Sep 4, 2025
a2e97c3
[WIP] CaGe tests
sebastiankreutzer Sep 4, 2025
1cd8af3
Add testing infrastructure for CaGe (WIP)
sebastiankreutzer Nov 14, 2025
db04374
[WIP] Cage test cases
sebastiankreutzer Dec 16, 2025
3747e75
Add CaGe test suites
Dec 17, 2025
668cd70
Adjust cage cxxflags
Dec 17, 2025
76068e7
Fix cage test cmake formatting
Dec 17, 2025
9c02df8
Fix metacg-config
Dec 17, 2025
558916c
Add CaGe tests to CI
Dec 17, 2025
a8e70d1
fixup! Fix metacg-config
Dec 17, 2025
4ca8c93
Clean up cage test script and add -g for tests
Dec 18, 2025
dc7136f
Add verbosity option to cage test runner
Dec 18, 2025
5593f02
Fix empty linkage name bug
Dec 18, 2025
1191333
[CaGe] Add option for handling indirect calls
Dec 18, 2025
de8247b
[CaGe] Add PTA pass options
Dec 18, 2025
34b2564
[CaGe] Add test variants for different PTA modes and fix tests
Dec 18, 2025
7244c3e
Formatting
Dec 18, 2025
e36a9ea
[CaGe] Add another test
Dec 18, 2025
805c45d
[CaGe] Set 'hasBody' correctly
Dec 18, 2025
65bda6c
[CaGe] Implement first metadata collector
Dec 18, 2025
0d8c543
[CaGe] Make export file configurable via pass option
Dec 19, 2025
15a238e
[CaGe] Use seperate metacg-config for test purposes
Dec 19, 2025
d433380
[CaGe] Update tools README
Dec 19, 2025
8cd020d
[CaGe] Refactoring metacg-config options and fixing CMake for LLVM<15
Jan 9, 2026
b1b2cb8
Formatting
sebastiankreutzer Jan 9, 2026
d0d4b86
Fix gitlab CI for CaGe
Jan 9, 2026
4d274c3
Explicitly turn on METAVIRT option in gitlab CI
Jan 9, 2026
368fa0a
[CaGe] Workaround in test script for different LLVM versions
Jan 9, 2026
f599708
[CaGe] Restrict CaGe to LLVM 16+ due to issues with MetaVirt
Jan 12, 2026
5bf74d9
[CaGe] Remove metadata collector for initial PR
Jan 12, 2026
307aebe
[CaGe] Update README
Jan 12, 2026
f16815f
[CaGe] Build with MetaVirt in container
Jan 12, 2026
6d971a8
[CaGe] Incorporate reviewer feedback
Jan 19, 2026
c59a8d8
fixup! [CaGe] Incorporate reviewer feedback
sebastiankreutzer Jan 19, 2026
34f0d1a
fixup! fixup! [CaGe] Incorporate reviewer feedback
sebastiankreutzer Jan 19, 2026
1f00058
fixup! fixup! fixup! [CaGe] Incorporate reviewer feedback
sebastiankreutzer Jan 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/mcg-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,13 @@ jobs:
run: |
cd /opt/metacg/tools/cgcollector2/test
bash testBase.sh
- name: Run CaGe tests
uses: addnab/docker-run-action@v3
with:
image: metacg-devel:latest
run: |
cd /opt/metacg/build/tools/cage/test
./runCaGeTests.sh
- name: Run cgvalidate tests
uses: addnab/docker-run-action@v3
with:
Expand Down
14 changes: 14 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ build-mcg:
-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
Expand Down Expand Up @@ -269,6 +270,19 @@ test-cgcollector2:
- cd tools/cgcollector2/test/
- ./testBase.sh -b $MCG_BUILD

test-cage:
<<: *job-setup
parallel:
matrix:
- GCC: 11
LLVM: [16.0.6, 17.0.6, 18.1.8]
stage: integration-test
needs: ["build-mcg"]
script:
- module load clang/$LLVM
- cd $MCG_BUILD/tools/cage/test
- ./runCaGeTests.sh

test-basic-pgis:
<<: *job-setup
stage: integration-test
Expand Down
24 changes: 24 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -91,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
15
)
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()

Expand Down Expand Up @@ -125,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
Expand Down
14 changes: 14 additions & 0 deletions cmake/metavirt.cmake
Original file line number Diff line number Diff line change
@@ -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)
1 change: 1 addition & 0 deletions container/full-build
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ RUN cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=Debug \
-DMETACG_BUILD_GRAPH_TOOLS=ON \
-DMETACG_BUILD_CGPATCH=ON \
-DMETACG_BUILD_GRAPH_TOOLS=ON \
-DCAGE_USE_METAVIRT=ON \
-DMETACG_BUILD_PYMETACG=ON \
-DPython_ROOT_DIR=/opt/metacg/.venv \
-DPYTEST_EXECUTABLE=/opt/metacg/.venv/bin/pytest \
Expand Down
1 change: 1 addition & 0 deletions graph/src/Callgraph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ CgNode* Callgraph::getMain(bool forceRecompute) const {

CgNode& Callgraph::insert(const std::string& function, std::optional<std::string> 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));
Expand Down
3 changes: 3 additions & 0 deletions tools/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ add_subdirectory(cgconvert)
add_subdirectory(cgformat)
add_subdirectory(cgdiff)
add_subdirectory(cgquery)
if(BUILD_CAGE)
add_subdirectory(cage)
endif()
43 changes: 43 additions & 0 deletions tools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <option>(=<val>)` to `metacg-config`.
Available options:
- `-cg-file=<filename>`: The output file for the generated call graph.
- `-pta=no/signature`: Controls the behavior of the points-to analysis for resolving indirect calls. Available options are:
- `no`: Indirect calls are ignored.
- `signature`: Adds all functions with matching signature as potential call targets.

## CGMerge2

CGMerge2 is a front-end for MetaCG's call graph merging functionality.
Expand Down
4 changes: 4 additions & 0 deletions tools/cage/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
add_subdirectory(src)
add_subdirectory(test)

install(TARGETS cage cage-plugin LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
22 changes: 22 additions & 0 deletions tools/cage/include/cage/Plugin.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* File: Plugin.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_CAGE_PLUGIN_H
#define METACG_CAGE_PLUGIN_H

#include "llvm/Pass.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Passes/PassPlugin.h"

namespace cage {

struct CaGe : llvm::PassInfoMixin<CaGe> {
llvm::PreservedAnalyses run(llvm::Module& M, llvm::ModuleAnalysisManager& MA);

static llvm::StringRef name() { return "CaGe"; }
};
} // namespace cage

#endif // METACG_CAGE_PLUGIN_H
22 changes: 22 additions & 0 deletions tools/cage/include/cage/generator/CallGraphConsumer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* File: CallGraphConsumer.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_CALLGRAPHCONSUMER_H
#define METACG_CALLGRAPHCONSUMER_H

namespace metacg {
class Callgraph;
}

namespace cage {

struct CallGraphConsumer {
virtual ~CallGraphConsumer() = default;
virtual void consumeCallGraph(metacg::Callgraph&) = 0;
};

} // namespace cage

#endif // METACG_CALLGRAPHCONSUMER_H
30 changes: 30 additions & 0 deletions tools/cage/include/cage/generator/CallgraphGenerator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* File: CallGraphGenerator.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
*/
#include "cage/generator/CallGraphConsumer.h"

#include "io/MCGWriter.h"

#include "llvm/IR/Module.h"
#include "llvm/IR/PassManager.h"

namespace cage {

enum PTAType { No, BySignature };

class Generator {
public:
explicit Generator(PTAType ptaType) : ptaType(ptaType) {};

void addConsumer(std::unique_ptr<CallGraphConsumer> consumer) { consumers.push_back(std::move(consumer)); }

bool run(llvm::Module& M, llvm::ModuleAnalysisManager* MA);

private:
PTAType ptaType;
std::vector<std::unique_ptr<CallGraphConsumer>> consumers;
};

} // namespace cage
26 changes: 26 additions & 0 deletions tools/cage/include/cage/generator/FileExporter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* File: FileExporter.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_FILEEXPORTER_H
#define METACG_FILEEXPORTER_H

#include "cage/generator/CallGraphConsumer.h"

#include <string>

namespace cage {

class FileExporter : public CallGraphConsumer {
public:
FileExporter(std::string outfile) : outfile(outfile) {}
void consumeCallGraph(metacg::Callgraph&) override;

private:
std::string outfile;
};

} // namespace cage

#endif // METACG_FILEEXPORTER_H
7 changes: 7 additions & 0 deletions tools/cage/src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
include(AddLLVM)

add_llvm_pass_plugin(cage-plugin Plugin.cpp)
target_include_directories(cage-plugin PRIVATE $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/tools/cage/include>)
target_link_libraries(cage-plugin PRIVATE cage)

add_subdirectory(generator)
97 changes: 97 additions & 0 deletions tools/cage/src/Plugin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/**
* File: Plugin.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 "cage/Plugin.h"

#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Passes/PassPlugin.h"

#include "cage/generator/CallgraphGenerator.h"
#include "cage/generator/FileExporter.h"

using namespace llvm;

using namespace llvm::cl;

static OptionCategory cageOpts("CaGe");

static opt<bool> cageVerbose("cage-verbose", desc("Print debugging output"), cat(cageOpts), init(false));

static opt<cage::PTAType> pta(
"pta", desc("Points-to analysis of indirect calls:"),
values(clEnumValN(cage::PTAType::No, "no", "Ignore indirect calls"),
clEnumValN(
cage::PTAType::BySignature, "signature",
"Treat all available valid function signatures for a given function pointer as potential call target ")),
cat(cageOpts), init(cage::PTAType::No));

static opt<std::string> cgout("cg-file", desc("Output file for the generated call graph"), cat(cageOpts), init(""));

namespace cage {
PreservedAnalyses CaGe::run(Module& M, ModuleAnalysisManager& MA) {
if (cageVerbose) {
outs() << "Running CaGe in verbose mode\n";
}

// First check explicit option
std::string outfile = cgout.getValue();
if (outfile.empty()) {
// If empty, check environment variable
const auto* cgNameEnv = std::getenv("CAGE_CG");
if (cgNameEnv) {
outfile = cgNameEnv;
} else {
// Default output file
outfile = "cage_callgraph.mcg";
}
}

Generator gen(pta);
gen.addConsumer(std::make_unique<FileExporter>(cgout));

if (!gen.run(M, &MA))
return PreservedAnalyses::all();

return PreservedAnalyses::none();
}
} // namespace cage

llvm::PassPluginLibraryInfo getPluginInfo() {
return {
LLVM_PLUGIN_API_VERSION, "CaGe", "0.2", [](PassBuilder& PB) {
// allow registration via optlevel (non-lto)
#if LLVM_VERSION_MAJOR >= 20
PB.registerOptimizerLastEPCallback([](ModulePassManager& PM, OptimizationLevel, ThinOrFullLTOPhase) {
#else
PB.registerOptimizerLastEPCallback([](ModulePassManager& PM, OptimizationLevel) {
#endif
outs() << "Registering CaGe to run during opt\n";
PM.addPass(cage::CaGe());
});

// registering via optlevel during lto appears to still be broken
PB.registerFullLinkTimeOptimizationLastEPCallback([](ModulePassManager& PM, OptimizationLevel o) {
outs() << "Registering CaGe to run during full LTO\n";
PM.addPass(cage::CaGe());
});

// allow registration via pipeline parser
PB.registerPipelineParsingCallback(
[](StringRef Name, ModulePassManager& MPM, ArrayRef<llvm::PassBuilder::PipelineElement>) {
if (Name == "CaGe") {
outs() << "Registering CaGe to run as pipeline described\n";
MPM.addPass(cage::CaGe());
return true;
} else {
outs() << "Did not register CaGe\n";
}
return false;
});
}
};
}

extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() { return getPluginInfo(); }
12 changes: 12 additions & 0 deletions tools/cage/src/generator/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
add_library(cage STATIC CallGraphGenerator.cpp FileExporter.cpp)
set_target_properties(cage PROPERTIES POSITION_INDEPENDENT_CODE ON)
target_include_directories(cage PRIVATE $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/tools/cage/include>)
target_include_directories(cage PUBLIC $<BUILD_INTERFACE:${LLVM_INCLUDE_DIRS}>)

if(${CAGE_USE_METAVIRT})
target_link_libraries(cage PRIVATE metavirt::Types)
target_compile_definitions(cage PRIVATE "HAVE_METAVIRT")
endif()

target_link_libraries(cage PUBLIC metacg::metacg)
add_config_include(cage)
Loading