├── .clang-format ├── .clang-tidy ├── .clangd ├── .dockerignore ├── .github ├── assets │ ├── graph1_dark.png │ ├── graph2_dark.png │ ├── graph3_dark.png │ ├── graph4_dark.png │ ├── graph5_dark.png │ ├── triskel-gui.png │ ├── triskel_dark.png │ ├── triskel_dark.svg │ ├── triskel_light.png │ └── triskel_light.svg └── diagrams │ └── GithubSteps.drawio ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── bin ├── CMakeLists.txt ├── bench │ ├── CMakeLists.txt │ ├── README.md │ ├── main.cpp │ └── src │ │ └── table.hpp ├── gui │ ├── CMakeLists.txt │ ├── README.md │ ├── assets │ │ ├── fonts │ │ │ ├── Lato-Bold.ttf │ │ │ ├── Lato-Light.ttf │ │ │ └── Roboto-Medium.ttf │ │ └── img │ │ │ └── Triskel.png │ ├── disas │ │ ├── CMakeLists.txt │ │ ├── rec_descent.cpp │ │ └── rec_descent.hpp │ ├── external │ │ ├── CMakeLists.txt │ │ ├── application.cpp │ │ ├── application.h │ │ ├── credit.md │ │ ├── platform.h │ │ ├── platform_glw.cpp │ │ ├── renderer.h │ │ └── renderer_ogl3.cpp │ ├── gui │ │ ├── CMakeLists.txt │ │ ├── arch │ │ │ ├── CMakeLists.txt │ │ │ ├── arch.hpp │ │ │ ├── arch_binary.cpp │ │ │ └── arch_llvm.cpp │ │ ├── triskel.cpp │ │ └── triskel.hpp │ └── main.cpp ├── img │ ├── CMakeLists.txt │ ├── README.md │ └── main.cpp └── wasm │ ├── CMakeLists.txt │ └── main.cpp ├── bindings ├── CMakeLists.txt └── python │ ├── .gitignore │ ├── CMakeLists.txt │ ├── README.md │ ├── build_wheels.sh │ ├── pyproject.toml │ ├── pytriskel.cpp │ ├── pytriskel │ ├── __init__.pyi │ └── pytriskel.pyi │ └── setup.py ├── cmake └── FindCairo.cmake ├── docker ├── fedora │ ├── Dockerfile │ ├── dependencies.sh │ └── imgui.CMakeLists.txt └── manylinux │ └── Dockerfile ├── lib ├── CMakeLists.txt ├── include │ └── triskel │ │ ├── analysis │ │ ├── dfs.hpp │ │ ├── lengauer_tarjan.hpp │ │ ├── patriarchal.hpp │ │ ├── sese.hpp │ │ └── udfs.hpp │ │ ├── datatypes │ │ ├── rtree.hpp │ │ └── rtree.ipp │ │ ├── graph │ │ ├── graph.hpp │ │ ├── graph_view.hpp │ │ ├── igraph.hpp │ │ └── subgraph.hpp │ │ ├── internal.hpp │ │ ├── layout │ │ ├── ilayout.hpp │ │ ├── layout.hpp │ │ ├── phantom_nodes.hpp │ │ └── sugiyama │ │ │ ├── layer_assignement.hpp │ │ │ ├── sugiyama.hpp │ │ │ └── vertex_ordering.hpp │ │ ├── llvm │ │ └── llvm.hpp │ │ ├── triskel.hpp │ │ └── utils │ │ ├── attribute.hpp │ │ ├── constants.hpp │ │ ├── point.hpp │ │ └── tree.hpp └── src │ ├── CMakeLists.txt │ ├── analysis │ ├── CMakeLists.txt │ ├── dfs.cpp │ ├── lengauer_tarjan.cpp │ ├── patriarchal.cpp │ ├── sese.cpp │ └── udfs.cpp │ ├── graph │ ├── CMakeLists.txt │ ├── graph.cpp │ ├── graph_view.cpp │ ├── igraph.cpp │ └── subgraph.cpp │ ├── layout │ ├── CMakeLists.txt │ ├── ilayout.cpp │ ├── layout.cpp │ ├── phantom_nodes.cpp │ └── sugiyama │ │ ├── CMakeLists.txt │ │ ├── network_simplex.cpp │ │ ├── sugiyama.cpp │ │ ├── tamassia.cpp │ │ └── vertex_ordering.cpp │ ├── llvm │ ├── CMakeLists.txt │ ├── llvm.cpp │ └── triskel.cpp │ ├── renderer │ ├── CMakeLists.txt │ ├── cairo.cpp │ ├── external │ │ ├── CMakeLists.txt │ │ ├── credit.md │ │ ├── imgui_canvas.cpp │ │ └── imgui_canvas.h │ └── imgui.cpp │ └── triskel.cpp └── test ├── CMakeLists.txt ├── analysis ├── CMakeLists.txt ├── dfs_test.cpp └── lengauer_tarjan_test.cpp ├── datatypes ├── CMakeLists.txt └── rtree_test.cpp ├── graph ├── CMakeLists.txt ├── attribute_test.cpp ├── graph_editor_test.cpp ├── graph_test.cpp └── subgraph_test.cpp └── triskel_test.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: chromium 4 | IndentWidth: 4 5 | AlignConsecutiveAssignments: true 6 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | HeaderFilterRegex: include 3 | FormatStyle: google 4 | Checks: > 5 | bugprone-*, 6 | clang-analyzer-*, 7 | google-*, 8 | misc-*, 9 | modernize-*, 10 | performance-*, 11 | portability-*, 12 | readability-*, 13 | -google-readability-todo, 14 | -misc-non-private-member-variables-in-classes, 15 | -modernize-use-emplace, 16 | -readability-identifier-length, 17 | -readability-magic-numbers, 18 | -readability-function-cognitive-complexity, 19 | -bugprone-easily-swappable-parameters, 20 | CheckOptions: 21 | - key: misc-include-cleaner.IgnoreHeaders 22 | value: fmt/printf.h;fmt/chrono.h;bits/ranges_algo.h 23 | - key: modernize-use-ranges.UseReversePipe 24 | value: true 25 | -------------------------------------------------------------------------------- /.clangd: -------------------------------------------------------------------------------- 1 | CompileFlags: 2 | CompilationDatabase: "build/debug" 3 | 4 | --- 5 | If: 6 | PathMatch: bindings/.* 7 | CompileFlags: 8 | CompilationDatabase: "build/bindings" 9 | 10 | --- 11 | If: 12 | PathMatch: bin/wasm/.* 13 | CompileFlags: 14 | CompilationDatabase: "build/wasm" 15 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .github 2 | .vscode/ 3 | .venv/ 4 | build/ 5 | 6 | makefile 7 | 8 | *.ini 9 | 10 | triskel-bench 11 | triskel-image 12 | triskel-gui 13 | 14 | *.csv -------------------------------------------------------------------------------- /.github/assets/graph1_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triskellib/triskel/4b02ad9e162788f4653a404ef47d1b26400ca62d/.github/assets/graph1_dark.png -------------------------------------------------------------------------------- /.github/assets/graph2_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triskellib/triskel/4b02ad9e162788f4653a404ef47d1b26400ca62d/.github/assets/graph2_dark.png -------------------------------------------------------------------------------- /.github/assets/graph3_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triskellib/triskel/4b02ad9e162788f4653a404ef47d1b26400ca62d/.github/assets/graph3_dark.png -------------------------------------------------------------------------------- /.github/assets/graph4_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triskellib/triskel/4b02ad9e162788f4653a404ef47d1b26400ca62d/.github/assets/graph4_dark.png -------------------------------------------------------------------------------- /.github/assets/graph5_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triskellib/triskel/4b02ad9e162788f4653a404ef47d1b26400ca62d/.github/assets/graph5_dark.png -------------------------------------------------------------------------------- /.github/assets/triskel-gui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triskellib/triskel/4b02ad9e162788f4653a404ef47d1b26400ca62d/.github/assets/triskel-gui.png -------------------------------------------------------------------------------- /.github/assets/triskel_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triskellib/triskel/4b02ad9e162788f4653a404ef47d1b26400ca62d/.github/assets/triskel_dark.png -------------------------------------------------------------------------------- /.github/assets/triskel_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triskellib/triskel/4b02ad9e162788f4653a404ef47d1b26400ca62d/.github/assets/triskel_light.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .venv/ 2 | .vscode/ 3 | 4 | build/ 5 | 6 | makefile 7 | 8 | 9 | triskel-bench 10 | triskel-img 11 | triskel-gui 12 | 13 | *.csv 14 | *.ini 15 | out.* -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.30) 2 | 3 | include(CMakeDependentOption) 4 | include(FetchContent) 5 | 6 | project(Triskel) 7 | 8 | set(CMAKE_CXX_STANDARD 23) 9 | set(CMAKE_CXX_STANDARD_REQUIRED True) 10 | 11 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 12 | 13 | option(ENABLE_LLVM "Adds utilities to convert LLVM functions to triskel graphs" OFF) 14 | option(ENABLE_IMGUI "Adds utilities to display graphs in imgui" OFF) 15 | option(ENABLE_CAIRO "Adds utilities to create SVG/PNG using cairo" OFF) 16 | 17 | option(ENABLE_LINTING "Linting" OFF) 18 | option(ENABLE_TESTING "Tests" OFF) 19 | 20 | cmake_dependent_option(BUILD_WASM "Builds to wasm" OFF "NOT ENABLE_CAIRO; NOT ENABLE_LLVM; NOT ENABLE_IMGUI" OFF) 21 | cmake_dependent_option(BUILD_BINDINGS "Builds the python bindings" OFF "ENABLE_CAIRO; NOT ENABLE_LLVM; NOT ENABLE_IMGUI" OFF) 22 | cmake_dependent_option(BUILD_BENCH "Builds the binary used for benchmarking" OFF "ENABLE_LLVM" OFF) 23 | cmake_dependent_option(BUILD_GUI "Builds the gui binary" OFF "ENABLE_LLVM; ENABLE_IMGUI" OFF) 24 | cmake_dependent_option(BUILD_IMG "Builds the img binary" OFF "ENABLE_LLVM; ENABLE_CAIRO" OFF) 25 | 26 | 27 | # libfmt 28 | FetchContent_Declare( 29 | fmt 30 | GIT_REPOSITORY https://github.com/fmtlib/fmt 31 | GIT_TAG 11.1.4 32 | FIND_PACKAGE_ARGS 33 | ) 34 | FetchContent_MakeAvailable(fmt) 35 | message(STATUS "Found fmt version ${fmt_VERSION}") 36 | 37 | if(ENABLE_TESTING) 38 | message(STATUS "Building tests") 39 | find_package(GTest CONFIG REQUIRED) 40 | add_subdirectory(test) 41 | endif() 42 | 43 | if (ENABLE_CAIRO) 44 | message(STATUS "Building with cairo support") 45 | include(${PROJECT_SOURCE_DIR}/cmake/FindCairo.cmake) 46 | endif() 47 | 48 | if(ENABLE_LLVM) 49 | message(STATUS "Building with LLVM support") 50 | find_package(LLVM CONFIG REQUIRED) 51 | if(LLVM_LINK_LLVM_DYLIB) 52 | set(llvm_libs LLVM) 53 | else() 54 | llvm_map_components_to_libnames(llvm_libs 55 | support core irreader 56 | bitreader bitwriter 57 | passes asmprinter 58 | aarch64info aarch64desc aarch64codegen aarch64asmparser 59 | armcodegen armasmparser 60 | interpreter mcjit 61 | nvptxdesc 62 | x86info x86codegen x86asmparser 63 | sparccodegen sparcasmparser 64 | webassemblydesc) 65 | endif() 66 | 67 | message(STATUS "LLVM Libraries: ${llvm_libs}") 68 | message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}") 69 | message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}") 70 | 71 | include_directories(SYSTEM ${LLVM_INCLUDE_DIRS}) 72 | 73 | string(REPLACE "." ";" LLVM_VERSION_LIST ${LLVM_PACKAGE_VERSION}) 74 | list(GET LLVM_VERSION_LIST 0 LLVM_MAJOR_VERSION) 75 | list(GET LLVM_VERSION_LIST 1 LLVM_MINOR_VERSION) 76 | 77 | set(LLVM_MAJOR_VERSION "${LLVM_MAJOR_VERSION}") 78 | set(LLVM_MINOR_VERSION "${LLVM_MINOR_VERSION}") 79 | endif() 80 | 81 | if (ENABLE_IMGUI) 82 | message(STATUS "Building with ImGui support") 83 | find_package(imgui REQUIRED) 84 | find_package(glfw3 REQUIRED) 85 | find_package(OpenGL REQUIRED) 86 | find_package(GLEW REQUIRED) 87 | endif() 88 | 89 | if(BUILD_BINDINGS) 90 | message(STATUS "Building bindings") 91 | add_subdirectory(bindings) 92 | endif() 93 | 94 | add_subdirectory(bin) 95 | add_subdirectory(lib) 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 | Shows a black logo in light color mode and a white one in dark color mode. 6 | 7 |

8 | 9 |

10 | 11 | PyPI - Version 12 | 13 | 14 | 15 | Discord 16 | 17 |

