From 5b806c15af712fdc3a7081ba3084b5b33aebeb96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20H=C3=BCck?= Date: Sat, 31 Jan 2026 19:27:03 +0100 Subject: [PATCH 1/9] Make compatible with LLVM 19 and 21 --- include/Util.h | 22 +++++++++++++++++++++- src/printer/LLVMTool.cpp | 8 ++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/include/Util.h b/include/Util.h index f7a1312..d104624 100644 --- a/include/Util.h +++ b/include/Util.h @@ -9,10 +9,30 @@ #include "llvm/Support/Regex.h" #include "llvm/Support/raw_ostream.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Config/llvm-config.h" #include "llvm/IR/Module.h" namespace irprinter::util { +template +inline bool starts_with_any_of(llvm::StringRef lhs, StringTy... rhs) { +#if LLVM_VERSION_MAJOR > 15 + return !lhs.empty() && ((lhs.starts_with(rhs)) || ...); +#else + return !lhs.empty() && ((lhs.startswith(rhs)) || ...); +#endif +} + +template +inline bool ends_with_any_of(llvm::StringRef lhs, StringTy... rhs) { +#if LLVM_VERSION_MAJOR > 15 + return !lhs.empty() && ((lhs.ends_with(rhs)) || ...); +#else + return !lhs.empty() && ((lhs.endswith(rhs)) || ...); +#endif +} + namespace detail { // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf : template @@ -75,7 +95,7 @@ inline std::string dump(const Val& s) { template inline std::string try_demangle(String s) { std::string name{s}; -#if LLVM_VERSION_MAJOR == 18 +#if LLVM_VERSION_MAJOR >= 18 auto demangle = llvm::itaniumDemangle(s.data()); #else auto demangle = llvm::itaniumDemangle(s.data(), nullptr, nullptr, nullptr); diff --git a/src/printer/LLVMTool.cpp b/src/printer/LLVMTool.cpp index 23ebf8f..4684c59 100644 --- a/src/printer/LLVMTool.cpp +++ b/src/printer/LLVMTool.cpp @@ -6,7 +6,9 @@ */ #include "printer/LLVMTool.h" +#include "Util.h" +#include #include #include @@ -29,7 +31,7 @@ ArgumentsAdjuster getStripOptFlagAdjuster() { CommandLineArguments AdjustedArgs; for (size_t i = 0, e = Args.size(); i < e; ++i) { StringRef Arg = Args[i]; - if (!Arg.startswith("-O") && !Arg.startswith("-g")) { + if (!util::starts_with_any_of(Arg, "-O", "-g")) { AdjustedArgs.push_back(Args[i]); } } @@ -49,7 +51,9 @@ class ExtractorAction : public CodeGenAction { bool BeginSourceFileAction(CompilerInstance& CI) { // FIXME workaround for error: "clang: Not enough positional command line arguments specified!" // when using clangtool, the commandline parser is executed twice, this removes a leftover causing the above error +#if LLVM_VERSION_MAJOR < 19 llvm::cl::TopLevelSubCommand->PositionalOpts.clear(); +#endif return CodeGenAction::BeginSourceFileAction(CI); } @@ -118,7 +122,7 @@ void LLVMTool::clearUserFlags() { void LLVMTool::commitUserArgs() { for (auto& arg : user_args) { StringRef flag = arg; - if (flag.startswith("-O") || flag.startswith("-g")) { + if (util::starts_with_any_of(flag, "-O", "-g")) { tool.appendArgumentsAdjuster(combineAdjusters( adjuster::getStripOptFlagAdjuster(), getInsertArgumentAdjuster(flag.data(), ArgumentInsertPosition::END))); } else { From 4492ad8670d063895d1e263c2cd8f014bdfdc903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20H=C3=BCck?= Date: Sun, 1 Feb 2026 12:54:21 +0100 Subject: [PATCH 2/9] Debug location based printing (#6) --- .github/workflows/basic-ci.yml | 37 +++++++--- README.md | 122 ++++++++++++++++++--------------- include/printer/IRNodeFinder.h | 2 + src/main.cpp | 18 ++++- src/printer/IRNodeFinder.cpp | 33 +++++++++ src/printer/LLVMTool.cpp | 2 +- 6 files changed, 144 insertions(+), 70 deletions(-) diff --git a/.github/workflows/basic-ci.yml b/.github/workflows/basic-ci.yml index 4f94ea5..96c9c36 100644 --- a/.github/workflows/basic-ci.yml +++ b/.github/workflows/basic-ci.yml @@ -11,18 +11,31 @@ env: jobs: format-check: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 + + - run: | + wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - + echo "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-18 main" | sudo tee /etc/apt/sources.list.d/llvm-18.list + + - name: Update apt + run: sudo apt-get update + + - name: Install clang-format + run: | + sudo apt-get remove clang-format-* + sudo apt-get install -t llvm-toolchain-noble-18 clang-format-18 - name: Format source code run: | find demo lib test \ -type f \ -a \( -name "*.c" -o -name "*.cpp" -o -name "*.h" \) \ + -not -path "*/lulesh/*" -not -path "*/CallSite.h" \ -print0 \ - | xargs -0 clang-format-14 -i + | xargs -0 clang-format-18 -i - name: Format check run: | @@ -33,28 +46,34 @@ jobs: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 - - uses: codespell-project/actions-codespell@v2 + - uses: actions/checkout@v5 + - uses: codespell-project/actions-codespell@v2.2 build-project: strategy: fail-fast: false matrix: include: - - llvm-version: 12 - os: ubuntu-20.04 - preset: develop - llvm-version: 14 os: ubuntu-22.04 preset: develop - llvm-version: 18 os: ubuntu-24.04 preset: develop + - llvm-version: 21 + os: ubuntu-24.04 + preset: develop runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 + + - name: LLVM apt + if: ${{ matrix.llvm-version >= 19 }} + run: | + wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - + echo "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-${{ matrix.llvm-version }} main" | sudo tee /etc/apt/sources.list.d/llvm-${{ matrix.llvm-version }}.list - name: Update apt run: sudo apt-get update diff --git a/README.md b/README.md index 9c6a7cb..b4672be 100644 --- a/README.md +++ b/README.md @@ -1,86 +1,94 @@ # irprinter · ![License](https://img.shields.io/github/license/ahueck/irprinter) -*irprinter* is a command-line tool for exploring LLVM Intermediate Representation (IR) code. -It allows users to print IR code for specific functions, which is particularly useful when dumping the entire translation unit would result in excessive output. - +*irprinter* is a command-line tool for exploring LLVM Intermediate Representation (IR). +It allows you to print IR for specific functions, which is especially useful when dumping an entire translation unit would produce excessive output. ## Features -* Print LLVM IR code for a translation unit (C/C++) to the console. -* Modify and add compiler flags (e.g., replace -g with -O2) and regenerate the (modified) IR. -* Regex matching of (demangled) function names, with options to print: +* Print LLVM IR for a translation unit (C/C++) to the console. +* Modify or add compiler flags (e.g., replacing `-g` with `-O2`) and regenerate the IR. +* Match (demangled) function names using regular expressions, with options to print: 1. Function signatures only. - 2. Functions including their bodies. -* Dump the entire IR code of the translation unit. + 2. Full function bodies. +* Dump the entire IR of a translation unit. +* Print statements within a specific line number range (based on debug information). ## Usage -See [main.cpp](src/main.cpp) for all possible command-line arguments. +Refer to [main.cpp](src/main.cpp) for a full list of command-line arguments. + +### Example +Assume `test.c` contains the following code: -### Example of using *irprinter* -Assume *test.c* contains the code: +```c +int foo () { + return 2; +} +int main() { + int val; + val = foo(); + return 0; +} +``` - ```c - int foo () { - return 2; - } - int main() { - int val; - val = foo(); - return 0; - } - ``` #### Using irprinter on test.c -In this example we - 1) load `test.c` with standard Clang flags, - 2) list all functions in `test.c`, - 3) print the body of main, - 4) optimize the code with `-O3`, and finally, - 5) print the body of main again. +In this example, we: +1. Load `test.c` with standard Clang and debug flags. +2. List all functions in `test.c`. +3. Print the body of `main`. +4. Print statements within the line range [6, 7]. +5. Optimize the code with `-O3`. +6. Print the body of `main` again to see the result of the optimization. ```console -ahueck@sys:~/irprint/install$ ./bin/irprinter ../test.c -- +$~/irprint/install$ ./bin/irprinter ../test.c -- -g ir-printer> l -Match 1 [foo]: -; Function Attrs: noinline nounwind optnone uwtable -define i32 @foo() #0 +Match 1 [foo()]: +; Function Attrs: mustprogress noinline nounwind optnone uwtable +define dso_local noundef i32 @_Z3foov() #0 !dbg !10 Match 2 [main]: -; Function Attrs: noinline nounwind optnone uwtable -define i32 @main() #0 +; Function Attrs: mustprogress noinline norecurse nounwind optnone uwtable +define dso_local noundef i32 @main() #1 !dbg !10 ir-printer> p main -Match 1 [main]: -; Function Attrs: noinline nounwind optnone uwtable -define i32 @main() #0 { - %1 = alloca i32, align 4 - %2 = alloca i32, align 4 - store i32 0, i32* %1, align 4 - %3 = call i32 @foo() - store i32 %3, i32* %2, align 4 - ret i32 0 +Match 1 [main]:; Function Attrs: mustprogress noinline norecurse nounwind optnone uwtable +define dso_local noundef i32 @main() #1 !dbg !10 { +entry: + %retval = alloca i32, align 4 + %val = alloca i32, align 4 + store i32 0, ptr %retval, align 4 + #dbg_declare(ptr %val, !16, !DIExpression(), !17) + %call = call noundef i32 @_Z3foov(), !dbg !18 + store i32 %call, ptr %val, align 4, !dbg !19 + ret i32 0, !dbg !20 } +ir-printer> 6 7 +main: + %call = call noundef i32 @_Z3foov(), !dbg !18 + store i32 %call, ptr %val, align 4, !dbg !19 + ret i32 0, !dbg !20 + ir-printer> f -O3 Set flag to -O3. Re-generating module... ir-printer> p main -Match 1 [main]: -; Function Attrs: norecurse nounwind readnone uwtable -define i32 @main() local_unnamed_addr #1 { +Match 1 [main]:; Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none) uwtable +define dso_local noundef i32 @main() local_unnamed_addr #0 { +entry: ret i32 0 } - ``` -## How to build -###### Requirements +## How to Build + +### Requirements - CMake >= 3.20 -- Clang/LLVM 12, 14, 18 (CMake needs to find the installation, see - the [LLVM CMake documentation](https://llvm.org/docs/CMake.html) or the [CI workflow](.github/workflows/basic-ci.yml)) -- C++17 compiler +- Clang/LLVM 12, 14, or 18-21 (CMake must be able to find the installation; see the [LLVM CMake documentation](https://llvm.org/docs/CMake.html) or the [CI workflow](.github/workflows/basic-ci.yml)) +- A C++17 compatible compiler -###### Build steps -In the root project folder, execute the following commands (see also [CI workflow](.github/workflows/basic-ci.yml)) +### Build Steps +From the root project folder, execute the following commands: - ``` - cmake -B build -DCMAKE_INSTALL_PREFIX=*your path* - cmake --build build --target install --parallel - ``` +```bash +cmake -B build -DCMAKE_INSTALL_PREFIX=/path/to/install +cmake --build build --target install --parallel +``` \ No newline at end of file diff --git a/include/printer/IRNodeFinder.h b/include/printer/IRNodeFinder.h index 461b591..cedf6bd 100644 --- a/include/printer/IRNodeFinder.h +++ b/include/printer/IRNodeFinder.h @@ -37,6 +37,8 @@ class IRNodeFinder { void printFunction(const std::string& regex = ".*") const; + void printByLocation(unsigned line_start_, unsigned line_end_ = 0) const; + void listFunction(const std::string& regex = ".*") const; static std::string demangle(const std::string& name); diff --git a/src/main.cpp b/src/main.cpp index 1f205f6..fa4e5d4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,11 +1,11 @@ #include #include -#include #include #include #include #include +#include #include using namespace llvm; @@ -53,7 +53,6 @@ int main(int argc, const char** argv) { irprinter::IRNodeFinder ir(op.get()); #endif - auto ret = ir.parse(); if (ret != 0) { llvm::outs() << "Error parsing. Quitting...\n"; @@ -67,6 +66,20 @@ int main(int argc, const char** argv) { if (cmd == "q" || cmd == "quit") { break; + } else if (unsigned start; !cmd.getAsInteger(10, start)) { + auto end_ref = lexWord(StringRef(cmd.end(), ref.end() - cmd.end())); + unsigned end{start}; + if (!end_ref.empty()) { + if (end_ref.getAsInteger(10, end)) { + llvm::outs() << "Invalid end location: " << end_ref << "\n"; + continue; + } + } + if (end < start) { + llvm::outs() << "Error: end location (" << end << ") is less than start location (" << start << ")\n"; + } else { + ir.printByLocation(start, end); + } } else if (cmd == "g" || cmd == "generate") { ir.parse(); } else if (cmd == "f" || cmd == "flag") { @@ -98,7 +111,6 @@ int main(int argc, const char** argv) { auto demangled_name = irprinter::IRNodeFinder::demangle(std::string{str}); llvm::outs() << "Demangled name: " << demangled_name << "\n"; } - llvm::outs().flush(); } diff --git a/src/printer/IRNodeFinder.cpp b/src/printer/IRNodeFinder.cpp index 04a356c..5646aae 100644 --- a/src/printer/IRNodeFinder.cpp +++ b/src/printer/IRNodeFinder.cpp @@ -9,8 +9,11 @@ #include "Util.h" #include +#include #include +#include #include +#include using namespace llvm; @@ -52,6 +55,36 @@ void IRNodeFinder::printFunction(const std::string& regex) const { applyToMatchingFunction(os, m, regex, [&](const Function* f) { f->print(os); }); } +void IRNodeFinder::printByLocation(unsigned line_start_, unsigned line_end_) const { + line_end_ = std::max(line_start_, line_end_); + std::string matches; + llvm::raw_string_ostream local_oss{matches}; + bool first_match{false}; + const auto* m = tool.getModule(); + for (const auto& f : *m) { + first_match = false; + for (const auto& bb : f) { + for (const auto& inst : bb) { + const auto& loc = inst.getDebugLoc(); + if (loc) { + const auto line = loc.getLine(); + if (line >= line_start_ && line <= line_end_) { + if (!first_match) { + local_oss << f.getName() << ":\n"; + first_match = true; + } + inst.print(local_oss); + local_oss << "\n"; + } + } + } + } + } + if (!local_oss.str().empty()) { + os << local_oss.str() << "\n"; + } +} + void IRNodeFinder::listFunction(const std::string& regex) const { const auto* m = tool.getModule(); applyToMatchingFunction(os, m, regex, [&](const Function* f) { diff --git a/src/printer/LLVMTool.cpp b/src/printer/LLVMTool.cpp index 4684c59..083286c 100644 --- a/src/printer/LLVMTool.cpp +++ b/src/printer/LLVMTool.cpp @@ -8,9 +8,9 @@ #include "printer/LLVMTool.h" #include "Util.h" -#include #include #include +#include #include #include From 8ba04f824a0097be403eafb34b1495ed568d2b10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20H=C3=BCck?= Date: Sun, 1 Feb 2026 16:06:19 +0100 Subject: [PATCH 3/9] Update CMake install rules --- src/CMakeLists.txt | 8 -------- src/printer/CMakeLists.txt | 8 +++----- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b0a122f..37e1efb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -11,11 +11,6 @@ target_project_compile_definitions(llvm-ir-printer${EXE_SUFFIX} LOG_LEVEL=${LOG_LEVEL} ) -target_include_directories(llvm-ir-printer${EXE_SUFFIX} - PRIVATE - ${PROJECT_SOURCE_DIR}/include - ) - target_link_libraries(llvm-ir-printer${EXE_SUFFIX} LLVMLineEditor llvm-ir-print @@ -24,7 +19,4 @@ target_link_libraries(llvm-ir-printer${EXE_SUFFIX} install( TARGETS llvm-ir-printer${EXE_SUFFIX} RUNTIME DESTINATION bin - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib - PUBLIC_HEADER DESTINATION include ) diff --git a/src/printer/CMakeLists.txt b/src/printer/CMakeLists.txt index 09dcd7e..8acb181 100644 --- a/src/printer/CMakeLists.txt +++ b/src/printer/CMakeLists.txt @@ -12,7 +12,8 @@ target_project_compile_definitions(llvm-ir-print target_include_directories(llvm-ir-print PUBLIC - ${PROJECT_SOURCE_DIR}/include + $ + $ ) target_include_directories(llvm-ir-print @@ -30,13 +31,10 @@ install( TARGETS llvm-ir-print LIBRARY DESTINATION lib ARCHIVE DESTINATION lib - PUBLIC_HEADER DESTINATION include ) install( - FILES - ${PROJECT_SOURCE_DIR}/include/printer/IRNodeFinder.h - ${PROJECT_SOURCE_DIR}/include/printer/LLVMTool.h + DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include ) From 1c93c3db3baa84725c7b3250ba18448fb6b325ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20H=C3=BCck?= Date: Sun, 1 Feb 2026 17:26:32 +0100 Subject: [PATCH 4/9] Constructor for compilation database --- include/printer/IRNodeFinder.h | 2 ++ include/printer/LLVMTool.h | 3 +++ src/printer/IRNodeFinder.cpp | 9 ++++++++- src/printer/LLVMTool.cpp | 6 +++++- 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/include/printer/IRNodeFinder.h b/include/printer/IRNodeFinder.h index cedf6bd..86f917f 100644 --- a/include/printer/IRNodeFinder.h +++ b/include/printer/IRNodeFinder.h @@ -28,6 +28,8 @@ class IRNodeFinder { public: explicit IRNodeFinder(clang::tooling::CommonOptionsParser& op, llvm::raw_ostream& os = llvm::outs()); + IRNodeFinder(const clang::tooling::CompilationDatabase& compilation_database, llvm::ArrayRef SourcePaths, + llvm::raw_ostream& os = llvm::outs()); int parse(); diff --git a/include/printer/LLVMTool.h b/include/printer/LLVMTool.h index e008d7f..669fbbc 100644 --- a/include/printer/LLVMTool.h +++ b/include/printer/LLVMTool.h @@ -9,8 +9,10 @@ #define SRC_PRINTER_LLVMTOOL_H_ #include +#include #include +#include #include #include #include @@ -35,6 +37,7 @@ class LLVMTool { public: explicit LLVMTool(clang::tooling::CommonOptionsParser&); + LLVMTool(const clang::tooling::CompilationDatabase&, llvm::ArrayRef SourcePaths); int execute(); diff --git a/src/printer/IRNodeFinder.cpp b/src/printer/IRNodeFinder.cpp index 5646aae..f586743 100644 --- a/src/printer/IRNodeFinder.cpp +++ b/src/printer/IRNodeFinder.cpp @@ -8,6 +8,7 @@ #include "printer/IRNodeFinder.h" #include "Util.h" +#include #include #include #include @@ -35,7 +36,13 @@ void applyToMatchingFunction(llvm::raw_ostream& os, const llvm::Module* m, const // using namespace util; -IRNodeFinder::IRNodeFinder(clang::tooling::CommonOptionsParser& op, llvm::raw_ostream& os) : tool(op), os(os) { +IRNodeFinder::IRNodeFinder(clang::tooling::CommonOptionsParser& op, llvm::raw_ostream& os) + : IRNodeFinder(op.getCompilations(), op.getSourcePathList(), os) { +} + +IRNodeFinder::IRNodeFinder(const clang::tooling::CompilationDatabase& compilation_database, + llvm::ArrayRef SourcePaths, llvm::raw_ostream& os) + : tool(compilation_database, SourcePaths), os(os) { } int IRNodeFinder::parse() { diff --git a/src/printer/LLVMTool.cpp b/src/printer/LLVMTool.cpp index 083286c..1a259b7 100644 --- a/src/printer/LLVMTool.cpp +++ b/src/printer/LLVMTool.cpp @@ -86,7 +86,11 @@ std::unique_ptr CreateExtractorActionFactory(LLV } // namespace action -LLVMTool::LLVMTool(CommonOptionsParser& op) : tool(op.getCompilations(), op.getSourcePathList()) { +LLVMTool::LLVMTool(CommonOptionsParser& op) : LLVMTool(op.getCompilations(), op.getSourcePathList()) { +} + +LLVMTool::LLVMTool(const CompilationDatabase& compilation_database, ArrayRef source_path) + : tool(compilation_database, source_path) { } int LLVMTool::execute() { From 9ef27977dd6a5051872b766cd63567fe85e3931f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20H=C3=BCck?= Date: Mon, 2 Feb 2026 19:38:06 +0100 Subject: [PATCH 5/9] Refactoring; use clang resource dir --- CMakeLists.txt | 2 +- cmake/ToolchainOptions.cmake | 14 ++++++++++++++ cmake/modules/clang-format.cmake | 4 ++-- cmake/modules/clang-tidy.cmake | 14 +++++++------- cmake/modules/log-util.cmake | 2 +- cmake/modules/target-util.cmake | 4 ++-- src/CMakeLists.txt | 8 ++++---- src/main.cpp | 16 +++++++++++----- src/printer/CMakeLists.txt | 12 ++++++++---- src/printer/IRNodeFinder.cpp | 4 ++-- src/printer/LLVMTool.cpp | 8 ++++++++ 11 files changed, 60 insertions(+), 28 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 06007d4..b11f139 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,7 +17,7 @@ list(APPEND CMAKE_MODULE_PATH include(ToolchainOptions) include(CMakePackageConfigHelpers) -add_format_target(format-sources +irprinter_add_format_target(format-sources "Formats project source files" TARGETS src/*.cpp include/*.h diff --git a/cmake/ToolchainOptions.cmake b/cmake/ToolchainOptions.cmake index 165da8b..719e793 100644 --- a/cmake/ToolchainOptions.cmake +++ b/cmake/ToolchainOptions.cmake @@ -29,6 +29,20 @@ include(target-util) set(LOG_LEVEL 0 CACHE STRING "Granularity of the logger. 3 is most verbose, 0 is least.") +option(IRPRINTER_AUTO_RESOURCE_DIR "Try to automatically set the Clang resource directory" OFF) + +if(IRPRINTER_AUTO_RESOURCE_DIR AND NOT IRPRINTER_CLANG_RESOURCE_DIR) + find_program(CLANG_EXECUTABLE NAMES clang-${LLVM_VERSION_MAJOR} clang) + if(CLANG_EXECUTABLE) + execute_process( + COMMAND ${CLANG_EXECUTABLE} -print-resource-dir + OUTPUT_VARIABLE CLANG_RESOURCE_DIR_VAR + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + set(IRPRINTER_CLANG_RESOURCE_DIR ${CLANG_RESOURCE_DIR_VAR} CACHE STRING "Clang resource directory") + endif() +endif() + if (NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug CACHE STRING "" FORCE) message(STATUS "Building as debug (default)") diff --git a/cmake/modules/clang-format.cmake b/cmake/modules/clang-format.cmake index 900e3e2..16c5064 100644 --- a/cmake/modules/clang-format.cmake +++ b/cmake/modules/clang-format.cmake @@ -1,4 +1,4 @@ -function(add_format_target target comment) +function(irprinter_add_format_target target comment) macro(filter_dir _dir_name_) foreach (SOURCE_FILE ${ALL_CXX_FILES}) string(FIND ${SOURCE_FILE} ${_dir_name_} EXCLUDE_FOUND) @@ -20,7 +20,7 @@ function(add_format_target target comment) endforeach() find_program(FORMAT_COMMAND - NAMES clang-format clang-format-12 clang-format-14 clang-format-18) + NAMES clang-format-${LLVM_VERSION_MAJOR} clang-format) if(FORMAT_COMMAND) add_custom_target(${target} COMMAND ${FORMAT_COMMAND} -i -style=file ${ARG_OTHER} ${ARG_UNPARSED_ARGUMENTS} diff --git a/cmake/modules/clang-tidy.cmake b/cmake/modules/clang-tidy.cmake index 33ededd..443a00b 100644 --- a/cmake/modules/clang-tidy.cmake +++ b/cmake/modules/clang-tidy.cmake @@ -1,4 +1,4 @@ -function(add_tidy_target target comment) +function(irprinter_add_tidy_target target comment) macro(filter_dir _name_) foreach (SOURCE_FILE ${ARG_SOURCES}) string(FIND ${SOURCE_FILE} ${_name_} EXCLUDE_FOUND) @@ -15,7 +15,7 @@ function(add_tidy_target target comment) endforeach() find_program(TIDY_COMMAND - NAMES clang-tidy clang-tidy-12 clang-tidy-14 clang-tidy-18) + NAMES clang-tidy-${LLVM_VERSION_MAJOR} clang-tidy) if(TIDY_COMMAND) add_custom_target(${target} COMMAND ${TIDY_COMMAND} -p ${CMAKE_BINARY_DIR} @@ -33,23 +33,23 @@ function(add_tidy_target target comment) endif() endfunction() -function(add_tidy_fix_target target comment) +function(irprinter_add_tidy_fix_target target comment) cmake_parse_arguments(ARG "" "" "SOURCES;EXCLUDES;OTHER" ${ARGN}) - add_tidy_target(${target} "${comment}" + irprinter_add_tidy_target(${target} "${comment}" SOURCES ${ARG_SOURCES} EXCLUDES ${ARG_EXCLUDES} OTHER ${ARG_OTHER} -fix ) endfunction() -function(make_tidy_check name sources) - add_tidy_target(tidy-run-on-${name} +function(irprinter_make_tidy_check name sources) + irprinter_add_tidy_target(tidy-run-on-${name} "Clang-tidy run on ${name} translation units" SOURCES ${sources} OTHER --header-filter=${CMAKE_CURRENT_SOURCE_DIR} ) - add_tidy_fix_target(tidy-fix-on-${name} + irprinter_add_tidy_fix_target(tidy-fix-on-${name} "Clang-tidy run with fixes on ${name} translation units" SOURCES ${sources} OTHER --header-filter=${CMAKE_CURRENT_SOURCE_DIR} -checks=-*,modernize-*,llvm-namespace-comment,google-explicit-constructor diff --git a/cmake/modules/log-util.cmake b/cmake/modules/log-util.cmake index 40ebd7f..9422cfa 100644 --- a/cmake/modules/log-util.cmake +++ b/cmake/modules/log-util.cmake @@ -1,4 +1,4 @@ -function(target_define_file_basename targetname) +function(irprinter_target_define_file_basename targetname) get_target_property(source_files "${targetname}" SOURCES) foreach(sourcefile ${source_files}) diff --git a/cmake/modules/target-util.cmake b/cmake/modules/target-util.cmake index a7a7112..128b5c4 100644 --- a/cmake/modules/target-util.cmake +++ b/cmake/modules/target-util.cmake @@ -1,4 +1,4 @@ -function(target_project_compile_options target) +function(irprinter_target_project_compile_options target) cmake_parse_arguments(ARG "" "" "PRIVATE_FLAGS;PUBLIC_FLAGS" ${ARGN}) target_compile_options(${target} PRIVATE @@ -22,7 +22,7 @@ function(target_project_compile_options target) endif () endfunction() -function(target_project_compile_definitions target) +function(irprinter_target_project_compile_definitions target) cmake_parse_arguments(ARG "" "" "PRIVATE_DEFS;PUBLIC_DEFS" ${ARGN}) if (ARG_PRIVATE_DEFS) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 37e1efb..a0ad622 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,11 +4,11 @@ add_executable(llvm-ir-printer${EXE_SUFFIX} main.cpp ) -target_define_file_basename(llvm-ir-printer${EXE_SUFFIX}) -target_project_compile_options(llvm-ir-printer${EXE_SUFFIX}) -target_project_compile_definitions(llvm-ir-printer${EXE_SUFFIX} +irprinter_target_define_file_basename(llvm-ir-printer${EXE_SUFFIX}) +irprinter_target_project_compile_options(llvm-ir-printer${EXE_SUFFIX}) +irprinter_target_project_compile_definitions(llvm-ir-printer${EXE_SUFFIX} PRIVATE_DEFS - LOG_LEVEL=${LOG_LEVEL} + LOG_LEVEL=${IRPRINTER_LOG_LEVEL} ) target_link_libraries(llvm-ir-printer${EXE_SUFFIX} diff --git a/src/main.cpp b/src/main.cpp index fa4e5d4..4cdec3c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -43,19 +44,24 @@ StringRef lexWord(StringRef word) { int main(int argc, const char** argv) { #if LLVM_VERSION_MAJOR < 14 CommonOptionsParser op(argc, argv, IRPrinter); - irprinter::IRNodeFinder ir(op); + const auto& sources = op.getSourcePathList(); + const auto& compilations = op.getCompilations(); #else - auto op = CommonOptionsParser::create(argc, argv, IRPrinter); - if (!op) { + auto op_res = CommonOptionsParser::create(argc, argv, IRPrinter); + if (!op_res) { llvm::outs() << "Erroneous input"; return 1; } - irprinter::IRNodeFinder ir(op.get()); + auto& op = op_res.get(); + const auto& sources = op.getSourcePathList(); + const auto& compilations = op.getCompilations(); #endif + irprinter::IRNodeFinder ir(compilations, sources); + auto ret = ir.parse(); if (ret != 0) { - llvm::outs() << "Error parsing. Quitting...\n"; + llvm::errs() << "Error parsing. Quitting...\n"; return ret; } diff --git a/src/printer/CMakeLists.txt b/src/printer/CMakeLists.txt index 8acb181..67c235d 100644 --- a/src/printer/CMakeLists.txt +++ b/src/printer/CMakeLists.txt @@ -3,13 +3,17 @@ add_library(llvm-ir-print STATIC LLVMTool.cpp ) -target_define_file_basename(llvm-ir-print) -target_project_compile_options(llvm-ir-print) -target_project_compile_definitions(llvm-ir-print +irprinter_target_define_file_basename(llvm-ir-print) +irprinter_target_project_compile_options(llvm-ir-print) +irprinter_target_project_compile_definitions(llvm-ir-print PRIVATE_DEFS - LOG_LEVEL=${LOG_LEVEL} + LOG_LEVEL=${IRPRINTER_LOG_LEVEL} ) +if(IRPRINTER_CLANG_RESOURCE_DIR) + target_compile_definitions(llvm-ir-print PRIVATE IRPRINTER_CLANG_RESOURCE_DIR="${IRPRINTER_CLANG_RESOURCE_DIR}") +endif() + target_include_directories(llvm-ir-print PUBLIC $ diff --git a/src/printer/IRNodeFinder.cpp b/src/printer/IRNodeFinder.cpp index f586743..c9a369a 100644 --- a/src/printer/IRNodeFinder.cpp +++ b/src/printer/IRNodeFinder.cpp @@ -41,8 +41,8 @@ IRNodeFinder::IRNodeFinder(clang::tooling::CommonOptionsParser& op, llvm::raw_os } IRNodeFinder::IRNodeFinder(const clang::tooling::CompilationDatabase& compilation_database, - llvm::ArrayRef SourcePaths, llvm::raw_ostream& os) - : tool(compilation_database, SourcePaths), os(os) { + llvm::ArrayRef source_path, llvm::raw_ostream& os) + : tool(compilation_database, source_path), os(os) { } int IRNodeFinder::parse() { diff --git a/src/printer/LLVMTool.cpp b/src/printer/LLVMTool.cpp index 1a259b7..f5b1a9f 100644 --- a/src/printer/LLVMTool.cpp +++ b/src/printer/LLVMTool.cpp @@ -91,6 +91,14 @@ LLVMTool::LLVMTool(CommonOptionsParser& op) : LLVMTool(op.getCompilations(), op. LLVMTool::LLVMTool(const CompilationDatabase& compilation_database, ArrayRef source_path) : tool(compilation_database, source_path) { +#ifdef IRPRINTER_CLANG_RESOURCE_DIR + const std::string resource_dir = IRPRINTER_CLANG_RESOURCE_DIR; + if (!resource_dir.empty()) { + const std::string resource_arg = "-resource-dir=" + resource_dir; + tool.appendArgumentsAdjuster( + clang::tooling::getInsertArgumentAdjuster(resource_arg.c_str(), clang::tooling::ArgumentInsertPosition::BEGIN)); + } +#endif } int LLVMTool::execute() { From 1ed3af5a30ea071d870ef670dd331caa2558eecd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20H=C3=BCck?= Date: Tue, 3 Feb 2026 19:58:53 +0100 Subject: [PATCH 6/9] Lit testing with coverage (#8) --- .github/workflows/basic-ci.yml | 21 ++++++---- CMakeLists.txt | 7 +++- CMakePresets.json | 73 +++++++++++++++++++++++++++++++++ cmake/ToolchainOptions.cmake | 3 +- cmake/modules/target-util.cmake | 51 +++++++++++++++++++++++ include/Util.h | 5 ++- src/main.cpp | 2 +- src/printer/IRNodeFinder.cpp | 13 ++++-- test/CMakeLists.txt | 54 ++++++++++++++++++++++++ test/codes/decl.cpp | 9 ++++ test/codes/demangle.cpp | 8 ++++ test/codes/dump.cpp | 7 ++++ test/codes/errors.cpp | 11 +++++ test/codes/flags.cpp | 8 ++++ test/codes/generate.cpp | 6 +++ test/codes/list.cpp | 9 ++++ test/codes/list_all.cpp | 9 ++++ test/codes/location.cpp | 12 ++++++ test/codes/opt_flag.cpp | 8 ++++ test/codes/quit.cpp | 6 +++ test/lit.cfg.py | 21 ++++++++++ test/lit.site.cfg.py.in | 13 ++++++ 22 files changed, 339 insertions(+), 17 deletions(-) create mode 100644 CMakePresets.json create mode 100644 test/CMakeLists.txt create mode 100644 test/codes/decl.cpp create mode 100644 test/codes/demangle.cpp create mode 100644 test/codes/dump.cpp create mode 100644 test/codes/errors.cpp create mode 100644 test/codes/flags.cpp create mode 100644 test/codes/generate.cpp create mode 100644 test/codes/list.cpp create mode 100644 test/codes/list_all.cpp create mode 100644 test/codes/location.cpp create mode 100644 test/codes/opt_flag.cpp create mode 100644 test/codes/quit.cpp create mode 100644 test/lit.cfg.py create mode 100644 test/lit.site.cfg.py.in diff --git a/.github/workflows/basic-ci.yml b/.github/workflows/basic-ci.yml index 96c9c36..f4a7574 100644 --- a/.github/workflows/basic-ci.yml +++ b/.github/workflows/basic-ci.yml @@ -57,9 +57,6 @@ jobs: - llvm-version: 14 os: ubuntu-22.04 preset: develop - - llvm-version: 18 - os: ubuntu-24.04 - preset: develop - llvm-version: 21 os: ubuntu-24.04 preset: develop @@ -92,12 +89,18 @@ jobs: echo "CLANG_CMAKE_DIR=/usr/lib/llvm-${{ matrix.llvm-version }}/lib/cmake/clang" >> $GITHUB_ENV echo "EXTERNAL_LIT=/usr/lib/llvm-${{ matrix.llvm-version }}/build/utils/lit/lit.py" >> $GITHUB_ENV - - name: Build IRPrinter - run: | - cmake -B build -DCMAKE_BUILD_TYPE=Debug -DLLVM_DIR=${LLVM_CMAKE_DIR} -DClang_DIR=${CLANG_CMAKE_DIR} - cmake --build build --parallel - - name: Build IRPrinter release run: | - cmake -B build_rel -DCMAKE_BUILD_TYPE=Release -DLLVM_DIR=${LLVM_CMAKE_DIR} -DClang_DIR=${CLANG_CMAKE_DIR} + cmake --preset release -DLLVM_DIR=${LLVM_CMAKE_DIR} -DClang_DIR=${CLANG_CMAKE_DIR} -DLLVM_EXTERNAL_LIT=${EXTERNAL_LIT} cmake --build build_rel --parallel --target install + + - name: Test and Coverage + run: | + cmake --preset coverage -DLLVM_DIR=${LLVM_CMAKE_DIR} -DClang_DIR=${CLANG_CMAKE_DIR} -DLLVM_EXTERNAL_LIT=${EXTERNAL_LIT} + cmake --build build_cov --target coverage-irprinter + + - name: Upload coverage + uses: actions/upload-artifact@v6 + with: + name: coverage-report-llvm${{ matrix.llvm-version }} + path: build_cov/coverage_report/ diff --git a/CMakeLists.txt b/CMakeLists.txt index b11f139..aa7de40 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 3.20) +CMAKE_MINIMUM_REQUIRED(VERSION 3.21) PROJECT(irprinter - VERSION 0.3 + VERSION 0.4 ) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) @@ -24,3 +24,6 @@ irprinter_add_format_target(format-sources ) add_subdirectory(src) +if(PROJECT_IS_TOP_LEVEL) + add_subdirectory(test) +endif() \ No newline at end of file diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..cb73115 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,73 @@ +{ + "version": 2, + "cmakeMinimumRequired": { + "major": 3, + "minor": 20, + "patch": 0 + }, + "configurePresets": [ + { + "name": "clang-toolchain", + "hidden": true, + "generator": "Unix Makefiles", + "cacheVariables": { + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++" + } + }, + { + "name": "develop", + "displayName": "Develop (Debug)", + "description": "Default develop build options for Clang", + "binaryDir": "${sourceDir}/build", + "inherits": [ + "clang-toolchain" + ], + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "release", + "displayName": "Release", + "description": "Default release build options for Clang", + "binaryDir": "${sourceDir}/build_rel", + "inherits": [ + "clang-toolchain" + ], + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "coverage", + "displayName": "Coverage (LLVM)", + "description": "Default coverage build options for Clang (LLVM-based)", + "binaryDir": "${sourceDir}/build_cov", + "inherits": [ + "clang-toolchain" + ], + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "IRPRINTER_ENABLE_COVERAGE": "ON", + "CMAKE_C_FLAGS": "-fprofile-instr-generate -fcoverage-mapping", + "CMAKE_CXX_FLAGS": "-fprofile-instr-generate -fcoverage-mapping", + "CMAKE_EXE_LINKER_FLAGS": "-fprofile-instr-generate -fcoverage-mapping" + } + } + ], + "buildPresets": [ + { + "name": "develop", + "configurePreset": "develop" + }, + { + "name": "release", + "configurePreset": "release" + }, + { + "name": "coverage", + "configurePreset": "coverage" + } + ] +} diff --git a/cmake/ToolchainOptions.cmake b/cmake/ToolchainOptions.cmake index 719e793..82e6212 100644 --- a/cmake/ToolchainOptions.cmake +++ b/cmake/ToolchainOptions.cmake @@ -27,9 +27,10 @@ include(clang-format) include(log-util) include(target-util) -set(LOG_LEVEL 0 CACHE STRING "Granularity of the logger. 3 is most verbose, 0 is least.") +set(IRPRINTER_LOG_LEVEL 0 CACHE STRING "Granularity of the logger. 3 is most verbose, 0 is least.") option(IRPRINTER_AUTO_RESOURCE_DIR "Try to automatically set the Clang resource directory" OFF) +option(IRPRINTER_ENABLE_COVERAGE "Enable LLVM-based coverage" OFF) if(IRPRINTER_AUTO_RESOURCE_DIR AND NOT IRPRINTER_CLANG_RESOURCE_DIR) find_program(CLANG_EXECUTABLE NAMES clang-${LLVM_VERSION_MAJOR} clang) diff --git a/cmake/modules/target-util.cmake b/cmake/modules/target-util.cmake index 128b5c4..ecc8ced 100644 --- a/cmake/modules/target-util.cmake +++ b/cmake/modules/target-util.cmake @@ -37,3 +37,54 @@ function(irprinter_target_project_compile_definitions target) ) endif () endfunction() + +function(irprinter_find_llvm_progs target names) + cmake_parse_arguments(ARG "ABORT_IF_MISSING;SHOW_VAR" "DEFAULT_EXE" "HINTS" ${ARGN}) + set(TARGET_TMP ${target}) + + find_program( + ${target} + NAMES ${names} + PATHS ${LLVM_TOOLS_BINARY_DIR} + NO_DEFAULT_PATH + ) + if(NOT ${target}) + find_program( + ${target} + NAMES ${names} + HINTS ${ARG_HINTS} + ) + endif() + + if(NOT ${target}) + set(target_missing_message "") + if(ARG_DEFAULT_EXE) + unset(${target} CACHE) + set(${target} + ${ARG_DEFAULT_EXE} + CACHE + STRING + "Default value for ${TARGET_TMP}." + ) + set(target_missing_message "Using default: ${ARG_DEFAULT_EXE}") + endif() + + set(message_status STATUS) + if(ARG_ABORT_IF_MISSING AND NOT ARG_DEFAULT_EXE) + set(message_status SEND_ERROR) + endif() + message(${message_status} + "Did find LLVM program " "${names}" + " in ${LLVM_TOOLS_BINARY_DIR}, in system path or hints " "\"${ARG_HINTS}\"" ". " + ${target_missing_message} + ) + endif() + + set(${TARGET_TMP} "${${TARGET_TMP}}" PARENT_SCOPE) + + if(ARG_SHOW_VAR) + mark_as_advanced(CLEAR ${target}) + else() + mark_as_advanced(${target}) + endif() +endfunction() diff --git a/include/Util.h b/include/Util.h index d104624..d4ea526 100644 --- a/include/Util.h +++ b/include/Util.h @@ -133,7 +133,10 @@ inline llvm::SmallVector regex_find(const llvm::Module if (use_mangle) { return detail::find(m, [&](const llvm::Function& f) { return r.match(f.getName()); }); } else { - return detail::find(m, [&](const llvm::Function& f) { return r.match(try_demangle(f.getName())); }); + return detail::find(m, [&](const llvm::Function& f) { + std::string name = try_demangle(f.getName()); + return r.match(name); + }); } } diff --git a/src/main.cpp b/src/main.cpp index 4cdec3c..2536bf2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -121,4 +121,4 @@ int main(int argc, const char** argv) { } return 0; -} +} \ No newline at end of file diff --git a/src/printer/IRNodeFinder.cpp b/src/printer/IRNodeFinder.cpp index c9a369a..d526559 100644 --- a/src/printer/IRNodeFinder.cpp +++ b/src/printer/IRNodeFinder.cpp @@ -21,13 +21,20 @@ using namespace llvm; namespace irprinter { namespace { +std::string strip_parens(std::string name) { + if (name.size() > 2 && name.substr(name.size() - 2) == "()") { + name.erase(name.size() - 2); + } + return name; +} + template void applyToMatchingFunction(llvm::raw_ostream& os, const llvm::Module* m, const std::string& regex, F&& func) { const auto fvec = util::regex_find(*m, regex); unsigned count{0}; for (auto f : fvec) { auto fname = f->getName(); - os << "Match " << ++count << " [" << util::try_demangle(fname) << "]:"; + os << "Match " << ++count << " [" << strip_parens(util::try_demangle(fname)) << "]:"; func(f); os << "\n"; } @@ -101,7 +108,7 @@ void IRNodeFinder::listFunction(const std::string& regex) const { oss.flush(); if (f->isDeclaration()) { - os << "\n" << s.substr(1); + os << "\n" << s; } else { llvm::Regex r("((;|define)[^{]+){"); SmallVector match; @@ -114,7 +121,7 @@ void IRNodeFinder::listFunction(const std::string& regex) const { } std::string IRNodeFinder::demangle(const std::string& name) { - return util::try_demangle(name); + return strip_parens(util::try_demangle(name)); } } /* namespace irprinter */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..2049198 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,54 @@ +set(LLVM_IR_PRINTER_BINARY $) + +irprinter_find_llvm_progs(IRPRINTER_FILECHECK_EXEC "FileCheck-${LLVM_VERSION_MAJOR};FileCheck") + +if(LLVM_EXTERNAL_LIT) + cmake_path(GET LLVM_EXTERNAL_LIT PARENT_PATH LLVM_EXTERNAL_LIT_DIR) +endif() +irprinter_find_llvm_progs(IRPRINTER_LIT_EXEC + "llvm-lit;lit;lit.py" + HINTS ${LLVM_EXTERNAL_LIT_DIR} /usr/lib/llvm-${LLVM_VERSION_MAJOR} /usr/lib/llvm /usr/bin /usr/local/bin /opt/local/bin + ABORT_IF_MISSING +) + +add_custom_target(check-irprinter + COMMAND ${IRPRINTER_LIT_EXEC} -vv ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS llvm-ir-printer${EXE_SUFFIX} + COMMENT "Running irprinter regression tests" + USES_TERMINAL +) + +if(IRPRINTER_ENABLE_COVERAGE) + set(COVERAGE_DIR ${CMAKE_BINARY_DIR}/coverage_report) + irprinter_find_llvm_progs(IRPRINTER_PROFDATA_EXEC "llvm-profdata-${LLVM_VERSION_MAJOR};llvm-profdata") + irprinter_find_llvm_progs(IRPRINTER_COV_EXEC "llvm-cov-${LLVM_VERSION_MAJOR};llvm-cov") + + set(IGNORE_REGEX ".*(test|build|build_cov|external).*") + + add_custom_target(coverage-irprinter + COMMAND ${CMAKE_COMMAND} -E make_directory ${COVERAGE_DIR} + COMMAND ${CMAKE_COMMAND} -E rm -f "${CMAKE_CURRENT_BINARY_DIR}/*.profraw" "${CMAKE_CURRENT_BINARY_DIR}/merged.profdata" + # Execute tests + COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target check-irprinter + # Merge all profraw files + COMMAND ${CMAKE_COMMAND} -E env bash -c "${IRPRINTER_PROFDATA_EXEC} merge -sparse ${CMAKE_CURRENT_BINARY_DIR}/*.profraw -o ${CMAKE_CURRENT_BINARY_DIR}/merged.profdata" + # Generate HTML + COMMAND ${IRPRINTER_COV_EXEC} show "${LLVM_IR_PRINTER_BINARY}" + "-instr-profile=${CMAKE_CURRENT_BINARY_DIR}/merged.profdata" + -format=html + "-output-dir=${COVERAGE_DIR}" + -show-line-counts-or-regions + "-ignore-filename-regex=${IGNORE_REGEX}" + # Show summary + COMMAND ${IRPRINTER_COV_EXEC} report "${LLVM_IR_PRINTER_BINARY}" + "-instr-profile=${CMAKE_CURRENT_BINARY_DIR}/merged.profdata" + "-ignore-filename-regex=${IGNORE_REGEX}" + COMMENT "Generating coverage report" + DEPENDS check-irprinter + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + VERBATIM + ) +endif() + +configure_file(lit.site.cfg.py.in lit.site.cfg.py.tmp @ONLY) +file(GENERATE OUTPUT lit.site.cfg.py INPUT ${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg.py.tmp) \ No newline at end of file diff --git a/test/codes/decl.cpp b/test/codes/decl.cpp new file mode 100644 index 0000000..c371f71 --- /dev/null +++ b/test/codes/decl.cpp @@ -0,0 +1,9 @@ +// RUN: echo "l printf" | %llvm-ir-printer %s -- | %filecheck %s + +// CHECK: Match 1 [printf]: +// CHECK-NEXT: declare i32 @printf +extern "C" int printf(const char*, ...); +int main() { + printf("test"); + return 0; +} \ No newline at end of file diff --git a/test/codes/demangle.cpp b/test/codes/demangle.cpp new file mode 100644 index 0000000..e5bad11 --- /dev/null +++ b/test/codes/demangle.cpp @@ -0,0 +1,8 @@ +// RUN: echo "d _Z3foov" | %llvm-ir-printer %s -- | %filecheck %s + +// CHECK: Demangled name: foo +void foo() { +} +int main() { + return 0; +} \ No newline at end of file diff --git a/test/codes/dump.cpp b/test/codes/dump.cpp new file mode 100644 index 0000000..fd37149 --- /dev/null +++ b/test/codes/dump.cpp @@ -0,0 +1,7 @@ +// RUN: echo "dump" | %llvm-ir-printer %s -- | %filecheck %s + +// CHECK: source_filename = "{{.*}}dump.cpp" +// CHECK: define {{.*}} @main +int main() { + return 0; +} diff --git a/test/codes/errors.cpp b/test/codes/errors.cpp new file mode 100644 index 0000000..7043e9c --- /dev/null +++ b/test/codes/errors.cpp @@ -0,0 +1,11 @@ +// RUN: echo "10 5" | %llvm-ir-printer %s -- | %filecheck %s --check-prefix=LOC-ORDER +// RUN: echo "5 invalid" | %llvm-ir-printer %s -- | %filecheck %s --check-prefix=LOC-PARSE +// RUN: echo "l [" | %llvm-ir-printer %s -- | %filecheck %s --check-prefix=REGEX-ERR + +// LOC-ORDER: Error: end location (5) is less than start location (10) +// LOC-PARSE: Invalid end location: invalid +// REGEX-ERR: Invalid regex ({{.*}}): "[" + +int main() { + return 0; +} diff --git a/test/codes/flags.cpp b/test/codes/flags.cpp new file mode 100644 index 0000000..b48c30b --- /dev/null +++ b/test/codes/flags.cpp @@ -0,0 +1,8 @@ +// RUN: echo -e "f -Wall\nf -O3\np main" | %llvm-ir-printer %s -- | %filecheck %s + +// CHECK: Set flag to -Wall +// CHECK: Set flag to -O3 +// CHECK: define {{.*}} @main +int main() { + return 0; +} diff --git a/test/codes/generate.cpp b/test/codes/generate.cpp new file mode 100644 index 0000000..df8518d --- /dev/null +++ b/test/codes/generate.cpp @@ -0,0 +1,6 @@ +// RUN: echo "g" | %llvm-ir-printer %s -- | %filecheck %s +// Without "-f -g" no effect, but no explicit error either. +// CHECK-NOT: Error +int main() { + return 0; +} diff --git a/test/codes/list.cpp b/test/codes/list.cpp new file mode 100644 index 0000000..4c85d5c --- /dev/null +++ b/test/codes/list.cpp @@ -0,0 +1,9 @@ +// RUN: echo "l foo" | %llvm-ir-printer %s -- | %filecheck %s + +// CHECK: _Z3foov +// CHECK-NOT: main +void foo() { +} +int main() { + return 0; +} \ No newline at end of file diff --git a/test/codes/list_all.cpp b/test/codes/list_all.cpp new file mode 100644 index 0000000..622ac2f --- /dev/null +++ b/test/codes/list_all.cpp @@ -0,0 +1,9 @@ +// RUN: echo "l" | %llvm-ir-printer %s -- | %filecheck %s + +// CHECK: Match 1 [foo] +// CHECK: Match 2 [main] +void foo() { +} +int main() { + return 0; +} \ No newline at end of file diff --git a/test/codes/location.cpp b/test/codes/location.cpp new file mode 100644 index 0000000..e016f40 --- /dev/null +++ b/test/codes/location.cpp @@ -0,0 +1,12 @@ +// RUN: echo "6 10" | %llvm-ir-printer %s -- -g | %filecheck %s + +// REQUIRES: llvm-21 + +// CHECK: main: +// CHECK: store i32 1, ptr {{.*}} +// CHECK: store i32 2, ptr {{.*}} +int main() { + int a = 1; + int b = 2; + return a + b; +} \ No newline at end of file diff --git a/test/codes/opt_flag.cpp b/test/codes/opt_flag.cpp new file mode 100644 index 0000000..2bdd39e --- /dev/null +++ b/test/codes/opt_flag.cpp @@ -0,0 +1,8 @@ +// RUN: echo -e "f -O3\np main" | %llvm-ir-printer %s -- | %filecheck %s + +// CHECK: Set flag to -O3. Re-generating module... +// CHECK: define {{.*}} @main{{.*}} +// CHECK-NOT: optnone +int main() { + return 0; +} diff --git a/test/codes/quit.cpp b/test/codes/quit.cpp new file mode 100644 index 0000000..77b9333 --- /dev/null +++ b/test/codes/quit.cpp @@ -0,0 +1,6 @@ +// RUN: echo "q" | %llvm-ir-printer %s -- | %filecheck %s + +// CHECK-NOT: ir-printer> +int main() { + return 0; +} diff --git a/test/lit.cfg.py b/test/lit.cfg.py new file mode 100644 index 0000000..f0818f6 --- /dev/null +++ b/test/lit.cfg.py @@ -0,0 +1,21 @@ +import lit.formats +import os + +config.name = 'LLVM-IR-PRINTER' +config.test_format = lit.formats.ShTest(execute_external=True) + +config.suffixes = ['.cpp', '.c', '.ll'] + +config.test_source_root = os.path.dirname(__file__) + +if config.llvm_version: + config.available_features.add(f"llvm-{config.llvm_version}") + +# Substitutions +config.substitutions.append(('%llvm-ir-printer', config.llvm_ir_printer_binary)) +config.substitutions.append(('%filecheck', config.filecheck_binary)) +config.substitutions.append(('%llvm-profdata', config.llvm_profdata_exe)) +config.substitutions.append(('%llvm-cov', config.llvm_cov_exe)) + +if config.enable_coverage.upper() in ['ON', 'YES', 'TRUE', '1']: + config.environment['LLVM_PROFILE_FILE'] = os.path.join(config.test_exec_root, "test-%p.profraw") diff --git a/test/lit.site.cfg.py.in b/test/lit.site.cfg.py.in new file mode 100644 index 0000000..24c848b --- /dev/null +++ b/test/lit.site.cfg.py.in @@ -0,0 +1,13 @@ +import os + +config.llvm_ir_printer_binary = "@LLVM_IR_PRINTER_BINARY@" +config.filecheck_binary = "@IRPRINTER_FILECHECK_EXEC@" +config.llvm_profdata_exe = "@IRPRINTER_PROFDATA_EXEC@" +config.llvm_cov_exe = "@IRPRINTER_COV_EXEC@" +config.enable_coverage = "@IRPRINTER_ENABLE_COVERAGE@" +config.test_source_root = "@CMAKE_CURRENT_SOURCE_DIR@" +config.test_exec_root = "@CMAKE_CURRENT_BINARY_DIR@" +config.llvm_version = "@LLVM_VERSION_MAJOR@" + +# Let the main config do the real work. +lit_config.load_config(config, os.path.join(config.test_source_root, "lit.cfg.py")) \ No newline at end of file From d55e94b5651aa2082db5c6dd1124e86b67f821e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20H=C3=BCck?= Date: Tue, 3 Feb 2026 21:24:27 +0100 Subject: [PATCH 7/9] Reduce iteration space --- src/printer/IRNodeFinder.cpp | 77 ++++++++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 21 deletions(-) diff --git a/src/printer/IRNodeFinder.cpp b/src/printer/IRNodeFinder.cpp index d526559..8511387 100644 --- a/src/printer/IRNodeFinder.cpp +++ b/src/printer/IRNodeFinder.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -69,33 +70,67 @@ void IRNodeFinder::printFunction(const std::string& regex) const { applyToMatchingFunction(os, m, regex, [&](const Function* f) { f->print(os); }); } -void IRNodeFinder::printByLocation(unsigned line_start_, unsigned line_end_) const { - line_end_ = std::max(line_start_, line_end_); - std::string matches; - llvm::raw_string_ostream local_oss{matches}; - bool first_match{false}; - const auto* m = tool.getModule(); - for (const auto& f : *m) { - first_match = false; - for (const auto& bb : f) { - for (const auto& inst : bb) { +void IRNodeFinder::printByLocation(unsigned line_start, unsigned line_end) const { + const unsigned search_end = std::max(line_start, line_end); + const auto* module = tool.getModule(); + const auto main_file_path = [&](const llvm::Module* m) -> std::optional { + auto* CUs = m->getNamedMetadata("llvm.dbg.cu"); + if (!CUs || CUs->getNumOperands() == 0) { + return std::nullopt; + } + auto* cu = llvm::cast(CUs->getOperand(0)); + llvm::SmallString<128> path; + if (!llvm::sys::fs::real_path(cu->getFilename(), path)) { + return path.str().str(); + } + return std::nullopt; + }(module); + + const auto is_relevant_function = [&](const llvm::Function& func) -> bool { + const auto* sub = func.getSubprogram(); + if (!sub) { + return false; + } + // TODO: investigate w.r.t. inlining? + // if (sub->getLine() > search_end) { + // return false; + // } + if (main_file_path) { + llvm::SmallString<128> func_path; + if (!llvm::sys::fs::real_path(sub->getFilename(), func_path)) { + return func_path == *main_file_path; + } + } + return true; + }; + + std::string matches_buffer; + llvm::raw_string_ostream buffer_stream{matches_buffer}; + + for (const auto& func : llvm::make_filter_range(*module, is_relevant_function)) { + bool function_header_printed = false; + for (const auto& block : func) { + for (const auto& inst : block) { const auto& loc = inst.getDebugLoc(); - if (loc) { - const auto line = loc.getLine(); - if (line >= line_start_ && line <= line_end_) { - if (!first_match) { - local_oss << f.getName() << ":\n"; - first_match = true; - } - inst.print(local_oss); - local_oss << "\n"; + if (!loc) { + continue; + } + const unsigned line = loc.getLine(); + + if (line >= line_start && line <= search_end) { + if (!function_header_printed) { + buffer_stream << func.getName() << ":\n"; + function_header_printed = true; } + inst.print(buffer_stream); + buffer_stream << "\n"; } } } } - if (!local_oss.str().empty()) { - os << local_oss.str() << "\n"; + + if (!buffer_stream.str().empty()) { + os << buffer_stream.str() << "\n"; } } From 6f5e71f6661d92d5be2db9d9c045a27199f1677f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20H=C3=BCck?= Date: Tue, 3 Feb 2026 21:26:57 +0100 Subject: [PATCH 8/9] Missing header --- src/printer/IRNodeFinder.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/printer/IRNodeFinder.cpp b/src/printer/IRNodeFinder.cpp index 8511387..6b8950a 100644 --- a/src/printer/IRNodeFinder.cpp +++ b/src/printer/IRNodeFinder.cpp @@ -17,6 +17,8 @@ #include #include +#include + using namespace llvm; namespace irprinter { From 3260e6093ba327c51a7eaeef174126ea6a2ee5e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20H=C3=BCck?= Date: Thu, 23 Apr 2026 21:06:35 +0200 Subject: [PATCH 9/9] Structured output (#10) --- .github/workflows/basic-ci.yml | 3 +- README.md | 40 ++-- cmake/ToolchainOptions.cmake | 8 +- include/printer/IRNodeFinder.h | 18 +- src/main.cpp | 83 +++++--- src/printer/IRNodeFinder.cpp | 287 ++++++++++++++++++++++------ src/printer/LLVMTool.cpp | 2 +- test/codes/decl.cpp | 4 +- test/codes/flags.cpp | 5 +- test/codes/location.cpp | 8 +- test/codes/location_line_prefix.cpp | 16 ++ test/codes/test.c | 28 +++ 12 files changed, 384 insertions(+), 118 deletions(-) create mode 100644 test/codes/location_line_prefix.cpp create mode 100644 test/codes/test.c diff --git a/.github/workflows/basic-ci.yml b/.github/workflows/basic-ci.yml index f4a7574..8d0cd8e 100644 --- a/.github/workflows/basic-ci.yml +++ b/.github/workflows/basic-ci.yml @@ -33,7 +33,6 @@ jobs: find demo lib test \ -type f \ -a \( -name "*.c" -o -name "*.cpp" -o -name "*.h" \) \ - -not -path "*/lulesh/*" -not -path "*/CallSite.h" \ -print0 \ | xargs -0 clang-format-18 -i @@ -57,7 +56,7 @@ jobs: - llvm-version: 14 os: ubuntu-22.04 preset: develop - - llvm-version: 21 + - llvm-version: 22 os: ubuntu-24.04 preset: develop diff --git a/README.md b/README.md index b4672be..1115162 100644 --- a/README.md +++ b/README.md @@ -11,15 +11,19 @@ It allows you to print IR for specific functions, which is especially useful whe 2. Full function bodies. * Dump the entire IR of a translation unit. * Print statements within a specific line number range (based on debug information). + * Printing backward dependencies for location queries is toggleable via `deps`. + * Line-number prefixes are toggleable via `lines`. + * Prefixes use `+` for dependency-discovered instructions. + * A prefix like `+0 | ...` means the instruction has no debug line metadata in IR. ## Usage Refer to [main.cpp](src/main.cpp) for a full list of command-line arguments. ### Example -Assume `test.c` contains the following code: +Assume [test.c](test/codes/test.c) contains the following code: ```c -int foo () { +int foo() { return 2; } int main() { @@ -38,35 +42,39 @@ In this example, we: 5. Optimize the code with `-O3`. 6. Print the body of `main` again to see the result of the optimization. +The regression test input for this flow lives at `test/codes/test.c`. + ```console -$~/irprint/install$ ./bin/irprinter ../test.c -- -g +$ ./bin/irprinter test/codes/test.c -- -g ir-printer> l Match 1 [foo()]: -; Function Attrs: mustprogress noinline nounwind optnone uwtable -define dso_local noundef i32 @_Z3foov() #0 !dbg !10 +; Function Attrs: noinline nounwind optnone uwtable +define dso_local i32 @foo() #0 !dbg !10 Match 2 [main]: -; Function Attrs: mustprogress noinline norecurse nounwind optnone uwtable -define dso_local noundef i32 @main() #1 !dbg !10 +; Function Attrs: noinline nounwind optnone uwtable +define dso_local i32 @main() #0 !dbg !10 ir-printer> p main -Match 1 [main]:; Function Attrs: mustprogress noinline norecurse nounwind optnone uwtable -define dso_local noundef i32 @main() #1 !dbg !10 { +Match 1 [main]:; Function Attrs: noinline nounwind optnone uwtable +define dso_local i32 @main() #0 !dbg !10 { entry: %retval = alloca i32, align 4 %val = alloca i32, align 4 store i32 0, ptr %retval, align 4 #dbg_declare(ptr %val, !16, !DIExpression(), !17) - %call = call noundef i32 @_Z3foov(), !dbg !18 + %call = call i32 @foo(), !dbg !18 store i32 %call, ptr %val, align 4, !dbg !19 ret i32 0, !dbg !20 } ir-printer> 6 7 main: - %call = call noundef i32 @_Z3foov(), !dbg !18 - store i32 %call, ptr %val, align 4, !dbg !19 - ret i32 0, !dbg !20 + entry: + +0 | %val = alloca i32, align 4 + 6 | %call = call i32 @foo(), !dbg !18 + 6 | store i32 %call, ptr %val, align 4, !dbg !19 + 7 | ret i32 0, !dbg !20 ir-printer> f -O3 Set flag to -O3. Re-generating module... @@ -78,11 +86,13 @@ entry: } ``` +Note: this output was captured with LLVM 20.1.8. Exact IR attributes and mangling can vary between LLVM versions. + ## How to Build ### Requirements - CMake >= 3.20 -- Clang/LLVM 12, 14, or 18-21 (CMake must be able to find the installation; see the [LLVM CMake documentation](https://llvm.org/docs/CMake.html) or the [CI workflow](.github/workflows/basic-ci.yml)) +- Clang/LLVM 12, 14, or 18-22 (CMake must be able to find the installation; see the [LLVM CMake documentation](https://llvm.org/docs/CMake.html) or the [CI workflow](.github/workflows/basic-ci.yml)) - A C++17 compatible compiler ### Build Steps @@ -91,4 +101,4 @@ From the root project folder, execute the following commands: ```bash cmake -B build -DCMAKE_INSTALL_PREFIX=/path/to/install cmake --build build --target install --parallel -``` \ No newline at end of file +``` diff --git a/cmake/ToolchainOptions.cmake b/cmake/ToolchainOptions.cmake index 82e6212..f902956 100644 --- a/cmake/ToolchainOptions.cmake +++ b/cmake/ToolchainOptions.cmake @@ -19,7 +19,13 @@ message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}") list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}") -find_package(Clang REQUIRED HINTS "${Clang_DIR}") +find_package(Clang QUIET HINTS "${Clang_DIR}") + +if(NOT Clang_FOUND) + # Optional: only needed for CIR-enabled Clang packages: + find_package(MLIR CONFIG QUIET HINTS "${MLIR_DIR}") + find_package(Clang REQUIRED HINTS "${Clang_DIR}") +endif() include(AddLLVM) include(clang-tidy) diff --git a/include/printer/IRNodeFinder.h b/include/printer/IRNodeFinder.h index 86f917f..1793f37 100644 --- a/include/printer/IRNodeFinder.h +++ b/include/printer/IRNodeFinder.h @@ -21,6 +21,21 @@ class CommonOptionsParser; namespace irprinter { +struct IRPrintingFlags { + bool include_dependencies{true}; + bool print_line{true}; + + IRPrintingFlags& setIncludeDependencies(bool enable) { + include_dependencies = enable; + return *this; + } + + IRPrintingFlags& setPrintLine(bool enable) { + print_line = enable; + return *this; + } +}; + class IRNodeFinder { private: LLVMTool tool; @@ -39,7 +54,8 @@ class IRNodeFinder { void printFunction(const std::string& regex = ".*") const; - void printByLocation(unsigned line_start_, unsigned line_end_ = 0) const; + void printByLocation(unsigned line_start_, unsigned line_end_ = 0, + const IRPrintingFlags& flags = IRPrintingFlags{}) const; void listFunction(const std::string& regex = ".*") const; diff --git a/src/main.cpp b/src/main.cpp index 2536bf2..cb2ceca 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -18,26 +19,24 @@ static llvm::cl::OptionCategory IRPrinter("IR Printer Sample"); // cl::cat(IRPrinter)); namespace { -StringRef lexWord(StringRef word) { - StringRef::iterator begin{word.begin()}; - StringRef::iterator end{word.end()}; - while (true) { - if (begin == end) { - return StringRef(begin, 0); - } - if (!isWhitespace(*begin)) { +SmallVector tokenize_words(StringRef line) { + SmallVector tokens; + while (!line.empty()) { + line = line.ltrim(); + if (line.empty()) { break; } - ++begin; - } - StringRef::iterator wordbegin = begin; - while (true) { - ++begin; - if (begin == end || isWhitespace(*begin)) { - return StringRef(wordbegin, begin - wordbegin); + const auto word_end = line.find_first_of(" \t\n\r\f\v"); + if (word_end == StringRef::npos) { + tokens.push_back(line); + break; } + + tokens.push_back(line.take_front(word_end)); + line = line.drop_front(word_end); } + return tokens; } } // namespace @@ -66,17 +65,24 @@ int main(int argc, const char** argv) { } llvm::LineEditor le("ir-printer"); + irprinter::IRPrintingFlags printing_flags; + llvm::outs().enable_colors(true); while (auto line = le.readLine()) { - StringRef ref = *line; - auto cmd = lexWord(ref); + const auto tokens = tokenize_words(*line); + if (tokens.empty()) { + continue; + } + + const auto cmd = tokens.front(); + const auto args = ArrayRef(tokens).drop_front(); if (cmd == "q" || cmd == "quit") { break; } else if (unsigned start; !cmd.getAsInteger(10, start)) { - auto end_ref = lexWord(StringRef(cmd.end(), ref.end() - cmd.end())); unsigned end{start}; - if (!end_ref.empty()) { - if (end_ref.getAsInteger(10, end)) { + if (!args.empty()) { + if (args.front().getAsInteger(10, end)) { + const auto end_ref = args.front(); llvm::outs() << "Invalid end location: " << end_ref << "\n"; continue; } @@ -84,22 +90,39 @@ int main(int argc, const char** argv) { if (end < start) { llvm::outs() << "Error: end location (" << end << ") is less than start location (" << start << ")\n"; } else { - ir.printByLocation(start, end); + ir.printByLocation(start, end, printing_flags); } + } else if (cmd == "deps") { + printing_flags.setIncludeDependencies(!printing_flags.include_dependencies); + llvm::outs() << "Dependency expansion " << (printing_flags.include_dependencies ? "enabled" : "disabled") << "\n"; + } else if (cmd == "lines") { + printing_flags.setPrintLine(!printing_flags.print_line); + llvm::outs() << "Line prefixes " << (printing_flags.print_line ? "enabled" : "disabled") << "\n"; } else if (cmd == "g" || cmd == "generate") { ir.parse(); } else if (cmd == "f" || cmd == "flag") { - auto str = lexWord(StringRef(cmd.end(), ref.end() - cmd.end())); - llvm::outs() << "Set flag to " << str << ". Re-generating module...\n"; - ir.setOptFlag(str); + std::string joined_flags; + for (const auto flag : args) { + if (!joined_flags.empty()) { + joined_flags += " "; + } + joined_flags += flag.str(); + ir.setOptFlag(flag); + } + + if (args.empty()) { + llvm::outs() << "No flag provided. Usage: f \n"; + continue; + } + + llvm::outs() << "Set flag" << (args.size() > 1 ? "s" : "") << " to " << joined_flags + << ". Re-generating module...\n"; ir.parse(); } else if (cmd == "dump") { ir.dump(); } else if (cmd == "l" || cmd == "list" || cmd == "p" || cmd == "print") { - auto str = lexWord(StringRef(cmd.end(), ref.end() - cmd.end())); - if (str == "") { - str = ".*"; - } else { + StringRef str = args.empty() ? StringRef(".*") : args.front(); + if (!args.empty()) { std::string error; llvm::Regex r(str); if (!r.isValid(error)) { @@ -113,7 +136,7 @@ int main(int argc, const char** argv) { ir.listFunction(std::string{str}); } } else if (cmd == "d" || cmd == "demangle") { - auto str = lexWord(StringRef(cmd.end(), ref.end() - cmd.end())); + auto str = args.empty() ? StringRef{} : args.front(); auto demangled_name = irprinter::IRNodeFinder::demangle(std::string{str}); llvm::outs() << "Demangled name: " << demangled_name << "\n"; } @@ -121,4 +144,4 @@ int main(int argc, const char** argv) { } return 0; -} \ No newline at end of file +} diff --git a/src/printer/IRNodeFinder.cpp b/src/printer/IRNodeFinder.cpp index 6b8950a..2cba49d 100644 --- a/src/printer/IRNodeFinder.cpp +++ b/src/printer/IRNodeFinder.cpp @@ -18,6 +18,10 @@ #include #include +#include +#include +#include +#include using namespace llvm; @@ -42,6 +46,225 @@ void applyToMatchingFunction(llvm::raw_ostream& os, const llvm::Module* m, const os << "\n"; } } + +struct LineInstruction { + const llvm::Instruction* inst; + const llvm::BasicBlock* block; + unsigned line; + bool is_dependency{false}; + + LineInstruction(const llvm::Instruction* inst, const llvm::BasicBlock* block, unsigned line, + bool is_dependency = false) + : inst(inst), block(block), line(line), is_dependency(is_dependency) { + } +}; + +struct FunctionInstructions { + const llvm::Function* func; + std::vector instructions; + + FunctionInstructions(const llvm::Function* func, std::vector instructions) + : func(func), instructions(std::move(instructions)) { + } +}; + +using LineMap = std::vector; +using InstructionSet = std::unordered_set; +using InstructionList = std::vector; + +std::optional get_main_file_path(const llvm::Module* module) { + auto* CUs = module->getNamedMetadata("llvm.dbg.cu"); + if (!CUs || CUs->getNumOperands() == 0) { + return std::nullopt; + } + + auto* cu = llvm::cast(CUs->getOperand(0)); + llvm::SmallString<128> path; + if (!llvm::sys::fs::real_path(cu->getFilename(), path)) { + return path.str().str(); + } + return std::nullopt; +} + +class LineMapBuilder { + const llvm::Module* module; + std::optional main_file_path; + unsigned line_start; + unsigned search_end; + bool include_deps; + + InstructionSet seed_instructions; + InstructionList seed_worklist; + InstructionSet dependency_instructions; + LineMap ordered_line_map; + std::unordered_map function_to_index; + + bool is_relevant_function(const llvm::Function& func) const { + const auto* sub = func.getSubprogram(); + if (!sub) { + return false; + } + + // TODO: investigate function location filtering w.r.t. inlining. + if (main_file_path) { + llvm::SmallString<128> func_path; + if (!llvm::sys::fs::real_path(sub->getFilename(), func_path)) { + return func_path == *main_file_path; + } + } + return true; + } + + template + void for_each_relevant_instruction(F&& visitor) const { + const auto relevant_functions = + llvm::make_filter_range(*module, [&](const llvm::Function& func) { return is_relevant_function(func); }); + + for (const auto& func : relevant_functions) { + for (const auto& block : func) { + for (const auto& inst : block) { + visitor(func, block, inst); + } + } + } + } + + static unsigned instruction_line_or_zero(const llvm::Instruction& inst) { + const auto& loc = inst.getDebugLoc(); + return loc ? loc.getLine() : 0; + } + + void collect_seed_instructions() { + for_each_relevant_instruction([&](const llvm::Function&, const llvm::BasicBlock&, const llvm::Instruction& inst) { + auto line = instruction_line_or_zero(inst); + if (line == 0) { + return; + } + if (line >= line_start && line <= search_end) { + if (seed_instructions.emplace(&inst).second) { + seed_worklist.push_back(&inst); + } + } + }); + } + + void collect_backward_dependencies() { + if (!include_deps) { + return; + } + + InstructionSet visited = seed_instructions; + auto worklist = seed_worklist; + while (!worklist.empty()) { + const llvm::Instruction* current = worklist.back(); + worklist.pop_back(); + + for (const llvm::Use& operand_use : current->operands()) { + const auto* operand_inst = llvm::dyn_cast(operand_use.get()); + if (!operand_inst) { + continue; + } + if (!visited.emplace(operand_inst).second) { + continue; + } + + dependency_instructions.emplace(operand_inst); + worklist.push_back(operand_inst); + } + } + } + + FunctionInstructions& ensure_function_bucket(const llvm::Function* func) { + const auto [it, inserted] = function_to_index.emplace(func, ordered_line_map.size()); + if (inserted) { + ordered_line_map.emplace_back(func, std::vector{}); + } + return ordered_line_map[it->second]; + } + + void build_ordered_line_map() { + for_each_relevant_instruction( + [&](const llvm::Function& func, const llvm::BasicBlock& block, const llvm::Instruction& inst) { + const bool is_seed = seed_instructions.find(&inst) != seed_instructions.end(); + const bool is_dep = dependency_instructions.find(&inst) != dependency_instructions.end(); + if (!is_seed && !is_dep) { + return; + } + + ensure_function_bucket(&func).instructions.emplace_back(&inst, &block, instruction_line_or_zero(inst), + !is_seed && is_dep); + }); + } + + public: + LineMapBuilder(const llvm::Module* module, const std::optional& main_file_path, unsigned line_start, + unsigned search_end, bool include_deps) + : module(module), + main_file_path(main_file_path), + line_start(line_start), + search_end(search_end), + include_deps(include_deps) { + } + + LineMap run() { + collect_seed_instructions(); + collect_backward_dependencies(); + build_ordered_line_map(); + return std::move(ordered_line_map); + } +}; + +LineMap collect_line_map(const llvm::Module* module, unsigned line_start, unsigned search_end, + const std::optional& main_file_path, bool include_deps) { + return LineMapBuilder(module, main_file_path, line_start, search_end, include_deps).run(); +} + +void print_line_map(llvm::raw_ostream& out, const LineMap& line_map, bool print_line_prefix) { + std::size_t line_number_width = [&print_line_prefix](const auto& line_map) { + unsigned width{0}; + if (print_line_prefix) { + unsigned max_line = 0; + for (const auto& [_, instructions] : line_map) { + for (const auto& line_instruction : instructions) { + max_line = std::max(max_line, line_instruction.line); + } + } + width = std::to_string(max_line).size(); + } + return width; + }(line_map); + + for (const auto& [func, instructions] : line_map) { + out << func->getName() << ":\n"; + const llvm::BasicBlock* current_block{nullptr}; + for (const auto& line_instruction : instructions) { + if (line_instruction.block != current_block) { + current_block = line_instruction.block; + if (!current_block) { + continue; + } + out.indent(2); + if (current_block->hasName()) { + out << current_block->getName() << ":\n"; + } else { + current_block->printAsOperand(out, false); + out << ":\n"; + } + } + out.indent(2); + if (print_line_prefix) { + out << (line_instruction.is_dependency ? '+' : ' '); + const std::string line = std::to_string(line_instruction.line); + if (line.size() < line_number_width) { + out.indent(line_number_width - line.size()); + } + out << line << " | "; + } + line_instruction.inst->print(out); + out << "\n"; + } + } +} } // namespace // using namespace util; @@ -72,67 +295,15 @@ void IRNodeFinder::printFunction(const std::string& regex) const { applyToMatchingFunction(os, m, regex, [&](const Function* f) { f->print(os); }); } -void IRNodeFinder::printByLocation(unsigned line_start, unsigned line_end) const { +void IRNodeFinder::printByLocation(unsigned line_start, unsigned line_end, const IRPrintingFlags& flags) const { const unsigned search_end = std::max(line_start, line_end); const auto* module = tool.getModule(); - const auto main_file_path = [&](const llvm::Module* m) -> std::optional { - auto* CUs = m->getNamedMetadata("llvm.dbg.cu"); - if (!CUs || CUs->getNumOperands() == 0) { - return std::nullopt; - } - auto* cu = llvm::cast(CUs->getOperand(0)); - llvm::SmallString<128> path; - if (!llvm::sys::fs::real_path(cu->getFilename(), path)) { - return path.str().str(); - } - return std::nullopt; - }(module); - - const auto is_relevant_function = [&](const llvm::Function& func) -> bool { - const auto* sub = func.getSubprogram(); - if (!sub) { - return false; - } - // TODO: investigate w.r.t. inlining? - // if (sub->getLine() > search_end) { - // return false; - // } - if (main_file_path) { - llvm::SmallString<128> func_path; - if (!llvm::sys::fs::real_path(sub->getFilename(), func_path)) { - return func_path == *main_file_path; - } - } - return true; - }; + const auto main_file_path = get_main_file_path(module); + const LineMap line_map = collect_line_map(module, line_start, search_end, main_file_path, flags.include_dependencies); - std::string matches_buffer; - llvm::raw_string_ostream buffer_stream{matches_buffer}; - - for (const auto& func : llvm::make_filter_range(*module, is_relevant_function)) { - bool function_header_printed = false; - for (const auto& block : func) { - for (const auto& inst : block) { - const auto& loc = inst.getDebugLoc(); - if (!loc) { - continue; - } - const unsigned line = loc.getLine(); - - if (line >= line_start && line <= search_end) { - if (!function_header_printed) { - buffer_stream << func.getName() << ":\n"; - function_header_printed = true; - } - inst.print(buffer_stream); - buffer_stream << "\n"; - } - } - } - } - - if (!buffer_stream.str().empty()) { - os << buffer_stream.str() << "\n"; + if (!line_map.empty()) { + print_line_map(os, line_map, flags.print_line); + os << "\n"; } } diff --git a/src/printer/LLVMTool.cpp b/src/printer/LLVMTool.cpp index f5b1a9f..69dc5a8 100644 --- a/src/printer/LLVMTool.cpp +++ b/src/printer/LLVMTool.cpp @@ -116,7 +116,7 @@ const llvm::Module* LLVMTool::getModule() const { } void LLVMTool::setFlag(StringRef flag) { - user_args.emplace_back(flag.data()); + user_args.emplace_back(flag.str()); } void LLVMTool::removeFlag(StringRef flag) { diff --git a/test/codes/decl.cpp b/test/codes/decl.cpp index c371f71..58f5db3 100644 --- a/test/codes/decl.cpp +++ b/test/codes/decl.cpp @@ -1,9 +1,9 @@ // RUN: echo "l printf" | %llvm-ir-printer %s -- | %filecheck %s // CHECK: Match 1 [printf]: -// CHECK-NEXT: declare i32 @printf +// CHECK-NEXT: declare {{.*}} @printf{{.*}} extern "C" int printf(const char*, ...); int main() { printf("test"); return 0; -} \ No newline at end of file +} diff --git a/test/codes/flags.cpp b/test/codes/flags.cpp index b48c30b..d06a6c6 100644 --- a/test/codes/flags.cpp +++ b/test/codes/flags.cpp @@ -1,7 +1,6 @@ -// RUN: echo -e "f -Wall\nf -O3\np main" | %llvm-ir-printer %s -- | %filecheck %s +// RUN: echo -e "f -Wall -O3\np main" | %llvm-ir-printer %s -- | %filecheck %s -// CHECK: Set flag to -Wall -// CHECK: Set flag to -O3 +// CHECK: Set flags to -Wall -O3 // CHECK: define {{.*}} @main int main() { return 0; diff --git a/test/codes/location.cpp b/test/codes/location.cpp index e016f40..e80f7e9 100644 --- a/test/codes/location.cpp +++ b/test/codes/location.cpp @@ -1,12 +1,10 @@ // RUN: echo "6 10" | %llvm-ir-printer %s -- -g | %filecheck %s -// REQUIRES: llvm-21 - // CHECK: main: -// CHECK: store i32 1, ptr {{.*}} -// CHECK: store i32 2, ptr {{.*}} +// CHECK: store i32 1, {{(ptr|i32\*)}} {{.*}} +// CHECK: store i32 2, {{(ptr|i32\*)}} {{.*}} int main() { int a = 1; int b = 2; return a + b; -} \ No newline at end of file +} diff --git a/test/codes/location_line_prefix.cpp b/test/codes/location_line_prefix.cpp new file mode 100644 index 0000000..4281088 --- /dev/null +++ b/test/codes/location_line_prefix.cpp @@ -0,0 +1,16 @@ +// RUN: printf "6\nlines\n6\n" | %llvm-ir-printer %s -- -g | %filecheck %s --check-prefixes=ON,OFF + +int main() { + int a = 1; + int b = a; + return b; +} + +// ON: main: +// ON: +0 | +// ON: 6 | +// ON: 6 | ret + +// OFF-LABEL: Line prefixes disabled +// OFF: main: +// OFF-NOT: | diff --git a/test/codes/test.c b/test/codes/test.c new file mode 100644 index 0000000..c179e5a --- /dev/null +++ b/test/codes/test.c @@ -0,0 +1,28 @@ +// RUN: printf "l\np main\n6 7\nf -O3\np main\n" | %llvm-ir-printer %s -- -g | %filecheck %s + +#line 1 "test.c" +int foo() { + return 2; +} +int main() { + int val; + val = foo(); + return 0; +} + +// CHECK: Match 1 [foo]: +// CHECK: Match 2 [main]: +// CHECK: Match 1 [main]: +// CHECK: define {{.*}} @main{{.*}} !dbg +// CHECK: {{%[0-9A-Za-z._]+}} = call {{.*}} @foo(), !dbg +// CHECK: store i32 {{%[0-9A-Za-z._]+}}, {{(ptr|i32\*)}} {{%[0-9A-Za-z._]+}}, align 4, !dbg +// CHECK: ret i32 0, !dbg +// CHECK: main: +// CHECK: {{[ +][[:space:]]*[0-9]+[[:space:]]*\|}} {{.*}}call {{.*}}@foo +// CHECK: {{[ +][[:space:]]*[0-9]+[[:space:]]*\|}} {{.*}}store i32 {{%[0-9A-Za-z._]+}} +// CHECK: {{[ +][[:space:]]*[0-9]+[[:space:]]*\|}} ret i32 0 +// CHECK: Set flag to -O3. Re-generating module... +// CHECK: Match 1 [main]: +// CHECK: define {{.*}} @main{{.*}} +// CHECK-NOT: alloca +// CHECK: ret i32 0