diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 288b09e..cceefe6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,7 +17,9 @@ jobs: uses: actions/checkout@v4 with: path: ASPIS - + submodules: recursive + fetch-depth: 0 + - name: Install system dependencies run: | sudo apt update @@ -46,7 +48,7 @@ jobs: ccache --zero-stats ccache --show-config ccache --show-stats - + mkdir build cmake -B build -DLLVM_DIR=${{ github.workspace }}/llvm-build/lib/cmake/llvm/ cmake --build build @@ -78,3 +80,11 @@ jobs: run: | pytest test.py --no-header -v + - name: Upload comparison counter artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: comparison-counter + path: ${{ github.workspace }}/ASPIS/testing/build/comparison_counter.csv + if-no-files-found: warn + diff --git a/aspis.sh b/aspis.sh index 994d887..a5e6d02 100755 --- a/aspis.sh +++ b/aspis.sh @@ -152,6 +152,8 @@ parse_commands() { --multiple-errbb Enables multiple error basic blocks in EDDI + --coarse-grained Enables coarse-grained duplication in EDDI + EOF exit 0 ;; @@ -242,6 +244,9 @@ EOF --multiple-errbb) eddi_options="$eddi_options -multiple-errbb=true"; ;; + --coarse-grained) + eddi_options="$eddi_options -coarse-grained=true"; + ;; -g) debug_enabled=true; clang_options="$clang_options $opt"; diff --git a/passes/ASPIS.h b/passes/ASPIS.h index 8f79c2d..5807769 100644 --- a/passes/ASPIS.h +++ b/passes/ASPIS.h @@ -8,6 +8,7 @@ #include "Utils/Utils.h" #include #include +#include using namespace llvm; @@ -41,6 +42,7 @@ class EDDI : public PassInfoMixin { std::set toHardenVariables; std::set DuplicatedCalls; std::unordered_multimap DuplicatedInstructionMap; + std::unordered_set ClonedInstructions; std::string entryPoint; @@ -48,6 +50,8 @@ class EDDI : public PassInfoMixin { bool duplicateAll; bool MultipleErrBBEnabled; + bool CoarseGrainedDuplicationEnabled; + int comparisonCounter = 0; void preprocess(Module &Md); void fixDuplicatedConstructors(Module &Md); @@ -72,8 +76,9 @@ class EDDI : public PassInfoMixin { Value *getDuplicateValue(Value *V, Instruction *I); void fixGlobalCtors(Module &M); + void repairBasicBlock(BasicBlock &BB); public: - explicit EDDI(bool duplicateAll, bool MultipleErrBBEnabled = false, std::string entryPoint = "main") : duplicateAll(duplicateAll), MultipleErrBBEnabled(MultipleErrBBEnabled), entryPoint(entryPoint) {} + explicit EDDI(bool duplicateAll, bool MultipleErrBBEnabled = false, bool CoarseGrainedDuplicationEnabled = true, std::string entryPoint = "main") : duplicateAll(duplicateAll), MultipleErrBBEnabled(MultipleErrBBEnabled), CoarseGrainedDuplicationEnabled(CoarseGrainedDuplicationEnabled), entryPoint(entryPoint) {} PreservedAnalyses run(Module &M, ModuleAnalysisManager &); diff --git a/passes/EDDI.cpp b/passes/EDDI.cpp index c26c8dd..b0e5394 100755 --- a/passes/EDDI.cpp +++ b/passes/EDDI.cpp @@ -586,6 +586,7 @@ EDDI::cloneInstr(Instruction &I) { } // else place it right after the instruction we are working on else { IClone->insertAfter(&I); + ClonedInstructions.insert(IClone); } DuplicatedInstructionMap.insert( std::pair(&I, IClone)); @@ -761,8 +762,10 @@ Value *EDDI::comparePtrs(Value &V1, Value &V2, IRBuilder<> &B) { Instruction *L1 = B.CreateLoad(F1->getType(), F1); Instruction *L2 = B.CreateLoad(F2->getType(), F2); if (L1->getType()->isFloatingPointTy()) { + comparisonCounter++; return B.CreateCmp(CmpInst::FCMP_UEQ, L1, L2); } else { + comparisonCounter++; return B.CreateCmp(CmpInst::ICMP_EQ, L1, L2); } } @@ -801,6 +804,7 @@ void EDDI::addConsistencyChecks( if (Original->getType()->isIntOrIntVectorTy() || Original->getType()->isPtrOrPtrVectorTy()) { // DuplicatedInstructionMap.insert(std::pair(&I, &I)); CmpInstructions.push_back(B.CreateCmp(CmpInst::ICMP_EQ, Original, Copy)); + comparisonCounter++; } } } @@ -854,9 +858,11 @@ void EDDI::addConsistencyChecks( if (OriginalElem->getType()->isFloatingPointTy()) { CmpInstructions.push_back( B.CreateCmp(CmpInst::FCMP_UEQ, OriginalElem, CopyElem)); + comparisonCounter++; } else if (OriginalElem->getType()->isIntOrIntVectorTy() || OriginalElem->getType()->isPtrOrPtrVectorTy()) { CmpInstructions.push_back( B.CreateCmp(CmpInst::ICMP_EQ, OriginalElem, CopyElem)); + comparisonCounter++; } else { errs() << "Warning: Didn't create a comparison for "; OriginalElem->getType()->print(errs()); @@ -871,9 +877,11 @@ void EDDI::addConsistencyChecks( if (Original->getType()->isFloatingPointTy()) { CmpInstructions.push_back( B.CreateCmp(CmpInst::FCMP_UEQ, Original, Copy)); + comparisonCounter++; } else if (Original->getType()->isIntOrIntVectorTy() || Original->getType()->isPtrOrPtrVectorTy()) { CmpInstructions.push_back( B.CreateCmp(CmpInst::ICMP_EQ, Original, Copy)); + comparisonCounter++; } else { errs() << "Warning: Didn't create a comparison for " << Original->getType() << " type\n"; } @@ -1584,6 +1592,7 @@ PreservedAnalyses EDDI::run(Module &Md, ModuleAnalysisManager &AM) { // list of duplicated instructions to remove since they are equal to the original std::set GrayAreaCallsToFix; + ClonedInstructions.clear(); int iFn = 1; LLVM_DEBUG(dbgs() << "Iterating over the functions...\n"); @@ -1651,6 +1660,15 @@ PreservedAnalyses EDDI::run(Module &Md, ModuleAnalysisManager &AM) { } } } + + if(CoarseGrainedDuplicationEnabled) { + LLVM_DEBUG(dbgs() << "Applying coarse grained duplication\n"); + // Apply coarse grained duplication + for (BasicBlock &BB : *Fn) { + repairBasicBlock(BB); + } + } + LLVM_DEBUG(dbgs() << " [done]\n"); // insert the code for calling the error basic block in case of a mismatch @@ -1849,6 +1867,8 @@ PreservedAnalyses EDDI::run(Module &Md, ModuleAnalysisManager &AM) { LLVM_DEBUG(dbgs() << "Persisting Compiled Functions...\n"); persistCompiledFunctions(CompiledFuncs, "compiled_eddi_functions.csv"); + std::cout << "Comparison Counter: " << comparisonCounter << "\n"; + return PreservedAnalyses::none(); } @@ -2074,6 +2094,68 @@ void EDDI::fixGlobalCtors(Module &M) { NewGlobalCtors->setSection(Section); } +/** + * REPAIR groups all original instructions first, followed by all their + * duplicates, producing the coarse-grain order: + * A, B, C, A_dup, B_dup, C_dup + * + * This layout increases the temporal and spatial distance between a MI/SI + * pair. As a result, a single transient fault (SEU) is less likely to + * corrupt both the original and its duplicate before a consistency check + * can detect the mismatch (improving error-detection coverage for + * spatially-correlated faults) + * + * The function is called after the EDDI duplication phase has already + * cloned every eligible instruction and wired up operands. It only moves + * instructions; + * + * @param BB The basic block whose instructions are to be + * reordered + */ +void EDDI::repairBasicBlock(BasicBlock &BB) { + + // Collect all duplicated instructions in this BB, + // preserving their relative order so that data-flow dependencies + // among duplicates remain satisfied after the move + std::vector DupsInOrder; + + Instruction *InsertionPoint = BB.getFirstNonPHI(); + for (Instruction &I : BB) { + // PHINodes must stay at the top of the block (LLVM invariant). + // AllocaInsts are kept in the entry block's alloca region. + // Terminators (br, ret, switch, …) must remain last. + // None of these should be relocated + + if (ClonedInstructions.find(&I) != ClonedInstructions.end()) { + // If this instruction belongs to the cloned set, it is a duplicate + // that needs to be sunk to the bottom of the block + DupsInOrder.push_back(&I); + } else if (I.isTerminator() || isa(I)) { + // If there are no duplicates in this block, nothing to reorder + if (DupsInOrder.empty()) { + continue; + } + + // Move every duplicate just before the terminator, in their + // original relative order + for (Instruction *Dup : DupsInOrder) { + if(isa(Dup)){ + Dup->moveBefore(InsertionPoint); + } + else{ + Dup->moveBefore(&I); + } + } + DupsInOrder.clear(); + InsertionPoint = I.getNextNode(); + if(InsertionPoint == nullptr) { + return; + } + } + } +} + + //----------------------------------------------------------------------------- // New PM Registration //----------------------------------------------------------------------------- @@ -2082,6 +2164,11 @@ static llvm::cl::opt MultipleErrBB( llvm::cl::desc("Enable multiple error basic blocks in EDDI"), llvm::cl::init(false)); +static llvm::cl::opt CoarseGrained( + "coarse-grained", + llvm::cl::desc("Enable coarse-grained duplication in EDDI"), + llvm::cl::init(false)); + llvm::PassPluginLibraryInfo getEDDIPluginInfo() { return {LLVM_PLUGIN_API_VERSION, "eddi-verify", LLVM_VERSION_STRING, [](PassBuilder &PB) { @@ -2099,9 +2186,9 @@ llvm::PassPluginLibraryInfo getEDDIPluginInfo() { ArrayRef) { if (Name == "eddi-verify") { #ifdef DUPLICATE_ALL - FPM.addPass(EDDI(true, MultipleErrBB)); + FPM.addPass(EDDI(true, MultipleErrBB, CoarseGrained)); #else - FPM.addPass(EDDI(false, MultipleErrBB)); + FPM.addPass(EDDI(false, MultipleErrBB, CoarseGrained)); #endif return true; } diff --git a/testing/test.py b/testing/test.py index 3410679..bcb5bfa 100644 --- a/testing/test.py +++ b/testing/test.py @@ -4,6 +4,8 @@ import pytest_timeout import tomllib +import os +import re # Default configurations ASPIS_SCRIPT = "../aspis.sh" # Path to the ASPIS compilation script @@ -12,10 +14,21 @@ DOCKER_SHARED_VOLUME = "/workspace/ASPIS/tmp" LOCAL_SHARED_VOLUME = "./tests/" DOCKER_COMPOSE_FILE = "../docker/docker-compose.yml" +COMPARISON_COUNTER_PATH = "./build/comparison_counter.csv" -data_techniques = ["--no-dup", "--eddi", "--eddi --multiple-errbb", "--reddi", "--reddi --multiple-errbb", "--seddi", "--fdsc"] +data_techniques = ["--no-dup", "--eddi", "--eddi --multiple-errbb", "--eddi --coarse-grained", "--eddi --multiple-errbb --coarse-grained", "--reddi", "--reddi --multiple-errbb", "--reddi --coarse-grained", "--reddi --multiple-errbb --coarse-grained", "--seddi", "--fdsc"] cfc_techniques = ["--no-cfc", "--cfcss", "--rasm", "--racfed", "--inter-rasm"] +def record_comparison_counter(test_name: str, output: str): + comparison_file = open(COMPARISON_COUNTER_PATH, "a+") + m = re.search(r"Comparison Counter:\s*(\d+)", output) + if m: + counter = m.group(1) + else: + counter = "NA" + comparison_file.write(f"{test_name},{counter}\n") + comparison_file.close() + # Load the test configuration def load_config(): assert os.path.exists("../aspis.sh") and "Cannot find aspis.sh, please run the tests from the ASPIS testing directory as `pytest test.py`" @@ -61,6 +74,15 @@ def execute_binary(local_build_dir, test_name): def pytest_generate_tests(metafunc): """Custom hook to parametrize tests based on the CLI --tests-file flag.""" if "test_data" in metafunc.fixturenames: + os.makedirs("./build", exist_ok=True) + + if os.path.exists(COMPARISON_COUNTER_PATH): + os.remove(COMPARISON_COUNTER_PATH) + + comparison_file = open(COMPARISON_COUNTER_PATH, "a+") + comparison_file.write("test_name,comparison_counter\n") + comparison_file.close() + tests_file_paths = metafunc.config.getoption("--tests-file") test_list = [] for file_path in tests_file_paths: @@ -121,7 +143,8 @@ def test_aspis(test_data, use_container, aspis_addopt, data_technique, cfc_techn test_name_complete = f"{test_name}_{data_technique}_{cfc_technique}".replace("--", "").replace(" ", "_").replace("=", "") # Compile the source file - compile_with_aspis(source_path, test_name_complete, aspis_options, llvm_bin, docker_build_dir) + compilation_result = compile_with_aspis(source_path, test_name_complete, aspis_options, llvm_bin, docker_build_dir) + record_comparison_counter(test_name_complete, compilation_result) # Execute the binary and check output result = execute_binary(local_build_dir, test_name_complete)