18 | 19 | 20 | 21 | **Triskel** is a Control Flow Graph (CFG) layout engine. It provides you with 22 | coordinates to draw CFGs in your reverse-engineering tools. 23 | 24 | - CFG specific layout, emphasizing Single Entry Single Exit Regions 25 | - Python bindings 26 | - Export to PNG / SVG (with cairo) 27 | - DearImgui integration 28 | - LLVM integration 29 | 30 | 31 | ## Quick start 32 | 33 | ### Python 34 | 35 | ``` 36 | $ pip install pytriskel 37 | ``` 38 | 39 | ```python 40 | from pytriskel.pytriskel import * 41 | 42 | builder = make_layout_builder() 43 | 44 | # Build the graph 45 | n1 = builder.make_node("Hello") 46 | n2 = builder.make_node("World") 47 | builder.make_edge(n1, n2) 48 | 49 | # Measure node size using font size 50 | png_renderer = make_png_renderer() 51 | builder.measure_nodes(png_renderer) 52 | 53 | # Export an image 54 | layout = builder.build() 55 | layout.save(png_renderer, "out.png") 56 | ``` 57 | 58 | ### C++ 59 | 60 | ```cpp 61 | #include 62 | 63 | int main(void) { 64 | auto builder = triskel::make_layout_builder(); 65 | 66 | auto n1 = builder->make_node("Hello"); 67 | auto n2 = builder->make_node("World"); 68 | builder->make_edge(n1, n2) 69 | 70 | auto renderer = triskel::make_svg_renderer(); 71 | builder->measure_nodes(renderer) 72 | auto layout = builder->build(); 73 | 74 | layout->render_and_save(*renderer, "./out.svg"); 75 | 76 | return 1; 77 | } 78 | ``` 79 | 80 | ## Theory 81 | 82 | Triskel is the implementation for the paper [Towards better CFG layouts](https://hal.science/hal-04996939). 83 | 84 | The key idea behind Triskel is to split the CFG into Single Entry Single Exit regions. 85 | We are then able to layout each region taking advantage of a divide and conquer approach. 86 | 87 | ## Walkthrough 88 | 89 | Initially we have a directed graph 90 | 91 | ![Directed graph](.github/assets/graph1_dark.png) 92 | 93 | The first step involves identifying Single Entry Single Exit (SESE) regions. (See the implementation here: [sese.cpp](https://github.com/triskellib/triskel/blob/master/lib/src/analysis/sese.cpp)) 94 | In the diagram, the region is in blue. Notice how a single edge enters and exits the blue border. 95 | 96 | ![SESE region identification](.github/assets/graph2_dark.png) 97 | 98 | We can then split each region out of the graph. At this step we have multiple smaller directed graphs. 99 | Note that how in the graph of the SESE region, we had to add 2 virtual nodes to represent the entry and exit points. 100 | In the other graph, we added an additional node to represent the region (still in blue). 101 | 102 | ![Splitting the graph](.github/assets/graph3_dark.png) 103 | 104 | The next step involves laying out each SESE region's graph using Sugiyama algorithm (See the implementation here: [sugiyama.cpp](https://github.com/triskellib/triskel/blob/master/lib/src/layout/sugiyama/sugiyama.cpp)). 105 | Note how we have to layout the SESE region first in order to know the coordinates of the blue node. 106 | 107 | ![Divide and conquer](.github/assets/graph4_dark.png) 108 | 109 | Finally, we can superimpose the layouts to obtain the final layout. 110 | 111 | ![Final layout](.github/assets/graph5_dark.png) 112 | 113 | ## Compilation 114 | 115 | Triskel relies on the following dependencies (the provided binaries also have their own dependencies) 116 | 117 | - [fmt](https://github.com/fmtlib/fmt) 118 | 119 | Triskel can then be compiled with cmake 120 | 121 | ``` 122 | $ git clone https://github.com/triskeles/triskel 123 | $ cd triskel 124 | $ cmake -B build 125 | $ cmake --build build 126 | ``` 127 | 128 | You can then link to Triskel 129 | 130 | ```cmake 131 | target_link_libraries(foo PRIVATE triskel) 132 | ``` 133 | 134 | ### CMake options 135 | 136 | To compile with all options and external dependencies check the [dockerfile](https://github.com/triskellib/triskel/tree/master/docker/fedora). 137 | 138 | #### `ENABLE_LLVM` 139 | 140 | Adds [LLVM](https://llvm.org/) integration. 141 | 142 | This also adds `LLVM 19` as a dependency. 143 | 144 | #### `ENABLE_IMGUI` 145 | 146 | Adds [ImGui](https://github.com/ocornut/imgui) integration, used for making GUIs. 147 | 148 | This adds the following dependencies: 149 | 150 | - `imgui` (To use compile imgui with CMake you can use the code in [`docker/fedora/dependencies.sh`](https://github.com/triskellib/triskel/blob/master/docker/fedora/dependencies.sh)) 151 | - `glfw3` 152 | - `OpenGL` 153 | - `GLEW` 154 | - `SDL2` 155 | - `stb_image` 156 | 157 | #### `ENABLE_CAIRO` 158 | 159 | Adds [Cairo](https://www.cairographics.org/) integration, used for exporting images. 160 | 161 | ## Binaries 162 | 163 | Triskel comes with many example binaries to help illustrate usage. 164 | 165 | These binaries all require an additional dependency: 166 | 167 | - [gflags](https://gflags.github.io/gflags/) 168 | 169 | ### [triskel-bench](https://github.com/triskellib/triskel/tree/master/bin/bench) 170 | 171 | > Used for testing and evaluation. 172 | 173 | This binary lays out each function in an LLVM module and outputs a CSV containing performance reviews. 174 | 175 | It can also be used on a single function to analyze the lay out with `perf`. 176 | 177 | #### Dependencies 178 | 179 | This binary only requires `triskel` built with `ENABLE_LLVM=ON`. 180 | 181 | 182 | 183 | 184 | 185 | ### [triskel-gui](https://github.com/triskellib/triskel/tree/master/bin/gui) 186 | 187 | An example implementation of a GUI using Dear ImGui. 188 | 189 | This application has a _very limited_ disassembler for x64 binaries. 190 | 191 | #### Dependencies 192 | 193 | This binary requires `triskel` built with `ENABLE_LLVM=ON` and `ENABLE_IMGUI=ON`. 194 | 195 | It also needs [LIEF](https://lief.re/) and [Capstone](http://www.capstone-engine.org/) for the disassembler. 196 | 197 | ### [triskel-img](https://github.com/triskellib/triskel/tree/master/bin/img) 198 | 199 | A binary that generates images for CFGs using cairo. 200 | 201 | #### Dependencies 202 | 203 | This binary requires `triskel` built with `ENABLE_LLVM=ON` and `ENABLE_CAIRO=ON`. 204 | 205 | It also requires [`capstone`](http://www.capstone-engine.org/) and [`LIEF`](https://lief.re/) 206 | 207 | ## Python bindings 208 | 209 | You can download the python bindings using pip: 210 | 211 | ``` 212 | $ pip install pytriskel 213 | ``` 214 | 215 | ## Contact 216 | - Discord: [Triskel](https://discord.gg/zgBb5VUKKS) 217 | 218 | ## Cite Triskel 219 | ```bibtex 220 | @inproceedings{royer:hal-04996939, 221 | AUTHOR = {Royer, Jack and Tronel, Fr{\'e}d{\'e}ric and Vin{\c c}ont, Ya{\"e}lle}, 222 | TITLE = {{Towards Better CFG Layouts}}, 223 | BOOKTITLE = {{Workshop on Binary Analysis Research 2025}}, 224 | ADDRESS = {San Diego (CA), United States}, 225 | MONTH = Feb, 226 | YEAR = {2025}, 227 | URL = {https://hal.science/hal-04996939}, 228 | DOI = {10.14722/bar.2025.23011}, 229 | } 230 | ``` 231 | 232 | -------------------------------------------------------------------------------- /bin/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if (BUILD_BENCH OR BUILD_IMG OR BUILD_GUI) 2 | find_package(gflags) 3 | endif() 4 | 5 | if(BUILD_BENCH) 6 | message(STATUS "Building triskel-bench") 7 | add_subdirectory(bench) 8 | endif() 9 | 10 | if (BUILD_IMG) 11 | message(STATUS "Building triskel-img") 12 | add_subdirectory(img) 13 | endif() 14 | 15 | if(BUILD_GUI) 16 | message(STATUS "Building triskel-gui") 17 | add_subdirectory(gui) 18 | endif() 19 | 20 | if (BUILD_WASM) 21 | message(STATUS "Building triskel-wasm") 22 | add_subdirectory(wasm) 23 | endif() 24 | 25 | -------------------------------------------------------------------------------- /bin/bench/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(triskel-bench 2 | VERSION 1.0.0 3 | DESCRIPTION "Benchmarking binary for the triskel CFG layout library" 4 | LANGUAGES CXX C 5 | ) 6 | 7 | add_executable(triskel-bench main.cpp) 8 | 9 | target_link_libraries(triskel-bench PRIVATE 10 | triskel 11 | fmt::fmt 12 | gflags 13 | ) 14 | 15 | if (ENABLE_LINTING) 16 | find_program(CLANG_TIDY NAMES "clang-tidy" REQUIRED) 17 | set_target_properties(triskel-bench PROPERTIES 18 | CXX_CLANG_TIDY ${CLANG_TIDY} 19 | ) 20 | endif() 21 | -------------------------------------------------------------------------------- /bin/bench/README.md: -------------------------------------------------------------------------------- 1 | # triskel-bench 2 | 3 | Lays out CFGs without displaying them. Useful for benchmarking. 4 | 5 | ## Usage 6 | 7 | It can be used on a file, it will lay out each function in the LLVM module and generate a CSV with statistics. 8 | 9 | ``` 10 | $ triskel-bench 11 | ``` 12 | 13 | It can also be used on a single function. 14 | 15 | ``` 16 | $ triskel-bench 17 | ``` -------------------------------------------------------------------------------- /bin/bench/src/table.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | template 14 | struct Entry { 15 | Entry(std::string name, const T& value, fmt::format_string fmt = "{}") 16 | : name{std::move(name)}, value{value}, fmt{fmt} {} 17 | 18 | std::string name; 19 | 20 | const T& value; 21 | 22 | fmt::format_string fmt; 23 | }; 24 | 25 | // NOLINTNEXTLINE(google-build-namespaces) 26 | namespace { 27 | 28 | struct Table { 29 | template 30 | explicit Table(Args... args) { 31 | entries = std::vector>{ 32 | {args.name, 33 | fmt::vformat(args.fmt, fmt::make_format_args(args.value))}...}; 34 | 35 | column_width = std::ranges::max( 36 | entries | std::ranges::views::transform([](const auto& entry) { 37 | return std::max(entry.first.size(), entry.second.size()); 38 | })); 39 | 40 | // For a space on either size 41 | column_width += 2; 42 | } 43 | 44 | auto print_separator() const -> void { 45 | for (size_t i = 0; i < entries.size(); ++i) { 46 | fmt::print("+{:->{}}", "", column_width); 47 | } 48 | 49 | fmt::print("+\n"); 50 | } 51 | 52 | template 53 | auto print_content() const -> void { 54 | for (const auto& entry : entries) { 55 | fmt::print("| {:>{}} ", std::get(entry), column_width - 2); 56 | } 57 | 58 | fmt::print("|\n"); 59 | } 60 | 61 | auto print() const -> void { 62 | print_separator(); 63 | print_content<0>(); 64 | print_separator(); 65 | print_content<1>(); 66 | print_separator(); 67 | } 68 | 69 | size_t column_width; 70 | 71 | std::vector> entries; 72 | }; 73 | 74 | } // namespace 75 | 76 | template 77 | auto print_table(Args... args) -> void { 78 | const auto table = Table(args...); 79 | table.print(); 80 | } 81 | -------------------------------------------------------------------------------- /bin/gui/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(triskel-gui 2 | VERSION 1.0.0 3 | DESCRIPTION "ImGui for the triskel CFG layout library" 4 | LANGUAGES CXX C 5 | ) 6 | 7 | add_executable(triskel-gui main.cpp) 8 | 9 | FetchContent_Declare( 10 | LIEF 11 | GIT_REPOSITORY https://github.com/lief-project/LIEF.git 12 | GIT_TAG 7e61aa2e56d67b46a0b055363a4cb4fbe4662ef8 # 0.16.3 13 | FIND_PACKAGE_ARGS 14 | ) 15 | FetchContent_MakeAvailable(LIEF) 16 | 17 | target_link_libraries(triskel-gui PRIVATE 18 | triskel 19 | fmt::fmt 20 | gflags 21 | imgui::imgui 22 | imgui::imgui_impl_glfw 23 | imgui::imgui_impl_opengl3 24 | glfw 25 | OpenGL::GL 26 | GLEW::GLEW 27 | LIEF::LIEF 28 | ) 29 | 30 | include(FindPkgConfig) 31 | pkg_check_modules(CAPSTONE REQUIRED capstone) 32 | target_include_directories(triskel-gui PRIVATE ${CAPSTONE_INCLUDE_DIRS}) 33 | target_link_libraries(triskel-gui PRIVATE ${CAPSTONE_LIBRARIES}) 34 | 35 | 36 | add_subdirectory(disas) 37 | add_subdirectory(external) 38 | add_subdirectory(gui) 39 | 40 | add_custom_target(copy_assets 41 | COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_LIST_DIR}/assets ${CMAKE_CURRENT_BINARY_DIR}/assets 42 | ) 43 | add_dependencies(triskel-gui copy_assets) 44 | target_compile_definitions(triskel-gui PRIVATE ASSET_PATH="${CMAKE_CURRENT_BINARY_DIR}/assets") -------------------------------------------------------------------------------- /bin/gui/README.md: -------------------------------------------------------------------------------- 1 | # Triskel GUI 2 | 3 | A sample program using the triskel ImGui widget: 4 | 5 | ```cpp 6 | #include 7 | 8 | imgui_renderer.Begin("##cfg", {42.0F, 10.0F}); 9 | layout->render(imgui_renderer); 10 | imgui_renderer.End(); 11 | ``` 12 | 13 | ![](https://github.com/triskellib/triskel/blob/master/.github/assets/triskel-gui.png?raw=true) 14 | 15 | ## Usage 16 | 17 | ``` 18 | $ imgui-gui 19 | ``` 20 | 21 | The GUI works with LLVM bytecode (`.ll` and `.bc` files). 22 | There is also limited support for `x64` binaries using a custom _recursive descent_ disassembler. 23 | 24 | In the GUI, functions can be selected on the left. 25 | To modify the canvas viewport 26 | - use the scroll wheel for zoom 27 | - use the right mouse button to pan 28 | -------------------------------------------------------------------------------- /bin/gui/assets/fonts/Lato-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triskellib/triskel/4b02ad9e162788f4653a404ef47d1b26400ca62d/bin/gui/assets/fonts/Lato-Bold.ttf -------------------------------------------------------------------------------- /bin/gui/assets/fonts/Lato-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triskellib/triskel/4b02ad9e162788f4653a404ef47d1b26400ca62d/bin/gui/assets/fonts/Lato-Light.ttf -------------------------------------------------------------------------------- /bin/gui/assets/fonts/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triskellib/triskel/4b02ad9e162788f4653a404ef47d1b26400ca62d/bin/gui/assets/fonts/Roboto-Medium.ttf -------------------------------------------------------------------------------- /bin/gui/assets/img/Triskel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triskellib/triskel/4b02ad9e162788f4653a404ef47d1b26400ca62d/bin/gui/assets/img/Triskel.png -------------------------------------------------------------------------------- /bin/gui/disas/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources(triskel-gui PRIVATE 2 | rec_descent.cpp 3 | ) 4 | -------------------------------------------------------------------------------- /bin/gui/disas/rec_descent.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "triskel/graph/graph.hpp" 9 | #include "triskel/triskel.hpp" 10 | #include "triskel/utils/attribute.hpp" 11 | 12 | using Binary = std::map; 13 | 14 | struct Instruction { 15 | /// @brief The address of the instruction 16 | size_t addr; 17 | 18 | /// @brief A string representing this instruction 19 | std::string repr; 20 | }; 21 | 22 | /// @brief The CFG of a binary program 23 | struct BinaryCFG { 24 | BinaryCFG(); 25 | 26 | /// @brief The graph 27 | std::unique_ptr graph; 28 | 29 | /// @brief The node information 30 | triskel::NodeAttribute> instructions; 31 | 32 | /// @brief The edge type 33 | triskel::EdgeAttribute edge_types; 34 | }; 35 | 36 | /// @brief Creates a CFG 37 | auto make_binary_graph(size_t start_addr, 38 | const Binary& binary) -> std::unique_ptr; 39 | -------------------------------------------------------------------------------- /bin/gui/external/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources(triskel-gui 2 | PRIVATE 3 | application.cpp 4 | platform_glw.cpp 5 | renderer_ogl3.cpp 6 | ) 7 | -------------------------------------------------------------------------------- /bin/gui/external/application.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | struct Platform; 7 | struct Renderer; 8 | 9 | struct Application { 10 | explicit Application(const char* name); 11 | Application(const char* name, int argc, char** argv); 12 | ~Application(); 13 | 14 | auto Create(int argc, char** argv, int width = -1, int height = -1) -> bool; 15 | 16 | auto Run() -> int; 17 | 18 | void SetTitle(const char* title); 19 | 20 | auto Close() -> bool; 21 | void Quit(); 22 | 23 | [[nodiscard]] auto GetName() const -> const std::string&; 24 | 25 | [[nodiscard]] auto DefaultFont() const -> ImFont*; 26 | [[nodiscard]] auto HeaderFont() const -> ImFont*; 27 | 28 | auto LoadTexture(const char* path) -> ImTextureID; 29 | auto CreateTexture(const void* data, int width, int height) -> ImTextureID; 30 | void DestroyTexture(ImTextureID texture); 31 | auto GetTextureWidth(ImTextureID texture) -> int; 32 | auto GetTextureHeight(ImTextureID texture) -> int; 33 | 34 | virtual void OnStart(int argc, char** argv) {} 35 | virtual void OnStop() {} 36 | virtual void OnFrame(float deltaTime) {} 37 | 38 | [[nodiscard]] virtual auto GetWindowFlags() const -> ImGuiWindowFlags; 39 | 40 | virtual auto CanClose() -> bool { return true; } 41 | 42 | private: 43 | void RecreateFontAtlas(float scale); 44 | 45 | void Frame(); 46 | 47 | std::string m_Name; 48 | std::string m_IniFilename; 49 | std::unique_ptr m_Platform; 50 | std::unique_ptr m_Renderer; 51 | ImGuiContext* m_Context = nullptr; 52 | ImFont* m_DefaultFont = nullptr; 53 | ImFont* m_HeaderFont = nullptr; 54 | }; 55 | 56 | /* 57 | * MIT License 58 | * 59 | * Copyright (c) 2019 Michał Cichoń 60 | * 61 | * Permission is hereby granted, free of charge, to any person obtaining a copy 62 | * of this software and associated documentation files (the "Software"), to deal 63 | * in the Software without restriction, including without limitation the rights 64 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 65 | * copies of the Software, and to permit persons to whom the Software is 66 | * furnished to do so, subject to the following conditions: 67 | * 68 | * The above copyright notice and this permission notice shall be included in 69 | * all copies or substantial portions of the Software. 70 | * 71 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 72 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 73 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 74 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 75 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 76 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 77 | * SOFTWARE. 78 | */ 79 | -------------------------------------------------------------------------------- /bin/gui/external/credit.md: -------------------------------------------------------------------------------- 1 | The following files were taken from 2 | `thedmd`'s [imgui-node-editor](https://github.com/thedmd/imgui-node-editor/tree/master): 3 | 4 | - `application.cpp` 5 | - `application.h` 6 | - `platform.h` 7 | - `platform_glw.cpp` 8 | - `renderer.h` 9 | 10 | These files fall under the following MIT license: 11 | 12 | ``` 13 | MIT License 14 | 15 | Copyright (c) 2019 Michał Cichoń 16 | 17 | Permission is hereby granted, free of charge, to any person obtaining a copy 18 | of this software and associated documentation files (the "Software"), to deal 19 | in the Software without restriction, including without limitation the rights 20 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 21 | copies of the Software, and to permit persons to whom the Software is 22 | furnished to do so, subject to the following conditions: 23 | 24 | The above copyright notice and this permission notice shall be included in all 25 | copies or substantial portions of the Software. 26 | 27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 28 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 29 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 30 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 31 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 32 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 33 | SOFTWARE. 34 | ``` -------------------------------------------------------------------------------- /bin/gui/external/platform.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | struct Application; 5 | struct Renderer; 6 | 7 | struct Platform { 8 | virtual ~Platform() = default; 9 | 10 | virtual auto ApplicationStart(int argc, char** argv) -> bool = 0; 11 | virtual void ApplicationStop() = 0; 12 | 13 | virtual auto OpenMainWindow(const char* title, 14 | int width, 15 | int height) -> bool = 0; 16 | virtual auto CloseMainWindow() -> bool = 0; 17 | [[nodiscard]] virtual auto GetMainWindowHandle() const -> void* = 0; 18 | virtual void SetMainWindowTitle(const char* title) = 0; 19 | virtual void ShowMainWindow() = 0; 20 | virtual auto ProcessMainWindowEvents() -> bool = 0; 21 | [[nodiscard]] virtual auto IsMainWindowVisible() const -> bool = 0; 22 | 23 | virtual void SetRenderer(Renderer* renderer) = 0; 24 | 25 | virtual void NewFrame() = 0; 26 | virtual void FinishFrame() = 0; 27 | 28 | virtual void Quit() = 0; 29 | 30 | [[nodiscard]] auto HasWindowScaleChanged() const -> bool { 31 | return m_WindowScaleChanged; 32 | } 33 | void AcknowledgeWindowScaleChanged() { m_WindowScaleChanged = false; } 34 | [[nodiscard]] auto GetWindowScale() const -> float { return m_WindowScale; } 35 | void SetWindowScale(float windowScale) { 36 | if (windowScale == m_WindowScale) { 37 | return; 38 | } 39 | m_WindowScale = windowScale; 40 | m_WindowScaleChanged = true; 41 | } 42 | 43 | [[nodiscard]] auto HasFramebufferScaleChanged() const -> bool { 44 | return m_FramebufferScaleChanged; 45 | } 46 | void AcknowledgeFramebufferScaleChanged() { 47 | m_FramebufferScaleChanged = false; 48 | } 49 | [[nodiscard]] auto GetFramebufferScale() const -> float { 50 | return m_FramebufferScale; 51 | } 52 | void SetFramebufferScale(float framebufferScale) { 53 | if (framebufferScale == m_FramebufferScale) { 54 | return; 55 | } 56 | m_FramebufferScale = framebufferScale; 57 | m_FramebufferScaleChanged = true; 58 | } 59 | 60 | private: 61 | bool m_WindowScaleChanged = false; 62 | float m_WindowScale = 1.0F; 63 | bool m_FramebufferScaleChanged = false; 64 | float m_FramebufferScale = 1.0F; 65 | }; 66 | 67 | auto CreatePlatform(Application& application) -> std::unique_ptr; 68 | 69 | /* 70 | * MIT License 71 | * 72 | * Copyright (c) 2019 Michał Cichoń 73 | * 74 | * Permission is hereby granted, free of charge, to any person obtaining a copy 75 | * of this software and associated documentation files (the "Software"), to deal 76 | * in the Software without restriction, including without limitation the rights 77 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 78 | * copies of the Software, and to permit persons to whom the Software is 79 | * furnished to do so, subject to the following conditions: 80 | * 81 | * The above copyright notice and this permission notice shall be included in 82 | * all copies or substantial portions of the Software. 83 | * 84 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 85 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 86 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 87 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 88 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 89 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 90 | * SOFTWARE. 91 | */ 92 | -------------------------------------------------------------------------------- /bin/gui/external/renderer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct Platform; 6 | struct ImDrawData; 7 | struct ImVec4; 8 | using ImTextureID = unsigned long long; 9 | 10 | struct Renderer { 11 | virtual ~Renderer(){}; 12 | 13 | virtual auto Create(Platform& platform) -> bool = 0; 14 | virtual void Destroy() = 0; 15 | 16 | virtual void NewFrame() = 0; 17 | 18 | virtual void RenderDrawData(ImDrawData* drawData) = 0; 19 | 20 | virtual void Clear(const ImVec4& color) = 0; 21 | virtual void Present() = 0; 22 | 23 | virtual void Resize(int width, int height) = 0; 24 | 25 | virtual auto CreateTexture(const void* data, 26 | int width, 27 | int height) -> ImTextureID = 0; 28 | virtual void DestroyTexture(ImTextureID texture) = 0; 29 | virtual auto GetTextureWidth(ImTextureID texture) -> int = 0; 30 | virtual auto GetTextureHeight(ImTextureID texture) -> int = 0; 31 | }; 32 | 33 | auto CreateRenderer() -> std::unique_ptr; 34 | 35 | /* 36 | * MIT License 37 | * 38 | * Copyright (c) 2019 Michał Cichoń 39 | * 40 | * Permission is hereby granted, free of charge, to any person obtaining a copy 41 | * of this software and associated documentation files (the "Software"), to deal 42 | * in the Software without restriction, including without limitation the rights 43 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 44 | * copies of the Software, and to permit persons to whom the Software is 45 | * furnished to do so, subject to the following conditions: 46 | * 47 | * The above copyright notice and this permission notice shall be included in 48 | * all copies or substantial portions of the Software. 49 | * 50 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 51 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 52 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 53 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 54 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 55 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 56 | * SOFTWARE. 57 | */ 58 | -------------------------------------------------------------------------------- /bin/gui/external/renderer_ogl3.cpp: -------------------------------------------------------------------------------- 1 | #include "renderer.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "platform.h" 8 | 9 | using std::find_if; 10 | 11 | struct ImTexture { 12 | GLuint TextureID = 0; 13 | int Width = 0; 14 | int Height = 0; 15 | }; 16 | 17 | struct RendererOpenGL3 final : Renderer { 18 | auto Create(Platform& platform) -> bool override; 19 | void Destroy() override; 20 | void NewFrame() override; 21 | void RenderDrawData(ImDrawData* drawData) override; 22 | void Clear(const ImVec4& color) override; 23 | void Present() override; 24 | void Resize(int width, int height) override; 25 | 26 | auto FindTexture(ImTextureID texture) -> ImVector::iterator; 27 | auto CreateTexture(const void* data, 28 | int width, 29 | int height) -> ImTextureID override; 30 | void DestroyTexture(ImTextureID texture) override; 31 | auto GetTextureWidth(ImTextureID texture) -> int override; 32 | auto GetTextureHeight(ImTextureID texture) -> int override; 33 | 34 | Platform* m_Platform = nullptr; 35 | ImVector m_Textures; 36 | }; 37 | 38 | auto CreateRenderer() -> std::unique_ptr { 39 | return std::make_unique(); 40 | } 41 | 42 | auto RendererOpenGL3::Create(Platform& platform) -> bool { 43 | m_Platform = &platform; 44 | 45 | // Technically we should initialize OpenGL context here, 46 | // but for now we relay on one created by GLFW3 47 | 48 | bool err = glewInit() != GLEW_OK; 49 | 50 | if (err) { 51 | return false; 52 | } 53 | 54 | const char* glslVersion = "#version 130"; 55 | 56 | if (!ImGui_ImplOpenGL3_Init(glslVersion)) { 57 | return false; 58 | } 59 | 60 | m_Platform->SetRenderer(this); 61 | 62 | return true; 63 | } 64 | 65 | void RendererOpenGL3::Destroy() { 66 | if (m_Platform == nullptr) { 67 | return; 68 | } 69 | 70 | m_Platform->SetRenderer(nullptr); 71 | 72 | ImGui_ImplOpenGL3_Shutdown(); 73 | } 74 | 75 | void RendererOpenGL3::NewFrame() { 76 | ImGui_ImplOpenGL3_NewFrame(); 77 | } 78 | 79 | void RendererOpenGL3::RenderDrawData(ImDrawData* drawData) { 80 | ImGui_ImplOpenGL3_RenderDrawData(drawData); 81 | } 82 | 83 | void RendererOpenGL3::Clear(const ImVec4& color) { 84 | glClearColor(color.x, color.y, color.z, color.w); 85 | glClear(GL_COLOR_BUFFER_BIT); 86 | } 87 | 88 | void RendererOpenGL3::Present() {} 89 | 90 | void RendererOpenGL3::Resize(int width, int height) { 91 | glViewport(0, 0, width, height); 92 | } 93 | 94 | auto RendererOpenGL3::CreateTexture(const void* data, 95 | int width, 96 | int height) -> ImTextureID { 97 | m_Textures.resize(m_Textures.size() + 1); 98 | ImTexture& texture = m_Textures.back(); 99 | 100 | // Upload texture to graphics system 101 | GLint last_texture = 0; 102 | glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); 103 | glGenTextures(1, &texture.TextureID); 104 | glBindTexture(GL_TEXTURE_2D, texture.TextureID); 105 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 106 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 107 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, 108 | GL_UNSIGNED_BYTE, data); 109 | glBindTexture(GL_TEXTURE_2D, last_texture); 110 | 111 | texture.Width = width; 112 | texture.Height = height; 113 | 114 | return texture.TextureID; 115 | } 116 | 117 | auto RendererOpenGL3::FindTexture(ImTextureID textureID) 118 | -> ImVector::iterator { 119 | return find_if(m_Textures.begin(), m_Textures.end(), 120 | [textureID](ImTexture& texture) { 121 | return texture.TextureID == textureID; 122 | }); 123 | } 124 | 125 | void RendererOpenGL3::DestroyTexture(ImTextureID texture) { 126 | auto* textureIt = FindTexture(texture); 127 | if (textureIt == m_Textures.end()) { 128 | return; 129 | } 130 | 131 | glDeleteTextures(1, &textureIt->TextureID); 132 | 133 | m_Textures.erase(textureIt); 134 | } 135 | 136 | auto RendererOpenGL3::GetTextureWidth(ImTextureID texture) -> int { 137 | auto* textureIt = FindTexture(texture); 138 | if (textureIt != m_Textures.end()) { 139 | return textureIt->Width; 140 | } 141 | return 0; 142 | } 143 | 144 | auto RendererOpenGL3::GetTextureHeight(ImTextureID texture) -> int { 145 | auto* textureIt = FindTexture(texture); 146 | if (textureIt != m_Textures.end()) { 147 | return textureIt->Height; 148 | } 149 | return 0; 150 | } 151 | 152 | /* 153 | * MIT License 154 | * 155 | * Copyright (c) 2019 Michał Cichoń 156 | * 157 | * Permission is hereby granted, free of charge, to any person obtaining a copy 158 | * of this software and associated documentation files (the "Software"), to deal 159 | * in the Software without restriction, including without limitation the rights 160 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 161 | * copies of the Software, and to permit persons to whom the Software is 162 | * furnished to do so, subject to the following conditions: 163 | * 164 | * The above copyright notice and this permission notice shall be included in 165 | * all copies or substantial portions of the Software. 166 | * 167 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 168 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 169 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 170 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 171 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 172 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 173 | * SOFTWARE. 174 | */ 175 | -------------------------------------------------------------------------------- /bin/gui/gui/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources(triskel-gui PRIVATE 2 | triskel.cpp 3 | ) 4 | 5 | add_subdirectory(arch) -------------------------------------------------------------------------------- /bin/gui/gui/arch/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources(triskel-gui PRIVATE 2 | arch_binary.cpp 3 | arch_llvm.cpp 4 | ) 5 | -------------------------------------------------------------------------------- /bin/gui/gui/arch/arch.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "triskel/triskel.hpp" 6 | 7 | struct Arch { 8 | virtual ~Arch() = default; 9 | 10 | /// @brief Initialization 11 | virtual void start(const std::string& path) = 0; 12 | 13 | /// @brief The functions in the binary 14 | std::vector function_names; 15 | 16 | /// @brief Selects a function 17 | [[nodiscard]] virtual auto select_function(const std::string& name, 18 | triskel::Renderer& renderer) 19 | -> std::unique_ptr = 0; 20 | }; 21 | 22 | auto make_llvm_arch() -> std::unique_ptr; 23 | auto make_bin_arch() -> std::unique_ptr; 24 | -------------------------------------------------------------------------------- /bin/gui/gui/arch/arch_binary.cpp: -------------------------------------------------------------------------------- 1 | #include "arch.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "triskel/graph/igraph.hpp" 15 | #include "triskel/layout/ilayout.hpp" 16 | 17 | #include "../../disas/rec_descent.hpp" 18 | #include "triskel/internal.hpp" 19 | #include "triskel/triskel.hpp" 20 | #include "triskel/utils/attribute.hpp" 21 | 22 | using triskel::IGraph; 23 | using triskel::ILayout; 24 | using triskel::NodeAttribute; 25 | 26 | namespace { 27 | 28 | struct BinArch : public Arch { 29 | BinArch() : instructions{0, {}} {} 30 | 31 | std::unique_ptr graph; 32 | std::unique_ptr layout; 33 | 34 | std::map functions; 35 | 36 | std::unique_ptr bin_; 37 | NodeAttribute> instructions; 38 | 39 | std::map binary; 40 | 41 | void load_binary_from_path(const std::string& path) { 42 | bin_ = LIEF::ELF::Parser::parse(path); 43 | 44 | if (bin_ == nullptr) { 45 | fmt::print("Error occurred while loading the binary\n"); 46 | } 47 | 48 | for (auto& segment : bin_->segments()) { 49 | auto vaddr = segment.virtual_address(); 50 | auto content = segment.content(); 51 | // auto content_size = segment.get_content_size(); 52 | 53 | for (auto byte : content) { 54 | binary[vaddr] = byte; 55 | vaddr += 1; 56 | } 57 | } 58 | } 59 | 60 | auto select_function(const std::string& name, triskel::Renderer& renderer) 61 | -> std::unique_ptr override { 62 | auto cfg = make_binary_graph(functions[name], binary); 63 | 64 | auto labels = NodeAttribute{*cfg->graph, {}}; 65 | auto widths = NodeAttribute{*cfg->graph, {}}; 66 | auto heights = NodeAttribute{*cfg->graph, {}}; 67 | 68 | for (const auto& node : cfg->graph->nodes()) { 69 | auto label = std::string{}; 70 | 71 | for (const auto& insn : cfg->instructions.get(node)) { 72 | label += fmt::format("{:x} {}\n", insn.addr, insn.repr); 73 | } 74 | 75 | auto bbox = renderer.measure_text(label, renderer.STYLE_TEXT); 76 | 77 | labels.set(node, label); 78 | widths.set(node, bbox.x); 79 | heights.set(node, bbox.y); 80 | } 81 | 82 | for (const auto& edge : cfg->graph->edges()) { 83 | const auto& from = edge.from(); 84 | 85 | if (from.child_edges().size() == 2) { 86 | auto last_addr = cfg->instructions.get(from).back().addr; 87 | } 88 | } 89 | 90 | return triskel::make_layout(std::move(cfg->graph), widths, heights, 91 | labels, cfg->edge_types); 92 | } 93 | 94 | void start(const std::string& path) override { 95 | load_binary_from_path(path); 96 | assert(bin_ != nullptr); 97 | 98 | for (auto& f : bin_->functions()) { 99 | if (f.address() == 0) { 100 | continue; 101 | } 102 | 103 | auto name = f.name(); 104 | if (name.empty()) { 105 | name = fmt::format("F_{:x}", f.address()); 106 | } 107 | functions[name] = f.address(); 108 | function_names.push_back(name); 109 | } 110 | 111 | std::ranges::sort(function_names); 112 | } 113 | }; 114 | 115 | } // namespace 116 | 117 | auto make_bin_arch() -> std::unique_ptr { 118 | return std::make_unique(); 119 | } -------------------------------------------------------------------------------- /bin/gui/gui/arch/arch_llvm.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "arch.hpp" 15 | #include "triskel/triskel.hpp" 16 | 17 | namespace { 18 | 19 | struct LLVMArch : public Arch { 20 | llvm::LLVMContext ctx; 21 | std::unique_ptr m; 22 | 23 | void load_module_from_path(const std::string& path) { 24 | llvm::SMDiagnostic err; 25 | fmt::print("Loading the ll file \"{}\"\n", path); 26 | 27 | m = llvm::parseIRFile(path, err, ctx); 28 | 29 | if (m == nullptr) { 30 | fmt::print("ERROR\n"); 31 | 32 | err.print("leviathan", ::llvm::errs()); 33 | fmt::print("Error while attempting to read the ll file {}", path); 34 | throw std::runtime_error("Error loading llvm module"); 35 | } 36 | } 37 | 38 | auto select_function(const std::string& name, triskel::Renderer& renderer) 39 | -> std::unique_ptr override { 40 | auto* f = m->getFunction(name); 41 | fmt::print("Selecting function {} ({} blocks)\n", f->getName().str(), 42 | f->size()); 43 | return triskel::make_layout(f, &renderer); 44 | } 45 | 46 | void start(const std::string& path) override { 47 | load_module_from_path(path); 48 | assert(m != nullptr); 49 | 50 | for (auto& f : *m) { 51 | function_names.push_back(f.getName().str()); 52 | } 53 | 54 | std::ranges::sort(function_names); 55 | } 56 | }; 57 | 58 | } // namespace 59 | 60 | auto make_llvm_arch() -> std::unique_ptr { 61 | return std::make_unique(); 62 | } -------------------------------------------------------------------------------- /bin/gui/gui/triskel.cpp: -------------------------------------------------------------------------------- 1 | #include "triskel.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "triskel/triskel.hpp" 11 | 12 | namespace { 13 | auto Splitter(bool split_vertically, 14 | float thickness, 15 | float* size1, 16 | float* size2, 17 | float min_size1, 18 | float min_size2, 19 | float splitter_long_axis_size = -1.0F) -> bool { 20 | ImGuiContext& g = *GImGui; 21 | ImGuiWindow* window = g.CurrentWindow; 22 | ImGuiID id = window->GetID("##Splitter"); 23 | ImRect bb; 24 | bb.Min = window->DC.CursorPos + 25 | (split_vertically ? ImVec2(*size1, 0.0F) : ImVec2(0.0F, *size1)); 26 | bb.Max = bb.Min + ImGui::CalcItemSize( 27 | split_vertically 28 | ? ImVec2(thickness, splitter_long_axis_size) 29 | : ImVec2(splitter_long_axis_size, thickness), 30 | 0.0F, 0.0F); 31 | return ImGui::SplitterBehavior(bb, id, 32 | split_vertically ? ImGuiAxis_X : ImGuiAxis_Y, 33 | size1, size2, min_size1, min_size2, 0.0F); 34 | } 35 | } // namespace 36 | 37 | void Triskel::select_function(const std::string& fname) { 38 | selected_function = fname; 39 | 40 | layout_ = arch->select_function(fname, *imgui_renderer_); 41 | 42 | imgui_renderer_->fit(layout_->get_width(), layout_->get_height()); 43 | } 44 | 45 | Triskel::Triskel() : Application("Triskel") {} 46 | 47 | void Triskel::OnStart(int argc, char** argv) { 48 | ImGui::StyleColorsLight(); 49 | 50 | SetTitle("Triskel"); 51 | 52 | // Image 53 | logo = LoadTexture(ASSET_PATH "/img/Triskel.png"); 54 | 55 | if (argc <= 1) { 56 | fmt::print("Missing path\n"); 57 | } 58 | std::string path = argv[1]; 59 | 60 | if (path.ends_with(".bc") || path.ends_with(".ll")) { 61 | arch = make_llvm_arch(); 62 | } else { 63 | arch = make_bin_arch(); 64 | } 65 | 66 | imgui_renderer_ = triskel::make_imgui_renderer(); 67 | 68 | arch->start(path); 69 | } 70 | 71 | void Triskel::OnFrame(float /*deltaTime*/) { 72 | auto availableRegion = ImGui::GetContentRegionAvail(); 73 | 74 | static float s_SplitterSize = 6.0F; 75 | static float s_SplitterArea = 0.0F; 76 | static float s_LeftPaneSize = 0.0F; 77 | static float s_RightPaneSize = 0.0F; 78 | 79 | if (s_SplitterArea != availableRegion.x) { 80 | if (s_SplitterArea == 0.0F) { 81 | s_SplitterArea = availableRegion.x; 82 | s_LeftPaneSize = ImFloor(availableRegion.x * 0.25F); 83 | s_RightPaneSize = 84 | availableRegion.x - s_LeftPaneSize - s_SplitterSize; 85 | } else { 86 | auto ratio = availableRegion.x / s_SplitterArea; 87 | s_SplitterArea = availableRegion.x; 88 | s_LeftPaneSize = s_LeftPaneSize * ratio; 89 | s_RightPaneSize = 90 | availableRegion.x - s_LeftPaneSize - s_SplitterSize; 91 | } 92 | } 93 | 94 | Splitter(true, s_SplitterSize, &s_LeftPaneSize, &s_RightPaneSize, 100.0F, 95 | 100.0F); 96 | 97 | FunctionSelector("##function_selection", ImVec2(s_LeftPaneSize, -1)); 98 | 99 | ImGui::SameLine(0.0F, s_SplitterSize); 100 | 101 | if (layout_ != nullptr) { 102 | imgui_renderer_->Begin("##main_graph", {s_RightPaneSize, 0.0F}); 103 | layout_->render(*imgui_renderer_); 104 | imgui_renderer_->End(); 105 | } else { 106 | DrawLogo(); 107 | } 108 | } 109 | 110 | void Triskel::DrawLogo() { 111 | auto cursor = ImGui::GetCursorScreenPos(); 112 | 113 | auto width = static_cast(GetTextureWidth(logo)); 114 | auto height = static_cast(GetTextureHeight(logo)); 115 | 116 | if (2 * width > ImGui::GetContentRegionAvail().x) { 117 | auto scale_factor = ImGui::GetContentRegionAvail().x / (2 * width); 118 | 119 | width *= scale_factor; 120 | height *= scale_factor; 121 | } 122 | 123 | float xOffset = (ImGui::GetContentRegionAvail().x - width) / 2.0F; 124 | float yOffset = (ImGui::GetContentRegionAvail().y - height) / 2.0F; 125 | 126 | // Ensure offsets are not negative 127 | xOffset = std::max(xOffset, 0.0F); 128 | yOffset = std::max(yOffset, 0.0F); 129 | 130 | // Adjust the cursor position 131 | ImGui::SetCursorPosX(cursor.x + xOffset); 132 | ImGui::SetCursorPosY(cursor.y + yOffset); 133 | ImGui::Image(logo, ImVec2(width, height)); 134 | } 135 | 136 | void Triskel::FunctionSelector(const char* id, ImVec2 size) { 137 | ImGui::BeginChild(id, size, 0, 0); 138 | { 139 | static char filter[128] = ""; 140 | ImGui::InputText("Search", filter, IM_ARRAYSIZE(filter)); 141 | 142 | ImGui::BeginChild("function-search", 143 | {ImGui::GetContentRegionAvail().x, 500.0F}); 144 | 145 | // Move this to arch 146 | if (arch != nullptr) { 147 | for (const auto& item : arch->function_names) { 148 | auto black_text = (item == selected_function); 149 | // TODO: disable declarations 150 | // auto disable = f->isDeclaration(); 151 | if (strlen(filter) == 0 || 152 | item.find(filter) != std::string::npos) { 153 | if (ImGui::Selectable(item.c_str(), 154 | item == selected_function)) { 155 | if (item != selected_function) { 156 | select_function(item); 157 | } 158 | } 159 | } 160 | } 161 | } 162 | 163 | ImGui::EndChild(); 164 | 165 | ImGui::Separator(); 166 | } 167 | ImGui::EndChild(); 168 | } 169 | -------------------------------------------------------------------------------- /bin/gui/gui/triskel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define IMGUI_DEFINE_MATH_OPERATORS 6 | #include 7 | #include 8 | #include 9 | 10 | #include "triskel/triskel.hpp" 11 | 12 | #include "../external/application.h" 13 | #include "arch/arch.hpp" 14 | 15 | struct Triskel : public Application { 16 | using Application::Application; 17 | 18 | constexpr static auto GUTTER = 50.0F; 19 | constexpr static auto SIZE = 100.0F; 20 | float x_gutter = 0.0F; 21 | float y_gutter = 0.0F; 22 | 23 | explicit Triskel(); 24 | 25 | void OnFrame(float /*deltaTime*/) override; 26 | void OnStart(int argc, char** argv) override; 27 | 28 | void FunctionSelector(const char*, ImVec2 size); 29 | 30 | void DrawLogo(); 31 | 32 | void select_function(const std::string& fname); 33 | std::string selected_function; 34 | 35 | std::unique_ptr arch; 36 | std::unique_ptr layout_; 37 | std::unique_ptr imgui_renderer_; 38 | 39 | // Image 40 | ImTextureID logo; 41 | 42 | void style() const; 43 | }; -------------------------------------------------------------------------------- /bin/gui/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "gui/triskel.hpp" 4 | 5 | auto main(int argc, char** argv) -> int { 6 | gflags::ParseCommandLineFlags(&argc, &argv, true); 7 | 8 | auto app = Triskel{}; 9 | 10 | if (app.Create(argc, argv)) { 11 | return app.Run(); 12 | } 13 | 14 | return 0; 15 | } -------------------------------------------------------------------------------- /bin/img/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(triskel-img 2 | VERSION 1.0.0 3 | DESCRIPTION "Image making binary for the triskel CFG layout library" 4 | LANGUAGES CXX C 5 | ) 6 | 7 | add_executable(triskel-img main.cpp) 8 | 9 | target_link_libraries(triskel-img 10 | PRIVATE 11 | triskel 12 | gflags 13 | fmt::fmt 14 | ) 15 | 16 | if (ENABLE_LINTING) 17 | find_program(CLANG_TIDY NAMES "clang-tidy" REQUIRED) 18 | set_target_properties(triskel-img PROPERTIES 19 | CXX_CLANG_TIDY ${CLANG_TIDY} 20 | ) 21 | endif() 22 | -------------------------------------------------------------------------------- /bin/img/README.md: -------------------------------------------------------------------------------- 1 | # triskel-img 2 | 3 | Generates an image for the CFG of an LLVM function 4 | 5 | ## Usage 6 | 7 | ``` 8 | $ triskel-img 9 | ``` 10 | -------------------------------------------------------------------------------- /bin/img/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "triskel/triskel.hpp" 15 | 16 | namespace { 17 | auto load_module_from_path(llvm::LLVMContext& ctx, const std::string& path) 18 | -> std::unique_ptr { 19 | llvm::SMDiagnostic err; 20 | fmt::print("Loading the ll file \"{}\"\n", path); 21 | 22 | auto m = llvm::parseIRFile(path, err, ctx); 23 | 24 | if (m == nullptr) { 25 | fmt::print("ERROR\n"); 26 | 27 | err.print("triskel", ::llvm::errs()); 28 | fmt::print("Error while attempting to read the ll file {}", path); 29 | return nullptr; 30 | } 31 | 32 | return m; 33 | } 34 | 35 | auto draw_function(llvm::Function* f) { 36 | auto renderer = triskel::make_svg_renderer(); 37 | 38 | auto layout = triskel::make_layout(f, renderer.get()); 39 | 40 | layout->render_and_save(*renderer, "./out.svg"); 41 | } 42 | 43 | }; // namespace 44 | 45 | auto main(int argc, char** argv) -> int { 46 | gflags::ParseCommandLineFlags(&argc, &argv, true); 47 | 48 | if (argc != 3) { 49 | fmt::print( 50 | "No module specified. Usage:\n" 51 | "{} \n", 52 | argv[0]); 53 | return 1; 54 | } 55 | 56 | llvm::LLVMContext ctx; 57 | 58 | auto module = load_module_from_path(ctx, argv[1]); 59 | 60 | auto* function = module->getFunction(argv[2]); 61 | 62 | if (function == nullptr) { 63 | fmt::print("No such function found: \"{}\"\n", argv[2]); 64 | return 1; 65 | } 66 | 67 | draw_function(function); 68 | 69 | return 0; 70 | } -------------------------------------------------------------------------------- /bin/wasm/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(triskel-wasm 2 | VERSION 1.0.0 3 | DESCRIPTION "WASM version of the triskel CFG layout library" 4 | LANGUAGES CXX C 5 | ) 6 | 7 | add_executable(triskel-wasm main.cpp) 8 | 9 | include_directories($ENV{EMSDK}/upstream/emscripten/system/include) 10 | 11 | target_link_libraries(triskel-wasm PRIVATE 12 | triskel 13 | embind 14 | ) 15 | 16 | if (ENABLE_LINTING) 17 | find_program(CLANG_TIDY NAMES "clang-tidy" REQUIRED) 18 | set_target_properties(triskel-img PROPERTIES 19 | CXX_CLANG_TIDY ${CLANG_TIDY} 20 | ) 21 | endif() 22 | 23 | if (WASM_DEBUG) 24 | set_target_properties(triskel-wasm PROPERTIES LINK_FLAGS "-g4 -gsource-map -sSAFE_HEAP=1 -sASSERTIONS=2 -sSTACK_OVERFLOW_CHECK=2 -sEXCEPTION_DEBUG=1 -sDEMANGLE_SUPPORT=1 -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_STACK=512MB -s INITIAL_MEMORY=1GB -s MAXIMUM_MEMORY=4GB") 25 | target_link_options(triskel-wasm PRIVATE 26 | --emit-tsd "triskel-wasm.d.ts" 27 | ) 28 | else() 29 | set_target_properties(triskel-wasm PROPERTIES COMPILER_FLAGS "-O3") 30 | set_target_properties(triskel-wasm PROPERTIES LINK_FLAGS "-s WASM=1 -s ALLOW_MEMORY_GROWTH=1") 31 | target_link_options(triskel-wasm PRIVATE 32 | --emit-tsd "triskel-wasm.d.ts" 33 | ) 34 | endif() -------------------------------------------------------------------------------- /bin/wasm/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "triskel/triskel.hpp" 6 | #include "triskel/utils/point.hpp" 7 | 8 | // Binding the struct and function to WebAssembly 9 | EMSCRIPTEN_BINDINGS(my_module) { 10 | emscripten::value_object("Point") 11 | .field("x", &triskel::Point::x) 12 | .field("y", &triskel::Point::y); 13 | 14 | emscripten::register_vector("PointVector"); 15 | 16 | emscripten::class_("CFGLayout") 17 | .function("edge_count", &triskel::CFGLayout::edge_count) 18 | .function("node_count", &triskel::CFGLayout::node_count) 19 | .function("get_height", &triskel::CFGLayout::get_height) 20 | .function("get_width", &triskel::CFGLayout::get_width) 21 | .function("get_coords", &triskel::CFGLayout::get_coords) 22 | .function("get_waypoints", &triskel::CFGLayout::get_waypoints); 23 | 24 | emscripten::class_("LayoutBuilder") 25 | .function("build", &triskel::LayoutBuilder::build) 26 | .function("graphviz", &triskel::LayoutBuilder::graphviz) 27 | .function("make_node", 28 | emscripten::select_overload( 29 | &triskel::LayoutBuilder::make_node)) 30 | .function("make_empty_node", emscripten::select_overload( 31 | &triskel::LayoutBuilder::make_node)) 32 | .function("make_edge", 33 | emscripten::select_overload( 34 | &triskel::LayoutBuilder::make_edge)); 35 | 36 | emscripten::function("make_layout_builder", &triskel::make_layout_builder); 37 | } -------------------------------------------------------------------------------- /bindings/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(python) -------------------------------------------------------------------------------- /bindings/python/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | _skbuild 3 | *.egg-info 4 | 5 | .pypirc -------------------------------------------------------------------------------- /bindings/python/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(pytriskel 2 | VERSION 1.0.1 3 | DESCRIPTION "Python bindings for the triskel CFG layout library" 4 | LANGUAGES CXX C) 5 | 6 | # Pybind 7 | find_package(Python COMPONENTS Development.Module Interpreter REQUIRED) 8 | FetchContent_Declare( 9 | pybind11 10 | GIT_REPOSITORY https://github.com/pybind/pybind11 11 | GIT_TAG a2e59f0e7065404b44dfe92a28aca47ba1378dc4 # 2.13.6 12 | FIND_PACKAGE_ARGS 13 | ) 14 | FetchContent_MakeAvailable(pybind11) 15 | 16 | 17 | pybind11_add_module(pytriskel pytriskel.cpp) 18 | 19 | target_link_libraries(pytriskel PRIVATE triskel) 20 | 21 | install(TARGETS pytriskel LIBRARY DESTINATION .) -------------------------------------------------------------------------------- /bindings/python/README.md: -------------------------------------------------------------------------------- 1 | ![](https://github.com/triskellib/triskel/raw/master/.github/assets/triskel_light.png) 2 | 3 | Python bindings for the [triskel](https://github.com/triskellib/triskel) library. 4 | 5 | ## Getting started 6 | 7 | ``` 8 | $ pip install pytriskel 9 | ``` 10 | 11 | ```python 12 | from pytriskel.pytriskel import * 13 | 14 | builder = make_layout_builder() 15 | 16 | # Build the graph 17 | n1 = builder.make_node("Hello") 18 | n2 = builder.make_node("World") 19 | builder.make_edge(n1, n2) 20 | 21 | # Measure node size using font size 22 | png_renderer = make_png_renderer() 23 | builder.measure_nodes(png_renderer) 24 | 25 | # Export an image 26 | layout = builder.build() 27 | layout.save(png_renderer, "out.png") 28 | ``` 29 | -------------------------------------------------------------------------------- /bindings/python/build_wheels.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e -u -x 3 | 4 | # Clear the previous wheels 5 | rm dist/* || true 6 | 7 | # Creates a build directory 8 | mkdir /build 9 | 10 | yum install -y cairo cairo-devel 11 | 12 | export PLAT="manylinux_2_34_x86_64" 13 | 14 | function repair_wheel { 15 | export LD_LIBRARY_PATH=/lib:$LD_LIBRARY_PATH 16 | export LD_LIBRARY_PATH=/lib64:$LD_LIBRARY_PATH 17 | 18 | wheel="$1" 19 | if ! auditwheel show "$wheel"; then 20 | echo "Skipping non-platform wheel $wheel" 21 | else 22 | auditwheel repair "$wheel" --plat "$PLAT" -w dist 23 | fi 24 | } 25 | 26 | # Compile wheels 27 | for PYBIN in /opt/python/*/bin; do 28 | export SKBUILD_DIR="/build/${PYBIN}" 29 | mkdir --parents "${SKBUILD_DIR}" 30 | Python_ROOT_DIR=${PYBIN} "${PYBIN}/pip" wheel -w /build/wheelhouse/ . || true 31 | done 32 | 33 | # Bundle external shared libraries into the wheels 34 | for whl in /build/wheelhouse/*.whl; do 35 | repair_wheel "$whl" 36 | done -------------------------------------------------------------------------------- /bindings/python/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | "scikit-build>=0.13", 5 | "cmake", 6 | "ninja", 7 | "wheel", 8 | "cibuildwheel", 9 | ] 10 | build-backend = "setuptools.build_meta" 11 | 12 | 13 | [tool.cibuildwheel] 14 | manylinux-x86_64-image = "quay.io/pypa/manylinux_2_28_x86_64" # Use a newer manylinux image 15 | -------------------------------------------------------------------------------- /bindings/python/pytriskel.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "triskel/triskel.hpp" 4 | #include "triskel/utils/point.hpp" 5 | 6 | namespace py = pybind11; 7 | 8 | // NOLINTNEXTLINE(google-build-using-namespace) 9 | using namespace pybind11::literals; 10 | 11 | using EdgeType = triskel::LayoutBuilder::EdgeType; 12 | 13 | PYBIND11_MODULE(pytriskel, m) { 14 | py::enum_(m, "EdgeType") 15 | .value("Default", EdgeType::Default) 16 | .value("T", EdgeType::True) 17 | .value("F", EdgeType::False) 18 | .export_values(); 19 | 20 | py::class_ Renderer(m, "Renderer"); 21 | 22 | py::class_ ExportingRenderer( 23 | m, "ExportingRenderer"); 24 | 25 | py::class_(m, "Point") 26 | .def(py::init()) 27 | .def_readwrite("x", &triskel::Point::x) 28 | .def_readwrite("y", &triskel::Point::y); 29 | 30 | py::class_(m, "CFGLayout") 31 | .def("get_coords", &triskel::CFGLayout::get_coords, 32 | "Gets the x and y coordinate of a node") 33 | .def("get_waypoints", &triskel::CFGLayout::get_waypoints, 34 | "Gets the waypoints of an edge") 35 | .def("get_height", &triskel::CFGLayout::get_height, 36 | "Gets height of the graph") 37 | .def("get_height", &triskel::CFGLayout::get_width, 38 | "Gets width of the graph") 39 | .def( 40 | "save", 41 | [](triskel::CFGLayout& layout, triskel::ExportingRenderer& renderer, 42 | const std::string& path) { 43 | layout.render_and_save(renderer, path); 44 | }, 45 | "Generate an image of the graph"); 46 | 47 | py::class_(m, "LayoutBuilder") 48 | .def("make_node", 49 | py::overload_cast<>(&triskel::LayoutBuilder::make_node), 50 | "Creates a new node") 51 | .def( 52 | "make_node", 53 | py::overload_cast(&triskel::LayoutBuilder::make_node), 54 | "Creates a new node with a width and height") 55 | .def("make_node", 56 | py::overload_cast( 57 | &triskel::LayoutBuilder::make_node), 58 | "Creates a new node with a label") 59 | .def("make_node", 60 | py::overload_cast( 61 | &triskel::LayoutBuilder::make_node), 62 | "Creates a new node using a renderer to determine the size of " 63 | "labels") 64 | .def("make_edge", 65 | py::overload_cast( 66 | &triskel::LayoutBuilder::make_edge), 67 | "Creates a new edge", "from", "to") 68 | .def("make_edge", 69 | py::overload_cast( 70 | &triskel::LayoutBuilder::make_edge), 71 | "Creates a new edge", "from", "to", "type") 72 | .def("measure_nodes", &triskel::LayoutBuilder::measure_nodes, 73 | "Calculates the dimension of each node using the renderer") 74 | .def("build", &triskel::LayoutBuilder::build, "Builds the layout"); 75 | 76 | m.def("make_layout_builder", &triskel::make_layout_builder); 77 | 78 | m.def("make_png_renderer", &triskel::make_png_renderer); 79 | 80 | m.def("make_svg_renderer", &triskel::make_svg_renderer); 81 | } -------------------------------------------------------------------------------- /bindings/python/pytriskel/__init__.pyi: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from pytriskel.pytriskel import CFGLayout 3 | from pytriskel.pytriskel import EdgeType 4 | from pytriskel.pytriskel import ExportingRenderer 5 | from pytriskel.pytriskel import LayoutBuilder 6 | from pytriskel.pytriskel import Point 7 | from pytriskel.pytriskel import Renderer 8 | from pytriskel.pytriskel import make_layout_builder 9 | from pytriskel.pytriskel import make_png_renderer 10 | from pytriskel.pytriskel import make_svg_renderer 11 | from . import pytriskel 12 | __all__: str = 'make_png_renderer' 13 | Default: EdgeType # value = 14 | F: EdgeType # value = 15 | T: EdgeType # value = 16 | -------------------------------------------------------------------------------- /bindings/python/pytriskel/pytriskel.pyi: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | import typing 3 | __all__ = ['CFGLayout', 'Default', 'EdgeType', 'ExportingRenderer', 'F', 'LayoutBuilder', 'Point', 'Renderer', 'T', 'make_layout_builder', 'make_png_renderer', 'make_svg_renderer'] 4 | class CFGLayout: 5 | @staticmethod 6 | def _pybind11_conduit_v1_(*args, **kwargs): 7 | ... 8 | def get_coords(self, arg0: int) -> Point: 9 | """ 10 | Gets the x and y coordinate of a node 11 | """ 12 | @typing.overload 13 | def get_height(self) -> float: 14 | """ 15 | Gets height of the graph 16 | """ 17 | @typing.overload 18 | def get_height(self) -> float: 19 | """ 20 | Gets width of the graph 21 | """ 22 | def get_waypoints(self, arg0: int) -> ...: 23 | """ 24 | Gets the waypoints of an edge 25 | """ 26 | def save(self, arg0: ExportingRenderer, arg1: str) -> None: 27 | """ 28 | Generate an image of the graph 29 | """ 30 | class EdgeType: 31 | """ 32 | Members: 33 | 34 | Default 35 | 36 | T 37 | 38 | F 39 | """ 40 | Default: typing.ClassVar[EdgeType] # value = 41 | F: typing.ClassVar[EdgeType] # value = 42 | T: typing.ClassVar[EdgeType] # value = 43 | __members__: typing.ClassVar[dict[str, EdgeType]] # value = {'Default': , 'T': , 'F': } 44 | @staticmethod 45 | def _pybind11_conduit_v1_(*args, **kwargs): 46 | ... 47 | def __eq__(self, other: typing.Any) -> bool: 48 | ... 49 | def __getstate__(self) -> int: 50 | ... 51 | def __hash__(self) -> int: 52 | ... 53 | def __index__(self) -> int: 54 | ... 55 | def __init__(self, value: int) -> None: 56 | ... 57 | def __int__(self) -> int: 58 | ... 59 | def __ne__(self, other: typing.Any) -> bool: 60 | ... 61 | def __repr__(self) -> str: 62 | ... 63 | def __setstate__(self, state: int) -> None: 64 | ... 65 | def __str__(self) -> str: 66 | ... 67 | @property 68 | def name(self) -> str: 69 | ... 70 | @property 71 | def value(self) -> int: 72 | ... 73 | class ExportingRenderer(Renderer): 74 | @staticmethod 75 | def _pybind11_conduit_v1_(*args, **kwargs): 76 | ... 77 | class LayoutBuilder: 78 | @staticmethod 79 | def _pybind11_conduit_v1_(*args, **kwargs): 80 | ... 81 | def build(self) -> CFGLayout: 82 | """ 83 | Builds the layout 84 | """ 85 | @typing.overload 86 | def make_edge(self, arg0: int, arg1: int) -> int: 87 | """ 88 | to 89 | """ 90 | @typing.overload 91 | def make_edge(self, arg0: int, arg1: int, arg2: EdgeType) -> int: 92 | """ 93 | type 94 | """ 95 | @typing.overload 96 | def make_node(self) -> int: 97 | """ 98 | Creates a new node 99 | """ 100 | @typing.overload 101 | def make_node(self, arg0: float, arg1: float) -> int: 102 | """ 103 | Creates a new node with a width and height 104 | """ 105 | @typing.overload 106 | def make_node(self, arg0: str) -> int: 107 | """ 108 | Creates a new node with a label 109 | """ 110 | @typing.overload 111 | def make_node(self, arg0: Renderer, arg1: str) -> int: 112 | """ 113 | Creates a new node using a renderer to determine the size of labels 114 | """ 115 | def measure_nodes(self, arg0: Renderer) -> None: 116 | """ 117 | Calculates the dimension of each node using the renderer 118 | """ 119 | class Point: 120 | x: float 121 | y: float 122 | @staticmethod 123 | def _pybind11_conduit_v1_(*args, **kwargs): 124 | ... 125 | def __init__(self, arg0: int, arg1: int) -> None: 126 | ... 127 | class Renderer: 128 | @staticmethod 129 | def _pybind11_conduit_v1_(*args, **kwargs): 130 | ... 131 | def make_layout_builder() -> LayoutBuilder: 132 | ... 133 | def make_png_renderer() -> ExportingRenderer: 134 | ... 135 | def make_svg_renderer() -> ExportingRenderer: 136 | ... 137 | Default: EdgeType # value = 138 | F: EdgeType # value = 139 | T: EdgeType # value = 140 | -------------------------------------------------------------------------------- /bindings/python/setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from skbuild import setup 3 | from pathlib import Path 4 | 5 | CMAKE_ARGS = ["-DCMAKE_BUILD_TYPE=Release","-DENABLE_IMGUI=OFF","-DENABLE_LLVM=OFF"] 6 | 7 | 8 | 9 | python_root = os.environ.get("Python_ROOT_DIR", "") 10 | if python_root != "": 11 | CMAKE_ARGS.append(f"-DPython_ROOT_DIR={python_root}") 12 | 13 | build_dir = os.environ.get("SKBUILD_DIR", ".") 14 | 15 | this_directory = Path(__file__).parent 16 | long_description = (this_directory / "README.md").read_text() 17 | 18 | setup( 19 | name="pytriskel", 20 | version="0.2.0", 21 | author="Jack Royer", 22 | author_email="jack.royer@inria.fr", 23 | license="MPL2.0", 24 | url="https://github.com/triskellib/triskel", 25 | description="CFG visualization library", 26 | long_description=long_description, 27 | long_description_content_type='text/markdown', 28 | packages=["pytriskel"], 29 | keywords=["cfg", "visualization", "reverse-engineering"], 30 | cmake_install_dir="pytriskel", 31 | cmake_source_dir="../..", 32 | cmake_args=CMAKE_ARGS, 33 | zip_safe=False, 34 | options={"egg_info": {"egg_base": build_dir}} 35 | ) -------------------------------------------------------------------------------- /cmake/FindCairo.cmake: -------------------------------------------------------------------------------- 1 | # FindCairo.cmake 2 | # 3 | # 4 | # CMake support for Cairo. 5 | # 6 | # License: 7 | # 8 | # Copyright (c) 2016 Evan Nemerson 9 | # 10 | # Permission is hereby granted, free of charge, to any person 11 | # obtaining a copy of this software and associated documentation 12 | # files (the "Software"), to deal in the Software without 13 | # restriction, including without limitation the rights to use, copy, 14 | # modify, merge, publish, distribute, sublicense, and/or sell copies 15 | # of the Software, and to permit persons to whom the Software is 16 | # furnished to do so, subject to the following conditions: 17 | # 18 | # The above copyright notice and this permission notice shall be 19 | # included in all copies or substantial portions of the Software. 20 | # 21 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 28 | # DEALINGS IN THE SOFTWARE. 29 | 30 | find_package(PkgConfig) 31 | 32 | set(Cairo_DEPS) 33 | 34 | if(PKG_CONFIG_FOUND) 35 | pkg_search_module(Cairo_PKG cairo) 36 | endif() 37 | 38 | find_library(Cairo_LIBRARY cairo HINTS ${Cairo_PKG_LIBRARY_DIRS}) 39 | set(Cairo cairo) 40 | 41 | if(Cairo_LIBRARY) 42 | add_library(${Cairo} SHARED IMPORTED) 43 | set_property(TARGET ${Cairo} PROPERTY IMPORTED_LOCATION "${Cairo_LIBRARY}") 44 | set_property(TARGET ${Cairo} PROPERTY INTERFACE_COMPILE_OPTIONS "${Cairo_PKG_CFLAGS_OTHER}") 45 | 46 | set(Cairo_INCLUDE_DIRS) 47 | 48 | find_path(Cairo_INCLUDE_DIR "cairo.h" 49 | HINTS ${Cairo_PKG_INCLUDE_DIRS}) 50 | 51 | if(Cairo_INCLUDE_DIR) 52 | file(STRINGS "${Cairo_INCLUDE_DIR}/cairo-version.h" Cairo_VERSION_MAJOR REGEX "^#define CAIRO_VERSION_MAJOR +\\(?([0-9]+)\\)?$") 53 | string(REGEX REPLACE "^#define CAIRO_VERSION_MAJOR \\(?([0-9]+)\\)?$" "\\1" Cairo_VERSION_MAJOR "${Cairo_VERSION_MAJOR}") 54 | file(STRINGS "${Cairo_INCLUDE_DIR}/cairo-version.h" Cairo_VERSION_MINOR REGEX "^#define CAIRO_VERSION_MINOR +\\(?([0-9]+)\\)?$") 55 | string(REGEX REPLACE "^#define CAIRO_VERSION_MINOR \\(?([0-9]+)\\)?$" "\\1" Cairo_VERSION_MINOR "${Cairo_VERSION_MINOR}") 56 | file(STRINGS "${Cairo_INCLUDE_DIR}/cairo-version.h" Cairo_VERSION_MICRO REGEX "^#define CAIRO_VERSION_MICRO +\\(?([0-9]+)\\)?$") 57 | string(REGEX REPLACE "^#define CAIRO_VERSION_MICRO \\(?([0-9]+)\\)?$" "\\1" Cairo_VERSION_MICRO "${Cairo_VERSION_MICRO}") 58 | set(Cairo_VERSION "${Cairo_VERSION_MAJOR}.${Cairo_VERSION_MINOR}.${Cairo_VERSION_MICRO}") 59 | unset(Cairo_VERSION_MAJOR) 60 | unset(Cairo_VERSION_MINOR) 61 | unset(Cairo_VERSION_MICRO) 62 | 63 | list(APPEND Cairo_INCLUDE_DIRS ${Cairo_INCLUDE_DIR}) 64 | set_property(TARGET ${Cairo} PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${Cairo_INCLUDE_DIR}") 65 | endif() 66 | endif() 67 | 68 | set(Cairo_DEPS_FOUND_VARS) 69 | foreach(cairo_dep ${Cairo_DEPS}) 70 | find_package(${cairo_dep}) 71 | 72 | list(APPEND Cairo_DEPS_FOUND_VARS "${cairo_dep}_FOUND") 73 | list(APPEND Cairo_INCLUDE_DIRS ${${cairo_dep}_INCLUDE_DIRS}) 74 | 75 | set_property (TARGET ${Cairo} APPEND PROPERTY INTERFACE_LINK_LIBRARIES "${${cairo_dep}}") 76 | endforeach(cairo_dep) 77 | 78 | include(FindPackageHandleStandardArgs) 79 | find_package_handle_standard_args(Cairo 80 | REQUIRED_VARS 81 | Cairo_LIBRARY 82 | Cairo_INCLUDE_DIRS 83 | ${Cairo_DEPS_FOUND_VARS} 84 | VERSION_VAR 85 | Cairo_VERSION) 86 | 87 | unset(Cairo_DEPS_FOUND_VARS) -------------------------------------------------------------------------------- /docker/fedora/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM fedora:41 2 | WORKDIR /cxx-common 3 | COPY ../.. . 4 | RUN ./docker/fedora/dependencies.sh 5 | RUN cmake -B build . \ 6 | -G Ninja \ 7 | -D CMAKE_C_COMPILER=clang -D CMAKE_CXX_COMPILER=clang++ \ 8 | -D ENABLE_LINTING=OFF \ 9 | -D ENABLE_TESTING=OFF \ 10 | -DENABLE_LLVM=ON \ 11 | -DENABLE_IMGUI=ON \ 12 | -DENABLE_CAIRO=ON \ 13 | -DBUILD_BENCH=ON \ 14 | -DBUILD_GUI=ON \ 15 | -DBUILD_IMG=ON 16 | RUN cmake --build build -------------------------------------------------------------------------------- /docker/fedora/dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sh 2 | set -e 3 | 4 | dnf update -y 5 | 6 | dnf install -y \ 7 | clang \ 8 | clang-tools-extra \ 9 | cmake \ 10 | fmt-devel \ 11 | git \ 12 | llvm-devel \ 13 | ninja-build \ 14 | pkg-config \ 15 | wget 16 | 17 | # Cairo 18 | dnf install -y cairo-devel 19 | 20 | # ImGui 21 | dnf install -y \ 22 | glew-devel \ 23 | glfw-devel \ 24 | SDL2-devel \ 25 | SDL2-static 26 | 27 | # Imgui doesn't provide nice build steps 28 | # so we need to revert to this :( 29 | git clone https://github.com/ocornut/imgui.git imgui 30 | 31 | cd imgui 32 | cp /cxx-common/docker/fedora/imgui.CMakeLists.txt ./CMakeLists.txt 33 | cmake -B build -G Ninja -DIMGUI_DIR=$(pwd) 34 | cmake --build build --config Release --target install 35 | cd .. 36 | 37 | # STBIMAGE 38 | wget "https://raw.githubusercontent.com/nothings/stb/master/stb_image.h" 39 | mv stb_image.h /usr/include/stb_image.h 40 | 41 | 42 | # Gflags 43 | dnf install -y gflags-devel 44 | 45 | # LLVM 46 | dnf install -y llvm-devel 47 | 48 | # Binary analysis 49 | dnf install -y capstone-devel 50 | -------------------------------------------------------------------------------- /docker/fedora/imgui.CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # https://github.com/tamaskenez/allthesmallthings/blob/master/s/imgui/CMakeLists.txt 2 | cmake_minimum_required(VERSION 3.20) 3 | 4 | include(CMakePackageConfigHelpers) 5 | 6 | project(imgui) 7 | 8 | if(NOT IMGUI_DIR) 9 | message(FATAL_ERROR "IMGUI_DIR must be set to the imgui root directory") 10 | endif() 11 | 12 | set(CMAKE_DEBUG_POSTFIX d) 13 | set(CMAKE_CXX_STANDARD 20) 14 | set(CMAKE_CXX_STANDARD_REQUIRED) 15 | 16 | find_package(glfw3 REQUIRED) 17 | find_package(OpenGL REQUIRED) 18 | find_package(SDL2 REQUIRED) 19 | 20 | set(ROOT ${IMGUI_DIR}) 21 | 22 | add_library(imgui STATIC 23 | ${ROOT}/imgui.cpp 24 | ${ROOT}/imgui.h 25 | ${ROOT}/imstb_rectpack.h 26 | ${ROOT}/imstb_textedit.h 27 | ${ROOT}/imstb_truetype.h 28 | ${ROOT}/imgui_demo.cpp 29 | ${ROOT}/imgui_draw.cpp 30 | ${ROOT}/imgui_internal.h 31 | ${ROOT}/imgui_tables.cpp 32 | ${ROOT}/imgui_widgets.cpp 33 | ) 34 | target_include_directories(imgui PUBLIC 35 | $ 36 | $) 37 | 38 | set(INSTALL_TARGETS imgui) 39 | set(INSTALL_HEADERS ${ROOT}/imgui.h ${ROOT}/imconfig.h ${ROOT}/imgui_internal.h) 40 | 41 | foreach(BACKEND glfw opengl3 sdl2 sdlrenderer2) 42 | set(NAME imgui_impl_${BACKEND}) 43 | set(HEADER ${ROOT}/backends/${NAME}.h) 44 | add_library(${NAME} STATIC ${ROOT}/backends/${NAME}.cpp ${HEADER}) 45 | target_link_libraries(${NAME} PUBLIC imgui) 46 | target_include_directories(${NAME} PUBLIC 47 | $ 48 | $) 49 | LIST(APPEND INSTALL_TARGETS ${NAME}) 50 | LIST(APPEND INSTALL_HEADERS ${HEADER}) 51 | endforeach() 52 | 53 | target_link_libraries(imgui_impl_glfw PRIVATE glfw) 54 | target_sources(imgui_impl_opengl3 PRIVATE ${ROOT}/backends/imgui_impl_opengl3_loader.h) 55 | target_link_libraries(imgui_impl_opengl3 PRIVATE OpenGL::GL) 56 | target_link_libraries(imgui_impl_sdl2 PRIVATE SDL2::SDL2-static) 57 | target_link_libraries(imgui_impl_sdlrenderer2 PRIVATE SDL2::SDL2-static) 58 | 59 | install(TARGETS ${INSTALL_TARGETS} 60 | EXPORT imgui-targets DESTINATION lib) 61 | install(EXPORT imgui-targets 62 | FILE imgui-config.cmake 63 | NAMESPACE imgui:: 64 | DESTINATION lib/cmake/imgui) 65 | install(FILES ${INSTALL_HEADERS} 66 | DESTINATION include) 67 | 68 | 69 | add_executable(example_glfw_opengl3 ${IMGUI_DIR}/examples/example_glfw_opengl3/main.cpp) 70 | target_link_libraries(example_glfw_opengl3 71 | PRIVATE imgui imgui_impl_glfw imgui_impl_opengl3 glfw 72 | ) 73 | 74 | add_executable(example_sdl2_sdlrenderer2 ${IMGUI_DIR}/examples/example_sdl2_sdlrenderer2/main.cpp) 75 | target_link_libraries(example_sdl2_sdlrenderer2 76 | PRIVATE imgui imgui_impl_sdl2 imgui_impl_sdlrenderer2 SDL2::SDL2-static SDL2::SDL2main 77 | ) 78 | 79 | add_executable(example_sdl2_opengl3 ${IMGUI_DIR}/examples/example_sdl2_opengl3/main.cpp) 80 | target_link_libraries(example_sdl2_opengl3 81 | PRIVATE imgui imgui_impl_sdl2 imgui_impl_opengl3 SDL2::SDL2-static SDL2::SDL2main 82 | ) 83 | -------------------------------------------------------------------------------- /docker/manylinux/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM quay.io/pypa/manylinux_2_34_x86_64 2 | 3 | WORKDIR /project -------------------------------------------------------------------------------- /lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(triskel 2 | VERSION 1.0.0 3 | DESCRIPTION "Triskel: A graphing library for Control Flow Graphs" 4 | LANGUAGES CXX C) 5 | 6 | # if(ENABLE_IMGUI) 7 | add_library(triskel STATIC) 8 | # else() 9 | # add_library(triskel SHARED) 10 | # endif() 11 | set_property(TARGET triskel PROPERTY POSITION_INDEPENDENT_CODE ON) 12 | 13 | add_subdirectory(src) 14 | 15 | target_include_directories(triskel PUBLIC include) 16 | 17 | target_link_libraries(triskel PRIVATE fmt::fmt) 18 | 19 | if(ENABLE_LLVM) 20 | target_link_libraries(triskel PUBLIC LLVM) 21 | target_compile_definitions(triskel PUBLIC TRISKEL_LLVM) 22 | endif() 23 | 24 | if (ENABLE_IMGUI) 25 | target_compile_definitions(triskel PUBLIC TRISKEL_IMGUI) 26 | target_link_libraries(triskel PUBLIC imgui::imgui) 27 | endif() 28 | 29 | if (ENABLE_CAIRO) 30 | target_compile_definitions(triskel PUBLIC TRISKEL_CAIRO) 31 | target_link_libraries(triskel PRIVATE cairo) 32 | endif() 33 | 34 | if (ENABLE_LINTING) 35 | find_program(CLANG_TIDY NAMES "clang-tidy" REQUIRED) 36 | set_target_properties(triskel PROPERTIES 37 | CXX_CLANG_TIDY ${CLANG_TIDY} 38 | ) 39 | endif() 40 | 41 | install(TARGETS triskel LIBRARY DESTINATION triskel) 42 | -------------------------------------------------------------------------------- /lib/include/triskel/analysis/dfs.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "triskel/analysis/patriarchal.hpp" 7 | #include "triskel/graph/igraph.hpp" 8 | #include "triskel/utils/attribute.hpp" 9 | 10 | namespace triskel { 11 | 12 | struct Node; 13 | struct Graph; 14 | struct Edge; 15 | 16 | struct DFSAnalysis : public Patriarchal { 17 | explicit DFSAnalysis(const IGraph& g); 18 | 19 | ~DFSAnalysis() override = default; 20 | 21 | /// @brief Returns the graph nodes in DFS order 22 | auto nodes() -> std::vector; 23 | 24 | /// @brief Is an edge a back edge 25 | auto is_backedge(EdgeId e) const -> bool; 26 | 27 | /// @brief Is an edge a tree edge 28 | auto is_tree(EdgeId e) const -> bool; 29 | 30 | /// @brief Is an edge a cross edge 31 | auto is_cross(EdgeId e) const -> bool; 32 | 33 | /// @brief Is an edge a forward edge 34 | auto is_forward(EdgeId e) const -> bool; 35 | 36 | /// @brief The index of a node in the dfs ordered set of nodes 37 | auto dfs_num(NodeId n) const -> size_t; 38 | 39 | /// @brief Prints the type of an edge 40 | // Mainly for debugging 41 | auto dump_type(EdgeId e) const -> std::string; 42 | 43 | private: 44 | enum class EdgeType : uint8_t { None, Tree, Back, Cross, Forward }; 45 | 46 | /// @brief Was this node previously visited in `dfs` 47 | auto was_visited(const Node& node) -> bool; 48 | 49 | /// @brief Recursive depth first search helper function 50 | void dfs(const Node& node); 51 | 52 | /// @brief Types the graphs edges 53 | void type_edges(); 54 | 55 | const IGraph& g_; 56 | 57 | NodeAttribute dfs_nums_; 58 | 59 | EdgeAttribute types_; 60 | 61 | std::vector nodes_; 62 | }; 63 | } // namespace triskel -------------------------------------------------------------------------------- /lib/include/triskel/analysis/lengauer_tarjan.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "triskel/graph/igraph.hpp" 4 | #include "triskel/utils/attribute.hpp" 5 | 6 | namespace triskel { 7 | 8 | /// @brief Calculates the immediate dominators of nodes in a graph 9 | [[nodiscard]] auto make_idoms(const IGraph& g) -> NodeAttribute; 10 | 11 | } // namespace triskel 12 | -------------------------------------------------------------------------------- /lib/include/triskel/analysis/patriarchal.hpp: -------------------------------------------------------------------------------- 1 | /// @file common operations for analysis that provide a "family" hierarchy with 2 | /// nodes having parents, childrens, ancestors and descendants 3 | #pragma once 4 | 5 | #include "triskel/utils/attribute.hpp" 6 | 7 | namespace triskel { 8 | 9 | struct Patriarchal { 10 | explicit Patriarchal(const IGraph& g); 11 | 12 | virtual ~Patriarchal() = default; 13 | 14 | /// @brief Gets this node's parent 15 | [[nodiscard]] auto parents(const Node& n) -> std::vector; 16 | 17 | /// @brief Gets this node's only parent. 18 | /// If this node has multiple parents raises an error 19 | [[nodiscard]] auto parent(const Node& n) -> Node; 20 | 21 | /// @brief Gets this node's only parent. 22 | /// If this node has multiple parents raises an error 23 | [[nodiscard]] auto parent(const NodeId& n) const -> NodeId; 24 | 25 | /// @brief Gets this node's children 26 | [[nodiscard]] auto children(const Node& n) -> std::vector; 27 | 28 | /// @brief Gets this node's only child. 29 | /// If this node has multiple children raises an error 30 | [[nodiscard]] auto child(const Node& n) -> Node; 31 | 32 | /// @brief Does node n1 precede n2 ? 33 | /// Checks using a node's children 34 | [[nodiscard]] auto precedes(const Node& n1, const Node& n2) -> bool; 35 | 36 | /// @brief Does node n1 succeed n2 ? 37 | /// Checks using a node's parents 38 | [[nodiscard]] auto succeed(const Node& n1, const Node& n2) -> bool; 39 | 40 | protected: 41 | /// @brief Makes a node a parent of another node 42 | void add_parent(const Node& parent, const Node& child); 43 | 44 | private: 45 | const IGraph& g_; 46 | NodeAttribute> parents_; 47 | NodeAttribute> children_; 48 | }; 49 | } // namespace triskel -------------------------------------------------------------------------------- /lib/include/triskel/analysis/sese.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "triskel/analysis/udfs.hpp" 8 | #include "triskel/graph/graph.hpp" 9 | #include "triskel/graph/igraph.hpp" 10 | #include "triskel/utils/attribute.hpp" 11 | #include "triskel/utils/tree.hpp" 12 | 13 | namespace triskel { 14 | 15 | void cycle_equiv(IGraph& g); 16 | 17 | // Single entry single exit 18 | struct SESE { 19 | explicit SESE(Graph& g); 20 | 21 | struct SESERegionData { 22 | SESERegionData() = default; 23 | 24 | EdgeId entry_edge; 25 | NodeId entry_node; 26 | 27 | EdgeId exit_edge; 28 | NodeId exit_node; 29 | 30 | std::vector nodes; 31 | }; 32 | using SESERegion = Tree::Node; 33 | 34 | /// @brief The program structure tree 35 | Tree regions; 36 | 37 | NodeAttribute node_regions; 38 | 39 | // index of edge's cycle equivalence set 40 | EdgeAttribute classes_; 41 | 42 | [[nodiscard]] auto get_region(const Node& node) const -> SESERegion& { 43 | return *node_regions.get(node); 44 | } 45 | 46 | private: 47 | using Bracket = EdgeId; 48 | using BracketList = std::vector; 49 | 50 | struct NodeClass { 51 | NodeId id; 52 | EdgeId edge; 53 | size_t edge_class; 54 | }; 55 | 56 | /// @brief Adds a single exit node and links it to the start 57 | void preprocess_graph(); 58 | 59 | /// @brief Is the edge `edge` a backedge from `from` to `to` 60 | [[nodiscard]] auto is_backedge_stating_from(const Edge& edge, 61 | const Node& from, 62 | const Node& to) -> bool; 63 | 64 | /// @brief Calculates hi0 65 | /// hi0 is the ? 66 | [[nodiscard]] auto get_hi0(const Node& node) -> size_t; 67 | 68 | /// @brief Calculates hi1 69 | [[nodiscard]] auto get_hi1(const Node& node) -> size_t; 70 | 71 | /// @brief Calculates hi2 72 | [[nodiscard]] auto get_hi2(const Node& node, size_t hi1) -> size_t; 73 | 74 | void create_capping_backedge(const Node& node, 75 | BracketList& blist, 76 | size_t hi2); 77 | 78 | [[nodiscard]] auto get_parent_tree_edge(const Node& node) -> Edge; 79 | 80 | void determine_class(const Node& node, BracketList& blist); 81 | 82 | void determine_region_boundaries( 83 | const Node& node, 84 | NodeAttribute& visited, 85 | const std::vector& visited_class); 86 | 87 | /// @brief Build the program structure tree using DFS once we know the entry 88 | /// and exit edges of each region 89 | void construct_program_structure_tree(const Node& node, 90 | SESERegion* current_region, 91 | NodeAttribute& visited); 92 | 93 | /// @brief The graph for which we are identifying SESE regions 94 | IGraph& g_; 95 | 96 | size_t edge_class = 1; 97 | [[nodiscard]] auto new_class() -> size_t; 98 | 99 | /// @brief An unordered depth first search of g 100 | std::unique_ptr udfs_; 101 | 102 | // descendant node of n 103 | NodeAttribute his_; 104 | 105 | // node's bracket list 106 | NodeAttribute blists_; 107 | 108 | // size of bracket set when e was most recently the topmost edge in a 109 | // bracket set 110 | EdgeAttribute recent_sizes_; 111 | 112 | // equivalence class number of ree edge for which e was most recently the 113 | // topmost bracket 114 | EdgeAttribute recent_classes_; 115 | 116 | std::vector capping_backedges_; 117 | 118 | EdgeAttribute entry_edge_; 119 | EdgeAttribute exit_edge_; 120 | }; 121 | } // namespace triskel -------------------------------------------------------------------------------- /lib/include/triskel/analysis/udfs.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "triskel/analysis/patriarchal.hpp" 7 | #include "triskel/graph/igraph.hpp" 8 | #include "triskel/utils/attribute.hpp" 9 | 10 | namespace triskel { 11 | 12 | struct UnorderedDFSAnalysis : public Patriarchal { 13 | enum class EdgeType : uint8_t { None, Tree, Back }; 14 | 15 | explicit UnorderedDFSAnalysis(const IGraph& g); 16 | 17 | ~UnorderedDFSAnalysis() override = default; 18 | 19 | /// @brief Returns the graph nodes in DFS order 20 | auto nodes() -> std::vector; 21 | 22 | /// @brief Is an edge a back edge 23 | /// In an unordered graph, a backedge is a ? 24 | auto is_backedge(const Edge& e) -> bool; 25 | 26 | /// @brief Make an edge a back edge 27 | void set_backedge(const Edge& e); 28 | 29 | /// @brief Is an edge a tree edge 30 | auto is_tree(const Edge& e) -> bool; 31 | 32 | /// @brief The index of a node in the dfs ordered set of nodes 33 | auto dfs_num(const Node& n) -> size_t; 34 | 35 | private: 36 | /// @brief Was this node previously visited in `udfs` 37 | auto was_visited(const Node& node) -> bool; 38 | 39 | /// @brief Recursive unordered depth first search helper function 40 | void udfs(const Node& node); 41 | 42 | const IGraph& g_; 43 | 44 | NodeAttribute dfs_nums_; 45 | 46 | EdgeAttribute types_; 47 | 48 | std::vector nodes_; 49 | }; 50 | 51 | } // namespace triskel 52 | -------------------------------------------------------------------------------- /lib/include/triskel/datatypes/rtree.hpp: -------------------------------------------------------------------------------- 1 | /// @file A radix tree 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | template 14 | auto starts_with(const std::span& vec, const std::span& prefix) -> bool; 15 | 16 | /// @brief A radix tree 17 | template 18 | struct RTree { 19 | using Key = std::span; 20 | 21 | // A radix node ish 22 | struct RNode { 23 | std::vector data; 24 | std::vector radix; 25 | std::unordered_map> children; 26 | 27 | explicit RNode(RTree& tree) { id = tree.id++; } 28 | 29 | /// @brief Save the node to a Graphviz graph 30 | void dump(); 31 | 32 | void new_child(RTree& tree, const T& value, const Key& key); 33 | 34 | void split(RTree& tree, const T& value, const Key& key); 35 | 36 | size_t id; 37 | }; 38 | 39 | RTree() : root{*this} {} 40 | 41 | /// @brief Inserts a value into the radix tree 42 | void insert(const T& value, const Key& key); 43 | 44 | [[nodiscard]] auto bfs() -> std::vector; 45 | 46 | /// @brief Save the node to a Graphviz graph 47 | void dump(); 48 | 49 | RNode root; 50 | 51 | private: 52 | size_t id; 53 | }; 54 | 55 | #include "rtree.ipp" -------------------------------------------------------------------------------- /lib/include/triskel/datatypes/rtree.ipp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "rtree.hpp" 3 | 4 | #include 5 | 6 | template 7 | auto starts_with(const std::span& vec, const std::span& prefix) -> bool { 8 | return vec.size() >= prefix.size() && 9 | std::equal(prefix.begin(), prefix.end(), vec.begin()); 10 | } 11 | 12 | /// @brief A radix tree 13 | template 14 | void RTree::RNode::dump() { 15 | fmt::print("n{} [label=\"{}\"]\n", id, radix); 16 | 17 | for (const auto& child : children) { 18 | fmt::print("n{} -> n{}\n", id, child.second->id); 19 | child.second->dump(); 20 | } 21 | 22 | for (size_t i = 0; i < data.size(); ++i) { 23 | fmt::print("n{}_{} [label=\"{}\", shape=square]\n", id, i, data[i]); 24 | fmt::print("n{} -> n{}_{}\n", id, id, i); 25 | } 26 | } 27 | 28 | template 29 | void RTree::RNode::new_child(RTree& tree, 30 | const T& value, 31 | const Key& key) { 32 | assert(key.size() > 0); 33 | 34 | auto node = std::make_unique(tree); 35 | node->radix.assign(key.begin(), key.end()); 36 | node->data = {value}; 37 | 38 | children[key.front()] = std::move(node); 39 | } 40 | 41 | template 42 | void RTree::RNode::split(RTree& tree, const T& value, const Key& key) { 43 | auto shared_prefix_len = 0; 44 | 45 | auto size = std::min(radix.size(), key.size()); 46 | while (shared_prefix_len < size) { 47 | if (radix[shared_prefix_len] != key[shared_prefix_len]) { 48 | break; 49 | } 50 | 51 | shared_prefix_len++; 52 | } 53 | 54 | assert(shared_prefix_len > 0); 55 | 56 | auto start = Key(radix).first(shared_prefix_len); 57 | auto end = Key(radix).subspan(shared_prefix_len); 58 | 59 | auto new_node = std::make_unique(tree); 60 | 61 | new_node->children = std::move(children); 62 | children.clear(); 63 | 64 | new_node->data = std::move(data); 65 | data.clear(); 66 | 67 | radix.assign(start.begin(), start.end()); 68 | new_node->radix.assign(end.begin(), end.end()); 69 | 70 | children[radix[shared_prefix_len]] = std::move(new_node); 71 | 72 | if (shared_prefix_len == key.size()) { 73 | data.push_back(value); 74 | } else { 75 | new_child(tree, value, key.subspan(shared_prefix_len)); 76 | } 77 | } 78 | 79 | template 80 | void RTree::insert(const T& value, const Key& key) { 81 | auto* cursor = &root; 82 | 83 | size_t i = 0; 84 | 85 | while (i < key.size()) { 86 | auto k = key[i]; 87 | 88 | if (!cursor->children.contains(k)) { 89 | // create a new node 90 | cursor->new_child(*this, value, key.subspan(i)); 91 | return; 92 | } 93 | 94 | cursor = cursor->children[k].get(); 95 | 96 | Key radix = cursor->radix; 97 | if (starts_with(key.subspan(i), radix)) { 98 | i += radix.size(); 99 | } else { 100 | cursor->split(*this, value, key.subspan(i)); 101 | return; 102 | } 103 | } 104 | 105 | // An element with this key is already in the tree 106 | cursor->data.push_back(value); 107 | } 108 | 109 | template 110 | auto RTree::bfs() -> std::vector { 111 | auto rnodes = std::vector{}; 112 | 113 | auto current_layer = std::make_unique>(); 114 | 115 | auto next_layer = std::make_unique>(); 116 | next_layer->push_back(&root); 117 | 118 | while (!next_layer->empty()) { 119 | std::swap(current_layer, next_layer); 120 | next_layer->clear(); 121 | 122 | for (auto* rnode : *current_layer) { 123 | rnodes.push_back(rnode); 124 | for (auto& child : rnode->children) { 125 | next_layer->push_back(child.second.get()); 126 | } 127 | } 128 | } 129 | 130 | return rnodes; 131 | } 132 | 133 | template 134 | void RTree::dump() { 135 | fmt::print("digraph G {{\n"); 136 | root.dump(); 137 | fmt::print("}}\n"); 138 | } 139 | -------------------------------------------------------------------------------- /lib/include/triskel/graph/graph.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "triskel/graph/igraph.hpp" 5 | 6 | namespace triskel { 7 | 8 | struct Graph; 9 | 10 | struct GraphEditor : public IGraphEditor { 11 | /// @brief A graph editor with source control 12 | explicit GraphEditor(Graph& g); 13 | 14 | /// @brief Debug tests 15 | ~GraphEditor() override; 16 | 17 | auto make_node() -> Node override; 18 | void remove_node(NodeId id) override; 19 | auto make_edge(NodeId from, NodeId to) -> Edge override; 20 | void edit_edge(EdgeId edge, NodeId new_from, NodeId new_to) override; 21 | void remove_edge(EdgeId edge) override; 22 | void push() override; 23 | void pop() override; 24 | void commit() override; 25 | 26 | private: 27 | struct Frame { 28 | size_t created_nodes_count; 29 | std::stack deleted_nodes; 30 | 31 | std::stack created_edges; 32 | std::stack deleted_edges; 33 | std::stack modified_edges; 34 | }; 35 | 36 | auto frame() -> Frame&; 37 | 38 | Graph& g_; 39 | 40 | std::stack frames; 41 | }; 42 | 43 | /// @brief A graph that owns its data 44 | struct Graph : public IGraph { 45 | Graph(); 46 | 47 | /// @brief The root of this graph 48 | [[nodiscard]] auto root() const -> Node override; 49 | 50 | /// @brief The nodes in this graph 51 | [[nodiscard]] auto nodes() const -> std::vector override; 52 | 53 | /// @brief The edges in this graph 54 | [[nodiscard]] auto edges() const -> std::vector override; 55 | 56 | /// @brief Turns a NodeId into a Node 57 | [[nodiscard]] auto get_node(NodeId id) const -> Node override; 58 | 59 | /// @brief Turns an EdgeId into an Edge 60 | [[nodiscard]] auto get_edge(EdgeId id) const -> Edge override; 61 | 62 | /// @brief The greatest id in this graph 63 | [[nodiscard]] auto max_node_id() const -> size_t override; 64 | 65 | /// @brief The greatest id in this graph 66 | [[nodiscard]] auto max_edge_id() const -> size_t override; 67 | 68 | /// @brief The number of nodes in this graph 69 | [[nodiscard]] auto node_count() const -> size_t override; 70 | 71 | /// @brief The number of edges in this graph 72 | [[nodiscard]] auto edge_count() const -> size_t override; 73 | 74 | /// @brief Gets the editor attached to this graph 75 | [[nodiscard]] auto editor() -> GraphEditor& override; 76 | 77 | private: 78 | GraphEditor editor_; 79 | 80 | GraphData data_; 81 | 82 | /// Gets the data of a specific node 83 | [[nodiscard]] auto get_node_data(NodeId id) -> NodeData&; 84 | [[nodiscard]] auto get_node_data(NodeId id) const -> const NodeData&; 85 | 86 | /// Gets the data of a specific edge 87 | [[nodiscard]] auto get_edge_data(EdgeId id) -> EdgeData&; 88 | [[nodiscard]] auto get_edge_data(EdgeId id) const -> const EdgeData&; 89 | 90 | friend struct GraphEditor; 91 | friend struct SubGraph; 92 | }; 93 | 94 | } // namespace triskel -------------------------------------------------------------------------------- /lib/include/triskel/graph/graph_view.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * A graph optimized for reading. 3 | * If the graph is modified, a new `graph view` needs to be generated 4 | */ 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "triskel/graph/igraph.hpp" 12 | 13 | namespace triskel { 14 | /// @brief A graph optimized for reading 15 | struct GraphView { 16 | struct Edge; 17 | 18 | struct Node : public Identifiable { 19 | explicit Node(NodeId id); 20 | 21 | [[nodiscard]] auto id() const -> NodeId final; 22 | 23 | [[nodiscard]] auto edges() const -> std::span; 24 | [[nodiscard]] auto child_edges() const -> std::span; 25 | [[nodiscard]] auto parent_edges() const -> std::span; 26 | 27 | [[nodiscard]] auto neighbors() const -> std::span; 28 | [[nodiscard]] auto child_nodes() const -> std::span; 29 | [[nodiscard]] auto parent_nodes() const -> std::span; 30 | 31 | [[nodiscard]] auto is_root() const -> bool; 32 | 33 | private: 34 | NodeId id_; 35 | 36 | // The first half are parent edge, the second half are child edges 37 | std::vector edges_; 38 | int64_t edge_separator = 0; 39 | 40 | // The first half are parent nodes, the second half are child nodes 41 | std::vector neighbors_; 42 | int64_t node_separator = 0; 43 | 44 | bool is_root_ = false; 45 | 46 | friend struct GraphView; 47 | }; 48 | 49 | struct Edge : public Identifiable { 50 | explicit Edge(EdgeId id, Node* to, Node* from); 51 | 52 | [[nodiscard]] auto id() const -> EdgeId final; 53 | 54 | [[nodiscard]] auto to() const -> const Node&; 55 | [[nodiscard]] auto from() const -> const Node&; 56 | 57 | /// @brief Returns the other side of the edge 58 | [[nodiscard]] auto other(NodeId n) const -> const Node&; 59 | 60 | private: 61 | EdgeId id_; 62 | 63 | Node* to_; 64 | Node* from_; 65 | 66 | friend struct GraphView; 67 | }; 68 | 69 | // TODO: can we do something cool with ownership to ensure the graph does 70 | // not get modified while the graph view exists ? 71 | explicit GraphView(const IGraph& g); 72 | 73 | /// @brief The root of this graph 74 | [[nodiscard]] auto root() const -> const Node&; 75 | 76 | /// @brief The nodes in this graph 77 | [[nodiscard]] auto nodes() const -> const std::vector&; 78 | 79 | /// @brief The edges in this graph 80 | [[nodiscard]] auto edges() const -> const std::vector&; 81 | 82 | /// @brief The number of nodes in this graph 83 | [[nodiscard]] auto node_count() const -> size_t; 84 | 85 | /// @brief The number of edges in this graph 86 | [[nodiscard]] auto edge_count() const -> size_t; 87 | 88 | private: 89 | std::vector nodes_; 90 | std::vector edges_; 91 | }; 92 | } // namespace triskel -------------------------------------------------------------------------------- /lib/include/triskel/graph/igraph.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace triskel { 12 | 13 | template 14 | struct ID { 15 | ID() : value(InvalidID.value) {} 16 | 17 | explicit ID(size_t value) : value{value} {} 18 | explicit operator size_t() const { return value; } 19 | 20 | static const ID InvalidID; 21 | 22 | auto operator==(ID other) const -> bool { return other.value == value; } 23 | 24 | friend auto operator<=>(ID lhs, ID rhs) -> std::strong_ordering { 25 | return lhs.value <=> rhs.value; 26 | } 27 | 28 | [[nodiscard]] auto is_valid() { return *this != InvalidID; } 29 | [[nodiscard]] auto is_invalid() { return *this == InvalidID; } 30 | 31 | private: 32 | size_t value; 33 | }; 34 | 35 | template 36 | const ID ID::InvalidID{static_cast(-1)}; 37 | 38 | template 39 | auto format_as(const ID& id) -> std::string { 40 | return std::to_string(static_cast(id)); 41 | } 42 | 43 | /// @brief A struct with and id 44 | template 45 | struct Identifiable { 46 | virtual ~Identifiable() = default; 47 | [[nodiscard]] virtual auto id() const -> ID = 0; 48 | 49 | auto operator==(const Identifiable& other) const -> bool { 50 | return id() == other.id(); 51 | } 52 | 53 | // NOLINTNEXTLINE(google-explicit-constructor) 54 | operator ID() const { return id(); } 55 | }; 56 | 57 | struct NodeTag {}; 58 | using NodeId = ID; 59 | static_assert(std::is_trivially_copyable_v); 60 | static_assert(std::strict_weak_order); 61 | 62 | struct EdgeTag {}; 63 | using EdgeId = ID; 64 | static_assert(std::is_trivially_copyable_v); 65 | 66 | struct GraphData; 67 | 68 | struct NodeData { 69 | NodeId id; 70 | std::vector edges; 71 | bool deleted; 72 | }; 73 | 74 | struct EdgeData { 75 | EdgeId id; 76 | NodeId from; 77 | NodeId to; 78 | bool deleted; 79 | }; 80 | 81 | struct GraphData { 82 | NodeId root; 83 | 84 | std::deque nodes; 85 | std::deque edges; 86 | }; 87 | 88 | struct Edge; 89 | struct IGraph; 90 | struct IGraphEditor; 91 | 92 | struct Node : public Identifiable { 93 | Node(const IGraph& g, const NodeData& n) : g_{g}, n_{&n} {} 94 | ~Node() override = default; 95 | 96 | auto operator=(const Node& node) -> Node&; 97 | 98 | [[nodiscard]] auto id() const -> NodeId final; 99 | [[nodiscard]] auto edges() const -> std::vector; 100 | 101 | [[nodiscard]] auto child_edges() const -> std::vector; 102 | [[nodiscard]] auto parent_edges() const -> std::vector; 103 | 104 | [[nodiscard]] auto child_nodes() const -> std::vector; 105 | [[nodiscard]] auto parent_nodes() const -> std::vector; 106 | [[nodiscard]] auto neighbors() const -> std::vector; 107 | 108 | [[nodiscard]] auto is_root() const -> bool; 109 | 110 | private: 111 | const IGraph& g_; 112 | const NodeData* n_; 113 | }; 114 | 115 | struct Edge : public Identifiable { 116 | Edge(const IGraph& g, const EdgeData& e); 117 | ~Edge() override = default; 118 | 119 | auto operator=(const Edge& other) -> Edge&; 120 | 121 | [[nodiscard]] auto id() const -> EdgeId final; 122 | [[nodiscard]] auto from() const -> Node; 123 | [[nodiscard]] auto to() const -> Node; 124 | 125 | /// @brief Returns the other side of the edge 126 | [[nodiscard]] auto other(NodeId n) const -> Node; 127 | 128 | private: 129 | const IGraph& g_; 130 | const EdgeData* e_; 131 | }; 132 | 133 | /// @brief An interface for a graph 134 | struct IGraph { 135 | virtual ~IGraph() = default; 136 | 137 | /// @brief The root of this graph 138 | [[nodiscard]] virtual auto root() const -> Node = 0; 139 | 140 | /// @brief The nodes in this graph 141 | [[nodiscard]] virtual auto nodes() const -> std::vector = 0; 142 | 143 | /// @brief The edges in this graph 144 | [[nodiscard]] virtual auto edges() const -> std::vector = 0; 145 | 146 | /// @brief Turns a NodeId into a Node 147 | [[nodiscard]] virtual auto get_node(NodeId id) const -> Node = 0; 148 | 149 | /// @brief Turns an EdgeId into an Edge 150 | [[nodiscard]] virtual auto get_edge(EdgeId id) const -> Edge = 0; 151 | 152 | /// @brief The greatest id in this graph 153 | [[nodiscard]] virtual auto max_node_id() const -> size_t = 0; 154 | 155 | /// @brief The greatest id in this graph 156 | [[nodiscard]] virtual auto max_edge_id() const -> size_t = 0; 157 | 158 | /// @brief The number of nodes in this graph 159 | [[nodiscard]] virtual auto node_count() const -> size_t = 0; 160 | 161 | /// @brief The number of edges in this graph 162 | [[nodiscard]] virtual auto edge_count() const -> size_t = 0; 163 | 164 | /// @brief Gets the editor attached to this graph 165 | [[nodiscard]] virtual auto editor() -> IGraphEditor& = 0; 166 | 167 | /// @brief Turns a NodeId into a Node 168 | [[nodiscard]] virtual auto get_nodes( 169 | const std::span& ids) const -> std::vector; 170 | 171 | /// @brief Turns an EdgeId into an Edge 172 | [[nodiscard]] virtual auto get_edges( 173 | const std::span& ids) const -> std::vector; 174 | 175 | protected: 176 | /// @brief Maps a range of NodeID's to a range of Nodes 177 | [[nodiscard]] auto node_view() const { 178 | return std::views::transform( 179 | [&](NodeId id) { return get_node(id); }) // 180 | | std::ranges::to>(); 181 | } 182 | 183 | /// @brief Maps a range of EdgeID's to a range of Edges 184 | [[nodiscard]] auto edge_view() const { 185 | return std::views::transform( 186 | [&](EdgeId id) { return get_edge(id); }) // 187 | | std::ranges::to>(); 188 | } 189 | }; 190 | 191 | auto format_as(const Node& n) -> std::string; 192 | auto format_as(const Edge& e) -> std::string; 193 | auto format_as(const IGraph& g) -> std::string; 194 | 195 | struct IGraphEditor { 196 | virtual ~IGraphEditor() = default; 197 | 198 | // ========== 199 | // Nodes 200 | // ========== 201 | /// @brief Adds a node to the graph 202 | virtual auto make_node() -> Node = 0; 203 | 204 | /// @brief Removes a node from the graph 205 | /// This will also remove all the edges that contain this node 206 | virtual void remove_node(NodeId node) = 0; 207 | 208 | // ========== 209 | // Edges 210 | // ========== 211 | /// @brief Create a new edge between two nodes 212 | virtual auto make_edge(NodeId from, NodeId to) -> Edge = 0; 213 | 214 | /// @brief Modifies the start and end point of an edge 215 | virtual void edit_edge(EdgeId edge, NodeId new_from, NodeId new_to) = 0; 216 | 217 | /// @brief Removes an edge from the graph 218 | virtual void remove_edge(EdgeId edge) = 0; 219 | 220 | // ========== 221 | // Version Control 222 | // ========== 223 | /// @brief Creates a new edit frame 224 | virtual void push() = 0; 225 | 226 | /// @brief Removes all changes from the current frame 227 | virtual void pop() = 0; 228 | 229 | /// @brief Writes all changes to the graph, erasing the modification frames 230 | virtual void commit() = 0; 231 | }; 232 | } // namespace triskel -------------------------------------------------------------------------------- /lib/include/triskel/graph/subgraph.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "triskel/graph/graph.hpp" 6 | #include "triskel/graph/igraph.hpp" 7 | 8 | namespace triskel { 9 | 10 | struct SubGraph; 11 | 12 | struct SubGraphEditor : public IGraphEditor { 13 | /// @brief A graph editor with source control 14 | explicit SubGraphEditor(SubGraph& g); 15 | 16 | /// @brief Adds a node from the graph to the subgraph 17 | void select_node(NodeId node); 18 | 19 | /// @brief Removes a node from the subgraph 20 | void unselect_node(NodeId node); 21 | 22 | /// @brief Make root 23 | void make_root(NodeId node); 24 | 25 | auto make_node() -> Node override; 26 | void remove_node(NodeId node) override; 27 | auto make_edge(NodeId from, NodeId to) -> Edge override; 28 | void edit_edge(EdgeId edge, NodeId new_from, NodeId new_to) override; 29 | void remove_edge(EdgeId edge) override; 30 | void push() override; 31 | void pop() override; 32 | void commit() override; 33 | 34 | private: 35 | SubGraph& g_; 36 | GraphEditor& editor_; 37 | 38 | /// @brief Add a node's edges to the subgraph 39 | void select_edges(NodeId node); 40 | 41 | /// @brief Remove a node's edges to the subgraph 42 | void unselect_edges(NodeId node); 43 | 44 | /// @brief Assert that an edge is in the subgraph 45 | void assert_present(EdgeId edge); 46 | 47 | /// @brief Assert that an edge is not the subgraph 48 | void assert_missing(EdgeId edge); 49 | }; 50 | 51 | /// @brief A graph that contains only some nodes of another graph 52 | struct SubGraph : public IGraph { 53 | explicit SubGraph(Graph& g); 54 | 55 | [[nodiscard]] auto root() const -> Node override; 56 | [[nodiscard]] auto nodes() const -> std::vector override; 57 | [[nodiscard]] auto edges() const -> std::vector override; 58 | [[nodiscard]] auto get_node(NodeId id) const -> Node override; 59 | [[nodiscard]] auto get_edge(EdgeId id) const -> Edge override; 60 | [[nodiscard]] auto max_node_id() const -> size_t override; 61 | [[nodiscard]] auto max_edge_id() const -> size_t override; 62 | [[nodiscard]] auto node_count() const -> size_t override; 63 | [[nodiscard]] auto edge_count() const -> size_t override; 64 | [[nodiscard]] auto editor() -> SubGraphEditor& override; 65 | 66 | [[nodiscard]] auto contains(NodeId node) -> bool; 67 | [[nodiscard]] auto contains(EdgeId edge) -> bool; 68 | 69 | [[nodiscard]] auto get_nodes(const std::span& ids) const 70 | -> std::vector override; 71 | 72 | [[nodiscard]] auto get_edges(const std::span& ids) const 73 | -> std::vector override; 74 | 75 | private: 76 | Graph& g_; 77 | 78 | NodeId root_; 79 | std::vector nodes_; 80 | std::vector edges_; 81 | 82 | SubGraphEditor editor_; 83 | 84 | friend struct SubGraphEditor; 85 | }; 86 | } // namespace triskel -------------------------------------------------------------------------------- /lib/include/triskel/internal.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "triskel/graph/graph.hpp" 7 | #include "triskel/triskel.hpp" 8 | #include "triskel/utils/attribute.hpp" 9 | 10 | namespace triskel { 11 | [[nodiscard]] auto make_layout(std::unique_ptr g, 12 | const NodeAttribute& width, 13 | const NodeAttribute& height, 14 | const NodeAttribute& label, 15 | const EdgeAttribute& 16 | edge_types) -> std::unique_ptr; 17 | } -------------------------------------------------------------------------------- /lib/include/triskel/layout/ilayout.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "triskel/graph/igraph.hpp" 4 | #include "triskel/utils/point.hpp" 5 | 6 | namespace triskel { 7 | 8 | struct ILayout { 9 | virtual ~ILayout() = default; 10 | 11 | [[nodiscard]] virtual auto get_x(NodeId node) const -> float = 0; 12 | [[nodiscard]] virtual auto get_y(NodeId node) const -> float = 0; 13 | 14 | [[nodiscard]] virtual auto get_xy(NodeId node) const -> Point; 15 | 16 | [[nodiscard]] virtual auto get_waypoints(EdgeId edge) const 17 | -> const std::vector& = 0; 18 | 19 | [[nodiscard]] virtual auto get_width(NodeId node) const -> float = 0; 20 | [[nodiscard]] virtual auto get_height(NodeId node) const -> float = 0; 21 | 22 | [[nodiscard]] virtual auto get_graph_width(const IGraph& graph) const 23 | -> float; 24 | [[nodiscard]] virtual auto get_graph_height(const IGraph& graph) const 25 | -> float; 26 | }; 27 | } // namespace triskel 28 | -------------------------------------------------------------------------------- /lib/include/triskel/layout/layout.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "triskel/analysis/sese.hpp" 10 | #include "triskel/graph/igraph.hpp" 11 | #include "triskel/graph/subgraph.hpp" 12 | #include "triskel/layout/ilayout.hpp" 13 | #include "triskel/layout/sugiyama/sugiyama.hpp" 14 | #include "triskel/utils/attribute.hpp" 15 | 16 | namespace triskel { 17 | 18 | struct Layout : public ILayout { 19 | Layout(Graph& g, 20 | const NodeAttribute& heights, 21 | const NodeAttribute& widths); 22 | explicit Layout(Graph& g); 23 | 24 | [[nodiscard]] auto get_x(NodeId node) const -> float override; 25 | [[nodiscard]] auto get_y(NodeId node) const -> float override; 26 | [[nodiscard]] auto get_waypoints(EdgeId edge) const 27 | -> const std::vector& override; 28 | 29 | [[nodiscard]] auto get_width(NodeId node) const -> float override; 30 | [[nodiscard]] auto get_height(NodeId node) const -> float override; 31 | 32 | [[nodiscard]] auto region_count() const -> size_t { 33 | return sese_->regions.nodes.size(); 34 | } 35 | 36 | private: 37 | NodeAttribute xs_; 38 | NodeAttribute ys_; 39 | 40 | NodeAttribute heights_; 41 | NodeAttribute widths_; 42 | 43 | EdgeAttribute> waypoints_; 44 | 45 | EdgeAttribute start_x_offset_; 46 | EdgeAttribute end_x_offset_; 47 | 48 | struct RegionData { 49 | explicit RegionData(Graph& g); 50 | 51 | SubGraph subgraph; 52 | NodeId node_id; 53 | 54 | std::vector entries; 55 | std::vector exits; 56 | 57 | std::map> io_waypoints; 58 | 59 | bool was_layout = false; 60 | 61 | float width; 62 | float height; 63 | }; 64 | 65 | /// @brief Remove SESE regions with a single node 66 | void remove_small_regions(); 67 | 68 | std::vector regions_data_; 69 | 70 | /// @brief Edits the region subgraphs so that each region's subgraph 71 | /// contains its children region phony nodes 72 | /// Edges also need to be stitched accordingly 73 | void edit_region_subgraph(); 74 | 75 | /// @brief Edit a region's entry edge 76 | void edit_region_entry(const SESE::SESERegion& r); 77 | 78 | /// @brief Edit a region's exit edge 79 | void edit_region_exit(const SESE::SESERegion& r); 80 | 81 | auto get_region_node(const SESE::SESERegion& r) const -> Node; 82 | 83 | auto get_editor(const SESE::SESERegion& r) -> SubGraphEditor&; 84 | 85 | void compute_layout(const SESE::SESERegion& r); 86 | 87 | void translate_region(const SESE::SESERegion& r, const Point& v); 88 | 89 | void translate_region(const SESE::SESERegion& r); 90 | 91 | /// @brief Initiates the regions 92 | void init_regions(); 93 | 94 | /// @brief Creates a subgraph for each SESE region 95 | void create_region_subgraphs(); 96 | 97 | /// @brief Creates a phony node for each SESE region 98 | void create_region_nodes(); 99 | 100 | std::unique_ptr sese_; 101 | Graph& g_; 102 | }; 103 | } // namespace triskel -------------------------------------------------------------------------------- /lib/include/triskel/layout/phantom_nodes.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "triskel/graph/igraph.hpp" 4 | 5 | namespace triskel { 6 | void create_phantom_nodes(IGraph& g); 7 | } -------------------------------------------------------------------------------- /lib/include/triskel/layout/sugiyama/layer_assignement.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "triskel/graph/igraph.hpp" 7 | #include "triskel/utils/attribute.hpp" 8 | 9 | namespace triskel { 10 | 11 | /// @brief The layers of nodes during layered graph drawing 12 | struct LayerAssignment { 13 | explicit LayerAssignment(const IGraph& graph) : layers{graph, 0} {} 14 | explicit LayerAssignment(const NodeAttribute& layers, 15 | size_t layer_count) 16 | : layers{layers}, layer_count{layer_count} {} 17 | 18 | NodeAttribute layers; 19 | size_t layer_count; 20 | }; 21 | 22 | /// @brief Calculates layer assignment using longest path from tamassia 23 | auto longest_path_tamassia(const IGraph& graph) 24 | -> std::unique_ptr; 25 | 26 | /// @brief Calculates layer assignment using network simplex 27 | auto network_simplex(const IGraph& graph) -> std::unique_ptr; 28 | 29 | } // namespace triskel -------------------------------------------------------------------------------- /lib/include/triskel/layout/sugiyama/sugiyama.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "triskel/graph/igraph.hpp" 11 | #include "triskel/layout/ilayout.hpp" 12 | #include "triskel/utils/attribute.hpp" 13 | 14 | namespace triskel { 15 | 16 | using Pair = std::pair; 17 | 18 | struct IOPair { 19 | NodeId node; 20 | EdgeId edge; 21 | 22 | // NOLINTNEXTLINE(google-explicit-constructor) 23 | operator Pair() const { 24 | return {static_cast(node), static_cast(edge)}; 25 | } 26 | 27 | friend auto operator==(IOPair lhs, IOPair rhs) -> bool { 28 | return static_cast(lhs) == static_cast(rhs); 29 | } 30 | 31 | friend auto operator<=>(IOPair lhs, IOPair rhs) -> std::strong_ordering { 32 | return static_cast(lhs) <=> static_cast(rhs); 33 | } 34 | }; 35 | static_assert(std::is_trivially_copyable_v); 36 | 37 | struct SugiyamaAnalysis : public ILayout { 38 | explicit SugiyamaAnalysis(IGraph& g); 39 | 40 | explicit SugiyamaAnalysis(IGraph& g, 41 | const NodeAttribute& heights, 42 | const NodeAttribute& widths); 43 | 44 | explicit SugiyamaAnalysis(IGraph& g, 45 | const NodeAttribute& heights, 46 | const NodeAttribute& widths, 47 | const EdgeAttribute& start_x_offset, 48 | const EdgeAttribute& end_x_offset, 49 | const std::vector& entries = {}, 50 | const std::vector& exits = {}); 51 | 52 | ~SugiyamaAnalysis() override = default; 53 | 54 | [[nodiscard]] auto get_x(NodeId node) const -> float override; 55 | [[nodiscard]] auto get_y(NodeId node) const -> float override; 56 | [[nodiscard]] auto get_waypoints(EdgeId edge) const 57 | -> const std::vector& override; 58 | 59 | [[nodiscard]] auto get_graph_width() const -> float; 60 | [[nodiscard]] auto get_graph_height() const -> float; 61 | 62 | [[nodiscard]] auto get_width(NodeId node) const -> float override; 63 | [[nodiscard]] auto get_height(NodeId node) const -> float override; 64 | 65 | [[nodiscard]] auto get_io_waypoints() const 66 | -> const std::map>&; 67 | 68 | private: 69 | NodeAttribute layers_; 70 | NodeAttribute orders_; 71 | NodeAttribute widths_; 72 | NodeAttribute heights_; 73 | NodeAttribute xs_; 74 | NodeAttribute ys_; 75 | 76 | struct Padding { 77 | float top; 78 | float bottom; 79 | 80 | float left; 81 | float right; 82 | 83 | [[nodiscard]] auto width() const -> float { return left + right; } 84 | 85 | [[nodiscard]] auto height() const -> float { return top + bottom; } 86 | 87 | [[nodiscard]] static auto all(float padding) -> Padding { 88 | return {padding, padding, padding, padding}; 89 | } 90 | 91 | [[nodiscard]] static auto horizontal(float padding) -> Padding { 92 | return {0.0F, 0.0F, padding, padding}; 93 | } 94 | 95 | [[nodiscard]] static auto vertical(float padding) -> Padding { 96 | return {padding, padding, 0.0F, 0.0F}; 97 | } 98 | }; 99 | 100 | NodeAttribute paddings_; 101 | 102 | // Priorities in the coordinate assignment 103 | NodeAttribute priorities_; 104 | 105 | EdgeAttribute> waypoints_; 106 | EdgeAttribute offsets_to_; 107 | EdgeAttribute offsets_from_; 108 | EdgeAttribute edge_weights_; 109 | 110 | auto layer_view(size_t layer); 111 | 112 | // Ensures the order on each layer has nodes 1 unit from each other 113 | void normalize_order(); 114 | 115 | std::vector self_loops_; 116 | void remove_self_loop(const Edge& edge); 117 | 118 | void draw_self_loops(); 119 | 120 | void cycle_removal(); 121 | 122 | void layer_assignment(); 123 | 124 | /// @brief After layer assignment attempts to move nodes that still have a 125 | /// degree of liberty to minimize the graph height 126 | void slide_nodes(); 127 | 128 | /// @brief Edit edges so that every edge is pointing down 129 | /// i.e.: layer(edge.to) > layer(edge.from) 130 | void flip_edges(); 131 | 132 | void remove_long_edges(); 133 | 134 | void vertex_ordering(); 135 | 136 | /// @brief Computes the x coordinate of each node 137 | void x_coordinate_assignment(); 138 | 139 | /// @brief Compute the y coordinate of each node 140 | void y_coordinate_assignment(); 141 | 142 | void coordinate_assignment_iteration(size_t layer, 143 | size_t next_layer, 144 | float graph_width); 145 | 146 | auto get_priority(const Node& node, size_t layer) -> size_t; 147 | 148 | auto min_x(std::vector& nodes, size_t id) -> float; 149 | 150 | auto max_x(std::vector& nodes, size_t id, float graph_width) -> float; 151 | 152 | auto average_position(const Node& node, 153 | size_t layer, 154 | bool is_going_down) -> float; 155 | 156 | void set_layer(const Node& node, size_t layer); 157 | 158 | /// @brief Creates waypoints to draw the edges connecting nodes 159 | void waypoint_creation(); 160 | 161 | /// @brief Translate edge waypoints after coordinate assignment 162 | void translate_waypoints(); 163 | 164 | /// @brief Calculates Y coordinates for waypoints 165 | void calculate_waypoints_y(); 166 | 167 | auto get_waypoint_y(size_t id, 168 | const std::vector& edges, 169 | std::vector& layers) -> int64_t; 170 | 171 | /// @brief Creates an edge waypoint and sets its layer 172 | auto create_ghost_node(size_t layer) -> Node; 173 | 174 | /// @brief Creates an edge waypoint 175 | auto create_waypoint() -> Node; 176 | 177 | void build_waypoints(EdgeId id); 178 | void build_long_edges_waypoints(); 179 | 180 | float width_; 181 | [[nodiscard]] auto compute_graph_width() -> float; 182 | 183 | float height_; 184 | [[nodiscard]] auto compute_graph_height() -> float; 185 | 186 | // ----- Entry and exits ----- 187 | /// @brief Ensures the entry/exit nodes are connected to the top/bottom 188 | /// layers 189 | void ensure_io_at_extremities(); 190 | 191 | std::vector entries; 192 | std::vector exits; 193 | 194 | EdgeAttribute start_x_offset_; 195 | EdgeAttribute end_x_offset_; 196 | 197 | std::map io_edges_; 198 | 199 | [[nodiscard]] auto is_io_edge(EdgeId edge) const -> bool; 200 | 201 | std::map> io_waypoints_; 202 | 203 | // Saves the data of the I/O waypoints 204 | void make_io_waypoint(IOPair pair); 205 | 206 | void make_io_waypoints(); 207 | // ----- 208 | 209 | bool has_top_loop_ = false; 210 | bool has_bottom_loop_ = false; 211 | 212 | // std::vector exit_waypoints_; 213 | // EdgeId exit_edge_ = EdgeId::InvalidID; 214 | 215 | EdgeAttribute> edge_waypoints_; 216 | std::vector deleted_edges_; 217 | 218 | EdgeAttribute is_flipped_; 219 | 220 | std::vector dummy_nodes_; 221 | 222 | /// @brief The nodes on a given layer 223 | std::vector> node_layers_; 224 | void init_node_layers(); 225 | 226 | std::default_random_engine rng_; 227 | 228 | size_t layer_count_; 229 | 230 | IGraph& g; 231 | 232 | friend struct Layout; 233 | }; 234 | } // namespace triskel -------------------------------------------------------------------------------- /lib/include/triskel/layout/sugiyama/vertex_ordering.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "triskel/graph/graph_view.hpp" 8 | #include "triskel/graph/igraph.hpp" 9 | #include "triskel/utils/attribute.hpp" 10 | 11 | namespace triskel { 12 | struct VertexOrdering { 13 | using NodeView = GraphView::Node; 14 | using EdgeView = GraphView::Node; 15 | 16 | VertexOrdering(const IGraph& g, 17 | const NodeAttribute& layers, 18 | size_t layer_count_); 19 | 20 | NodeAttribute orders_; 21 | 22 | private: 23 | GraphView g_; 24 | 25 | const NodeAttribute& layers_; 26 | 27 | std::vector> node_layers_; 28 | 29 | std::default_random_engine rng_; 30 | 31 | void get_neighbor_orders(const NodeView& n, 32 | std::vector& orders_top, 33 | std::vector& orders_bottom) const; 34 | 35 | [[nodiscard]] auto count_crossings(const NodeView& node1, 36 | const NodeView& node2) const -> size_t; 37 | 38 | [[nodiscard]] auto count_crossings_with_layer(size_t l1, 39 | size_t l2) -> size_t; 40 | 41 | // Save the vectors to avoid reallocating memory every time 42 | mutable std::vector orders_top1; 43 | mutable std::vector orders_top2; 44 | mutable std::vector orders_bottom1; 45 | mutable std::vector orders_bottom2; 46 | [[nodiscard]] auto count_crossings() -> size_t; 47 | 48 | /// @brief transform the order to the index in the layer 49 | void normalize_order(); 50 | 51 | void median(size_t iter); 52 | void transpose(); 53 | }; 54 | } // namespace triskel -------------------------------------------------------------------------------- /lib/include/triskel/llvm/llvm.hpp: -------------------------------------------------------------------------------- 1 | #ifdef TRISKEL_LLVM 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "triskel/graph/graph.hpp" 9 | #include "triskel/utils/attribute.hpp" 10 | 11 | namespace triskel { 12 | auto to_string(const llvm::Value& v, 13 | llvm::ModuleSlotTracker* MST = nullptr) -> std::string; 14 | 15 | /// @brief LLVM support for libfmt 16 | auto format_as(const llvm::Value& v) -> std::string; 17 | 18 | /// @brief A cfg for LLVM functions 19 | struct LLVMCFG { 20 | enum class EdgeType : uint8_t { Default, True, False }; 21 | 22 | /// @brief Build a CFG from an LLVM function 23 | explicit LLVMCFG(llvm::Function* function); 24 | 25 | /// @brief The CFG's function 26 | llvm::Function* function; 27 | 28 | /// @brief The CFG 29 | Graph graph; 30 | 31 | /// @brief A map from the CFG node to LLVM basic blocks 32 | NodeAttribute block_map; 33 | 34 | /// @brief The color of the edges 35 | EdgeAttribute edge_types; 36 | }; 37 | 38 | } // namespace triskel 39 | #endif -------------------------------------------------------------------------------- /lib/include/triskel/utils/attribute.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "triskel/graph/igraph.hpp" 8 | 9 | namespace triskel { 10 | 11 | /// @brief A structure to add data to a graph. 12 | /// Attributes function like maps taking advantage of the node / edge's ids. 13 | /// They can be seen as a way of adding labels to an existing graph 14 | 15 | template 16 | struct Attribute { 17 | // Bool needs special treatment because of how std::vector is 18 | // implemented 19 | using ConstRef = 20 | std::conditional_t, T, const T&>; 21 | 22 | Attribute(size_t size, const T& v) : v_{v} { data_.resize(size, v); } 23 | 24 | virtual ~Attribute() = default; 25 | 26 | /// @brief Get by reference 27 | template 28 | [[nodiscard]] auto get(const Identifiable& n) 29 | -> T& requires(!std::is_same_v) { return get(n.id()); } 30 | 31 | /// @brief Get by reference 32 | template 33 | [[nodiscard]] auto get(const ID& id) 34 | -> T& requires(!std::is_same_v) { 35 | auto id_ = static_cast(id); 36 | resize_if_necessary(id_); 37 | return data_[id_]; 38 | } 39 | 40 | /// @brief Get by reference 41 | [[nodiscard]] auto get(const Identifiable& n) -> 42 | typename std::vector::reference 43 | requires(std::is_same_v) 44 | { 45 | return get(n.id()); 46 | } 47 | 48 | /// @brief Get by reference 49 | [[nodiscard]] auto get(const ID& id) -> 50 | typename std::vector::reference 51 | requires(std::is_same_v) 52 | { 53 | auto id_ = static_cast(id); 54 | resize_if_necessary(id_); 55 | return data_[id_]; 56 | } 57 | 58 | /// @brief Get by const reference 59 | [[nodiscard]] auto get(const Identifiable& n) const -> ConstRef { 60 | return get(n.id()); 61 | } 62 | 63 | /// @brief Get by const reference 64 | [[nodiscard]] auto get(const ID& id) const -> ConstRef { 65 | auto id_ = static_cast(id); 66 | resize_if_necessary(id_); 67 | return data_[id_]; 68 | } 69 | 70 | template 71 | [[nodiscard]] auto operator[](const ID& id) 72 | -> T& requires(!std::is_same_v) { return get(id); } 73 | 74 | [[nodiscard]] auto operator[](const ID& id) -> 75 | typename std::vector::reference 76 | requires(std::is_same_v) 77 | { 78 | return get(id); 79 | } 80 | 81 | template 82 | [[nodiscard]] auto operator[](const ID& id) const -> ConstRef { 83 | return get(id); 84 | } 85 | 86 | void set(const ID& id, T v) { 87 | auto id_ = static_cast(id); 88 | resize_if_necessary(id_); 89 | data_[id_] = std::move(v); 90 | } 91 | 92 | private: 93 | /// @brief This is a const function thanks to mutable 94 | auto resize_if_necessary(size_t id) const { 95 | if (id >= data_.size()) { 96 | data_.resize(id + 1, v_); 97 | } 98 | } 99 | 100 | /// @brief This is mutable as the data_ vector might get resized to account 101 | /// for new nodes 102 | mutable std::vector data_; 103 | 104 | /// @brief default value to add when the graph is resized 105 | T v_; 106 | }; 107 | 108 | template 109 | struct NodeAttribute : public Attribute { 110 | NodeAttribute(const IGraph& g, const T& v) 111 | : Attribute{g.max_node_id(), v} {} 112 | 113 | /// @deprecated 114 | NodeAttribute(size_t size, const T& v) : Attribute{size, v} {} 115 | 116 | [[nodiscard]] auto dump(IGraph& g) const -> std::string { 117 | auto s = std::string{}; 118 | for (auto node : g.nodes()) { 119 | s += fmt::format("- {} -> {}\n", node, this->get(node)); 120 | } 121 | return s; 122 | } 123 | }; 124 | 125 | template 126 | struct EdgeAttribute : public Attribute { 127 | EdgeAttribute(const IGraph& g, const T& v) 128 | : Attribute{g.max_edge_id(), v} {} 129 | 130 | /// @deprecated 131 | EdgeAttribute(size_t size, const T& v) : Attribute{size, v} {} 132 | 133 | [[nodiscard]] auto dump(IGraph& g) const -> std::string { 134 | auto s = std::string{}; 135 | for (auto edge : g.edges()) { 136 | s += fmt::format("- {} -> {}\n", edge, this->get(edge)); 137 | } 138 | return s; 139 | } 140 | }; 141 | 142 | }; // namespace triskel -------------------------------------------------------------------------------- /lib/include/triskel/utils/constants.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace triskel { 6 | 7 | // ============================================================================= 8 | // Layout 9 | // ============================================================================= 10 | constexpr float WAYPOINT_WIDTH = 0.0F; 11 | constexpr float WAYPOINT_HEIGHT = 0.0F; 12 | 13 | // Node priorities 14 | constexpr uint8_t DEFAULT_PRIORITY = 0; 15 | constexpr uint8_t WAYPOINT_PRIORITY = 1; 16 | constexpr uint8_t ENTRY_WAYPOINT_PRIORITY = 2; 17 | constexpr uint8_t EXIT_WAYPOINT_PRIORITY = 2; 18 | 19 | // The space between nodes 20 | constexpr float X_GUTTER = 50.0F; 21 | 22 | // The space between nodes and the first / last edge 23 | constexpr float Y_GUTTER = 40.0F; 24 | 25 | // The height of an edge 26 | constexpr float EDGE_HEIGHT = 30.0F; 27 | 28 | // ============================================================================= 29 | // Display 30 | // ============================================================================= 31 | constexpr float DEFAULT_X_GUTTER = 0.0F; 32 | constexpr float DEFAULT_Y_GUTTER = 0.0F; 33 | constexpr float PADDING = 150.0F; 34 | 35 | } // namespace triskel -------------------------------------------------------------------------------- /lib/include/triskel/utils/point.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace triskel { 4 | 5 | struct Point { 6 | float x; 7 | float y; 8 | }; 9 | 10 | inline auto operator+(const Point& l, const Point& r) -> Point { 11 | return {.x = l.x + r.x, .y = l.y + r.y}; 12 | } 13 | 14 | inline auto operator+=(Point& l, const Point& r) -> Point& { 15 | l = l + r; 16 | return l; 17 | } 18 | 19 | } // namespace triskel -------------------------------------------------------------------------------- /lib/include/triskel/utils/tree.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace triskel { 10 | 11 | template 12 | struct Tree { 13 | struct Node { 14 | auto operator->() -> T* { return &label; } 15 | auto operator->() const -> const T* { return &label; } 16 | 17 | auto operator==(const Node& other) const -> bool { 18 | return id == other.id; 19 | } 20 | 21 | /// @brief Is this node the root 22 | [[nodiscard]] auto is_root() const -> bool { 23 | return parent_ == nullptr; 24 | } 25 | 26 | /// @brief Finds the root of this tree 27 | [[nodiscard]] auto root() -> Node& { 28 | if (is_root()) { 29 | return *this; 30 | } 31 | 32 | return parent_->is_root(); 33 | } 34 | 35 | /// @brief The parent of this node. The node shouldn't be the root 36 | [[nodiscard]] auto parent() -> Node& { 37 | assert(!is_root()); 38 | return *parent_; 39 | } 40 | 41 | /// @brief The parent of this node. The node shouldn't be the root 42 | [[nodiscard]] auto parent() const -> const Node& { 43 | assert(!is_root()); 44 | return *parent_; 45 | } 46 | 47 | /// @brief Is this node the root 48 | [[nodiscard]] auto children() const -> const std::vector& { 49 | return children_; 50 | } 51 | 52 | /// @brief Adds a child to this node 53 | void add_child(Node* node) { 54 | node->parent_ = this; 55 | node->depth = depth + 1; 56 | 57 | children_.push_back(node); 58 | } 59 | 60 | void remove_child(const Node* node) { 61 | children_.erase( 62 | std::remove(children_.begin(), children_.end(), node), 63 | children_.end()); 64 | } 65 | 66 | /// @brief The data in the label 67 | T label; 68 | 69 | size_t id; 70 | 71 | // The depth of this node in the tree 72 | size_t depth = 0; 73 | 74 | private: 75 | Node* parent_; 76 | std::vector children_; 77 | 78 | template 79 | friend struct Tree; 80 | }; 81 | 82 | [[nodiscard]] auto make_node() -> Node& { 83 | nodes.push_back(std::make_unique()); 84 | auto& node = nodes.back(); 85 | 86 | node->id = nodes.size() - 1; 87 | return *node; 88 | } 89 | 90 | void remove_node(Node* node) { 91 | assert(node != root); 92 | assert(node->children_.empty()); 93 | 94 | node->parent().remove_child(node); 95 | nodes.erase(std::remove_if(nodes.begin(), nodes.end(), 96 | [node](const std::unique_ptr& a) { 97 | return a.get() == node; 98 | }), 99 | nodes.end()); 100 | } 101 | 102 | Node* root; 103 | std::vector> nodes; 104 | }; 105 | 106 | } // namespace triskel -------------------------------------------------------------------------------- /lib/src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(analysis) 2 | add_subdirectory(graph) 3 | add_subdirectory(layout) 4 | add_subdirectory(renderer) 5 | 6 | if(ENABLE_LLVM) 7 | add_subdirectory(llvm) 8 | endif() 9 | 10 | target_sources(triskel PRIVATE 11 | triskel.cpp 12 | ) -------------------------------------------------------------------------------- /lib/src/analysis/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources(triskel PRIVATE 2 | dfs.cpp 3 | patriarchal.cpp 4 | sese.cpp 5 | udfs.cpp 6 | lengauer_tarjan.cpp 7 | ) -------------------------------------------------------------------------------- /lib/src/analysis/dfs.cpp: -------------------------------------------------------------------------------- 1 | #include "triskel/analysis/dfs.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "triskel/analysis/patriarchal.hpp" 8 | #include "triskel/graph/igraph.hpp" 9 | 10 | // NOLINTNEXTLINE(google-build-using-namespace) 11 | using namespace triskel; 12 | 13 | using DFS = DFSAnalysis; 14 | 15 | DFS::DFSAnalysis(const IGraph& g) 16 | : Patriarchal(g), 17 | g_{g}, 18 | dfs_nums_(g.max_node_id(), 0), 19 | types_(g.max_edge_id(), EdgeType::None) { 20 | nodes_.reserve(g.node_count()); 21 | 22 | dfs(g.root()); 23 | type_edges(); 24 | } 25 | 26 | auto DFS::was_visited(const Node& node) -> bool { 27 | return (dfs_nums_.get(node) != 0) || node.is_root(); 28 | } 29 | 30 | // NOLINTNEXTLINE(misc-no-recursion) 31 | void DFS::dfs(const Node& node) { 32 | nodes_.push_back(node.id()); 33 | dfs_nums_.set(node, nodes_.size() - 1); 34 | 35 | for (const auto& edge : node.edges()) { 36 | const auto child = edge.to(); 37 | if (child == node) { 38 | continue; 39 | } 40 | 41 | if (!was_visited(child)) { 42 | dfs(child); 43 | 44 | add_parent(node, child); 45 | types_.set(edge, DFS::EdgeType::Tree); 46 | continue; 47 | } 48 | } 49 | } 50 | 51 | void DFS::type_edges() { 52 | for (const auto& edge : g_.edges()) { 53 | if (is_tree(edge)) { 54 | continue; 55 | } 56 | 57 | if (edge.to() == edge.from()) { 58 | types_.set(edge, DFS::EdgeType::Back); 59 | continue; 60 | } 61 | 62 | if (succeed(edge.from(), edge.to())) { 63 | types_.set(edge, DFS::EdgeType::Back); 64 | continue; 65 | } 66 | 67 | if (succeed(edge.to(), edge.from())) { 68 | types_.set(edge, DFS::EdgeType::Forward); 69 | continue; 70 | } 71 | 72 | types_.set(edge, DFS::EdgeType::Cross); 73 | } 74 | } 75 | 76 | auto DFS::nodes() -> std::vector { 77 | return g_.get_nodes(nodes_); 78 | } 79 | 80 | auto DFS::is_tree(EdgeId e) const -> bool { 81 | return types_.get(e) == EdgeType::Tree; 82 | } 83 | 84 | auto DFS::is_backedge(EdgeId e) const -> bool { 85 | return types_.get(e) == EdgeType::Back; 86 | } 87 | 88 | auto DFS::is_forward(EdgeId e) const -> bool { 89 | return types_.get(e) == EdgeType::Forward; 90 | } 91 | 92 | auto DFS::is_cross(EdgeId e) const -> bool { 93 | return types_.get(e) == EdgeType::Cross; 94 | } 95 | 96 | auto DFS::dump_type(EdgeId e) const -> std::string { 97 | switch (types_.get(e)) { 98 | case EdgeType::None: 99 | return "None"; 100 | case EdgeType::Tree: 101 | return "Tree"; 102 | case EdgeType::Back: 103 | return "Back"; 104 | case EdgeType::Cross: 105 | return "Cross"; 106 | case EdgeType::Forward: 107 | return "Forward"; 108 | } 109 | } 110 | 111 | auto DFS::dfs_num(NodeId n) const -> size_t { 112 | return dfs_nums_.get(n); 113 | } 114 | -------------------------------------------------------------------------------- /lib/src/analysis/lengauer_tarjan.cpp: -------------------------------------------------------------------------------- 1 | /// An Explanation of Lengauer-Tarjan Dominators Algorithm, Jayadev Misra 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "triskel/analysis/dfs.hpp" 10 | #include "triskel/graph/igraph.hpp" 11 | #include "triskel/utils/attribute.hpp" 12 | 13 | // NOLINTNEXTLINE(google-build-using-namespace) 14 | using namespace triskel; 15 | 16 | namespace { 17 | using Bucket = std::set; 18 | 19 | struct Forest { 20 | explicit Forest(const IGraph& g, const NodeAttribute& semis) 21 | : ancestors{g, NodeId::InvalidID}, 22 | label(g, NodeId::InvalidID), 23 | semis{semis} { 24 | // Initialize the labels 25 | for (const auto& v : g.nodes()) { 26 | label[v] = v; 27 | } 28 | } 29 | 30 | void link(NodeId v, NodeId w) { ancestors[w] = v; } 31 | 32 | auto eval(NodeId v) -> NodeId { 33 | if (is_root(v)) { 34 | return v; 35 | } 36 | 37 | compress(v); 38 | 39 | return label[v]; 40 | } 41 | 42 | // NOLINTNEXTLINE(misc-no-recursion) 43 | void compress(NodeId v) { 44 | // This function assumes that v is not a root in the forest 45 | assert(!is_root(v)); 46 | 47 | if (!is_root(ancestors[v])) { 48 | compress(ancestors[v]); 49 | 50 | auto& ancestor = ancestors[v]; 51 | 52 | if (semis[label[ancestor]] < semis[label[v]]) { 53 | label[v] = label[ancestor]; 54 | } 55 | 56 | ancestor = ancestors[ancestor]; 57 | } 58 | } 59 | 60 | // Is a node a root in the forest 61 | [[nodiscard]] auto is_root(NodeId v) const -> bool { 62 | return ancestors[v] == NodeId::InvalidID; 63 | } 64 | 65 | // The ancestor of a node in the forest 66 | NodeAttribute ancestors; 67 | 68 | // IDK lol 69 | NodeAttribute label; 70 | 71 | // NodeAttribute size; 72 | // NodeAttribute child; 73 | 74 | // The semi dominator of a node 75 | const NodeAttribute& semis; 76 | }; 77 | 78 | } // namespace 79 | 80 | auto triskel::make_idoms(const IGraph& g) -> NodeAttribute { 81 | auto dfs = DFSAnalysis(g); 82 | 83 | auto nodes = dfs.nodes(); 84 | // TODO: remove root 85 | 86 | // Immediate Dominator 87 | auto doms = NodeAttribute{g, NodeId::InvalidID}; 88 | 89 | // SemiDominator 90 | auto semis = NodeAttribute{g, 0}; 91 | 92 | /// Vertices w in bucket[v] have sd[w] = v 93 | auto buckets = NodeAttribute{g, Bucket{}}; 94 | 95 | auto forest = Forest{g, semis}; 96 | 97 | for (const auto& node : nodes) { 98 | semis[node] = dfs.dfs_num(node); 99 | } 100 | 101 | for (const auto& w : 102 | nodes | std::ranges::views::drop(1) | std::ranges::views::reverse) { 103 | for (const auto& v : w.parent_nodes()) { 104 | const auto u = forest.eval(v); 105 | semis[w] = std::min(semis[w], semis[u]); 106 | } 107 | 108 | buckets[nodes[semis[w]]].insert(w); 109 | const auto parent_w = dfs.parent(w); 110 | forest.link(parent_w, w); 111 | 112 | for (const auto& v : buckets[parent_w]) { 113 | const auto u = forest.eval(v); 114 | 115 | if (semis[u] < semis[v]) { 116 | doms[v] = u; 117 | } else { 118 | doms[v] = parent_w; 119 | } 120 | } 121 | 122 | buckets[parent_w].clear(); 123 | } 124 | 125 | for (const auto& w : nodes | std::ranges::views::drop(1)) { 126 | if (doms[w] != nodes[semis[w]]) { 127 | doms[w] = doms[doms[w]]; 128 | } 129 | } 130 | 131 | return doms; 132 | } 133 | -------------------------------------------------------------------------------- /lib/src/analysis/patriarchal.cpp: -------------------------------------------------------------------------------- 1 | #include "triskel/analysis/patriarchal.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "triskel/graph/igraph.hpp" 9 | #include "triskel/utils/attribute.hpp" 10 | 11 | // NOLINTNEXTLINE(google-build-using-namespace) 12 | using namespace triskel; 13 | 14 | namespace { 15 | 16 | using Next = std::function(const Node&)>; 17 | 18 | auto bfs(const IGraph& g, 19 | const Next& next, 20 | const Node& n1, 21 | const Node& n2) -> bool { 22 | auto visited = NodeAttribute{g.max_node_id(), false}; 23 | auto stack = std::queue{{n1.id()}}; 24 | 25 | while (!stack.empty()) { 26 | const auto& n = g.get_node(stack.front()); 27 | stack.pop(); 28 | 29 | if (visited.get(n)) { 30 | continue; 31 | } 32 | 33 | visited.set(n, true); 34 | 35 | for (const auto& child : next(n)) { 36 | if (visited.get(child)) { 37 | continue; 38 | } 39 | 40 | if (child == n2) { 41 | return true; 42 | } 43 | 44 | stack.push(child.id()); 45 | } 46 | } 47 | 48 | return false; 49 | } 50 | } // namespace 51 | 52 | Patriarchal::Patriarchal(const IGraph& g) 53 | : g_{g}, parents_{g.max_node_id(), {}}, children_{g.max_node_id(), {}} {} 54 | 55 | void Patriarchal::add_parent(const Node& parent, const Node& child) { 56 | auto& parents = parents_.get(child); 57 | auto& children = children_.get(parent); 58 | 59 | parents.push_back(parent.id()); 60 | children.push_back(child.id()); 61 | } 62 | 63 | auto Patriarchal::parents(const Node& n) -> std::vector { 64 | return g_.get_nodes(parents_.get(n)); 65 | } 66 | 67 | auto Patriarchal::parent(const Node& n) -> Node { 68 | return g_.get_node(parent(n.id())); 69 | } 70 | 71 | auto Patriarchal::parent(const NodeId& n) const -> NodeId { 72 | const auto& parents = parents_.get(n); 73 | assert(parents.size() == 1); 74 | return parents.front(); 75 | } 76 | 77 | auto Patriarchal::children(const Node& n) -> std::vector { 78 | return g_.get_nodes(children_.get(n)); 79 | } 80 | 81 | auto Patriarchal::child(const Node& n) -> Node { 82 | auto& children = children_.get(n); 83 | assert(children.size() == 1); 84 | return g_.get_node(children.front()); 85 | } 86 | 87 | auto Patriarchal::precedes(const Node& n1, const Node& n2) -> bool { 88 | return bfs(g_, [&](const Node& n) { return children(n); }, n1, n2); 89 | } 90 | 91 | auto Patriarchal::succeed(const Node& n1, const Node& n2) -> bool { 92 | return bfs(g_, [&](const Node& n) { return parents(n); }, n1, n2); 93 | } -------------------------------------------------------------------------------- /lib/src/analysis/udfs.cpp: -------------------------------------------------------------------------------- 1 | #include "triskel/analysis/udfs.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include "triskel/analysis/patriarchal.hpp" 7 | #include "triskel/graph/igraph.hpp" 8 | 9 | // NOLINTNEXTLINE(google-build-using-namespace) 10 | using namespace triskel; 11 | 12 | using UDFS = UnorderedDFSAnalysis; 13 | 14 | UDFS::UnorderedDFSAnalysis(const IGraph& g) 15 | : Patriarchal(g), 16 | g_{g}, 17 | dfs_nums_(g.max_node_id(), 0), 18 | types_(g.max_edge_id(), EdgeType::None) { 19 | nodes_.reserve(g.node_count()); 20 | 21 | udfs(g.root()); 22 | } 23 | 24 | // NOLINTNEXTLINE(misc-no-recursion) 25 | void UDFS::udfs(const Node& node) { 26 | nodes_.push_back(node.id()); 27 | dfs_nums_.set(node, nodes_.size() - 1); 28 | 29 | for (const auto& edge : node.edges()) { 30 | const auto& child = edge.other(node); 31 | 32 | if (!was_visited(child)) { 33 | udfs(child); 34 | 35 | add_parent(node, child); 36 | types_.set(edge, UDFS::EdgeType::Tree); 37 | continue; 38 | } 39 | 40 | if (types_.get(edge) == UDFS::EdgeType::None) { 41 | types_.set(edge, UDFS::EdgeType::Back); 42 | } 43 | } 44 | } 45 | 46 | auto UDFS::was_visited(const Node& node) -> bool { 47 | return (dfs_nums_.get(node) != 0) || (node.is_root()); 48 | } 49 | 50 | auto UDFS::nodes() -> std::vector { 51 | return g_.get_nodes(nodes_); 52 | } 53 | 54 | auto UDFS::is_tree(const Edge& e) -> bool { 55 | return types_.get(e) == EdgeType::Tree; 56 | } 57 | 58 | auto UDFS::is_backedge(const Edge& e) -> bool { 59 | return types_.get(e) == EdgeType::Back; 60 | } 61 | 62 | void UDFS::set_backedge(const Edge& e) { 63 | types_.set(e, EdgeType::Back); 64 | } 65 | 66 | auto UDFS::dfs_num(const Node& n) -> size_t { 67 | return dfs_nums_.get(n); 68 | } -------------------------------------------------------------------------------- /lib/src/graph/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources(triskel PRIVATE 2 | graph_view.cpp 3 | graph.cpp 4 | igraph.cpp 5 | subgraph.cpp 6 | ) -------------------------------------------------------------------------------- /lib/src/graph/graph_view.cpp: -------------------------------------------------------------------------------- 1 | #include "triskel/graph/graph_view.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "triskel/graph/igraph.hpp" 9 | #include "triskel/utils/attribute.hpp" 10 | 11 | // NOLINTNEXTLINE(google-build-using-namespace) 12 | using namespace triskel; 13 | 14 | using NodeView = GraphView::Node; 15 | using EdgeView = GraphView::Edge; 16 | 17 | NodeView::Node(NodeId id) : id_{id} {} 18 | 19 | auto NodeView::id() const -> NodeId { 20 | return id_; 21 | } 22 | 23 | auto NodeView::edges() const -> std::span { 24 | return edges_; 25 | } 26 | 27 | auto NodeView::parent_edges() const -> std::span { 28 | return {edges_.begin(), edges_.begin() + edge_separator}; 29 | } 30 | auto NodeView::child_edges() const -> std::span { 31 | return {edges_.begin() + edge_separator, edges_.end()}; 32 | } 33 | 34 | auto NodeView::neighbors() const -> std::span { 35 | return neighbors_; 36 | } 37 | auto NodeView::parent_nodes() const -> std::span { 38 | return {neighbors_.begin(), neighbors_.begin() + node_separator}; 39 | } 40 | auto NodeView::child_nodes() const -> std::span { 41 | return {neighbors_.begin() + node_separator, neighbors_.end()}; 42 | } 43 | 44 | auto NodeView::is_root() const -> bool { 45 | return is_root_; 46 | } 47 | 48 | // ============================================================================= 49 | 50 | EdgeView::Edge(EdgeId id, NodeView* to, NodeView* from) 51 | : id_{id}, to_{to}, from_{from} {} 52 | 53 | auto EdgeView::id() const -> EdgeId { 54 | return id_; 55 | } 56 | 57 | auto EdgeView::to() const -> const NodeView& { 58 | return *to_; 59 | } 60 | 61 | auto EdgeView::from() const -> const NodeView& { 62 | return *from_; 63 | } 64 | 65 | auto EdgeView::other(NodeId n) const -> const NodeView& { 66 | if (n == to()) { 67 | return from(); 68 | } 69 | assert(n == from()); 70 | return to(); 71 | } 72 | 73 | // ============================================================================= 74 | 75 | GraphView::GraphView(const IGraph& g) { 76 | nodes_.reserve(g.node_count()); 77 | edges_.reserve(g.edge_count()); 78 | 79 | NodeAttribute node_map{g, nullptr}; 80 | EdgeAttribute edge_map{g, nullptr}; 81 | 82 | for (const auto& node : g.nodes()) { 83 | nodes_.emplace_back(node); 84 | node_map.set(node, &nodes_.back()); 85 | } 86 | 87 | for (const auto& edge : g.edges()) { 88 | edges_.emplace_back(edge, node_map.get(edge.to()), 89 | node_map.get(edge.from())); 90 | edge_map.set(edge, &edges_.back()); 91 | } 92 | 93 | for (const auto& node : g.nodes()) { 94 | auto& node_view = *node_map.get(node); 95 | 96 | for (const auto& parent : node.parent_nodes()) { 97 | node_view.neighbors_.push_back(node_map.get(parent)); 98 | node_view.node_separator++; 99 | } 100 | for (const auto& child : node.child_nodes()) { 101 | node_view.neighbors_.push_back(node_map.get(child)); 102 | } 103 | 104 | for (const auto& parent : node.parent_edges()) { 105 | node_view.edges_.push_back(edge_map.get(parent)); 106 | node_view.edge_separator++; 107 | } 108 | for (const auto& child : node.child_edges()) { 109 | node_view.edges_.push_back(edge_map.get(child)); 110 | } 111 | } 112 | } 113 | 114 | auto GraphView::root() const -> const Node& { 115 | return nodes_.front(); 116 | } 117 | 118 | auto GraphView::nodes() const -> const std::vector& { 119 | return nodes_; 120 | } 121 | 122 | auto GraphView::edges() const -> const std::vector& { 123 | return edges_; 124 | } 125 | 126 | auto GraphView::node_count() const -> size_t { 127 | return nodes_.size(); 128 | } 129 | 130 | auto GraphView::edge_count() const -> size_t { 131 | return nodes_.size(); 132 | } -------------------------------------------------------------------------------- /lib/src/graph/igraph.cpp: -------------------------------------------------------------------------------- 1 | #include "triskel/graph/igraph.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | // NOLINTNEXTLINE(google-build-using-namespace) 13 | using namespace triskel; 14 | 15 | // ============================================================================= 16 | // Nodes 17 | // ============================================================================= 18 | auto Node::operator=(const Node& node) -> Node& { 19 | if (this == &node) { 20 | return *this; 21 | } 22 | 23 | assert(&g_ == &node.g_); 24 | n_ = node.n_; 25 | return *this; 26 | } 27 | 28 | auto Node::id() const -> NodeId { 29 | return n_->id; 30 | } 31 | 32 | auto Node::edges() const -> std::vector { 33 | return g_.get_edges(n_->edges); 34 | } 35 | 36 | auto Node::child_edges() const -> std::vector { 37 | return edges() // 38 | | std::ranges::views::filter( 39 | [&](const Edge& e) { return e.from() == *this; }) // 40 | | std::ranges::to>(); 41 | } 42 | 43 | auto Node::parent_edges() const -> std::vector { 44 | return edges() // 45 | | std::ranges::views::filter( 46 | [&](const Edge& e) { return e.to() == *this; }) // 47 | | std::ranges::to>(); 48 | } 49 | 50 | auto Node::child_nodes() const -> std::vector { 51 | return edges() // 52 | | std::ranges::views::filter( 53 | [&](const Edge& e) { return e.from() == *this; }) // 54 | | std::ranges::views::transform( 55 | [&](const Edge& e) { return e.to(); }) // 56 | | std::ranges::to>(); 57 | } 58 | 59 | auto Node::parent_nodes() const -> std::vector { 60 | return edges() // 61 | | std::ranges::views::filter( 62 | [&](const Edge& e) { return e.to() == *this; }) // 63 | | std::ranges::views::transform( 64 | [&](const Edge& e) { return e.from(); }) // 65 | | std::ranges::to>(); 66 | } 67 | 68 | auto Node::neighbors() const -> std::vector { 69 | return edges() // 70 | | std::ranges::views::transform( 71 | [&](const Edge& e) { return e.other(*this); }) // 72 | | std::ranges::to>(); 73 | } 74 | 75 | auto Node::is_root() const -> bool { 76 | return *this == g_.root(); 77 | } 78 | 79 | // ============================================================================= 80 | // Edges 81 | // ============================================================================= 82 | Edge::Edge(const IGraph& g, const EdgeData& e) : g_{g}, e_{&e} {} 83 | 84 | auto Edge::operator=(const Edge& other) -> Edge& { 85 | if (this == &other) { 86 | return *this; 87 | } 88 | 89 | assert(&g_ == &other.g_); 90 | e_ = other.e_; 91 | return *this; 92 | } 93 | 94 | auto Edge::id() const -> EdgeId { 95 | return e_->id; 96 | } 97 | 98 | auto Edge::from() const -> Node { 99 | return g_.get_node(e_->from); 100 | } 101 | 102 | auto Edge::to() const -> Node { 103 | return g_.get_node(e_->to); 104 | } 105 | 106 | auto Edge::other(NodeId n) const -> Node { 107 | if (n == to()) { 108 | return from(); 109 | } 110 | 111 | return to(); 112 | } 113 | 114 | // ============================================================================= 115 | // Graphs 116 | // ============================================================================= 117 | auto IGraph::get_nodes(const std::span& ids) const 118 | -> std::vector { 119 | return ids | node_view(); 120 | } 121 | 122 | auto IGraph::get_edges(const std::span& ids) const 123 | -> std::vector { 124 | return ids | edge_view(); 125 | } 126 | 127 | // ============================================================================= 128 | // Formats 129 | // ============================================================================= 130 | auto triskel::format_as(const Node& n) -> std::string { 131 | return fmt::format("n{}", n.id()); 132 | } 133 | 134 | auto triskel::format_as(const Edge& e) -> std::string { 135 | return fmt::format("{} -> {}", e.from(), e.to()); 136 | } 137 | 138 | auto triskel::format_as(const IGraph& g) -> std::string { 139 | auto s = std::string{"digraph G {\n"}; 140 | 141 | for (auto node : g.nodes()) { 142 | s += fmt::format("{}\n", node); 143 | } 144 | 145 | s += "\n"; 146 | 147 | for (auto edge : g.edges()) { 148 | s += fmt::format("{}\n", edge); 149 | } 150 | 151 | s += "}\n"; 152 | 153 | return s; 154 | } 155 | -------------------------------------------------------------------------------- /lib/src/layout/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources(triskel PRIVATE 2 | ilayout.cpp 3 | layout.cpp 4 | phantom_nodes.cpp 5 | ) 6 | 7 | add_subdirectory(sugiyama) 8 | -------------------------------------------------------------------------------- /lib/src/layout/ilayout.cpp: -------------------------------------------------------------------------------- 1 | #include "triskel/layout/ilayout.hpp" 2 | 3 | #include 4 | 5 | #include "triskel/graph/igraph.hpp" 6 | #include "triskel/utils/point.hpp" 7 | 8 | // NOLINTNEXTLINE(google-build-using-namespace) 9 | using namespace triskel; 10 | 11 | auto ILayout::get_xy(NodeId node) const -> Point { 12 | return {.x = get_x(node), .y = get_y(node)}; 13 | } 14 | 15 | auto ILayout::get_graph_width(const IGraph& graph) const -> float { 16 | auto max_x = 0.0F; 17 | 18 | for (const auto& node : graph.nodes()) { 19 | max_x = std::max(max_x, get_x(node) + get_width(node)); 20 | } 21 | 22 | for (const auto& edge : graph.edges()) { 23 | for (const auto& waypoint : get_waypoints(edge)) { 24 | max_x = std::max(max_x, waypoint.x); 25 | } 26 | } 27 | 28 | return max_x; 29 | } 30 | 31 | auto ILayout::get_graph_height(const IGraph& graph) const -> float { 32 | auto max_y = 0.0F; 33 | 34 | for (const auto& node : graph.nodes()) { 35 | max_y = std::max(max_y, get_y(node) + get_height(node)); 36 | } 37 | 38 | for (const auto& edge : graph.edges()) { 39 | for (const auto& waypoint : get_waypoints(edge)) { 40 | max_y = std::max(max_y, waypoint.y); 41 | } 42 | } 43 | 44 | return max_y; 45 | } -------------------------------------------------------------------------------- /lib/src/layout/sugiyama/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources(triskel 2 | PRIVATE 3 | network_simplex.cpp 4 | sugiyama.cpp 5 | vertex_ordering.cpp 6 | tamassia.cpp 7 | ) 8 | -------------------------------------------------------------------------------- /lib/src/layout/sugiyama/network_simplex.cpp: -------------------------------------------------------------------------------- 1 | // Based on https://www.graphviz.org/documentation/TSE93.pdf 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "triskel/graph/igraph.hpp" 10 | #include "triskel/layout/sugiyama/layer_assignement.hpp" 11 | #include "triskel/utils/attribute.hpp" 12 | 13 | // NOLINTNEXTLINE(google-build-using-namespace) 14 | using namespace triskel; 15 | 16 | namespace { 17 | 18 | struct SpanningNode { 19 | EdgeId tree_edge; 20 | NodeId node; 21 | 22 | SpanningNode* parent; 23 | std::vector children; 24 | }; 25 | 26 | struct SpanningTree { 27 | explicit SpanningTree(const IGraph& g) 28 | : g{g}, ranks{g, static_cast(-1)}, in_tree{g, false} {} 29 | 30 | [[nodiscard]] auto slack(EdgeId e) const -> size_t { 31 | auto edge = g.get_edge(e); 32 | 33 | return ranks.get(edge.to()) - ranks.get(edge.from()) - 1; 34 | } 35 | 36 | [[nodiscard]] auto is_tight(EdgeId e) const -> bool { 37 | return slack(e) == 0; 38 | } 39 | 40 | auto init_rank() { 41 | const auto nodes = g.nodes(); 42 | 43 | ranks.set(g.root(), 0); 44 | size_t found_nodes = 1; 45 | size_t rank = 1; 46 | 47 | while (found_nodes < nodes.size()) { 48 | for (const auto& node : nodes) { 49 | if (ranks.get(node) < rank) { 50 | continue; 51 | } 52 | 53 | for (const auto& parent : node.parent_nodes()) { 54 | if (ranks.get(parent) >= rank) { 55 | goto continue_loop; 56 | } 57 | } 58 | 59 | ranks.set(node, rank); 60 | found_nodes += 1; 61 | 62 | continue_loop: 63 | } 64 | 65 | rank += 1; 66 | } 67 | } 68 | 69 | // NOLINTNEXTLINE(misc-no-recursion) 70 | void tight_tree_recurs(SpanningNode& spanning_node) { 71 | const auto& node = g.get_node(spanning_node.node); 72 | in_tree.set(node, true); 73 | 74 | for (const auto& edge : node.edges()) { 75 | auto neighbor = edge.other(node); 76 | 77 | if (in_tree.get(neighbor)) { 78 | continue; 79 | } 80 | 81 | // Only keep tight edges 82 | if (!is_tight(edge)) { 83 | continue; 84 | } 85 | 86 | tree.push_back(SpanningNode{.tree_edge = EdgeId::InvalidID, 87 | .node = neighbor, 88 | .parent = &spanning_node, 89 | .children = {}}); 90 | 91 | spanning_node.children.push_back(&tree.back()); 92 | 93 | tight_tree_recurs(tree.back()); 94 | } 95 | } 96 | 97 | /// @brief Constructs a tight spanning tree and returns the number of nodes 98 | /// in that tree 99 | auto tight_tree() -> size_t { 100 | for (auto& spanning_node : tree) { 101 | tight_tree_recurs(spanning_node); 102 | } 103 | 104 | return tree.size(); 105 | } 106 | 107 | auto feasible_tree() { 108 | init_rank(); 109 | 110 | // Adds the root to the tree 111 | tree.push_back(SpanningNode{.tree_edge = EdgeId::InvalidID, 112 | .node = g.root(), 113 | .parent = nullptr, 114 | .children = {}}); 115 | 116 | const auto& nodes = g.nodes(); 117 | while (tight_tree() < nodes.size()) { 118 | auto e = EdgeId::InvalidID; 119 | size_t min_slack = -1; 120 | 121 | for (const auto& node : nodes) { 122 | if (in_tree.get(node)) { 123 | continue; 124 | } 125 | 126 | for (const auto& edge : node.edges()) { 127 | auto neighbor = edge.other(node); 128 | 129 | // We want incident edges 130 | if (!in_tree.get(neighbor)) { 131 | continue; 132 | } 133 | 134 | auto edge_slack = slack(edge); 135 | assert(edge_slack != 1); 136 | 137 | if (edge_slack < min_slack) { 138 | min_slack = edge_slack; 139 | e = edge; 140 | } 141 | } 142 | } 143 | 144 | auto edge = g.get_edge(e); 145 | auto delta = slack(e); 146 | 147 | if (in_tree.get(edge.to())) { 148 | delta = -delta; 149 | } 150 | 151 | for (const auto& spanning_node : tree) { 152 | auto node = spanning_node.node; 153 | auto rank = ranks.get(node); 154 | ranks.set(node, rank + delta); 155 | } 156 | } 157 | 158 | // INIT CUT VALUES 159 | } 160 | 161 | auto normalize_ranks() -> size_t { 162 | size_t max_rank = 0; 163 | 164 | for (const auto& node : g.nodes()) { 165 | max_rank = std::max(max_rank, ranks.get(node)); 166 | } 167 | 168 | size_t rank_count = 0; 169 | for (const auto& node : g.nodes()) { 170 | auto rank = max_rank - ranks.get(node) + 1; 171 | ranks.set(node, rank); 172 | rank_count = std::max(rank_count, rank); 173 | } 174 | 175 | return rank_count + 1; 176 | } 177 | 178 | std::deque tree; 179 | 180 | NodeAttribute in_tree; 181 | 182 | NodeAttribute ranks; 183 | const IGraph& g; 184 | }; 185 | 186 | } // namespace 187 | 188 | auto triskel::network_simplex(const IGraph& graph) 189 | -> std::unique_ptr { 190 | auto spanning_tree = SpanningTree(graph); 191 | spanning_tree.feasible_tree(); 192 | auto rank_count = spanning_tree.normalize_ranks(); 193 | 194 | // TODO: the rest of network simplex. This works already really well though 195 | 196 | return std::make_unique(spanning_tree.ranks, rank_count); 197 | } 198 | -------------------------------------------------------------------------------- /lib/src/layout/sugiyama/tamassia.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "triskel/graph/igraph.hpp" 8 | #include "triskel/layout/sugiyama/layer_assignement.hpp" 9 | #include "triskel/utils/attribute.hpp" 10 | 11 | // NOLINTNEXTLINE(google-build-using-namespace) 12 | using namespace triskel; 13 | 14 | namespace { 15 | 16 | auto select_node(const IGraph& g, 17 | std::vector& U, 18 | std::vector& Z) -> std::optional { 19 | for (const auto& node : g.nodes()) { 20 | if (std::ranges::contains(U, node.id())) { 21 | continue; 22 | } 23 | 24 | auto valid = true; 25 | for (const auto& child : node.child_nodes()) { 26 | if (!std::ranges::contains(Z, child.id())) { 27 | valid = false; 28 | break; 29 | } 30 | } 31 | 32 | if (valid) { 33 | return node; 34 | } 35 | } 36 | 37 | return {}; 38 | } 39 | 40 | auto layer_assignment(const IGraph& g, 41 | NodeAttribute& layers) -> size_t { 42 | auto U = std::vector{}; 43 | auto Z = std::vector{}; 44 | 45 | size_t current_layer = 1; 46 | 47 | while (U.size() < g.node_count()) { 48 | auto v = select_node(g, U, Z); 49 | 50 | if (v.has_value()) { 51 | layers.set(*v, current_layer); 52 | U.push_back(v->id()); 53 | continue; 54 | } 55 | 56 | current_layer++; 57 | Z.insert(Z.end(), U.begin(), U.end()); 58 | } 59 | 60 | return current_layer + 1; 61 | } 62 | } // namespace 63 | 64 | auto triskel::longest_path_tamassia(const IGraph& graph) 65 | -> std::unique_ptr { 66 | auto layers = std::make_unique(graph); 67 | layers->layer_count = layer_assignment(graph, layers->layers); 68 | 69 | return layers; 70 | } 71 | -------------------------------------------------------------------------------- /lib/src/llvm/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources(triskel PRIVATE 2 | llvm.cpp 3 | triskel.cpp 4 | ) -------------------------------------------------------------------------------- /lib/src/llvm/llvm.cpp: -------------------------------------------------------------------------------- 1 | #include "triskel/llvm/llvm.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | 22 | // NOLINTNEXTLINE(google-build-using-namespace) 23 | using namespace triskel; 24 | 25 | namespace { 26 | 27 | auto truncate_str(const std::string& str, 28 | const std::string& ellipsis = "(...)", 29 | size_t max_length = 80) -> std::string { 30 | const size_t ellipsisLength = ellipsis.length(); 31 | 32 | if (str.length() <= max_length) { 33 | return str; 34 | } 35 | 36 | return str.substr(0, max_length - ellipsisLength) + ellipsis; 37 | } 38 | 39 | } // namespace 40 | 41 | auto triskel::to_string(const llvm::Value& v, 42 | llvm::ModuleSlotTracker* MST) -> std::string { 43 | std::string s; 44 | ::llvm::raw_string_ostream os{s}; 45 | if (MST != nullptr) { 46 | v.print(os, *MST); 47 | } else { 48 | v.print(os); 49 | } 50 | 51 | s = truncate_str(os.str()); 52 | 53 | std::ranges::replace(s, '\n', ' '); 54 | return s; 55 | } 56 | 57 | auto triskel::format_as(const llvm::Value& v) -> std::string { 58 | return to_string(v); 59 | } 60 | 61 | LLVMCFG::LLVMCFG(llvm::Function* function) 62 | : function{function}, 63 | block_map(function->size(), nullptr), 64 | edge_types(function->size(), EdgeType::Default) { 65 | // Important, otherwise the CFGs are not necessarily well defined 66 | llvm::EliminateUnreachableBlocks(*function); 67 | 68 | auto& editor = graph.editor(); 69 | editor.push(); 70 | 71 | std::map reverse_map; 72 | 73 | for (auto& block : *function) { 74 | auto node = editor.make_node(); 75 | 76 | // Adds the block to the maps 77 | block_map.set(node, &block); 78 | reverse_map.insert_or_assign(&block, node.id()); 79 | } 80 | 81 | for (auto& block : *function) { 82 | auto node = graph.get_node(reverse_map.at(&block)); 83 | 84 | llvm::BasicBlock* true_edge = nullptr; 85 | llvm::BasicBlock* false_edge = nullptr; 86 | 87 | const auto& insn = block.back(); 88 | if (const auto* branch = llvm::dyn_cast(&insn)) { 89 | if (branch->isConditional()) { 90 | true_edge = branch->getSuccessor(0); 91 | false_edge = branch->getSuccessor(1); 92 | } 93 | } 94 | 95 | for (auto* child : llvm::successors(&block)) { 96 | auto child_node = reverse_map.at(child); 97 | 98 | auto edge = editor.make_edge(node, child_node); 99 | 100 | if (child == true_edge) { 101 | edge_types.set(edge, EdgeType::True); 102 | } else if (child == false_edge) { 103 | edge_types.set(edge, EdgeType::False); 104 | } 105 | } 106 | } 107 | 108 | editor.commit(); 109 | } -------------------------------------------------------------------------------- /lib/src/llvm/triskel.cpp: -------------------------------------------------------------------------------- 1 | #include "triskel/triskel.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "llvm/IR/ModuleSlotTracker.h" 13 | 14 | #include "triskel/llvm/llvm.hpp" 15 | 16 | auto triskel::make_layout(llvm::Function* function, 17 | Renderer* render, 18 | llvm::ModuleSlotTracker* MST) 19 | -> std::unique_ptr { 20 | auto builder = make_layout_builder(); 21 | 22 | // Important, otherwise the CFGs are not necessarily well defined 23 | llvm::EliminateUnreachableBlocks(*function); 24 | 25 | std::map reverse_map; 26 | 27 | for (auto& block : *function) { 28 | auto content = std::string{}; 29 | 30 | for (const auto& insn : block) { 31 | content += triskel::to_string(insn, MST) + "\n"; 32 | } 33 | 34 | auto node = builder->make_node(content); 35 | 36 | // Adds the block to the maps 37 | reverse_map.insert_or_assign(&block, node); 38 | } 39 | 40 | for (auto& block : *function) { 41 | auto node = reverse_map.at(&block); 42 | 43 | llvm::BasicBlock* true_edge = nullptr; 44 | llvm::BasicBlock* false_edge = nullptr; 45 | 46 | const auto& insn = block.back(); 47 | if (const auto* branch = llvm::dyn_cast(&insn)) { 48 | if (branch->isConditional()) { 49 | true_edge = branch->getSuccessor(0); 50 | false_edge = branch->getSuccessor(1); 51 | } 52 | } 53 | 54 | for (auto* child : llvm::successors(&block)) { 55 | auto child_node = reverse_map.at(child); 56 | 57 | auto type = LayoutBuilder::EdgeType::Default; 58 | 59 | if (child == true_edge) { 60 | type = LayoutBuilder::EdgeType::True; 61 | } else if (child == false_edge) { 62 | type = LayoutBuilder::EdgeType::False; 63 | } 64 | 65 | builder->make_edge(node, child_node, type); 66 | } 67 | } 68 | 69 | if (render != nullptr) { 70 | builder->measure_nodes(*render); 71 | } 72 | 73 | return builder->build(); 74 | } 75 | -------------------------------------------------------------------------------- /lib/src/renderer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if (ENABLE_CAIRO) 2 | target_sources(triskel PRIVATE cairo.cpp) 3 | endif() 4 | 5 | if (ENABLE_IMGUI) 6 | target_sources(triskel PRIVATE imgui.cpp) 7 | add_subdirectory(external) 8 | endif() 9 | -------------------------------------------------------------------------------- /lib/src/renderer/external/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources(triskel 2 | PRIVATE 3 | imgui_canvas.cpp 4 | ) -------------------------------------------------------------------------------- /lib/src/renderer/external/credit.md: -------------------------------------------------------------------------------- 1 | The `imgui_canvas.cpp` and `imgui_canvas.h` files were taken from 2 | `thedmd`'s [imgui-node-editor](https://github.com/thedmd/imgui-node-editor/tree/master). 3 | 4 | This repository is under the following MIT license: 5 | 6 | ``` 7 | MIT License 8 | 9 | Copyright (c) 2019 Michał Cichoń 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in all 19 | copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | SOFTWARE. 28 | ``` -------------------------------------------------------------------------------- /lib/src/renderer/imgui.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define IMGUI_DEFINE_MATH_OPERATORS 6 | #include 7 | 8 | #include "./external/imgui_canvas.h" 9 | #include "triskel/triskel.hpp" 10 | #include "triskel/utils/point.hpp" 11 | 12 | /// NOLINTNEXTLINE(google-build-using-namespace) 13 | using namespace triskel; 14 | 15 | namespace { 16 | 17 | struct ImGuiRendererImpl : public ImguiRenderer { 18 | ImGuiRendererImpl() { 19 | STYLE_BASICBLOCK_BORDER = {.thickness = 3.0F, .color = Black}; 20 | STYLE_EDGE = {.thickness = 2.0F, .color = Black}; 21 | STYLE_EDGE_T = {.thickness = 2.0F, .color = Green}; 22 | STYLE_EDGE_F = {.thickness = 2.0F, .color = Red}; 23 | BLOCK_PADDING = 20.0F; 24 | TRIANGLE_SIZE = 20.0F; 25 | PADDING = 200.0F; 26 | } 27 | 28 | void Begin(const char* id, Point size) override { 29 | auto canvas_rect = canvas_.Rect(); 30 | auto view_rect = canvas_.ViewRect(); 31 | 32 | canvas_.Begin(id, convert(size)); 33 | 34 | if (should_refit) { 35 | refit(width_, height_); 36 | should_refit = false; 37 | } 38 | 39 | translate(); 40 | scale(); 41 | } 42 | 43 | void scale() { 44 | auto& io = ImGui::GetIO(); 45 | auto view_scale = canvas_.ViewScale(); 46 | auto view_origin = canvas_.ViewOrigin(); 47 | 48 | if (ImGui::IsItemHovered() && (io.MouseWheel != 0)) { 49 | auto scale = view_scale; 50 | if (view_scale > 1) { 51 | scale += io.MouseWheel; 52 | } else { 53 | if (io.MouseWheel > 0) { 54 | scale *= 2; 55 | } else { 56 | scale /= 2; 57 | } 58 | } 59 | 60 | // TODO: clamp zoom 61 | // TODO: zoom not to corner 62 | canvas_.SetView((view_origin / view_scale * scale), scale); 63 | } 64 | } 65 | 66 | void translate() { 67 | auto view_scale = canvas_.ViewScale(); 68 | auto view_origin = canvas_.ViewOrigin(); 69 | static bool is_dragging = false; 70 | static ImVec2 draw_start_point; 71 | 72 | if ((is_dragging || ImGui::IsItemHovered()) && 73 | ImGui::IsMouseDragging(1, 0.0F)) { 74 | if (!is_dragging) { 75 | is_dragging = true; 76 | draw_start_point = view_origin; 77 | } 78 | 79 | canvas_.SetView(draw_start_point + 80 | ImGui::GetMouseDragDelta(1, 0.0F) * view_scale, 81 | view_scale); 82 | } else if (is_dragging) { 83 | is_dragging = false; 84 | } 85 | } 86 | 87 | void End() override { canvas_.End(); } 88 | 89 | void draw_line(Point start, Point end, const StrokeStyle& style) override { 90 | ImDrawList* draw_list = ImGui::GetWindowDrawList(); 91 | 92 | auto start_ = convert(start); 93 | auto end_ = convert(end); 94 | 95 | auto color = convert(style.color); 96 | 97 | draw_list->AddLine(start_, end_, color, style.thickness); 98 | } 99 | 100 | void draw_triangle(Point v1, Point v2, Point v3, Color fill) override { 101 | ImDrawList* draw_list = ImGui::GetWindowDrawList(); 102 | 103 | auto v1_ = convert(v1); 104 | auto v2_ = convert(v2); 105 | auto v3_ = convert(v3); 106 | 107 | auto fill_ = convert(fill); 108 | 109 | draw_list->AddTriangleFilled(v1_, v2_, v3_, fill_); 110 | }; 111 | 112 | void draw_rectangle(Point tl, 113 | float width, 114 | float height, 115 | Color fill) override { 116 | ImDrawList* draw_list = ImGui::GetWindowDrawList(); 117 | 118 | auto top_left = convert(tl); 119 | auto bottom_right = top_left + ImVec2{width, height}; 120 | 121 | auto color = convert(fill); 122 | 123 | draw_list->AddRectFilled(top_left, bottom_right, color); 124 | }; 125 | 126 | void draw_rectangle_border(Point tl, 127 | float width, 128 | float height, 129 | const StrokeStyle& style) override { 130 | ImDrawList* draw_list = ImGui::GetWindowDrawList(); 131 | 132 | auto top_left = convert(tl); 133 | auto bottom_right = top_left + ImVec2{width, height}; 134 | 135 | auto color = convert(style.color); 136 | 137 | draw_list->AddRect(top_left, bottom_right, color, 0.0F, 0, 138 | style.thickness); 139 | }; 140 | 141 | void draw_text(Point tl, 142 | const std::string& text, 143 | const TextStyle& style) override { 144 | ImGui::PushStyleColor(ImGuiCol_Text, convert(style.color)); 145 | 146 | ImGui::SetCursorPos( 147 | convert(tl + Point{.x = BLOCK_PADDING, .y = BLOCK_PADDING})); 148 | ImGui::TextUnformatted(text.c_str()); 149 | 150 | ImGui::PopStyleColor(); 151 | }; 152 | 153 | [[nodiscard]] auto measure_text(const std::string& text, 154 | const TextStyle& /*style*/) const 155 | -> Point override { 156 | return convert(ImGui::CalcTextSize(text.c_str())) + 157 | Point{.x = 2 * BLOCK_PADDING, .y = 2 * BLOCK_PADDING}; 158 | } 159 | 160 | [[nodiscard]] static auto convert(Point pt) -> ImVec2 { 161 | return {pt.x, pt.y}; 162 | } 163 | 164 | [[nodiscard]] static auto convert(ImVec2 v) -> Point { 165 | return {.x = v.x, .y = v.y}; 166 | } 167 | 168 | [[nodiscard]] static auto convert(Color color) -> ImU32 { 169 | return IM_COL32(color.r, color.g, color.b, color.a); 170 | } 171 | 172 | void fit(float width, float height) override { 173 | should_refit = true; 174 | width_ = width; 175 | height_ = height; 176 | } 177 | 178 | void refit(float width, float height) { 179 | auto view_scale = canvas_.ViewScale(); 180 | auto view_origin = canvas_.ViewOrigin(); 181 | 182 | auto canvas_size = canvas_.ViewRect(); 183 | 184 | if (canvas_size.GetHeight() == 0 || canvas_size.GetWidth() == 0) { 185 | // TODO: center the first function 186 | return; 187 | } 188 | 189 | const float scale_x = 190 | canvas_size.GetWidth() / (width + 2 * PADDING) * view_scale; 191 | const float scale_y = 192 | canvas_size.GetHeight() / (height + 2 * PADDING) * view_scale; 193 | view_scale = std::min(scale_x, scale_y); 194 | canvas_.SetView(view_origin, view_scale); 195 | 196 | canvas_.CenterView(ImVec2{width / 2, height / 2}); 197 | } 198 | 199 | ImGuiEx::Canvas canvas_; 200 | 201 | bool should_refit = false; 202 | float width_; 203 | float height_; 204 | }; 205 | 206 | } // namespace 207 | 208 | auto triskel::make_imgui_renderer() -> std::unique_ptr { 209 | return std::make_unique(); 210 | } -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(triskel_test) 2 | 3 | enable_testing() 4 | 5 | add_executable(triskel_test) 6 | 7 | target_sources(triskel_test PRIVATE 8 | triskel_test.cpp 9 | ) 10 | 11 | target_link_libraries(triskel_test PRIVATE 12 | triskel 13 | fmt::fmt 14 | GTest::gtest_main 15 | GTest::gtest 16 | ) 17 | 18 | add_subdirectory(analysis) 19 | add_subdirectory(graph) 20 | add_subdirectory(datatypes) 21 | 22 | 23 | include(GoogleTest) 24 | gtest_discover_tests(triskel_test) 25 | -------------------------------------------------------------------------------- /test/analysis/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources(triskel_test PRIVATE 2 | dfs_test.cpp 3 | lengauer_tarjan_test.cpp 4 | ) -------------------------------------------------------------------------------- /test/analysis/dfs_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | // NOLINTNEXTLINE(google-build-using-namespace) 9 | using namespace triskel; 10 | 11 | // The graph from the wikipedia example 12 | // https://en.wikipedia.org/wiki/Depth-first_search#Output_of_a_depth-first_search 13 | #define GRAPH1 \ 14 | auto g = Graph{}; \ 15 | auto& ge = g.editor(); \ 16 | ge.push(); \ 17 | \ 18 | auto n1 = ge.make_node(); \ 19 | auto n2 = ge.make_node(); \ 20 | auto n3 = ge.make_node(); \ 21 | auto n4 = ge.make_node(); \ 22 | auto n5 = ge.make_node(); \ 23 | auto n6 = ge.make_node(); \ 24 | auto n7 = ge.make_node(); \ 25 | auto n8 = ge.make_node(); \ 26 | \ 27 | auto e1_2 = ge.make_edge(n1, n2); \ 28 | auto e1_5 = ge.make_edge(n1, n5); \ 29 | auto e1_8 = ge.make_edge(n1, n8); \ 30 | \ 31 | auto e2_3 = ge.make_edge(n2, n3); \ 32 | \ 33 | auto e3_4 = ge.make_edge(n3, n4); \ 34 | \ 35 | auto e4_2 = ge.make_edge(n4, n2); \ 36 | \ 37 | auto e5_6 = ge.make_edge(n5, n6); \ 38 | \ 39 | auto e6_3 = ge.make_edge(n6, n3); \ 40 | auto e6_7 = ge.make_edge(n6, n7); \ 41 | auto e6_8 = ge.make_edge(n6, n8); \ 42 | ge.commit(); 43 | 44 | TEST(DFSAnalysis, Smoke) { 45 | GRAPH1; 46 | 47 | ASSERT_NO_THROW(auto dfs = DFSAnalysis(g)); 48 | } 49 | 50 | TEST(DFSAnalysis, EdgeTypes) { 51 | GRAPH1; 52 | 53 | auto dfs = DFSAnalysis(g); 54 | 55 | fmt::print("Node order: \n"); 56 | for (const auto& node : dfs.nodes()) { 57 | fmt::print("- {}\n", node); 58 | } 59 | 60 | ASSERT_TRUE(dfs.is_backedge(e4_2)); 61 | 62 | ASSERT_TRUE(dfs.is_forward(e1_8)); 63 | 64 | ASSERT_TRUE(dfs.is_cross(e6_3)); 65 | 66 | for (const auto& e : {e1_2, e1_5, e2_3, e5_6, e3_4, e6_7, e6_8}) { 67 | fmt::print("Testing edge {}\n", e); 68 | ASSERT_TRUE(dfs.is_tree(e)); 69 | } 70 | } 71 | 72 | #undef GRAPH1 73 | -------------------------------------------------------------------------------- /test/analysis/lengauer_tarjan_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include "triskel/graph/igraph.hpp" 8 | 9 | // NOLINTNEXTLINE(google-build-using-namespace) 10 | using namespace triskel; 11 | 12 | // The graph from the wikipedia example 13 | // https://en.wikipedia.org/wiki/Depth-first_search#Output_of_a_depth-first_search 14 | #define GRAPH1 \ 15 | auto graph = Graph{}; \ 16 | auto& ge = graph.editor(); \ 17 | ge.push(); \ 18 | \ 19 | auto r = ge.make_node(); \ 20 | auto a = ge.make_node(); \ 21 | auto b = ge.make_node(); \ 22 | auto c = ge.make_node(); \ 23 | auto d = ge.make_node(); \ 24 | auto e = ge.make_node(); \ 25 | auto f = ge.make_node(); \ 26 | auto g = ge.make_node(); \ 27 | auto h = ge.make_node(); \ 28 | auto i = ge.make_node(); \ 29 | auto j = ge.make_node(); \ 30 | auto k = ge.make_node(); \ 31 | auto l = ge.make_node(); \ 32 | \ 33 | auto er_a = ge.make_edge(r, a); \ 34 | auto er_b = ge.make_edge(r, b); \ 35 | auto er_c = ge.make_edge(r, c); \ 36 | \ 37 | auto ea_d = ge.make_edge(a, d); \ 38 | \ 39 | auto eb_a = ge.make_edge(b, a); \ 40 | auto eb_d = ge.make_edge(b, d); \ 41 | auto eb_e = ge.make_edge(b, e); \ 42 | \ 43 | auto ec_f = ge.make_edge(c, f); \ 44 | auto ec_g = ge.make_edge(c, g); \ 45 | \ 46 | auto ed_l = ge.make_edge(d, l); \ 47 | \ 48 | auto ee_h = ge.make_edge(e, h); \ 49 | \ 50 | auto ef_i = ge.make_edge(f, i); \ 51 | \ 52 | auto eg_i = ge.make_edge(g, i); \ 53 | auto eg_j = ge.make_edge(g, j); \ 54 | \ 55 | auto eh_e = ge.make_edge(h, e); \ 56 | auto eh_k = ge.make_edge(h, k); \ 57 | \ 58 | auto ei_k = ge.make_edge(i, k); \ 59 | \ 60 | auto ej_i = ge.make_edge(j, i); \ 61 | \ 62 | auto ek_i = ge.make_edge(k, i); \ 63 | auto ek_r = ge.make_edge(k, r); \ 64 | \ 65 | auto el_h = ge.make_edge(l, h); \ 66 | \ 67 | ge.commit(); 68 | 69 | TEST(Domination, Smoke) { 70 | GRAPH1; 71 | 72 | auto idom = make_idoms(graph); 73 | 74 | ASSERT_EQ(idom[r], NodeId::InvalidID); 75 | 76 | ASSERT_EQ(idom[a], r); 77 | ASSERT_EQ(idom[b], r); 78 | ASSERT_EQ(idom[c], r); 79 | ASSERT_EQ(idom[d], r); 80 | ASSERT_EQ(idom[e], r); 81 | ASSERT_EQ(idom[f], c); 82 | ASSERT_EQ(idom[g], c); 83 | ASSERT_EQ(idom[h], r); 84 | ASSERT_EQ(idom[i], r); 85 | ASSERT_EQ(idom[j], g); 86 | ASSERT_EQ(idom[k], r); 87 | ASSERT_EQ(idom[l], d); 88 | } 89 | 90 | #undef GRAPH1 91 | -------------------------------------------------------------------------------- /test/datatypes/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources(triskel_test PRIVATE 2 | rtree_test.cpp 3 | ) -------------------------------------------------------------------------------- /test/datatypes/rtree_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | void test_tree(RTree& rtree) { 9 | ASSERT_EQ(rtree.root.children.size(), 1); 10 | 11 | auto& node_r = rtree.root.children.at('r'); 12 | ASSERT_EQ(node_r->radix, std::vector{'r'}); 13 | ASSERT_EQ(node_r->data.size(), 0); 14 | ASSERT_EQ(node_r->children.size(), 2); 15 | 16 | auto& node_rom = node_r->children.at('o'); 17 | ASSERT_EQ(node_rom->radix, (std::vector{'o', 'm'})); 18 | ASSERT_EQ(node_rom->data.size(), 0); 19 | ASSERT_EQ(node_rom->children.size(), 2); 20 | 21 | auto& node_romulus = node_rom->children.at('u'); 22 | ASSERT_EQ(node_romulus->radix, 23 | (std::vector{'u', 'l', 'u', 's', '\x00'})); 24 | ASSERT_EQ(node_romulus->data.size(), 1); 25 | ASSERT_EQ(node_romulus->data.front(), 3); 26 | ASSERT_EQ(node_romulus->children.size(), 0); 27 | 28 | auto& node_roman = node_rom->children.at('a'); 29 | ASSERT_EQ(node_roman->radix, (std::vector{'a', 'n'})); 30 | ASSERT_EQ(node_roman->data.size(), 0); 31 | ASSERT_EQ(node_roman->children.size(), 2); 32 | 33 | auto& node_romane = node_roman->children.at('e'); 34 | ASSERT_EQ(node_romane->radix, (std::vector{'e', '\x00'})); 35 | ASSERT_EQ(node_romane->data.size(), 1); 36 | ASSERT_EQ(node_romane->data.front(), 1); 37 | ASSERT_EQ(node_romane->children.size(), 0); 38 | 39 | auto& node_romanus = node_roman->children.at('u'); 40 | ASSERT_EQ(node_romanus->radix, (std::vector{'u', 's', '\x00'})); 41 | ASSERT_EQ(node_romanus->data.size(), 1); 42 | ASSERT_EQ(node_romanus->data.front(), 2); 43 | ASSERT_EQ(node_romanus->children.size(), 0); 44 | 45 | auto& node_rub = node_r->children.at('u'); 46 | ASSERT_EQ(node_rub->radix, (std::vector{'u', 'b'})); 47 | ASSERT_EQ(node_rub->data.size(), 0); 48 | ASSERT_EQ(node_rub->children.size(), 2); 49 | 50 | auto& node_rube = node_rub->children.at('e'); 51 | ASSERT_EQ(node_rube->radix, (std::vector{'e'})); 52 | ASSERT_EQ(node_rube->data.size(), 0); 53 | ASSERT_EQ(node_rube->children.size(), 2); 54 | 55 | auto& node_ruben = node_rube->children.at('n'); 56 | ASSERT_EQ(node_ruben->radix, (std::vector{'n', 's', '\x00'})); 57 | ASSERT_EQ(node_ruben->data.size(), 1); 58 | ASSERT_EQ(node_ruben->data.front(), 4); 59 | ASSERT_EQ(node_ruben->children.size(), 0); 60 | 61 | auto& node_ruber = node_rube->children.at('r'); 62 | ASSERT_EQ(node_ruber->radix, (std::vector{'r', '\x00'})); 63 | ASSERT_EQ(node_ruber->data.size(), 1); 64 | ASSERT_EQ(node_ruber->data.front(), 5); 65 | ASSERT_EQ(node_ruber->children.size(), 0); 66 | 67 | auto& node_rubic = node_rub->children.at('i'); 68 | ASSERT_EQ(node_rubic->radix, (std::vector{'i', 'c'})); 69 | ASSERT_EQ(node_rubic->data.size(), 0); 70 | ASSERT_EQ(node_rubic->children.size(), 2); 71 | 72 | auto& node_rubicon = node_rubic->children.at('o'); 73 | ASSERT_EQ(node_rubicon->radix, (std::vector{'o', 'n', '\x00'})); 74 | ASSERT_EQ(node_rubicon->data.size(), 1); 75 | ASSERT_EQ(node_rubicon->data.front(), 6); 76 | ASSERT_EQ(node_rubicon->children.size(), 0); 77 | 78 | auto& node_rubicundus = node_rubic->children.at('u'); 79 | ASSERT_EQ(node_rubicundus->radix, 80 | (std::vector{'u', 'n', 'd', 'u', 's', '\x00'})); 81 | ASSERT_EQ(node_rubicundus->data.size(), 2); 82 | ASSERT_EQ(node_rubicundus->children.size(), 0); 83 | } 84 | 85 | TEST(RTree, Smoke) { 86 | auto rtree = RTree(); 87 | 88 | // From https://en.wikipedia.org/wiki/Radix_tree 89 | rtree.insert(1, "romane"); 90 | rtree.insert(2, "romanus"); 91 | rtree.insert(3, "romulus"); 92 | rtree.insert(4, "rubens"); 93 | rtree.insert(5, "ruber"); 94 | rtree.insert(6, "rubicon"); 95 | rtree.insert(7, "rubicundus"); 96 | rtree.insert(8, "rubicundus"); 97 | 98 | rtree.dump(); 99 | 100 | test_tree(rtree); 101 | } 102 | 103 | TEST(RTree, Smoke2) { 104 | auto rtree = RTree(); 105 | 106 | // From https://en.wikipedia.org/wiki/Radix_tree 107 | rtree.insert(8, "rubicundus"); 108 | rtree.insert(7, "rubicundus"); 109 | rtree.insert(6, "rubicon"); 110 | rtree.insert(5, "ruber"); 111 | rtree.insert(4, "rubens"); 112 | rtree.insert(3, "romulus"); 113 | rtree.insert(2, "romanus"); 114 | rtree.insert(1, "romane"); 115 | 116 | rtree.dump(); 117 | 118 | test_tree(rtree); 119 | } 120 | 121 | TEST(RTree, Smoke3) { 122 | auto rtree = RTree(); 123 | 124 | // From https://en.wikipedia.org/wiki/Radix_tree 125 | rtree.insert(7, "rubicundus"); 126 | rtree.insert(2, "romanus"); 127 | rtree.insert(5, "ruber"); 128 | rtree.insert(8, "rubicundus"); 129 | rtree.insert(3, "romulus"); 130 | rtree.insert(1, "romane"); 131 | rtree.insert(6, "rubicon"); 132 | rtree.insert(4, "rubens"); 133 | 134 | rtree.dump(); 135 | 136 | test_tree(rtree); 137 | } 138 | -------------------------------------------------------------------------------- /test/graph/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources(triskel_test PRIVATE 2 | attribute_test.cpp 3 | graph_editor_test.cpp 4 | graph_test.cpp 5 | subgraph_test.cpp 6 | ) -------------------------------------------------------------------------------- /test/graph/attribute_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | // NOLINTNEXTLINE(google-build-using-namespace) 7 | using namespace triskel; 8 | 9 | // The graph from the wikipedia example 10 | // https://en.wikipedia.org/wiki/Depth-first_search#Output_of_a_depth-first_search 11 | #define GRAPH1 \ 12 | auto g = Graph{}; \ 13 | auto ge = g.editor(); \ 14 | ge.push(); \ 15 | \ 16 | auto n1 = ge.make_node(); \ 17 | auto n2 = ge.make_node(); \ 18 | auto n3 = ge.make_node(); \ 19 | auto n4 = ge.make_node(); \ 20 | auto n5 = ge.make_node(); \ 21 | auto n6 = ge.make_node(); \ 22 | auto n7 = ge.make_node(); \ 23 | auto n8 = ge.make_node(); \ 24 | \ 25 | auto e1_2 = ge.make_edge(n1, n2); \ 26 | auto e1_5 = ge.make_edge(n1, n5); \ 27 | auto e1_8 = ge.make_edge(n1, n8); \ 28 | \ 29 | auto e2_3 = ge.make_edge(n2, n3); \ 30 | \ 31 | auto e3_4 = ge.make_edge(n3, n4); \ 32 | \ 33 | auto e4_2 = ge.make_edge(n4, n2); \ 34 | \ 35 | auto e5_6 = ge.make_edge(n5, n6); \ 36 | \ 37 | auto e6_3 = ge.make_edge(n6, n3); \ 38 | auto e6_7 = ge.make_edge(n6, n7); \ 39 | auto e6_8 = ge.make_edge(n6, n8); \ 40 | ge.commit(); 41 | 42 | TEST(Attribute, Node) { 43 | GRAPH1 44 | 45 | auto test_attribute = NodeAttribute{g.max_node_id(), 42}; 46 | 47 | ASSERT_EQ(test_attribute.get(n7), 42); 48 | 49 | test_attribute.set(n7, 0); 50 | 51 | ASSERT_EQ(test_attribute.get(n7), 0); 52 | } 53 | 54 | TEST(Attribute, Edge) { 55 | GRAPH1 56 | 57 | auto test_attribute = EdgeAttribute{g.max_edge_id(), 42}; 58 | 59 | ASSERT_EQ(test_attribute.get(e6_3), 42); 60 | 61 | test_attribute.set(e6_3, 0); 62 | 63 | ASSERT_EQ(test_attribute.get(e6_3), 0); 64 | } 65 | 66 | TEST(Attribute, Resize) { 67 | GRAPH1 68 | 69 | auto test_attribute = NodeAttribute{1, 42}; 70 | 71 | ASSERT_EQ(test_attribute.get(n1), 42); 72 | 73 | fmt::print("Reading value for n2\n"); 74 | ASSERT_EQ(test_attribute.get(n2), 42); 75 | 76 | fmt::print("Setting value for n4\n"); 77 | test_attribute.set(n4, 0); 78 | 79 | ASSERT_EQ(test_attribute.get(n4), 0); 80 | } 81 | 82 | #undef GRAPH1 83 | -------------------------------------------------------------------------------- /test/graph/graph_editor_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | // NOLINTNEXTLINE(google-build-using-namespace) 9 | using namespace triskel; 10 | 11 | // The graph from the wikipedia example 12 | // https://en.wikipedia.org/wiki/Depth-first_search#Output_of_a_depth-first_search 13 | #define GRAPH1 \ 14 | auto g = Graph{}; \ 15 | auto ge = g.editor(); \ 16 | ge.push(); \ 17 | \ 18 | auto n1 = ge.make_node(); \ 19 | auto n2 = ge.make_node(); \ 20 | auto n3 = ge.make_node(); \ 21 | auto n4 = ge.make_node(); \ 22 | auto n5 = ge.make_node(); \ 23 | auto n6 = ge.make_node(); \ 24 | auto n7 = ge.make_node(); \ 25 | auto n8 = ge.make_node(); \ 26 | \ 27 | auto e1_2 = ge.make_edge(n1, n2); \ 28 | auto e1_5 = ge.make_edge(n1, n5); \ 29 | auto e1_8 = ge.make_edge(n1, n8); \ 30 | \ 31 | auto e2_3 = ge.make_edge(n2, n3); \ 32 | \ 33 | auto e3_4 = ge.make_edge(n3, n4); \ 34 | \ 35 | auto e4_2 = ge.make_edge(n4, n2); \ 36 | \ 37 | auto e5_6 = ge.make_edge(n5, n6); \ 38 | \ 39 | auto e6_3 = ge.make_edge(n6, n3); \ 40 | auto e6_7 = ge.make_edge(n6, n7); \ 41 | auto e6_8 = ge.make_edge(n6, n8); \ 42 | ge.commit(); 43 | 44 | TEST(Attribute, addNode) { 45 | GRAPH1 46 | 47 | ge.push(); 48 | 49 | size_t og_size = g.node_count(); 50 | 51 | auto new_node = ge.make_node(); 52 | 53 | ASSERT_EQ(g.node_count(), og_size + 1); 54 | 55 | ge.pop(); 56 | 57 | ASSERT_EQ(g.node_count(), og_size); 58 | 59 | for (const auto& n : g.nodes()) { 60 | ASSERT_NE(n, new_node); 61 | } 62 | } 63 | 64 | TEST(Attribute, rmNode) { 65 | GRAPH1 66 | 67 | size_t og_size = g.node_count(); 68 | 69 | ge.push(); 70 | 71 | ge.remove_node(n3); 72 | 73 | ASSERT_EQ(g.node_count(), og_size - 1); 74 | 75 | for (const auto& n : g.nodes()) { 76 | ASSERT_NE(n, n3); 77 | } 78 | 79 | for (const auto& e : g.edges()) { 80 | ASSERT_NE(e.from(), n3); 81 | ASSERT_NE(e.to(), n3); 82 | } 83 | 84 | ge.pop(); 85 | 86 | ASSERT_EQ(g.node_count(), og_size); 87 | 88 | ASSERT_TRUE(std::ranges::contains(g.edges(), e2_3)); 89 | } 90 | 91 | TEST(Attribute, addEdge) { 92 | GRAPH1 93 | 94 | ge.push(); 95 | 96 | size_t og_size = g.edge_count(); 97 | 98 | auto new_edge = ge.make_edge(n1, n5); 99 | 100 | ASSERT_TRUE(std::ranges::contains(n1.edges(), new_edge)); 101 | ASSERT_TRUE(std::ranges::contains(n5.edges(), new_edge)); 102 | 103 | ASSERT_EQ(g.edge_count(), og_size + 1); 104 | 105 | ge.pop(); 106 | 107 | ASSERT_EQ(g.edge_count(), og_size); 108 | 109 | for (const auto& e : g.edges()) { 110 | ASSERT_NE(e, new_edge); 111 | } 112 | } 113 | 114 | TEST(Attribute, rmEdge) { 115 | GRAPH1 116 | 117 | ge.push(); 118 | 119 | size_t og_size = g.edge_count(); 120 | 121 | ge.remove_edge(e3_4); 122 | 123 | ASSERT_EQ(g.edge_count(), og_size - 1); 124 | 125 | ASSERT_FALSE(std::ranges::contains(n3.edges(), e3_4)); 126 | ASSERT_FALSE(std::ranges::contains(n4.edges(), e3_4)); 127 | ASSERT_FALSE(std::ranges::contains(g.edges(), e3_4)); 128 | 129 | ge.pop(); 130 | 131 | ASSERT_EQ(g.edge_count(), og_size); 132 | 133 | ASSERT_TRUE(std::ranges::contains(n3.edges(), e3_4)); 134 | ASSERT_TRUE(std::ranges::contains(n4.edges(), e3_4)); 135 | ASSERT_TRUE(std::ranges::contains(g.edges(), e3_4)); 136 | } 137 | 138 | TEST(Attribute, editEdge) { 139 | GRAPH1 140 | 141 | ge.push(); 142 | 143 | ge.edit_edge(e3_4, n1, n5); 144 | 145 | ASSERT_EQ(e3_4.from(), n1); 146 | ASSERT_EQ(e3_4.to(), n5); 147 | 148 | ASSERT_TRUE(std::ranges::contains(n1.edges(), e3_4)); 149 | ASSERT_TRUE(std::ranges::contains(n5.edges(), e3_4)); 150 | 151 | ASSERT_FALSE(std::ranges::contains(n3.edges(), e3_4)); 152 | ASSERT_FALSE(std::ranges::contains(n4.edges(), e3_4)); 153 | 154 | ge.pop(); 155 | 156 | ASSERT_EQ(e3_4.from(), n3); 157 | ASSERT_EQ(e3_4.to(), n4); 158 | 159 | ASSERT_FALSE(std::ranges::contains(n1.edges(), e3_4)); 160 | ASSERT_FALSE(std::ranges::contains(n5.edges(), e3_4)); 161 | 162 | ASSERT_TRUE(std::ranges::contains(n3.edges(), e3_4)); 163 | ASSERT_TRUE(std::ranges::contains(n4.edges(), e3_4)); 164 | } 165 | 166 | #undef GRAPH1 167 | -------------------------------------------------------------------------------- /test/graph/graph_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | // NOLINTNEXTLINE(google-build-using-namespace) 6 | using namespace triskel; 7 | 8 | // The graph from the wikipedia example 9 | // https://en.wikipedia.org/wiki/Depth-first_search#Output_of_a_depth-first_search 10 | #define GRAPH1 \ 11 | auto g = Graph{}; \ 12 | auto ge = g.editor(); \ 13 | ge.push(); \ 14 | \ 15 | auto n1 = ge.make_node(); \ 16 | auto n2 = ge.make_node(); \ 17 | auto n3 = ge.make_node(); \ 18 | auto n4 = ge.make_node(); \ 19 | auto n5 = ge.make_node(); \ 20 | auto n6 = ge.make_node(); \ 21 | auto n7 = ge.make_node(); \ 22 | auto n8 = ge.make_node(); \ 23 | \ 24 | auto e1_2 = ge.make_edge(n1, n2); \ 25 | auto e1_5 = ge.make_edge(n1, n5); \ 26 | auto e1_8 = ge.make_edge(n1, n8); \ 27 | \ 28 | auto e2_3 = ge.make_edge(n2, n3); \ 29 | \ 30 | auto e3_4 = ge.make_edge(n3, n4); \ 31 | \ 32 | auto e4_2 = ge.make_edge(n4, n2); \ 33 | \ 34 | auto e5_6 = ge.make_edge(n5, n6); \ 35 | \ 36 | auto e6_3 = ge.make_edge(n6, n3); \ 37 | auto e6_7 = ge.make_edge(n6, n7); \ 38 | auto e6_8 = ge.make_edge(n6, n8); \ 39 | ge.commit(); 40 | 41 | TEST(Graph, Smoke) { 42 | ASSERT_NO_THROW(GRAPH1); 43 | } 44 | 45 | #undef GRAPH1 46 | -------------------------------------------------------------------------------- /test/graph/subgraph_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | // NOLINTNEXTLINE(google-build-using-namespace) 9 | using namespace triskel; 10 | 11 | #define GRAPH1 \ 12 | auto gg = Graph{}; \ 13 | auto gge = gg.editor(); \ 14 | gge.push(); \ 15 | \ 16 | auto n1 = gge.make_node(); \ 17 | auto n2 = gge.make_node(); \ 18 | auto n3 = gge.make_node(); \ 19 | auto n4 = gge.make_node(); \ 20 | auto n5 = gge.make_node(); \ 21 | auto n6 = gge.make_node(); \ 22 | auto n7 = gge.make_node(); \ 23 | auto n8 = gge.make_node(); \ 24 | \ 25 | auto e1_2 = gge.make_edge(n1, n2); \ 26 | auto e1_5 = gge.make_edge(n1, n5); \ 27 | auto e1_8 = gge.make_edge(n1, n8); \ 28 | \ 29 | auto e2_3 = gge.make_edge(n2, n3); \ 30 | \ 31 | auto e3_4 = gge.make_edge(n3, n4); \ 32 | \ 33 | auto e4_2 = gge.make_edge(n4, n2); \ 34 | \ 35 | auto e5_6 = gge.make_edge(n5, n6); \ 36 | \ 37 | auto e6_3 = gge.make_edge(n6, n3); \ 38 | auto e6_7 = gge.make_edge(n6, n7); \ 39 | auto e6_8 = gge.make_edge(n6, n8); \ 40 | gge.commit(); \ 41 | \ 42 | auto g = SubGraph(gg); \ 43 | auto ge = g.editor(); 44 | 45 | TEST(SubGraph, Smoke) { 46 | GRAPH1; 47 | 48 | ge.select_node(n1); 49 | 50 | ASSERT_EQ(g.node_count(), 1); 51 | ASSERT_TRUE(std::ranges::contains(g.nodes(), n1)); 52 | } 53 | 54 | TEST(SubGraph, AddEdge) { 55 | GRAPH1; 56 | 57 | ge.select_node(n1); 58 | ge.select_node(n2); 59 | 60 | ASSERT_EQ(g.edge_count(), 1); 61 | ASSERT_TRUE(std::ranges::contains(g.edges(), e1_2)); 62 | } 63 | 64 | TEST(SubGraph, addNode) { 65 | GRAPH1 66 | 67 | ge.push(); 68 | 69 | size_t og_size = g.node_count(); 70 | 71 | auto new_node = ge.make_node(); 72 | 73 | ASSERT_EQ(g.node_count(), og_size + 1); 74 | 75 | ge.pop(); 76 | 77 | ASSERT_EQ(g.node_count(), og_size); 78 | 79 | for (const auto& n : g.nodes()) { 80 | ASSERT_NE(n, new_node); 81 | } 82 | } 83 | 84 | TEST(SubGraph, rmNode) { 85 | GRAPH1 86 | ge.select_node(n2); 87 | ge.select_node(n3); 88 | 89 | size_t og_size = g.node_count(); 90 | 91 | ge.push(); 92 | 93 | ge.remove_node(n3); 94 | 95 | ASSERT_EQ(g.node_count(), og_size - 1); 96 | ASSERT_FALSE(std::ranges::contains(g.edges(), e2_3)); 97 | 98 | for (const auto& n : g.nodes()) { 99 | ASSERT_NE(n, n3); 100 | } 101 | 102 | for (const auto& e : g.edges()) { 103 | ASSERT_NE(e.from(), n3); 104 | ASSERT_NE(e.to(), n3); 105 | } 106 | 107 | ge.pop(); 108 | 109 | ASSERT_EQ(g.node_count(), og_size); 110 | ASSERT_TRUE(std::ranges::contains(g.edges(), e2_3)); 111 | } 112 | 113 | TEST(SubGraph, addEdge) { 114 | GRAPH1 115 | ge.select_node(n1); 116 | ge.select_node(n5); 117 | 118 | ge.push(); 119 | 120 | size_t og_size = g.edge_count(); 121 | 122 | auto new_edge = ge.make_edge(n1, n5); 123 | 124 | ASSERT_TRUE(std::ranges::contains(n1.edges(), new_edge)); 125 | ASSERT_TRUE(std::ranges::contains(n5.edges(), new_edge)); 126 | 127 | ASSERT_EQ(g.edge_count(), og_size + 1); 128 | 129 | ge.pop(); 130 | 131 | ASSERT_EQ(g.edge_count(), og_size); 132 | 133 | for (const auto& e : g.edges()) { 134 | ASSERT_NE(e, new_edge); 135 | } 136 | } 137 | 138 | TEST(SubGraph, rmEdge) { 139 | GRAPH1 140 | 141 | ge.select_node(n3); 142 | ge.select_node(n4); 143 | 144 | ge.push(); 145 | 146 | size_t og_size = g.edge_count(); 147 | 148 | ge.remove_edge(e3_4); 149 | 150 | ASSERT_EQ(g.edge_count(), og_size - 1); 151 | 152 | ASSERT_FALSE(std::ranges::contains(n3.edges(), e3_4)); 153 | ASSERT_FALSE(std::ranges::contains(n4.edges(), e3_4)); 154 | ASSERT_FALSE(std::ranges::contains(g.edges(), e3_4)); 155 | 156 | ge.pop(); 157 | 158 | ASSERT_EQ(g.edge_count(), og_size); 159 | 160 | ASSERT_TRUE(std::ranges::contains(n3.edges(), e3_4)); 161 | ASSERT_TRUE(std::ranges::contains(n4.edges(), e3_4)); 162 | ASSERT_TRUE(std::ranges::contains(g.edges(), e3_4)); 163 | } 164 | 165 | TEST(SubGraph, editEdge) { 166 | GRAPH1 167 | 168 | ge.select_node(n1); 169 | ge.select_node(n3); 170 | ge.select_node(n4); 171 | ge.select_node(n5); 172 | 173 | ge.push(); 174 | 175 | ge.edit_edge(e3_4, n1, n5); 176 | 177 | ASSERT_EQ(e3_4.from(), n1); 178 | ASSERT_EQ(e3_4.to(), n5); 179 | 180 | ASSERT_TRUE(std::ranges::contains(n1.edges(), e3_4)); 181 | ASSERT_TRUE(std::ranges::contains(n5.edges(), e3_4)); 182 | 183 | ASSERT_FALSE(std::ranges::contains(n3.edges(), e3_4)); 184 | ASSERT_FALSE(std::ranges::contains(n4.edges(), e3_4)); 185 | 186 | ge.pop(); 187 | 188 | ASSERT_EQ(e3_4.from(), n3); 189 | ASSERT_EQ(e3_4.to(), n4); 190 | 191 | ASSERT_FALSE(std::ranges::contains(n1.edges(), e3_4)); 192 | ASSERT_FALSE(std::ranges::contains(n5.edges(), e3_4)); 193 | 194 | ASSERT_TRUE(std::ranges::contains(n3.edges(), e3_4)); 195 | ASSERT_TRUE(std::ranges::contains(n4.edges(), e3_4)); 196 | } 197 | 198 | #undef GRAPH1 -------------------------------------------------------------------------------- /test/triskel_test.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include 5 | 6 | // NOLINTNEXTLINE(google-build-using-namespace) 7 | using namespace triskel; 8 | 9 | TEST(Triskel, Smoke1) { 10 | auto builder = make_layout_builder(); 11 | 12 | const auto a = builder->make_node(100, 100); 13 | const auto b = builder->make_node(100, 100); 14 | const auto c = builder->make_node(100, 100); 15 | const auto d = builder->make_node(100, 100); 16 | const auto e = builder->make_node(100, 100); 17 | const auto f = builder->make_node(100, 100); 18 | const auto g = builder->make_node(100, 100); 19 | 20 | builder->make_edge(a, b); 21 | builder->make_edge(a, e); 22 | builder->make_edge(b, c); 23 | builder->make_edge(b, d); 24 | builder->make_edge(e, f); 25 | builder->make_edge(f, e); 26 | builder->make_edge(c, g); 27 | builder->make_edge(d, g); 28 | builder->make_edge(e, g); 29 | builder->make_edge(g, a); 30 | 31 | ASSERT_NO_THROW(const auto layout = builder->build()); 32 | } 33 | --------------------------------------------------------------------------------