diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..5c52e65 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,11 @@ +kind: pipeline +type: docker +name: llvmta-build +steps: +- name: Build + image: ls12-nvm-oma1:5000/cppdev + commands: + - mkdir build + - cd build + - cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=1 -DLT_LLVM_INSTALL_DIR=/usr .. + - ninja -j 2 \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index aa8dcd9..15b2235 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,21 +4,21 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ - { - "type": "lldb", - "request": "launch", - "name": "LLDB Unrolling", - "program": "/usr/bin/opt", - "args": [ - "-load-pass-plugin", - "${workspaceFolder}/build/libCacheAnalysisPass.so", - "-passes=lru-misses", - "${workspaceFolder}/test/cnt.ll", - "-o", - "/dev/null" - ], - "cwd": "${workspaceFolder}" - }, + // { + // "type": "lldb", + // "request": "launch", + // "name": "LLDB Unrolling", + // "program": "/usr/bin/opt", + // "args": [ + // "-load-pass-plugin", + // "${workspaceFolder}/build/libCacheAnalysisPass.so", + // "-passes=lru-misses", + // "${workspaceFolder}/test/cnt.ll", + // "-o", + // "/dev/null" + // ], + // "cwd": "${workspaceFolder}" + // }, { "type": "lldb", "request": "launch", diff --git a/CMakeLists.txt b/CMakeLists.txt index bbc8701..9e6c2cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,19 @@ cmake_minimum_required(VERSION 3.13.4) project(RTSA-lab01-CacheAnalysis) +#=============================================================================== +# 0. Add GoogleTest +#=============================================================================== +include(FetchContent) +FetchContent_Declare( + googletest + URL + URL https://github.com/google/googletest/archive/refs/heads/main.zip +) +# For Windows: Prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) + #=============================================================================== # 1. VERIFY LLVM INSTALLATION DIR # This is just a bit of a sanity checking. @@ -108,4 +121,5 @@ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib") # available for the sub-projects. #=============================================================================== add_subdirectory(CacheAnalysisPass) # Use your pass name here. +add_subdirectory(UnitTest) #add_subdirectory(lib) \ No newline at end of file diff --git a/CacheAnalysisPass/CacheAnalysisPass.cpp b/CacheAnalysisPass/CacheAnalysisPass.cpp index 9313f12..86184b4 100644 --- a/CacheAnalysisPass/CacheAnalysisPass.cpp +++ b/CacheAnalysisPass/CacheAnalysisPass.cpp @@ -215,15 +215,15 @@ struct CacheAnalysisPass : PassInfoMixin { } PreservedAnalyses run(Module &M, ModuleAnalysisManager &MAM) { - FunctionAnalysisManager &FAM = - MAM.getResult(M).getManager(); + // FunctionAnalysisManager &FAM = + // MAM.getResult(M).getManager(); addressCollector(M); - Function *EntryFunction; + // Function *EntryFunction; for (Function &F : M.getFunctionList()) { // Start iterating through CFG from entry point if (F.getName().equals(EntryPoint)) { - EntryFunction = &F; + // EntryFunction = &F; initEdges(F); } if (PrintAddresses) diff --git a/README.md b/README.md index 15163a9..0d1f103 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,18 @@ # RTSA-lab01-CacheAnalysis +[![Build Status](http://129.217.34.203/api/badges/nils.hoelscher/RTSA-lab01-CacheAnalysis/status.svg)](http://129.217.34.203/nils.hoelscher/RTSA-lab01-CacheAnalysis) + In this lab session you will learn how to implement a LRU cache in abstract representation. -The Goal is to implement an LRU must Join in include/AbstractState.h. -For this we assume a "Set-Associative LRU Cache", with 16 sets an associativity of 4 and a cacheline size of two times instruction size. + +## Exercise +Implement a LRU Must-Join as described in the lecture, WCET - Cache Analysis. +A 16 SetCache with an associativity of 4 Assumed, and cache lines can hold two memory words -> CacheSize 1024kB. +In order to do so, complete the function "mustJoin" inside [include/AbstractState.h:138](https://git.cs.tu-dortmund.de/nils.hoelscher/RTSA-lab01-CacheAnalysis/src/branch/master/include/AbstractState.h#L138). +The goal is to join the inbound state into the "this" state. + The Project can be build, tested and Evaluated with the "helper" script. +The Setup and some nice to knows are described in the following sections. ## Disclaimer @@ -82,6 +90,7 @@ This is my personally preferred IDE setup for C/C++ and by no means needed to ac clangd, Clang-Format, CodeLLDB, +C++ TestMate, Docker and Remote Development @@ -145,6 +154,12 @@ You can also set the following variables in the CacheAnalysisPass/CacheAnalysisP Helpful to understand what the program does but not very so much for the actual exercise. +## UnitTest + +The best way to see what your function does is to use the [UnitTest.cpp](https://git.cs.tu-dortmund.de/nils.hoelscher/RTSA-lab01-CacheAnalysis/src/branch/master/UnitTest/UnitTest.cpp). +With "C++ TestMate" install you can simply run or debug the test from the side panel in VS Code (Flask Icon). +The "C++ TestMate" is not installed in the VM as I just added this feature now. +Please feel free to add more test cases to your liking in [UnitTest.cpp](https://git.cs.tu-dortmund.de/nils.hoelscher/RTSA-lab01-CacheAnalysis/src/branch/master/UnitTest/UnitTest.cpp). ## Use the Helper script Again if you work on a Pool PC use poolhelper.sh insted of the helper.sh script. diff --git a/UnitTest/CMakeLists.txt b/UnitTest/CMakeLists.txt new file mode 100644 index 0000000..ea179d5 --- /dev/null +++ b/UnitTest/CMakeLists.txt @@ -0,0 +1,52 @@ +cmake_minimum_required(VERSION 3.13.4) +project(UnitTest) + +#=============================================================================== +# 1. LOAD LLVM CONFIGURATION +#=============================================================================== +# Set this to a valid LLVM installation dir +set(LT_LLVM_INSTALL_DIR "" CACHE PATH "LLVM installation directory") + +# Add the location of LLVMConfig.cmake to CMake search paths (so that +# find_package can locate it) +list(APPEND CMAKE_PREFIX_PATH "${LT_LLVM_INSTALL_DIR}/lib/cmake/llvm/") + +# FIXME: This is a warkaround for #25. Remove once resolved and use +# find_package(LLVM 11.0.0 REQUIRED CONFIG) instead. +find_package(LLVM REQUIRED CONFIG) + +# UnitTest includes headers from LLVM - update the include paths accordingly +include_directories(SYSTEM ${LLVM_INCLUDE_DIRS}, "${CMAKE_CURRENT_SOURCE_DIR}/../include") + +#=============================================================================== +# 2. LLVM-TUTOR BUILD CONFIGURATION +#=============================================================================== +# Use the same C++ standard as LLVM does +set(CMAKE_CXX_STANDARD 14 CACHE STRING "") + +# LLVM is normally built without RTTI. Be consistent with that. +if(NOT LLVM_ENABLE_RTTI) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti") +endif() + +#=============================================================================== +# 3. ADD THE TARGET +#=============================================================================== +enable_testing() + +add_executable(UnitTest + # List your source files here. + UnitTest.cpp +) + + +# Allow undefined symbols in shared objects on Darwin (this is the default +# behaviour on Linux) +target_link_libraries(UnitTest + "$<$:-undefined dynamic_lookup>") + target_link_libraries( + UnitTest + gtest_main +) +include(GoogleTest) +gtest_discover_tests(UnitTest) \ No newline at end of file diff --git a/UnitTest/UnitTest.cpp b/UnitTest/UnitTest.cpp new file mode 100644 index 0000000..4c825f3 --- /dev/null +++ b/UnitTest/UnitTest.cpp @@ -0,0 +1,174 @@ +#include +#include + +#include "../include/AbstractCache.h" + +void fillSet(AbstractState &In, unsigned int Set) { + for (int I = 0; I < 4; I++) { + In.Sets[Set].Associativity[I].Blocks.push_front(I); + } +} + +// Joined Set should remain the same +TEST(MustJoinTests, MustJoinSameStates) { + AbstractCache AC; + AC.addEmptyNode(0); + fillSet(AC.Nodes[0], 0); + AC.addEmptyNode(1); + fillSet(AC.Nodes[1], 0); + + AC.Nodes[0].mustJoin(AC.Nodes[1]); + for (auto B : AC.Nodes[0].Sets[0].Associativity) { + uint Age = B.first; + EXPECT_EQ(AC.Nodes[0].Sets[0].Associativity[Age].Blocks.front(), + AC.Nodes[1].Sets[0].Associativity[Age].Blocks.front()); + } +} + +void counterFillSet(AbstractState &In, unsigned int Set) { + for (int I = 0; I < 4; I++) { + In.Sets[Set].Associativity[I].Blocks.push_front((I + 1) % 4); + } +} + +// Resulting state should be +// Set[0]: +// Age[0]: +// Age[1]: 1 +// Age[2]: 2 +// Age[3]: 0 3 +TEST(MustJoinTests, MustJoinDifferentStates) { + AbstractCache AC; + AC.addEmptyNode(0); + counterFillSet(AC.Nodes[0], 0); + AC.addEmptyNode(1); + fillSet(AC.Nodes[1], 0); + std::cout << "==Before:==\n"; + AC.Nodes[0].dumpSet(0); + AC.Nodes[1].dumpSet(0); + + AC.Nodes[0].mustJoin(AC.Nodes[1]); + std::cout << "\n==After:==\n"; + AC.Nodes[0].dumpSet(0); + for (auto B : AC.Nodes[0].Sets[0].Associativity) { + uint Age = B.first; + switch (Age) { + case 0: + EXPECT_TRUE(AC.Nodes[0].Sets[0].Associativity[Age].Blocks.empty()); + break; + case 1: + EXPECT_EQ(AC.Nodes[0].Sets[0].Associativity[Age].Blocks.front(), 1); + break; + case 2: + EXPECT_EQ(AC.Nodes[0].Sets[0].Associativity[Age].Blocks.front(), 2); + break; + case 3: + // this test is not very exact the Set 0 with age 3 should contain (0,3) + // in arbitrary order + EXPECT_EQ(AC.Nodes[0].Sets[0].Associativity[Age].Blocks.size(), 2); + break; + default: + // should never be reached! + EXPECT_TRUE(false); + } + } +} + +void fillSetHighNumbers(AbstractState &In, unsigned int Set) { + for (int I = 0; I < 4; I++) { + In.Sets[Set].Associativity[I].Blocks.push_front(10 + I); + } +} + +// resulting state should be empty +TEST(MustJoinTests, MustJoinDisjunctStates) { + AbstractCache AC; + AC.addEmptyNode(0); + fillSetHighNumbers(AC.Nodes[0], 0); + AC.addEmptyNode(1); + fillSet(AC.Nodes[1], 0); + std::cout << "==Before:==\n"; + AC.Nodes[0].dumpSet(0); + AC.Nodes[1].dumpSet(0); + + AC.Nodes[0].mustJoin(AC.Nodes[1]); + std::cout << "\n==After:==\n"; + AC.Nodes[0].dumpSet(0); + for (auto B : AC.Nodes[0].Sets[0].Associativity) { + uint Age = B.first; + EXPECT_TRUE(AC.Nodes[0].Sets[0].Associativity[Age].Blocks.empty()); + } +} + +TEST(MustJoinTests, MustJoinDisjunctStatesAllSets) { + AbstractCache AC; + AC.addEmptyNode(0); + AC.addEmptyNode(1); + for (int I = 0; I < 16; I++) { + fillSetHighNumbers(AC.Nodes[0], I); + fillSet(AC.Nodes[1], I); + } + + std::cout << "==Before:==\n"; + AC.Nodes[0].dump(); + AC.Nodes[1].dump(); + + AC.Nodes[0].mustJoin(AC.Nodes[1]); + std::cout << "\n==After:==\n"; + AC.Nodes[0].dump(); + for (auto Set : AC.Nodes[0].Sets) { + for (auto B : Set.second.Associativity) { + EXPECT_TRUE(B.second.Blocks.empty()); + } + } +} + +TEST(MustJoinTests, MustJoinDifferentStatesAllSets) { + AbstractCache AC; + AC.addEmptyNode(0); + AC.addEmptyNode(1); + for (int I = 0; I < 16; I++) { + if (I % 2) + counterFillSet(AC.Nodes[0], I); + fillSet(AC.Nodes[1], I); + } + std::cout << "==Before:==\n"; + AC.Nodes[0].dump(); + AC.Nodes[1].dump(); + + AC.Nodes[0].mustJoin(AC.Nodes[1]); + + AC.Nodes[1].mustJoin(AC.Nodes[0]); + std::cout << "\n==After:==\n"; + AC.Nodes[1].dump(); + for (auto Set : AC.Nodes[1].Sets) { + for (auto B : Set.second.Associativity) { + uint SetNr = Set.first; + uint Age = B.first; + if (SetNr % 2) { + switch (Age) { + case 0: + EXPECT_TRUE(B.second.Blocks.empty()); + break; + case 1: + EXPECT_EQ(B.second.Blocks.front(), 1); + break; + case 2: + EXPECT_EQ(B.second.Blocks.front(), 2); + break; + case 3: + // this test is not very exact the Set 0 with age 3 should contain + // (0,3) in arbitrary order + EXPECT_EQ(B.second.Blocks.size(), 2); + break; + default: + // should never be reached! + EXPECT_TRUE(false); + } + } else { + EXPECT_TRUE(B.second.Blocks.empty()); + } + } + } + EXPECT_TRUE(AC.Nodes[1] == AC.Nodes[0]); +} \ No newline at end of file diff --git a/helper.sh b/helper.sh index 6afb7bf..fdbac44 100755 --- a/helper.sh +++ b/helper.sh @@ -11,7 +11,7 @@ config () { mkdir build cd build echo "==== Configuring cmake ====" - cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=1 -DLT_LLVM_INSTALL_DIR=$LLVM_DIR ../CacheAnalysisPass/ + cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=1 -DLT_LLVM_INSTALL_DIR=/usr .. cd .. cp build/compile_commands.json compile_commands.json echo "==== Done! ====" @@ -27,7 +27,7 @@ compile () { run () { echo "==== Running $1 ====" - opt -load-pass-plugin build/libCacheAnalysisPass.so \ + opt -load-pass-plugin build/lib/libCacheAnalysisPass.so \ -passes='lru-misses(function(loop-unroll-and-jam))' \ test/$1.ll -o /dev/null #llvm-dis < out.bc > out.ll diff --git a/include/AbstractCache.h b/include/AbstractCache.h index b4cf544..7829e23 100644 --- a/include/AbstractCache.h +++ b/include/AbstractCache.h @@ -1,4 +1,3 @@ - #ifndef ABSTRACHTCACHESTATE_H #define ABSTRACHTCACHESTATE_H @@ -11,9 +10,6 @@ #include #include -#include -#include - #include "AbstractState.h" #include "Address.h" #include "ConcreteState.h" @@ -92,7 +88,7 @@ public: // everything is public, because IDGAF } Visited[Visitor.first] = true; } - return false; + return Ret; } /** @@ -104,8 +100,6 @@ public: // everything is public, because IDGAF void removeNestedLoops( std::list LoopBodyIn, std::map OrigNodeToUnrolledNode) { - unsigned int LoopHead = LoopBodyIn.front(); - unsigned int LoopTail = LoopBodyIn.back(); unsigned int NestLoopTail; for (unsigned int NodeNr : LoopBodyIn) { bool IsLoopHead = false; @@ -294,34 +288,34 @@ public: // everything is public, because IDGAF removeNestedLoops(LoopBody, OrigNodeToUnrolledNode); if (Verbose && FoundLoopBody) { - llvm::outs() << "Found LoopHead @: " << NodeNr << "\n"; - llvm::outs() << "With LoopTail @: " << LoopTail << "\n"; - llvm::outs() << "With Body: {\n"; + std::cout << "Found LoopHead @: " << NodeNr << "\n"; + std::cout << "With LoopTail @: " << LoopTail << "\n"; + std::cout << "With Body: {\n"; int I = 1; for (auto Node : LoopBody) { - llvm::outs() << Node << ", "; + std::cout << Node << ", "; if (!(I++ % 5)) { - llvm::outs() << "\n"; + std::cout << "\n"; } } - llvm::outs() << "}\n"; - llvm::outs() << "Unrolled States: {\n"; + std::cout << "}\n"; + std::cout << "Unrolled States: {\n"; I = 1; for (auto Node : LoopBody) { - llvm::outs() << OrigNodeToUnrolledNode[Node] << ", "; + std::cout << OrigNodeToUnrolledNode[Node] << ", "; if (!(I++ % 5)) { - llvm::outs() << "\n"; + std::cout << "\n"; } } - llvm::outs() << "}\n"; + std::cout << "}\n"; I = 1; - llvm::outs() << "OrigNodeToUnrolledNode: {\n"; + std::cout << "OrigNodeToUnrolledNode: {\n"; for (auto Nr : OrigNodeToUnrolledNode) { - llvm::outs() << Nr.first << "->" << Nr.second << ", "; + std::cout << Nr.first << "->" << Nr.second << ", "; if (!(I++ % 3)) - llvm::outs() << "\n"; + std::cout << "\n"; } - llvm::outs() << "}\n"; + std::cout << "}\n"; } } } @@ -402,19 +396,19 @@ public: // everything is public, because IDGAF * */ void dumpEdges() { - llvm::outs() << "Dumping Edges:\n"; + std::cout << "Dumping Edges:\n"; for (auto const &E : Edges) { - llvm::outs() << E.first; + std::cout << E.first; bool FirstPrint = true; for (unsigned int To : E.second) { if (FirstPrint) { - llvm::outs() << " -> " << To; + std::cout << " -> " << To; FirstPrint = false; } else { - llvm::outs() << ", " << To; + std::cout << ", " << To; } } - llvm::outs() << "\n"; + std::cout << "\n"; } } @@ -455,5 +449,6 @@ public: // everything is public, because IDGAF Nodes[E.first].dump(); } } + }; // namespace #endif // ABSTRACHTCACHESTATE_H \ No newline at end of file diff --git a/include/AbstractState.h b/include/AbstractState.h index b5defef..de001b8 100644 --- a/include/AbstractState.h +++ b/include/AbstractState.h @@ -12,8 +12,6 @@ #include #include -#include - #include "Address.h" // Forward declarations @@ -103,8 +101,8 @@ public: // everything is public, because IDGAF void setUnrolled(unsigned int In) { Unrolled = In; } bool operator==(AbstractState In) { - for (int Index; Index < 16; Index++) { - for (int Age; Age < 4; Age++) { + for (int Index = 0; Index < 16; Index++) { + for (int Age = 0; Age < 4; Age++) { for (auto E1 : Sets[Index].Associativity[Age].Blocks) { // find E1 in In States Set and Age. if (std::find(In.Sets[Index].Associativity[Age].Blocks.begin(), @@ -228,46 +226,60 @@ public: // everything is public, because IDGAF } } if (Verbose) { - llvm::outs() << "Before:\n"; + std::cout << "Before:\n"; this->dump(); } // update this with PreAddr this->update(PreAddr); if (Verbose) { - llvm::outs() << "Update Tag: " << PreAddr.Tag << "\n"; - llvm::outs() << "Update Set: " << PreAddr.Index << "\n"; - llvm::outs() << "After:\n"; + std::cout << "Update Tag: " << PreAddr.Tag << "\n"; + std::cout << "Update Set: " << PreAddr.Index << "\n"; + std::cout << "After:\n"; this->dump(); } } - void dump() { - llvm::outs() << Addr << " {\n"; - llvm::outs() << "Unrolled: " << Unrolled << "\n"; - llvm::outs() << "Computed: " << Computed << "\n"; - llvm::outs() << "Predecessors: "; - for (auto PreNr : Predecessors) { - llvm::outs() << PreNr << " "; - } - llvm::outs() << "\n"; + void dumpSet(unsigned int Set) { + std::cout << Addr << " {\n"; - llvm::outs() << "Successors: "; - for (auto SuccNr : Successors) { - llvm::outs() << SuccNr << " "; + std::cout << "Set[" << Set << "]: \n"; + for (auto EntryPair : this->Sets[Set].Associativity) { + std::cout << " Age[" << EntryPair.first << "]: "; + for (auto Block : EntryPair.second.Blocks) { + std::cout << Block << " "; + } + std::cout << "\n"; + } + std::cout << "}\n"; + } + + void dump() { + std::cout << Addr << " {\n"; + std::cout << "Unrolled: " << Unrolled << "\n"; + std::cout << "Computed: " << Computed << "\n"; + std::cout << "Predecessors: "; + for (auto PreNr : Predecessors) { + std::cout << PreNr << " "; } - llvm::outs() << "\n"; + std::cout << "\n"; + + std::cout << "Successors: "; + for (auto SuccNr : Successors) { + std::cout << SuccNr << " "; + } + std::cout << "\n"; for (auto SetPair : Sets) { - llvm::outs() << "Set[" << SetPair.first << "]: \n"; + std::cout << "Set[" << SetPair.first << "]: \n"; for (auto EntryPair : SetPair.second.Associativity) { - llvm::outs() << " Age[" << EntryPair.first << "]: "; + std::cout << " Age[" << EntryPair.first << "]: "; for (auto Block : EntryPair.second.Blocks) { - llvm::outs() << Block << " "; + std::cout << Block << " "; } - llvm::outs() << "\n"; + std::cout << "\n"; } } - llvm::outs() << "}\n"; + std::cout << "}\n"; } }; // namespace #endif // STATE_H \ No newline at end of file