├── .github └── workflows │ └── build.yaml ├── .gitignore ├── BrokerFacade.h ├── Brokers ├── AsmFileBroker.cpp ├── AsmFileBroker.h ├── AsmUtils │ ├── CodeRegion.cpp │ ├── CodeRegion.h │ ├── CodeRegionGenerator.cpp │ └── CodeRegionGenerator.h ├── Broker.h ├── BrokerPlugin.cpp ├── BrokerPlugin.h ├── RawBytesBroker.cpp └── RawBytesBroker.h ├── CMakeLists.txt ├── COMMIT_ID ├── CustomHWUnits ├── AbstractBranchPredictorUnit.h ├── Cache.h ├── CustomSourceMgr.cpp ├── CustomSourceMgr.h ├── IndirectBPU.cpp ├── IndirectBPU.h ├── LocalBPU.cpp ├── LocalBPU.h ├── MCADLSUnit.cpp ├── MCADLSUnit.h ├── NaiveBranchPredictorUnit.cpp ├── NaiveBranchPredictorUnit.h ├── SkylakeBranchUnit.cpp └── SkylakeBranchUnit.h ├── CustomStages ├── MCADFetchDelayStage.cpp └── MCADFetchDelayStage.h ├── LICENSE ├── LLVM_COMMIT_ID.txt ├── MCAViews ├── InstructionView.cpp ├── InstructionView.h ├── SummaryView.cpp ├── SummaryView.h ├── TimelineView.cpp └── TimelineView.h ├── MCAWorker.cpp ├── MCAWorker.h ├── MetadataCategories.cpp ├── MetadataCategories.h ├── MetadataRegistry.h ├── PipelinePrinter.cpp ├── PipelinePrinter.h ├── README.md ├── RegionMarker.h ├── Statistics.h ├── ci └── run.sh ├── cmake ├── FindGperftools.cmake └── FindLibunwind.cmake ├── doc └── cache-simulation.md ├── docker ├── Dockerfile ├── compose.yml ├── setup-build.sh ├── setup-deps.sh ├── setup-llvm.sh ├── setup-ssh.sh └── up ├── eval ├── branch_prediction │ ├── Makefile │ ├── gen_bp.py │ └── plot.py └── cache │ ├── Makefile │ ├── gen_cache.py │ ├── plot.py │ └── results_10000.csv ├── lib └── gem5 │ ├── intmath.h │ └── sat_counter.h ├── llvm-mcad.cpp ├── patches ├── 0001-MCAD-Patch-0-Add-identifier-field-to-mca-Instruction.patch └── 0002-MCAD-Patch-2-WIP-Start-mapping-e500-itinerary-model-.patch ├── plugins ├── CMakeLists.txt ├── binja-broker │ ├── Broker.cpp │ ├── Broker.exports │ ├── CMakeLists.txt │ ├── README.md │ ├── binja-plugin │ │ ├── MCADBridge.py │ │ ├── __init__.py │ │ ├── binja_pb2.py │ │ ├── binja_pb2.pyi │ │ ├── binja_pb2_grpc.py │ │ ├── ghidra-script.py │ │ ├── grpcclient.py │ │ ├── infoctx.py │ │ └── test_script.py │ ├── binja.grpc.pb.cc │ ├── binja.grpc.pb.h │ ├── binja.pb.cc │ ├── binja.pb.h │ └── binja.proto ├── qemu-broker │ ├── BinaryRegions.cpp │ ├── BinaryRegions.h │ ├── Broker.cpp │ ├── Broker.exports │ ├── CMakeLists.txt │ ├── Qemu │ │ ├── CMakeLists.txt │ │ └── Plugin.cpp │ ├── README.md │ ├── Serialization │ │ ├── mcad.fbs │ │ └── mcad_generated.h │ ├── patches │ │ ├── qemu-patch.diff │ │ └── riscv-arm-x86-qemu.patch │ └── utils │ │ ├── CMakeLists.txt │ │ └── qemu-broker-client.cpp ├── tracer-broker │ ├── Broker.cpp │ ├── Broker.exports │ ├── CMakeLists.txt │ ├── LocalTraceGenerator.cpp │ ├── LocalTraceGenerator.h │ ├── README.md │ └── tools │ │ └── CMakeLists.txt └── vivisect-broker │ ├── Broker.cpp │ ├── Broker.exports │ ├── CMakeLists.txt │ ├── README.md │ ├── cm2350.patch │ ├── emulator.grpc.pb.cc │ ├── emulator.grpc.pb.h │ ├── emulator.pb.cc │ ├── emulator.pb.h │ ├── emulator.proto │ ├── generate_interface.sh │ └── grpc_client │ ├── __init__.py │ ├── emulator_pb2.py │ ├── emulator_pb2.pyi │ ├── emulator_pb2_grpc.py │ └── grpc_client.py └── test ├── binja-broker ├── binja_pb2.py ├── binja_pb2_grpc.py ├── empty_arg.py ├── empty_arg.s ├── invalid_arg.py ├── invalid_arg.s ├── sanity-x64.py ├── sanity-x64.s └── utils.py ├── lit.cfg.py └── my-lit.py /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build LLVM and LLVM-MCAD inside Docker 2 | on: 3 | push: 4 | schedule: 5 | # Run nightly at 08:00 UTC (aka 00:00 Pacific, aka 03:00 Eastern) 6 | - cron: '0 8 * * *' 7 | 8 | permissions: 9 | contents: read # Default everything to read-only 10 | 11 | env: 12 | WORKSPACE_PATH: ${{ github.workspace }} 13 | 14 | jobs: 15 | build: 16 | # We need to run self-hosted because the GitHub runners run out of disk space. 17 | runs-on: self-hosted 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | path: 'LLVM-MCA-Daemon' 22 | - name: setup and build inside Docker 23 | run: cd LLVM-MCA-Daemon/docker && ./up 24 | - name: clean up - delete docker image 25 | if: always() # this line is needed to make this action run even if the previous step fails 26 | run: docker image rm mcad_dev; docker system prune -f # remove image after we're done to conserve space 27 | 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .build/ 3 | .build*/ 4 | __pycache__/ 5 | *.pyc 6 | compile_commands.json 7 | .cache/ 8 | .vscode/ 9 | .venv/ 10 | -------------------------------------------------------------------------------- /BrokerFacade.h: -------------------------------------------------------------------------------- 1 | #ifndef LLVM_MCAD_BROKERFACADE_H 2 | #define LLVM_MCAD_BROKERFACADE_H 3 | #include 4 | 5 | namespace llvm { 6 | class Target; 7 | class MCAsmInfo; 8 | class MCContext; 9 | class MCSubtargetInfo; 10 | class MCInstrInfo; 11 | class SourceMgr; 12 | 13 | namespace mca { 14 | class HWEventListener; 15 | } 16 | 17 | namespace mcad { 18 | class Broker; 19 | class MCAWorker; 20 | 21 | // An interface that provides objects that might be needed 22 | // to build a Broker. It's also the interface to register a 23 | // Broker. 24 | // This class is trivially-copyable 25 | class BrokerFacade { 26 | friend class MCAWorker; 27 | MCAWorker &Worker; 28 | 29 | explicit BrokerFacade(MCAWorker &W) : Worker(W) {} 30 | 31 | public: 32 | void setBroker(std::unique_ptr &&B); 33 | 34 | void registerListener(mca::HWEventListener *EL); 35 | 36 | const Target &getTarget() const; 37 | 38 | MCContext &getCtx() const; 39 | 40 | const MCAsmInfo &getAsmInfo() const; 41 | 42 | const MCInstrInfo &getInstrInfo() const; 43 | 44 | const MCSubtargetInfo &getSTI() const; 45 | 46 | llvm::SourceMgr &getSourceMgr() const; 47 | }; 48 | } // end namespace mcad 49 | } // end namespace llvm 50 | #endif 51 | -------------------------------------------------------------------------------- /Brokers/AsmFileBroker.cpp: -------------------------------------------------------------------------------- 1 | #include "llvm/Support/CommandLine.h" 2 | #include "llvm/Support/MemoryBuffer.h" 3 | #include "llvm/Support/TargetSelect.h" 4 | #include "llvm/Support/WithColor.h" 5 | #include 6 | #include 7 | 8 | #include "AsmFileBroker.h" 9 | 10 | using namespace llvm; 11 | using namespace mcad; 12 | 13 | static cl::OptionCategory AsmFileBrokerCat("Assembly File Broker Options", 14 | "These options are belong to " 15 | "Assmebly File Broker"); 16 | 17 | static cl::opt 18 | InputFilename("input-asm-file", cl::desc("The input assembly file"), 19 | cl::init("-"), 20 | cl::cat(AsmFileBrokerCat)); 21 | 22 | AsmFileBroker::AsmFileBroker(const Target &T, MCContext &Ctx, 23 | const MCAsmInfo &MAI, const MCSubtargetInfo &STI, 24 | const MCInstrInfo &MII, llvm::SourceMgr &SM) 25 | : SrcMgr(SM), CRG(T, SrcMgr, Ctx, MAI, STI, MII), Regions(nullptr), 26 | CurInstIdx(0U), IsInvalid(false) { 27 | llvm::InitializeAllAsmParsers(); 28 | 29 | auto ErrOrBuffer = MemoryBuffer::getFileOrSTDIN(InputFilename); 30 | if (std::error_code EC = ErrOrBuffer.getError()) { 31 | WithColor::error() << InputFilename << ": " << EC.message() << '\n'; 32 | return; 33 | } 34 | 35 | SrcMgr.AddNewSourceBuffer(std::move(*ErrOrBuffer), SMLoc()); 36 | } 37 | 38 | void AsmFileBroker::Register(BrokerFacade BF) { 39 | BF.setBroker(std::make_unique( 40 | BF.getTarget(), BF.getCtx(), BF.getAsmInfo(), BF.getSTI(), 41 | BF.getInstrInfo(), BF.getSourceMgr())); 42 | } 43 | 44 | bool AsmFileBroker::parseIfNeeded() { 45 | if (!IsInvalid && !Regions) { 46 | auto RegionsOrErr = CRG.parseCodeRegions(); 47 | if (!RegionsOrErr) { 48 | handleAllErrors(RegionsOrErr.takeError(), 49 | [](const ErrorInfoBase &E) { 50 | E.log(WithColor::error()); 51 | errs() << "\n"; 52 | }); 53 | IsInvalid = true; 54 | } else { 55 | Regions = &*RegionsOrErr; 56 | IterRegion = Regions->begin(); 57 | IterRegionEnd = Regions->end(); 58 | } 59 | } 60 | return !IsInvalid; 61 | } 62 | 63 | const MCInst *AsmFileBroker::fetch() { 64 | if(!parseIfNeeded() || 65 | IterRegion == IterRegionEnd) return nullptr; 66 | 67 | ArrayRef Insts = (*IterRegion)->getInstructions(); 68 | while (CurInstIdx >= Insts.size()) { 69 | // Switch to next region if feasible 70 | if (++IterRegion == IterRegionEnd) return nullptr; 71 | Insts = (*IterRegion)->getInstructions(); 72 | CurInstIdx = 0U; 73 | } 74 | 75 | return &Insts[CurInstIdx++]; 76 | } 77 | 78 | int AsmFileBroker::fetch(MutableArrayRef MCIS, int Size, 79 | std::optional MDE) { 80 | assert(Size <= int(MCIS.size())); 81 | size_t MaxLen = Size < 0? MCIS.size() : Size; 82 | 83 | size_t i; 84 | for (i = 0U; i < MaxLen; ++i) { 85 | const MCInst *MCI = fetch(); 86 | if (!MCI) { 87 | // End of stream 88 | if (!i) 89 | return -1; 90 | else 91 | break; 92 | } 93 | MCIS[i] = MCI; 94 | } 95 | 96 | return int(i); 97 | } 98 | 99 | std::pair 100 | AsmFileBroker::fetchRegion(MutableArrayRef MCIS, int Size, 101 | std::optional MDE) { 102 | assert(Size <= int(MCIS.size())); 103 | size_t MaxLen = Size < 0? MCIS.size() : Size; 104 | StringRef LastRegionDescription; 105 | 106 | size_t i, NumInsts = 0U; 107 | bool EndOfRegion = false; 108 | for (i = 0U; i < MaxLen; ++i) { 109 | const MCInst *MCI = fetch(); 110 | if (!MCI) { 111 | // End of stream 112 | if (!i) 113 | return std::make_pair(-1, 114 | RegionDescriptor(true, LastRegionDescription)); 115 | else { 116 | EndOfRegion = true; 117 | break; 118 | } 119 | } 120 | MCIS[i] = MCI; 121 | if (!NumInsts) { 122 | assert(IterRegion != IterRegionEnd); 123 | NumInsts = (*IterRegion)->getInstructions().size(); 124 | LastRegionDescription = (*IterRegion)->getDescription(); 125 | } 126 | 127 | if (CurInstIdx >= NumInsts) { 128 | // Is going to switch to different region in the next 129 | // `fetch` invocation 130 | EndOfRegion = true; 131 | ++i; 132 | break; 133 | } 134 | } 135 | 136 | return std::make_pair(int(i), 137 | RegionDescriptor(EndOfRegion, LastRegionDescription)); 138 | } 139 | -------------------------------------------------------------------------------- /Brokers/AsmFileBroker.h: -------------------------------------------------------------------------------- 1 | #ifndef LLVM_MCAD_BROKERS_ASMFILEBROKER_H 2 | #define LLVM_MCAD_BROKERS_ASMFILEBROKER_H 3 | #include "llvm/Support/SourceMgr.h" 4 | #include "AsmUtils/CodeRegion.h" 5 | #include "AsmUtils/CodeRegionGenerator.h" 6 | #include "Broker.h" 7 | #include "MCAWorker.h" 8 | 9 | namespace llvm { 10 | namespace mcad { 11 | // A broker that simply reads from local assembly file. 12 | // Useful for testing. 13 | class AsmFileBroker : public Broker { 14 | llvm::SourceMgr &SrcMgr; 15 | mca::AsmCodeRegionGenerator CRG; 16 | 17 | const mca::CodeRegions *Regions; 18 | mca::CodeRegions::const_iterator IterRegion, IterRegionEnd; 19 | size_t CurInstIdx; 20 | 21 | bool IsInvalid; 22 | 23 | bool parseIfNeeded(); 24 | 25 | const MCInst *fetch(); 26 | 27 | public: 28 | AsmFileBroker(const Target &T, MCContext &C, const MCAsmInfo &A, 29 | const MCSubtargetInfo &S, const MCInstrInfo &I, 30 | llvm::SourceMgr &SM); 31 | 32 | static void Register(BrokerFacade BF); 33 | 34 | unsigned getFeatures() const override { return Broker::Feature_Region; } 35 | 36 | int fetch(MutableArrayRef MCIS, int Size = -1, 37 | std::optional MDE = std::nullopt) override; 38 | 39 | std::pair 40 | fetchRegion(MutableArrayRef MCIS, int Size = -1, 41 | std::optional MDE = std::nullopt) override; 42 | }; 43 | } // end namespace mcad 44 | } // end namespace llvm 45 | #endif 46 | -------------------------------------------------------------------------------- /Brokers/AsmUtils/CodeRegion.cpp: -------------------------------------------------------------------------------- 1 | //===-------------------------- CodeRegion.cpp -----------------*- C++ -* -===// 2 | // 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 | // See https://llvm.org/LICENSE.txt for license information. 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 | // 7 | //===----------------------------------------------------------------------===// 8 | /// \file 9 | /// 10 | /// This file implements methods from the CodeRegions interface. 11 | /// 12 | //===----------------------------------------------------------------------===// 13 | 14 | #include "CodeRegion.h" 15 | 16 | namespace llvm { 17 | namespace mca { 18 | 19 | CodeRegions::CodeRegions(llvm::SourceMgr &S) : SM(S), FoundErrors(false) { 20 | // Create a default region for the input code sequence. 21 | Regions.emplace_back(std::make_unique("", SMLoc())); 22 | } 23 | 24 | bool CodeRegion::isLocInRange(SMLoc Loc) const { 25 | if (RangeEnd.isValid() && Loc.getPointer() > RangeEnd.getPointer()) 26 | return false; 27 | if (RangeStart.isValid() && Loc.getPointer() < RangeStart.getPointer()) 28 | return false; 29 | return true; 30 | } 31 | 32 | void CodeRegions::beginRegion(StringRef Description, SMLoc Loc) { 33 | if (ActiveRegions.empty()) { 34 | // Remove the default region if there is at least one user defined region. 35 | // By construction, only the default region has an invalid start location. 36 | if (Regions.size() == 1 && !Regions[0]->startLoc().isValid() && 37 | !Regions[0]->endLoc().isValid()) { 38 | ActiveRegions[Description] = 0; 39 | Regions[0] = std::make_unique(Description, Loc); 40 | return; 41 | } 42 | } else { 43 | auto It = ActiveRegions.find(Description); 44 | if (It != ActiveRegions.end()) { 45 | const CodeRegion &R = *Regions[It->second]; 46 | if (Description.empty()) { 47 | SM.PrintMessage(Loc, SourceMgr::DK_Error, 48 | "found multiple overlapping anonymous regions"); 49 | SM.PrintMessage(R.startLoc(), SourceMgr::DK_Note, 50 | "Previous anonymous region was defined here"); 51 | FoundErrors = true; 52 | return; 53 | } 54 | 55 | SM.PrintMessage(Loc, SourceMgr::DK_Error, 56 | "overlapping regions cannot have the same name"); 57 | SM.PrintMessage(R.startLoc(), SourceMgr::DK_Note, 58 | "region " + Description + " was previously defined here"); 59 | FoundErrors = true; 60 | return; 61 | } 62 | } 63 | 64 | ActiveRegions[Description] = Regions.size(); 65 | Regions.emplace_back(std::make_unique(Description, Loc)); 66 | } 67 | 68 | void CodeRegions::endRegion(StringRef Description, SMLoc Loc) { 69 | if (Description.empty()) { 70 | // Special case where there is only one user defined region, 71 | // and this LLVM-MCA-END directive doesn't provide a region name. 72 | // In this case, we assume that the user simply wanted to just terminate 73 | // the only active region. 74 | if (ActiveRegions.size() == 1) { 75 | auto It = ActiveRegions.begin(); 76 | Regions[It->second]->setEndLocation(Loc); 77 | ActiveRegions.erase(It); 78 | return; 79 | } 80 | 81 | // Special case where the region end marker applies to the default region. 82 | if (ActiveRegions.empty() && Regions.size() == 1 && 83 | !Regions[0]->startLoc().isValid() && !Regions[0]->endLoc().isValid()) { 84 | Regions[0]->setEndLocation(Loc); 85 | return; 86 | } 87 | } 88 | 89 | auto It = ActiveRegions.find(Description); 90 | if (It != ActiveRegions.end()) { 91 | Regions[It->second]->setEndLocation(Loc); 92 | ActiveRegions.erase(It); 93 | return; 94 | } 95 | 96 | FoundErrors = true; 97 | SM.PrintMessage(Loc, SourceMgr::DK_Error, 98 | "found an invalid region end directive"); 99 | if (!Description.empty()) { 100 | SM.PrintMessage(Loc, SourceMgr::DK_Note, 101 | "unable to find an active region named " + Description); 102 | } else { 103 | SM.PrintMessage(Loc, SourceMgr::DK_Note, 104 | "unable to find an active anonymous region"); 105 | } 106 | } 107 | 108 | void CodeRegions::addInstruction(const MCInst &Instruction) { 109 | SMLoc Loc = Instruction.getLoc(); 110 | for (UniqueCodeRegion &Region : Regions) 111 | if (Region->isLocInRange(Loc)) 112 | Region->addInstruction(Instruction); 113 | } 114 | 115 | } // namespace mca 116 | } // namespace llvm 117 | -------------------------------------------------------------------------------- /Brokers/AsmUtils/CodeRegion.h: -------------------------------------------------------------------------------- 1 | //===-------------------------- CodeRegion.h -------------------*- C++ -* -===// 2 | // 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 | // See https://llvm.org/LICENSE.txt for license information. 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 | // 7 | //===----------------------------------------------------------------------===// 8 | /// \file 9 | /// 10 | /// This file implements class CodeRegion and CodeRegions. 11 | /// 12 | /// A CodeRegion describes a region of assembly code guarded by special LLVM-MCA 13 | /// comment directives. 14 | /// 15 | /// # LLVM-MCA-BEGIN foo 16 | /// ... ## asm 17 | /// # LLVM-MCA-END 18 | /// 19 | /// A comment starting with substring LLVM-MCA-BEGIN marks the beginning of a 20 | /// new region of code. 21 | /// A comment starting with substring LLVM-MCA-END marks the end of the 22 | /// last-seen region of code. 23 | /// 24 | /// Code regions are not allowed to overlap. Each region can have a optional 25 | /// description; internally, regions are described by a range of source 26 | /// locations (SMLoc objects). 27 | /// 28 | /// An instruction (a MCInst) is added to a region R only if its location is in 29 | /// range [R.RangeStart, R.RangeEnd]. 30 | // 31 | //===----------------------------------------------------------------------===// 32 | 33 | #ifndef LLVM_TOOLS_LLVM_MCA_CODEREGION_H 34 | #define LLVM_TOOLS_LLVM_MCA_CODEREGION_H 35 | 36 | #include "llvm/ADT/ArrayRef.h" 37 | #include "llvm/ADT/SmallVector.h" 38 | #include "llvm/ADT/StringMap.h" 39 | #include "llvm/ADT/StringRef.h" 40 | #include "llvm/MC/MCInst.h" 41 | #include "llvm/Support/Error.h" 42 | #include "llvm/Support/SMLoc.h" 43 | #include "llvm/Support/SourceMgr.h" 44 | #include 45 | 46 | namespace llvm { 47 | namespace mca { 48 | 49 | /// A region of assembly code. 50 | /// 51 | /// It identifies a sequence of machine instructions. 52 | class CodeRegion { 53 | // An optional descriptor for this region. 54 | llvm::StringRef Description; 55 | // Instructions that form this region. 56 | llvm::SmallVector Instructions; 57 | // Source location range. 58 | llvm::SMLoc RangeStart; 59 | llvm::SMLoc RangeEnd; 60 | 61 | CodeRegion(const CodeRegion &) = delete; 62 | CodeRegion &operator=(const CodeRegion &) = delete; 63 | 64 | public: 65 | CodeRegion(llvm::StringRef Desc, llvm::SMLoc Start) 66 | : Description(Desc), RangeStart(Start), RangeEnd() {} 67 | 68 | void addInstruction(const llvm::MCInst &Instruction) { 69 | Instructions.emplace_back(Instruction); 70 | } 71 | 72 | llvm::SMLoc startLoc() const { return RangeStart; } 73 | llvm::SMLoc endLoc() const { return RangeEnd; } 74 | 75 | void setEndLocation(llvm::SMLoc End) { RangeEnd = End; } 76 | bool empty() const { return Instructions.empty(); } 77 | bool isLocInRange(llvm::SMLoc Loc) const; 78 | 79 | llvm::ArrayRef getInstructions() const { return Instructions; } 80 | 81 | llvm::StringRef getDescription() const { return Description; } 82 | }; 83 | 84 | class CodeRegionParseError final : public Error {}; 85 | 86 | class CodeRegions { 87 | // A source manager. Used by the tool to generate meaningful warnings. 88 | llvm::SourceMgr &SM; 89 | 90 | using UniqueCodeRegion = std::unique_ptr; 91 | std::vector Regions; 92 | llvm::StringMap ActiveRegions; 93 | bool FoundErrors; 94 | 95 | CodeRegions(const CodeRegions &) = delete; 96 | CodeRegions &operator=(const CodeRegions &) = delete; 97 | 98 | public: 99 | CodeRegions(llvm::SourceMgr &S); 100 | 101 | typedef std::vector::iterator iterator; 102 | typedef std::vector::const_iterator const_iterator; 103 | 104 | iterator begin() { return Regions.begin(); } 105 | iterator end() { return Regions.end(); } 106 | const_iterator begin() const { return Regions.cbegin(); } 107 | const_iterator end() const { return Regions.cend(); } 108 | 109 | void beginRegion(llvm::StringRef Description, llvm::SMLoc Loc); 110 | void endRegion(llvm::StringRef Description, llvm::SMLoc Loc); 111 | void addInstruction(const llvm::MCInst &Instruction); 112 | llvm::SourceMgr &getSourceMgr() const { return SM; } 113 | 114 | llvm::ArrayRef getInstructionSequence(unsigned Idx) const { 115 | return Regions[Idx]->getInstructions(); 116 | } 117 | 118 | bool empty() const { 119 | return llvm::all_of(Regions, [](const UniqueCodeRegion &Region) { 120 | return Region->empty(); 121 | }); 122 | } 123 | 124 | bool isValid() const { return !FoundErrors; } 125 | }; 126 | 127 | } // namespace mca 128 | } // namespace llvm 129 | 130 | #endif 131 | -------------------------------------------------------------------------------- /Brokers/AsmUtils/CodeRegionGenerator.cpp: -------------------------------------------------------------------------------- 1 | //===----------------------- CodeRegionGenerator.cpp ------------*- C++ -*-===// 2 | // 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 | // See https://llvm.org/LICENSE.txt for license information. 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 | // 7 | //===----------------------------------------------------------------------===// 8 | /// \file 9 | /// 10 | /// This file defines classes responsible for generating llvm-mca 11 | /// CodeRegions from various types of input. llvm-mca only analyzes CodeRegions, 12 | /// so the classes here provide the input-to-CodeRegions translation. 13 | // 14 | //===----------------------------------------------------------------------===// 15 | 16 | #include "CodeRegionGenerator.h" 17 | #include "llvm/ADT/ArrayRef.h" 18 | #include "llvm/ADT/StringRef.h" 19 | #include "llvm/MC/MCParser/MCAsmLexer.h" 20 | #include "llvm/MC/MCParser/MCTargetAsmParser.h" 21 | #include "llvm/MC/MCStreamer.h" 22 | #include "llvm/MC/MCInstPrinter.h" 23 | #include "llvm/MC/MCTargetOptions.h" 24 | #include "llvm/Support/Error.h" 25 | #include "llvm/Support/SMLoc.h" 26 | #include 27 | 28 | namespace llvm { 29 | namespace mca { 30 | 31 | // This virtual dtor serves as the anchor for the CodeRegionGenerator class. 32 | CodeRegionGenerator::~CodeRegionGenerator() {} 33 | 34 | // A comment consumer that parses strings. The only valid tokens are strings. 35 | class MCACommentConsumer : public AsmCommentConsumer { 36 | public: 37 | CodeRegions &Regions; 38 | 39 | MCACommentConsumer(CodeRegions &R) : Regions(R) {} 40 | void HandleComment(SMLoc Loc, StringRef CommentText); 41 | }; 42 | 43 | // This class provides the callbacks that occur when parsing input assembly. 44 | class MCStreamerWrapper final : public MCStreamer { 45 | CodeRegions &Regions; 46 | 47 | public: 48 | MCStreamerWrapper(MCContext &Context, mca::CodeRegions &R) 49 | : MCStreamer(Context), Regions(R) {} 50 | 51 | // We only want to intercept the emission of new instructions. 52 | virtual void emitInstruction(const MCInst &Inst, 53 | const MCSubtargetInfo & /* unused */) override { 54 | Regions.addInstruction(Inst); 55 | } 56 | 57 | bool emitSymbolAttribute(MCSymbol *Symbol, MCSymbolAttr Attribute) override { 58 | return true; 59 | } 60 | 61 | void emitCommonSymbol(MCSymbol *Symbol, uint64_t Size, Align Alignment) 62 | override {} 63 | void emitZerofill(MCSection *Section, MCSymbol *Symbol = nullptr, 64 | uint64_t Size = 0, Align ByteAlignment = Align(1), 65 | SMLoc Loc = SMLoc()) override {} 66 | void BeginCOFFSymbolDef(const MCSymbol *Symbol) {} 67 | void EmitCOFFSymbolStorageClass(int StorageClass) {} 68 | void EmitCOFFSymbolType(int Type) {} 69 | void EndCOFFSymbolDef() {} 70 | 71 | ArrayRef GetInstructionSequence(unsigned Index) const { 72 | return Regions.getInstructionSequence(Index); 73 | } 74 | }; 75 | 76 | void MCACommentConsumer::HandleComment(SMLoc Loc, StringRef CommentText) { 77 | // Skip empty comments. 78 | StringRef Comment(CommentText); 79 | if (Comment.empty()) 80 | return; 81 | 82 | // Skip spaces and tabs. 83 | unsigned Position = Comment.find_first_not_of(" \t"); 84 | if (Position >= Comment.size()) 85 | // We reached the end of the comment. Bail out. 86 | return; 87 | 88 | Comment = Comment.drop_front(Position); 89 | if (Comment.consume_front("LLVM-MCA-END")) { 90 | // Skip spaces and tabs. 91 | Position = Comment.find_first_not_of(" \t"); 92 | if (Position < Comment.size()) 93 | Comment = Comment.drop_front(Position); 94 | Regions.endRegion(Comment, Loc); 95 | return; 96 | } 97 | 98 | // Try to parse the LLVM-MCA-BEGIN comment. 99 | if (!Comment.consume_front("LLVM-MCA-BEGIN")) 100 | return; 101 | 102 | // Skip spaces and tabs. 103 | Position = Comment.find_first_not_of(" \t"); 104 | if (Position < Comment.size()) 105 | Comment = Comment.drop_front(Position); 106 | // Use the rest of the string as a descriptor for this code snippet. 107 | Regions.beginRegion(Comment, Loc); 108 | } 109 | 110 | Expected AsmCodeRegionGenerator::parseCodeRegions() { 111 | 112 | std::string Error; 113 | raw_string_ostream ErrorStream(Error); 114 | formatted_raw_ostream InstPrinterOStream(ErrorStream); 115 | const std::unique_ptr InstPrinter( 116 | TheTarget.createMCInstPrinter( 117 | Ctx.getTargetTriple(), MAI.getAssemblerDialect(), 118 | MAI, MCII, *Ctx.getRegisterInfo())); 119 | 120 | MCTargetOptions Opts; 121 | Opts.PreserveAsmComments = false; 122 | // The following call will take care of calling Streamer.setTargetStreamer. 123 | MCStreamerWrapper Streamer(Ctx, Regions); 124 | TheTarget.createAsmTargetStreamer(Streamer, InstPrinterOStream, 125 | InstPrinter.get()); 126 | if (!Streamer.getTargetStreamer()) 127 | return make_error("cannot create target asm streamer", inconvertibleErrorCode()); 128 | 129 | const std::unique_ptr AsmParser( 130 | createMCAsmParser(Regions.getSourceMgr(), Ctx, Streamer, MAI)); 131 | if (!AsmParser) 132 | return make_error("cannot create asm parser", inconvertibleErrorCode()); 133 | MCACommentConsumer CC(Regions); 134 | AsmParser->getLexer().setCommentConsumer(&CC); 135 | // Enable support for MASM literal numbers (example: 05h, 101b). 136 | AsmParser->getLexer().setLexMasmIntegers(true); 137 | 138 | // Create a MCAsmParser and setup the lexer to recognize llvm-mca ASM 139 | // comments. 140 | const std::unique_ptr TargetAsmParser( 141 | TheTarget.createMCAsmParser(STI, *AsmParser, MCII, Opts)); 142 | if (!TargetAsmParser) 143 | return make_error("cannot create target asm parser", inconvertibleErrorCode()); 144 | AsmParser->setTargetParser(*TargetAsmParser); 145 | AsmParser->Run(false); 146 | 147 | // Set the assembler dialect from the input. llvm-mca will use this as the 148 | // default dialect when printing reports. 149 | AssemblerDialect = AsmParser->getAssemblerDialect(); 150 | 151 | return Regions; 152 | } 153 | 154 | } // namespace mca 155 | } // namespace llvm 156 | -------------------------------------------------------------------------------- /Brokers/AsmUtils/CodeRegionGenerator.h: -------------------------------------------------------------------------------- 1 | //===----------------------- CodeRegionGenerator.h --------------*- C++ -*-===// 2 | // 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 | // See https://llvm.org/LICENSE.txt for license information. 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 | // 7 | //===----------------------------------------------------------------------===// 8 | /// \file 9 | /// 10 | /// This file declares classes responsible for generating llvm-mca 11 | /// CodeRegions from various types of input. llvm-mca only analyzes CodeRegions, 12 | /// so the classes here provide the input-to-CodeRegions translation. 13 | // 14 | //===----------------------------------------------------------------------===// 15 | 16 | #ifndef LLVM_TOOLS_LLVM_MCA_CODEREGION_GENERATOR_H 17 | #define LLVM_TOOLS_LLVM_MCA_CODEREGION_GENERATOR_H 18 | 19 | #include "CodeRegion.h" 20 | #include "llvm/MC/MCAsmInfo.h" 21 | #include "llvm/MC/MCContext.h" 22 | #include "llvm/MC/MCSubtargetInfo.h" 23 | #include "llvm/MC/TargetRegistry.h" 24 | #include "llvm/Support/Error.h" 25 | #include "llvm/Support/SourceMgr.h" 26 | #include 27 | 28 | namespace llvm { 29 | namespace mca { 30 | 31 | /// This class is responsible for parsing the input given to the llvm-mca 32 | /// driver, and converting that into a CodeRegions instance. 33 | class CodeRegionGenerator { 34 | protected: 35 | CodeRegions Regions; 36 | CodeRegionGenerator(const CodeRegionGenerator &) = delete; 37 | CodeRegionGenerator &operator=(const CodeRegionGenerator &) = delete; 38 | 39 | public: 40 | CodeRegionGenerator(llvm::SourceMgr &SM) : Regions(SM) {} 41 | virtual ~CodeRegionGenerator(); 42 | virtual Expected parseCodeRegions() = 0; 43 | }; 44 | 45 | /// This class is responsible for parsing input ASM and generating 46 | /// a CodeRegions instance. 47 | class AsmCodeRegionGenerator final : public CodeRegionGenerator { 48 | const Target &TheTarget; 49 | MCContext &Ctx; 50 | const MCAsmInfo &MAI; 51 | const MCSubtargetInfo &STI; 52 | const MCInstrInfo &MCII; 53 | unsigned AssemblerDialect; // This is set during parsing. 54 | 55 | public: 56 | AsmCodeRegionGenerator(const Target &T, llvm::SourceMgr &SM, MCContext &C, 57 | const MCAsmInfo &A, const MCSubtargetInfo &S, 58 | const MCInstrInfo &I) 59 | : CodeRegionGenerator(SM), TheTarget(T), Ctx(C), MAI(A), STI(S), MCII(I), 60 | AssemblerDialect(0) {} 61 | 62 | unsigned getAssemblerDialect() const { return AssemblerDialect; } 63 | Expected parseCodeRegions() override; 64 | }; 65 | 66 | } // namespace mca 67 | } // namespace llvm 68 | 69 | #endif // LLVM_TOOLS_LLVM_MCA_CODEREGION_GENERATOR_H 70 | -------------------------------------------------------------------------------- /Brokers/Broker.h: -------------------------------------------------------------------------------- 1 | #ifndef LLVM_MCAD_BROKERS_BROKER_H 2 | #define LLVM_MCAD_BROKERS_BROKER_H 3 | #include "llvm/Support/Error.h" 4 | #include "llvm/ADT/ArrayRef.h" 5 | #include "llvm/ADT/DenseMap.h" 6 | #include "llvm/Option/Option.h" 7 | #include "llvm/ADT/StringRef.h" 8 | #include "llvm/MC/MCInst.h" 9 | #include 10 | #include 11 | 12 | #include "MetadataRegistry.h" 13 | 14 | namespace llvm { 15 | 16 | namespace mcad { 17 | struct MDExchanger { 18 | MetadataRegistry &MDRegistry; 19 | // Mapping MCInst index in the current batch 20 | // to index in MetadataRegistry 21 | DenseMap &IndexMap; 22 | }; 23 | 24 | // A simple interface for MCAWorker to fetch next MCInst 25 | // 26 | // Currently it's totally up to the Brokers to control their 27 | // lifecycle. Client of this interface only cares about MCInsts. 28 | struct Broker { 29 | // Region is similar to `CodeRegion` in the original llvm-mca. Basically 30 | // MCAD will create a separate MCA pipeline to analyze each Region. 31 | // If a Broker supports Region this bit should be set and MCAD will 32 | // use `fetchRegion` method instead. 33 | static constexpr unsigned Feature_Region = 1; 34 | static constexpr unsigned Feature_Metadata = (1 << 1); 35 | 36 | // If a broker supports "InstructionError", its signalInstructionError 37 | // implementation will be called once for each instruction that resulted 38 | // in an error when ran through MCA 39 | static constexpr unsigned Feature_InstructionError = (1 << 2); 40 | 41 | // Broker should own the MCInst so only return the pointer 42 | // 43 | // Fetch MCInsts in batch. Size is the desired number of MCInsts 44 | // requested by the caller. When it's -1 the Broker will put until MCIS 45 | // is full. Of course, it's totally possible that Broker will only give out 46 | // MCInsts that are less than Size. 47 | // Note that for performance reason we're using mutable ArrayRef so the caller 48 | // should supply a fixed size array. And the Broker will always write from 49 | // index 0. 50 | // Return the number of MCInst put into the buffer, or -1 if no MCInst left 51 | virtual int fetch(MutableArrayRef MCIS, int Size = -1, 52 | std::optional MDE = std::nullopt) { 53 | return -1; 54 | } 55 | 56 | // Return feature bit vector 57 | virtual unsigned getFeatures() const { return 0; } 58 | template 59 | inline bool hasFeature() const { 60 | return getFeatures() & Mask; 61 | } 62 | 63 | struct RegionDescriptor { 64 | static constexpr StringRef END_OF_STREAM = "END_OF_STREAM"; 65 | 66 | bool IsEnd; 67 | llvm::StringRef Description; 68 | 69 | explicit RegionDescriptor(bool End, llvm::StringRef Text = "") 70 | : IsEnd(End), Description(Text) {} 71 | 72 | // Return true if it's the end of a region 73 | inline operator bool() const { return IsEnd; } 74 | }; 75 | 76 | // Similar to `fetch`, but returns the number of MCInst fetched and whether 77 | // the last element in MCIS is also the last instructions in the current Region. 78 | // Note that MCIS always aligned with the boundary of Region (i.e. the last 79 | // instruction of a Region will not be in the middle of MCIS) 80 | virtual std::pair 81 | fetchRegion(MutableArrayRef MCIS, int Size = -1, 82 | std::optional MDE = std::nullopt) { 83 | return std::make_pair(-1, RegionDescriptor(true)); 84 | } 85 | 86 | virtual void signalWorkerComplete() {}; 87 | 88 | virtual void signalInstructionError(int Index, llvm::Error Err) {}; 89 | 90 | virtual ~Broker() {} 91 | }; 92 | } // end namespace mcad 93 | } // end namespace llvm 94 | #endif 95 | -------------------------------------------------------------------------------- /Brokers/BrokerPlugin.cpp: -------------------------------------------------------------------------------- 1 | #include "BrokerPlugin.h" 2 | 3 | using namespace llvm; 4 | using namespace mcad; 5 | 6 | Expected BrokerPlugin::Load(const std::string &Filename) { 7 | std::string Error; 8 | auto Library = 9 | sys::DynamicLibrary::getPermanentLibrary(Filename.c_str(), &Error); 10 | if (!Library.isValid()) 11 | return make_error(Twine("Could not load library '") + 12 | Filename + "': " + Error, 13 | inconvertibleErrorCode()); 14 | 15 | BrokerPlugin P{Filename, Library}; 16 | intptr_t getDetailsFn = 17 | (intptr_t)Library.SearchForAddressOfSymbol("mcadGetBrokerPluginInfo"); 18 | 19 | if (!getDetailsFn) 20 | // If the symbol isn't found, this is probably a legacy plugin, which is an 21 | // error 22 | return make_error(Twine("Plugin entry point not found in '") + 23 | Filename + "'. Is this a legacy plugin?", 24 | inconvertibleErrorCode()); 25 | 26 | P.Info = reinterpret_cast(getDetailsFn)(); 27 | 28 | if (P.Info.APIVersion != LLVM_MCAD_BROKER_PLUGIN_API_VERSION) 29 | return make_error( 30 | Twine("Wrong API version on plugin '") + Filename + "'. Got version " + 31 | Twine(P.Info.APIVersion) + ", supported version is " + 32 | Twine(LLVM_MCAD_BROKER_PLUGIN_API_VERSION) + ".", 33 | inconvertibleErrorCode()); 34 | 35 | if (!P.Info.BrokerRegistrationCallback) 36 | return make_error(Twine("Empty entry callback in plugin '") + 37 | Filename + "'.'", 38 | inconvertibleErrorCode()); 39 | 40 | return P; 41 | } 42 | -------------------------------------------------------------------------------- /Brokers/BrokerPlugin.h: -------------------------------------------------------------------------------- 1 | #ifndef LLVM_MCAD_BROKER_BROKERPLUGIN_H 2 | #define LLVM_MCAD_BROKER_BROKERPLUGIN_H 3 | #include "llvm/ADT/ArrayRef.h" 4 | #include "llvm/ADT/StringRef.h" 5 | #include "llvm/Support/Compiler.h" 6 | #include "llvm/Support/DynamicLibrary.h" 7 | #include "llvm/Support/Error.h" 8 | #include 9 | #include 10 | 11 | namespace llvm { 12 | namespace mcad { 13 | // Forward declaration 14 | class BrokerFacade; 15 | 16 | #define LLVM_MCAD_BROKER_PLUGIN_API_VERSION 1 17 | 18 | extern "C" { 19 | struct BrokerPluginLibraryInfo { 20 | uint32_t APIVersion; 21 | 22 | const char *PluginName; 23 | 24 | const char *PluginVersion; 25 | 26 | void (*BrokerRegistrationCallback)(int argc, const char *const *argv, 27 | BrokerFacade &); 28 | }; 29 | } 30 | 31 | struct BrokerPlugin { 32 | static Expected Load(const std::string &Filename); 33 | 34 | StringRef getFilename() const { return Filename; } 35 | 36 | StringRef getPluginName() const { return Info.PluginName; } 37 | 38 | StringRef getPluginVersion() const { return Info.PluginVersion; } 39 | 40 | uint32_t getAPIVersion() const { return Info.APIVersion; } 41 | 42 | void registerBroker(ArrayRef Args, BrokerFacade &BF) const { 43 | Info.BrokerRegistrationCallback(Args.size(), Args.data(), BF); 44 | } 45 | 46 | private: 47 | BrokerPlugin(const std::string &Filename, const sys::DynamicLibrary &Library) 48 | : Filename(Filename), Library(Library), Info() {} 49 | 50 | std::string Filename; 51 | sys::DynamicLibrary Library; 52 | BrokerPluginLibraryInfo Info; 53 | }; 54 | } // end namespace mcad 55 | } // end namespace llvm 56 | 57 | extern "C" ::llvm::mcad::BrokerPluginLibraryInfo LLVM_ATTRIBUTE_WEAK 58 | mcadGetBrokerPluginInfo(); 59 | #endif 60 | -------------------------------------------------------------------------------- /Brokers/RawBytesBroker.cpp: -------------------------------------------------------------------------------- 1 | #include "RawBytesBroker.h" 2 | -------------------------------------------------------------------------------- /Brokers/RawBytesBroker.h: -------------------------------------------------------------------------------- 1 | #include "Broker.h" 2 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | project(llvm-mcad) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | 6 | find_package(LLVM REQUIRED CONFIG) 7 | 8 | message(STATUS "Using LLVM ${LLVM_PACKAGE_VERSION}") 9 | 10 | add_definitions(${LLVM_DEFINITIONS}) 11 | include_directories(${LLVM_INCLUDE_DIRS}) 12 | link_directories(${LLVM_LIBRARY_DIR}) 13 | 14 | list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}") 15 | include(AddLLVM) 16 | 17 | # Handle RTTI stuff, which often leads to error 18 | if(NOT ${LLVM_ENABLE_RTTI}) 19 | if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" OR 20 | "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR 21 | "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel") 22 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti") 23 | message(STATUS "Disable RTTI") 24 | elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") 25 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /GR-") 26 | message(STATUS "Disable RTTI") 27 | endif() 28 | # Do not give any flags for other less widely used 29 | # compilers 30 | endif() 31 | 32 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") 33 | 34 | option(LLVM_MCAD_ENABLE_TCMALLOC "Enable tcmalloc in gpertools for memory profiling" OFF) 35 | 36 | option(LLVM_MCAD_ENABLE_PROFILER "Enable CPU profiler in gpertools" OFF) 37 | 38 | # See plugins/CMakeLists.txt for LLVM_MCAD_ENABLE_PLUGINS option 39 | 40 | # Sanitizers 41 | option(LLVM_MCAD_ENABLE_ASAN "Enable address sanitizer" OFF) 42 | 43 | if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") 44 | # Clang ASAN currently has some problem using with TCMalloc 45 | if (LLVM_MCAD_ENABLE_TCMALLOC AND LLVM_MCAD_ENABLE_ASAN) 46 | message(FATAL_ERROR "TCMalloc can not be used with Clang's ASAN") 47 | endif() 48 | endif() 49 | 50 | if (LLVM_MCAD_ENABLE_ASAN) 51 | message(STATUS "Address sanitizer is enabled") 52 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address") 53 | endif() 54 | 55 | set(_EXTRA_LIBS ) 56 | 57 | if (LLVM_MCAD_ENABLE_TCMALLOC OR LLVM_MCAD_ENABLE_PROFILER) 58 | set(_GPERF_COMPONENTS ) 59 | if (LLVM_MCAD_ENABLE_TCMALLOC) 60 | list(APPEND _GPERF_COMPONENTS "tcmalloc") 61 | add_compile_definitions(LLVM_MCAD_ENABLE_TCMALLOC) 62 | endif () 63 | if (LLVM_MCAD_ENABLE_PROFILER) 64 | list(APPEND _GPERF_COMPONENTS "profiler") 65 | add_compile_definitions(LLVM_MCAD_ENABLE_PROFILER) 66 | endif () 67 | 68 | find_package(Gperftools REQUIRED 69 | COMPONENTS ${_GPERF_COMPONENTS}) 70 | 71 | include_directories(${GPERFTOOLS_INCLUDE_DIRS}) 72 | 73 | list(APPEND _EXTRA_LIBS ${GPERFTOOLS_LIBRARIES}) 74 | endif() 75 | 76 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}) 77 | 78 | set(LLVM_LINK_COMPONENTS 79 | AllTargetsAsmParsers 80 | AllTargetsMCAs 81 | AllTargetsDescs 82 | AllTargetsDisassemblers 83 | AllTargetsInfos 84 | MC 85 | MCA 86 | MCParser 87 | Support 88 | TargetParser 89 | ) 90 | 91 | set(_MCAVIEWS_SOURCE_FILES 92 | MCAViews/InstructionView.cpp 93 | MCAViews/SummaryView.cpp 94 | MCAViews/TimelineView.cpp 95 | ) 96 | 97 | set(_CUSTOMHW_SOURCE_FILES 98 | CustomHWUnits/CustomSourceMgr.cpp 99 | CustomHWUnits/MCADLSUnit.cpp 100 | CustomHWUnits/NaiveBranchPredictorUnit.cpp 101 | CustomHWUnits/LocalBPU.cpp 102 | CustomHWUnits/IndirectBPU.cpp 103 | CustomHWUnits/SkylakeBranchUnit.cpp 104 | ) 105 | 106 | set(_CUSTOM_STAGES_SOURCE_FILES 107 | CustomStages/MCADFetchDelayStage.cpp 108 | ) 109 | 110 | set(_BROKERS_SOURCE_FILES 111 | Brokers/BrokerPlugin.cpp 112 | Brokers/RawBytesBroker.cpp 113 | Brokers/AsmFileBroker.cpp 114 | Brokers/AsmUtils/CodeRegion.cpp 115 | Brokers/AsmUtils/CodeRegionGenerator.cpp 116 | ) 117 | 118 | set(_SOURCE_FILES 119 | llvm-mcad.cpp 120 | ${_MCAVIEWS_SOURCE_FILES} 121 | ${_CUSTOMHW_SOURCE_FILES} 122 | ${_CUSTOM_STAGES_SOURCE_FILES} 123 | ${_BROKERS_SOURCE_FILES} 124 | MCAWorker.cpp 125 | MetadataCategories.cpp 126 | PipelinePrinter.cpp 127 | ) 128 | 129 | add_llvm_executable(llvm-mcad 130 | ${_SOURCE_FILES} 131 | 132 | ${SUPPORT_PLUGINS} 133 | ) 134 | export_executable_symbols(llvm-mcad) 135 | 136 | target_link_libraries(llvm-mcad 137 | PRIVATE ${_EXTRA_LIBS} 138 | ) 139 | 140 | unset(LLVM_LINK_COMPONENTS) 141 | 142 | add_subdirectory(plugins) 143 | -------------------------------------------------------------------------------- /COMMIT_ID: -------------------------------------------------------------------------------- 1 | securesystemslab/llvm-project: 1164ac80b169586372c5c9ae5d16f3402f4da0c2 2 | llvm/llvm-project (June 4 2024): 7103e60f65cb920c2b8dc43aaa9f9402dca4b7a5 3 | -------------------------------------------------------------------------------- /CustomHWUnits/AbstractBranchPredictorUnit.h: -------------------------------------------------------------------------------- 1 | #ifndef LLVM_MCAD_ABSTRACT_BRANCH_PREDICTOR_UNIT_H 2 | #define LLVM_MCAD_ABSTRACT_BRANCH_PREDICTOR_UNIT_H 3 | 4 | #include 5 | #include "llvm/MCA/Instruction.h" 6 | #include "llvm/MCA/HardwareUnits/HardwareUnit.h" 7 | #include "MetadataRegistry.h" 8 | #include "MetadataCategories.h" 9 | 10 | namespace llvm { 11 | namespace mcad { 12 | 13 | class AbstractBranchPredictorUnit : public llvm::mca::HardwareUnit { 14 | 15 | public: 16 | 17 | enum BranchDirection {TAKEN, NOT_TAKEN}; 18 | 19 | ~AbstractBranchPredictorUnit() {} 20 | /* This method is called by the FetchDelay stage after a branch was 21 | * executed to inform the branch predictor unit what path the execution 22 | * took (branch taken vs. not taken). 23 | * 24 | * instrAddr: address of the branch instruction itself 25 | * nextInstrDirection: whether the branch was taken or not (i.e. 26 | * TAKEN iff. nextInstrAddr == destAddr) 27 | */ 28 | virtual void recordTakenBranch(MDInstrAddr instrAddr, BranchDirection nextInstrDirection) = 0; 29 | 30 | /* This method is called by the FetchDelay stage whenever it encounters 31 | * a branch instruction with metadata to attempt to predict a branching 32 | * decision. A mispredict penalty will be added to the next instruction if 33 | * the BPU predicts wrong. */ 34 | virtual BranchDirection predictBranch(MDInstrAddr instrAddr) = 0; 35 | 36 | virtual unsigned getMispredictionPenalty() = 0; 37 | 38 | }; 39 | 40 | /* Similar to the AbstractBranchPredictorUnit, but it precdicts the branch 41 | * target address instead of the direction. 42 | */ 43 | class AbstractIndirectBranchPredictorUnit : public llvm::mca::HardwareUnit { 44 | public: 45 | ~AbstractIndirectBranchPredictorUnit() {} 46 | virtual void recordTakenBranch(MDInstrAddr IA, MDInstrAddr destAddr) = 0; 47 | virtual MDInstrAddr predictBranch(MDInstrAddr IA) = 0; 48 | virtual unsigned getMispredictionPenalty() = 0; 49 | }; 50 | 51 | } 52 | } 53 | 54 | #endif -------------------------------------------------------------------------------- /CustomHWUnits/Cache.h: -------------------------------------------------------------------------------- 1 | #ifndef LLVM_MCAD_CACHE_H 2 | #define LLVM_MCAD_CACHE_H 3 | 4 | #include "CustomHWUnits/AbstractBranchPredictorUnit.h" 5 | #include "MetadataCategories.h" 6 | #include "Statistics.h" 7 | 8 | #include 9 | #include 10 | 11 | namespace llvm { 12 | namespace mcad { 13 | 14 | class CacheUnit { 15 | private: 16 | /// Size of the cache in number of bytes. 17 | unsigned size = 2048; 18 | /// Number of bytes in a cache line. 19 | unsigned lineSize = 64; 20 | /// Number of lines in the cache 21 | unsigned numLines; 22 | /// Number of ways in the cache. 23 | unsigned numWays = 2; 24 | /// Number of the sets in the cache. 25 | unsigned numSets; 26 | /// Latency to access the cache. 27 | unsigned latency = 2; 28 | 29 | struct CacheEntry { 30 | MDInstrAddr cachedAddress; 31 | unsigned lastAccess; 32 | }; 33 | 34 | std::vector> table; 35 | std::vector nextFree = {}; 36 | std::shared_ptr nextLevelCache; 37 | 38 | // Monotonically increasing by one for every load or store that this 39 | // CacheUnit sees. Used for the LRU policy. 40 | // FIXME: fix wraparound 41 | unsigned long long localClock = 0; 42 | 43 | protected: 44 | // A protected constructor to allow for the creation of a memory object. 45 | CacheUnit() = default; 46 | 47 | public: 48 | 49 | struct Statistics { 50 | OverflowableCount numLoadMisses = {}; 51 | OverflowableCount numLoadCycles = {}; 52 | OverflowableCount numStoreCycles = {}; 53 | }; 54 | 55 | Statistics stats = {}; 56 | 57 | CacheUnit(unsigned size, unsigned numWays, std::shared_ptr nextLevelCache, unsigned latency) 58 | : size(size), numWays(numWays), numLines(size / lineSize), 59 | numSets(numLines / numWays), 60 | table(numSets, std::vector(numWays)), 61 | nextFree(numSets), 62 | nextLevelCache(nextLevelCache), 63 | latency(latency) { 64 | assert(size % lineSize == 0); 65 | assert(nextLevelCache != nullptr); 66 | assert(stats.numLoadMisses.count == 0); 67 | }; 68 | 69 | /// Loads the cacheline from the cache. 70 | /// If the cacheline is not in the cache, we will evict an entry and load the cacheline from the next level. 71 | virtual unsigned load(MDInstrAddr IA) { 72 | localClock++; 73 | unsigned rtn = latency; 74 | struct CacheEntry* entry = getEntry(IA); 75 | if (entry == nullptr) { 76 | entry = evictEntry(IA); 77 | entry->cachedAddress = getCachelineAddress(IA); 78 | rtn += nextLevelCache->load(IA); 79 | stats.numLoadMisses.inc(1); 80 | } 81 | entry->lastAccess = localClock; 82 | stats.numLoadCycles.inc(rtn); 83 | return rtn; 84 | } 85 | 86 | /// Write the address to the cache and write-through to the next level. 87 | /// If there is no existing entry, we will evict an entry and replace it with the new cacheline. 88 | virtual unsigned store(MDInstrAddr IA) { 89 | localClock++; 90 | struct CacheEntry *entry = getEntry(IA); 91 | if (entry == nullptr) { 92 | entry = evictEntry(IA); 93 | entry->cachedAddress = getCachelineAddress(IA); 94 | } 95 | entry->lastAccess = localClock; 96 | const unsigned rtn = latency + nextLevelCache->store(IA); 97 | stats.numStoreCycles.inc(rtn); 98 | return rtn; 99 | } 100 | 101 | /// Return the cacheline-aligned address. 102 | MDInstrAddr getCachelineAddress(MDInstrAddr IA) { 103 | MDInstrAddr rtn = IA; 104 | rtn.addr -= rtn.addr % lineSize; 105 | return rtn; 106 | } 107 | 108 | /// Returns the the index of the set that the address maps to. 109 | unsigned getSetIndex(MDInstrAddr IA) { 110 | return (getCachelineAddress(IA).addr / lineSize) % numSets; 111 | } 112 | 113 | private: 114 | /// Return the entry if it is in the cache, otherwise return nullptr. 115 | /// 116 | /// Since we are only simulating the latency, we can simply return the address of 117 | /// the cacheline and omit the data. 118 | struct CacheEntry *getEntry(MDInstrAddr IA) { 119 | // Check if the address is in the cache 120 | unsigned setIndex = getSetIndex(IA); 121 | for (auto &way : table[setIndex]) { 122 | if (way.cachedAddress.addr == IA.addr) { 123 | return &way; 124 | } 125 | } 126 | 127 | // The address is not in the cache. 128 | return nullptr; 129 | } 130 | 131 | /// Find an entry to evict and returns the evicted cacheline. 132 | /// 133 | /// For simplicity, we will randomly evict an entry in the set if the set if full. 134 | struct CacheEntry *evictEntry(MDInstrAddr IA) { 135 | unsigned setIndex = getSetIndex(IA); 136 | 137 | // Attempt to find an empty entry. 138 | // This will happen rarely (only for the first couple of accesses at program 139 | // startup) -- most of the time, we will evict. 140 | if(nextFree[setIndex] < numWays) { 141 | unsigned wayIndex = nextFree[setIndex]; 142 | nextFree[setIndex]++; 143 | return &table[setIndex][wayIndex]; 144 | } 145 | 146 | // Evict the least recently used entry. 147 | struct CacheEntry *leastRecentEntry = nullptr; 148 | unsigned long long leastRecentTime = (unsigned long long)-1; 149 | for(auto &way: table[setIndex]) { 150 | if(way.lastAccess <= leastRecentTime) { 151 | leastRecentEntry = &way; 152 | leastRecentTime = way.lastAccess; 153 | } 154 | } 155 | return leastRecentEntry; 156 | } 157 | 158 | }; 159 | 160 | /// A simple memory object that simulates a memory with a fixed load/store latency. 161 | class MemoryUnit : public CacheUnit { 162 | public: 163 | MemoryUnit(unsigned latency) : CacheUnit(), latency(latency) {} 164 | 165 | unsigned load(MDInstrAddr IA) override { return latency; } 166 | unsigned store(MDInstrAddr IA) override { return latency; } 167 | 168 | private: 169 | unsigned latency = 300; 170 | }; 171 | 172 | } // namespace mcad 173 | } // namespace llvm 174 | 175 | #endif /* LLVM_MCAD_CACHE_H */ 176 | -------------------------------------------------------------------------------- /CustomHWUnits/CustomSourceMgr.cpp: -------------------------------------------------------------------------------- 1 | #include "CustomSourceMgr.h" 2 | 3 | using namespace llvm; 4 | using namespace mcad; 5 | 6 | void CustomSourceMgr::updateNext() { 7 | BatchCount += 1; 8 | IncrementalSourceMgr::updateNext(); 9 | } 10 | 11 | void CustomSourceMgr::clear() { 12 | CountTillNow += BatchCount; 13 | BatchCount = 0U; 14 | IncrementalSourceMgr::clear(); 15 | } 16 | -------------------------------------------------------------------------------- /CustomHWUnits/CustomSourceMgr.h: -------------------------------------------------------------------------------- 1 | #ifndef LLVM_MCAD_CUSTOMSOURCEMGR_H 2 | #define LLVM_MCAD_CUSTOMSOURCEMGR_H 3 | 4 | #include "llvm/MCA/IncrementalSourceMgr.h" 5 | 6 | namespace llvm { 7 | namespace mcad { 8 | 9 | class CustomSourceMgr : public mca::IncrementalSourceMgr { 10 | unsigned CountTillNow = 0U; 11 | unsigned BatchCount = 0U; 12 | 13 | public: 14 | CustomSourceMgr() = default; 15 | 16 | unsigned getCountTillNow() const { return CountTillNow; } 17 | 18 | void updateNext(); 19 | void clear(); 20 | }; 21 | 22 | } // namespace mcad 23 | } // namespace llvm 24 | 25 | #endif // LLVM_MCAD_CUSTOMSOURCEMGR_H 26 | -------------------------------------------------------------------------------- /CustomHWUnits/IndirectBPU.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "CustomHWUnits/IndirectBPU.h" 3 | 4 | namespace llvm { 5 | namespace mcad { 6 | 7 | void IndirectBPU::recordTakenBranch(MDInstrAddr IA, MDInstrAddr destAddr) { 8 | // Look for the entry in the table. 9 | auto& set = indirectBranchTable[getSetIndex(IA)]; 10 | for (auto &way : set) { 11 | if (way.tag == IA) { 12 | way.target = destAddr; 13 | return; 14 | } 15 | } 16 | 17 | // If we didn't find an entry, we need to evict one. 18 | // We will choose a random entry to evict. 19 | auto &way = set[rand() % numWays]; 20 | way.tag = IA; 21 | way.target = destAddr; 22 | } 23 | 24 | MDInstrAddr IndirectBPU::predictBranch(MDInstrAddr IA) { 25 | // Look for the entry in the table. 26 | auto& set = indirectBranchTable[getSetIndex(IA)]; 27 | for (auto &way : set) { 28 | if (way.tag == IA) { 29 | return way.target; 30 | } 31 | } 32 | 33 | // If we didn't find an entry, we will predict the next byte. 34 | return MDInstrAddr { IA.addr + 1 }; 35 | } 36 | 37 | unsigned IndirectBPU::getSetIndex(MDInstrAddr IA) const { 38 | return IA.addr % numSets; 39 | } 40 | } // namespace mcad 41 | } // namespace llvm 42 | -------------------------------------------------------------------------------- /CustomHWUnits/IndirectBPU.h: -------------------------------------------------------------------------------- 1 | #ifndef LLVM_MCAD_INDIRECT_BPU_H 2 | #define LLVM_MCAD_INDIRECT_BPU_H 3 | 4 | #include "CustomHWUnits/AbstractBranchPredictorUnit.h" 5 | 6 | #include 7 | 8 | #include "lib/gem5/sat_counter.h" 9 | #include "lib/gem5/intmath.h" 10 | 11 | namespace llvm { 12 | namespace mcad { 13 | 14 | /// @brief A simple indirect branch predictor. 15 | /// 16 | /// This branch predictor is based off of the `IndirectBP` in gem5. 17 | /// It maintains a set-associative table of indirect branch targets. 18 | class IndirectBPU : public AbstractIndirectBranchPredictorUnit { 19 | private: 20 | const unsigned numSets; 21 | const unsigned numWays; 22 | 23 | struct IndirectBranchEntry { 24 | MDInstrAddr tag = {0}; 25 | MDInstrAddr target = {0}; 26 | }; 27 | 28 | std::vector> indirectBranchTable; 29 | 30 | unsigned getSetIndex(MDInstrAddr IA) const; 31 | 32 | 33 | public: 34 | IndirectBPU(unsigned numSets = 256, unsigned numWays = 2) 35 | : numSets(numSets), numWays(numWays), indirectBranchTable(numSets, std::vector(numWays)) {} 36 | 37 | void recordTakenBranch(MDInstrAddr IA, MDInstrAddr destAddr) override; 38 | MDInstrAddr predictBranch(MDInstrAddr IA) override; 39 | }; 40 | 41 | } // namespace mcad 42 | } // namespace llvm 43 | 44 | #endif /* LLVM_MCAD_INDIRECT_BPU_H */ 45 | -------------------------------------------------------------------------------- /CustomHWUnits/LocalBPU.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "CustomHWUnits/LocalBPU.h" 3 | 4 | namespace llvm { 5 | namespace mcad { 6 | 7 | void LocalBPU::recordTakenBranch(MDInstrAddr IA, BranchDirection nextInstrDirection) { 8 | bool isTaken = nextInstrDirection == BranchDirection::TAKEN; 9 | unsigned idx = getPredictorIndex(IA); 10 | predictorTable[idx] += isTaken; 11 | } 12 | 13 | AbstractBranchPredictorUnit::BranchDirection LocalBPU::predictBranch(MDInstrAddr IA) { 14 | return getPrediction(IA) ? BranchDirection::TAKEN : BranchDirection::NOT_TAKEN; 15 | } 16 | 17 | unsigned LocalBPU::getPredictorIndex(MDInstrAddr IA) const { 18 | // TODO: this could probably be improved. gem5 shifts it by 2 then mask it. 19 | return IA.addr % numPredictorSets; 20 | } 21 | 22 | bool LocalBPU::getPrediction(MDInstrAddr IA) const { 23 | unsigned idx = getPredictorIndex(IA); 24 | // Return the MSB of the counter. 25 | return predictorTable[idx] >> (numCtrlBits - 1); 26 | } 27 | } // namespace mcad 28 | } // namespace llvm 29 | -------------------------------------------------------------------------------- /CustomHWUnits/LocalBPU.h: -------------------------------------------------------------------------------- 1 | #ifndef LLVM_MCAD_TWO_BIT_LOCAL_BPU_H 2 | #define LLVM_MCAD_TWO_BIT_LOCAL_BPU_H 3 | 4 | #include "CustomHWUnits/AbstractBranchPredictorUnit.h" 5 | 6 | #include 7 | 8 | #include "lib/gem5/sat_counter.h" 9 | #include "lib/gem5/intmath.h" 10 | 11 | namespace llvm { 12 | namespace mcad { 13 | 14 | /// @brief A simple two-bit local branch predictor. 15 | /// 16 | /// This branch predictor is based off of the `LocalBP` in gem5. 17 | /// It uses a table of n-bit saturating counters to predict whether a branch 18 | /// will be taken or not. 19 | class LocalBPU : public AbstractBranchPredictorUnit { 20 | private: 21 | /// @brief The penalty for a misprediction. 22 | const unsigned mispredictionPenalty; 23 | /// @brief The number of control bits per entry in the predictor table. 24 | const unsigned numCtrlBits; 25 | /// @brief The size of the predictor table in bits. 26 | const unsigned predictorSize; 27 | /// @brief The number of entries in the predictor table. 28 | const unsigned numPredictorSets; 29 | /// @brief The branch predictor table with n-bit saturating counters as entries. 30 | std::vector predictorTable; 31 | 32 | public: 33 | LocalBPU(unsigned mispredictionPenalty = 20, unsigned numCtrlBits = 2, 34 | unsigned predictorSize = 2048) 35 | : mispredictionPenalty(mispredictionPenalty), 36 | numCtrlBits(numCtrlBits), predictorSize(predictorSize) 37 | ,numPredictorSets(predictorSize / numCtrlBits) 38 | ,predictorTable(numPredictorSets, gem5::SatCounter8(numCtrlBits)) 39 | { 40 | assert(gem5::isPowerOf2(predictorSize)); 41 | assert(numCtrlBits > 0); 42 | }; 43 | 44 | void recordTakenBranch(MDInstrAddr IA, BranchDirection nextInstrDirection) override; 45 | BranchDirection predictBranch(MDInstrAddr IA) override; 46 | unsigned getMispredictionPenalty() override { return mispredictionPenalty; } 47 | 48 | private: 49 | /// @brief Get the index of the predictor table for the given instruction address. 50 | unsigned getPredictorIndex(MDInstrAddr IA) const; 51 | /// @brief Get the prediction for the given instruction address. 52 | bool getPrediction(MDInstrAddr IA) const; 53 | }; 54 | 55 | } // namespace mcad 56 | } // namespace llvm 57 | 58 | #endif /* LLVM_MCAD_TWO_BIT_LOCAL_BPU_H */ 59 | -------------------------------------------------------------------------------- /CustomHWUnits/NaiveBranchPredictorUnit.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "CustomHWUnits/NaiveBranchPredictorUnit.h" 3 | 4 | namespace llvm { 5 | namespace mcad { 6 | 7 | void NaiveBranchPredictorUnit::recordTakenBranch(MDInstrAddr instrAddr, BranchDirection nextInstrDirection) { 8 | // If no entry exists yet, add one 9 | if(branchHistory.find(instrAddr) == branchHistory.end()) { 10 | branchHistory[instrAddr] = {}; 11 | } 12 | // Update the entry 13 | branchHistory[instrAddr].lastDirection = nextInstrDirection; 14 | branchHistory[instrAddr].lastUse = nAccesses; 15 | 16 | // Evict the least recently used entry if we are at capacity 17 | if(branchHistory.size() >= historyCapacity) { 18 | auto min_it = branchHistory.begin(); 19 | for(auto it = branchHistory.begin(); it != branchHistory.end(); it++) { 20 | if(it->second.lastUse < min_it->second.lastUse) { 21 | min_it = it; 22 | } 23 | } 24 | branchHistory.erase(min_it); 25 | } 26 | } 27 | 28 | AbstractBranchPredictorUnit::BranchDirection NaiveBranchPredictorUnit::predictBranch(MDInstrAddr instrAddr) { 29 | ++nAccesses; 30 | if(branchHistory.find(instrAddr) != branchHistory.end()) { 31 | branchHistory[instrAddr].lastUse = nAccesses; 32 | return branchHistory[instrAddr].lastDirection; 33 | } 34 | // We have no history on this; predict a fall-through branch 35 | return NOT_TAKEN; 36 | } 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /CustomHWUnits/NaiveBranchPredictorUnit.h: -------------------------------------------------------------------------------- 1 | #ifndef LLVM_MCAD_NAIVE_BRANCH_PREDICTOR_UNIT_H 2 | #define LLVM_MCAD_NAIVE_BRANCH_PREDICTOR_UNIT_H 3 | 4 | #include 5 | #include "CustomHWUnits/AbstractBranchPredictorUnit.h" 6 | 7 | namespace llvm { 8 | namespace mcad { 9 | 10 | class NaiveBranchPredictorUnit : public AbstractBranchPredictorUnit { 11 | 12 | struct HistoryEntry { 13 | AbstractBranchPredictorUnit::BranchDirection lastDirection; 14 | unsigned lastUse; 15 | }; 16 | 17 | unsigned mispredictionPenalty; 18 | unsigned historyCapacity; 19 | unsigned nAccesses; 20 | std::map branchHistory = {}; 21 | 22 | public: 23 | NaiveBranchPredictorUnit(unsigned mispredictionPenalty, unsigned branchHistoryTableSize) 24 | : mispredictionPenalty(mispredictionPenalty), 25 | historyCapacity(branchHistoryTableSize) { }; 26 | 27 | void recordTakenBranch(MDInstrAddr instrAddr, BranchDirection nextInstrDirection) override; 28 | 29 | BranchDirection predictBranch(MDInstrAddr instrAddr) override; 30 | 31 | unsigned getMispredictionPenalty() override { 32 | return mispredictionPenalty; 33 | } 34 | 35 | }; 36 | 37 | } 38 | } 39 | 40 | #endif -------------------------------------------------------------------------------- /CustomHWUnits/SkylakeBranchUnit.cpp: -------------------------------------------------------------------------------- 1 | 2 | //===----------------------- BranchUnit.cpp -----------------------*- C++-*-===// 3 | // 4 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 5 | // See https://llvm.org/LICENSE.txt for license information. 6 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 7 | // 8 | //===----------------------------------------------------------------------===// 9 | /// \file 10 | /// 11 | /// Work in progress 12 | /// 13 | //===----------------------------------------------------------------------===// 14 | 15 | 16 | #define DEBUG_TYPE "llvm-mca" 17 | 18 | #include 19 | #include "MetadataCategories.h" 20 | #include "AbstractBranchPredictorUnit.h" 21 | #include "SkylakeBranchUnit.h" 22 | #include 23 | #include 24 | #include 25 | 26 | namespace llvm { 27 | namespace mcad { 28 | 29 | 30 | SkylakeBranchUnit::SkylakeBranchUnit(uint32_t penalty_) { 31 | penalty = penalty_; 32 | } 33 | 34 | uint32_t SkylakeBranchUnit::getMispredictionPenalty() { 35 | return penalty; 36 | } 37 | 38 | AbstractBranchPredictorUnit::BranchDirection SkylakeBranchUnit::predictBranch(MDInstrAddr pc) { 39 | return predictBranch(pc, MDInstrAddr{0}); 40 | } 41 | 42 | AbstractBranchPredictorUnit::BranchDirection SkylakeBranchUnit::predictBranch(MDInstrAddr pc, MDInstrAddr target) { 43 | SkylakeBranchEntry* entry = nullptr; 44 | // See if present in any table 45 | // Greedily accepts first table where present 46 | auto test = updatePHR(pc, target); 47 | entry = getTable(pht1, getPHTIndex(test, 1, 6), pc, entry); 48 | entry = getTable(pht2, getPHTIndex(test, 10, 3), pc, entry); 49 | entry = getTable(pht3, getPHTIndex(test, 10, 3), pc, entry); 50 | entry = getTable(base, SkylakePHR(pc.addr & 0x1FFF), pc, entry); // Check base last 51 | 52 | if (entry != nullptr) { 53 | // Branch taken 54 | entry->useful++; 55 | return BranchDirection::TAKEN; 56 | } 57 | return BranchDirection::NOT_TAKEN; 58 | } 59 | 60 | SkylakeBranchUnit::SkylakeBranchEntry* SkylakeBranchUnit::getTable(SkylakeBranchTable& pht, 61 | SkylakePHR index, 62 | MDInstrAddr pc, 63 | SkylakeBranchEntry* out) { 64 | if (out != nullptr) 65 | return out; 66 | 67 | auto exists = pht.find(index); 68 | if (exists != pht.end()) 69 | // If index exists, put into that table 70 | for (int i = 0; i < exists->second.size(); i++) 71 | if (exists->second[i].pc == pc.addr && exists->second[i].useful > 0) 72 | return &exists->second[i]; 73 | return out; 74 | } 75 | 76 | // Functions currently implementing Skylake behavior 77 | // Can make class virtual for architecture compatibility in the future 78 | void SkylakeBranchUnit::insertTable(SkylakeBranchTable& pht, MDInstrAddr pc, SkylakePHR index) { 79 | 80 | auto exists = pht.find(index); 81 | if (exists != pht.end()) 82 | // If index exists, put into that table 83 | phtSetPush(pht, pc, index); 84 | else if (pht.size() < 2048) { 85 | // Does not exist, but room to add table 86 | pht[index] = {}; 87 | phtSetPush(pht, pc, index); 88 | } 89 | else { 90 | // Need to evict a table 91 | // Evict row where total prediction score is minimum 92 | SkylakeBranchTable::iterator to_remove; 93 | uint32_t check = UINT_MAX; 94 | for (auto e = pht.begin(); e != pht.end(); e++) { 95 | uint32_t current = 0; 96 | for (auto i = e->second.begin(); i != e->second.end(); i++) 97 | current += i->useful; 98 | to_remove = (check < current) ? to_remove : e; 99 | check = check < current ? check : current; 100 | } 101 | pht.erase(to_remove); 102 | pht[index] = {}; 103 | phtSetPush(pht, pc, index); 104 | } 105 | } 106 | void SkylakeBranchUnit::phtSetPush(SkylakeBranchTable& pht, MDInstrAddr pc, SkylakePHR index) { 107 | if (pht[index].size() >= 4) { 108 | auto to_remove = std::min_element(pht[index].begin(), pht[index].end()); 109 | pht[index].erase(to_remove); 110 | } 111 | pht[index].push_back(SkylakeBranchEntry(pc.addr,0)); 112 | } 113 | 114 | void SkylakeBranchUnit::recordTakenBranch(MDInstrAddr pc, BranchDirection nextInstrDirection) { 115 | if (nextInstrDirection == BranchDirection::TAKEN) 116 | recordTakenBranch(pc, {0}); 117 | } 118 | 119 | void SkylakeBranchUnit::recordTakenBranch(MDInstrAddr pc, MDInstrAddr target) { 120 | // TODO: Get correct index for each table 121 | 122 | // Base predictor 123 | auto base_index = SkylakePHR(pc.addr & 0x1FFF); 124 | phtSetPush(base, pc, base_index); 125 | 126 | // PHTs 127 | // See page 9 of H&H 128 | phr = updatePHR(pc, target); 129 | insertTable(pht1, pc, getPHTIndex(phr, 1, 6)); 130 | insertTable(pht2, pc, getPHTIndex(phr, 10, 3)); 131 | insertTable(pht3, pc, getPHTIndex(phr, 10, 3)); 132 | } 133 | 134 | 135 | // Each table has its own indexing 136 | // Work in progress 137 | SkylakeBranchUnit::SkylakePHR SkylakeBranchUnit::getPHTIndex(SkylakePHR phr, int start1, int start2) { 138 | // Convert PHR to the index for a PHT table 139 | const SkylakePHR base("101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101"); 140 | 141 | 142 | // Get range of bits from 16(i)-6 to 16(i)+8 143 | auto index = base << (93 - (start1+14)); 144 | index = index >> (93-14-start1); 145 | index &= phr; 146 | 147 | auto index2 = base << (93 - (start2+14)); 148 | index2 = index2 >> (93-14-start2); 149 | index2 &= phr; 150 | 151 | // xor two indices together to get final index 152 | return index ^ index2; 153 | } 154 | 155 | // Part of PHR 156 | unsigned long long SkylakeBranchUnit::getFootprint(MDInstrAddr branchInstr, MDInstrAddr targetInstr) { 157 | // branchAddr = (branchAddr >> 3) & 0x3FFFF; 158 | uint32_t branchAddr = branchInstr.addr, targetAddr = targetInstr.addr; 159 | 160 | targetAddr = targetAddr & 0x001F; 161 | 162 | uint32_t result = 0; 163 | uint32_t branchRight = ((branchAddr & 0x18) >> 3) 164 | | ((branchAddr & 0x180) >> 5) 165 | | ((branchAddr & 0x1800) >> 7); 166 | 167 | uint32_t branchLeft = ((branchAddr & 0x60) >> 5) 168 | | ((branchAddr & 0x600) >> 7) 169 | | ((branchAddr & 0x7E000) >> 9); 170 | 171 | result |= branchRight ^ targetAddr; 172 | result |= branchLeft << 6; 173 | 174 | return result; 175 | } 176 | 177 | SkylakeBranchUnit::SkylakePHR SkylakeBranchUnit::updatePHR(MDInstrAddr currentAddr, MDInstrAddr targetAddr) { 178 | auto next = phr << 2; 179 | return next ^ SkylakePHR(getFootprint(currentAddr, targetAddr)); 180 | } 181 | 182 | 183 | } // namespace mca 184 | } // namespace llvm 185 | 186 | -------------------------------------------------------------------------------- /CustomHWUnits/SkylakeBranchUnit.h: -------------------------------------------------------------------------------- 1 | 2 | //===------------------------- BranchUnit.h -----------------------*- C++-*-===// 3 | // 4 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 5 | // See https://llvm.org/LICENSE.txt for license information. 6 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 7 | // 8 | //===----------------------------------------------------------------------===// 9 | /// \file 10 | /// 11 | /// Work in progress 12 | /// 13 | /// 14 | //===----------------------------------------------------------------------===// 15 | 16 | #ifndef LLVM_MCA_HARDWAREUNITS_BRANCHUNIT_H 17 | #define LLVM_MCA_HARDWAREUNITS_BRANCHUNIT_H 18 | 19 | #include "llvm/ADT/DenseMap.h" 20 | #include "llvm/ADT/SmallVector.h" 21 | #include 22 | #include "MetadataCategories.h" 23 | #include "AbstractBranchPredictorUnit.h" 24 | #include 25 | #include 26 | #include 27 | 28 | namespace llvm { 29 | namespace mcad { 30 | 31 | // Branch Predictor implemented according to Half&Half description 32 | // of Intel Skylake Branch Predictor 33 | 34 | 35 | class BranchUnit { 36 | public: 37 | virtual void recordTakenBranch(unsigned long long key, uint32_t target) = 0; 38 | virtual void predictCond(unsigned long long key, uint32_t target) = 0; 39 | virtual void predictInd(unsigned long long key, uint32_t target) = 0; 40 | }; 41 | 42 | class GenericBranchUnit : public BranchUnit { 43 | 44 | }; 45 | 46 | class SkylakeBranchUnit : public AbstractBranchPredictorUnit { 47 | public: 48 | // Maps Branch History to 4-way set of branch PC 49 | struct SkylakeBranchEntry { 50 | unsigned long long pc; 51 | unsigned long long useful; // decides eviction 52 | 53 | SkylakeBranchEntry(unsigned long long pc_, unsigned long long useful_) { 54 | pc = pc_; 55 | useful = useful_; 56 | } 57 | 58 | SkylakeBranchEntry(unsigned long long useful_) { 59 | useful = useful_; 60 | } 61 | bool operator<(const SkylakeBranchEntry& other) const { 62 | return useful < other.useful; 63 | } 64 | unsigned long long operator+(const SkylakeBranchEntry& other) { 65 | return useful + other.useful; 66 | } 67 | 68 | }; 69 | using SkylakePHR = std::bitset<93>; 70 | using SkylakeBranchTable = std::unordered_map>; 71 | 72 | SkylakeBranchUnit(uint32_t penalty_); 73 | BranchDirection predictBranch(MDInstrAddr pc, MDInstrAddr target); 74 | BranchDirection predictBranch(MDInstrAddr pc) override; 75 | void recordTakenBranch(MDInstrAddr pc, MDInstrAddr target); 76 | void recordTakenBranch(MDInstrAddr instrAddr, BranchDirection nextInstrDirection) override; 77 | uint32_t getMispredictionPenalty() override; 78 | private: 79 | 80 | // Each table records progressively further away branches 81 | SkylakeBranchTable base; 82 | SkylakeBranchTable pht1; 83 | SkylakeBranchTable pht2; 84 | SkylakeBranchTable pht3; 85 | SkylakePHR phr; 86 | uint32_t penalty; 87 | 88 | 89 | 90 | void insertTable(SkylakeBranchTable& pht, MDInstrAddr pc, SkylakePHR phr); 91 | SkylakeBranchEntry* getTable(SkylakeBranchTable& pht, 92 | SkylakePHR phr, 93 | MDInstrAddr pc, 94 | SkylakeBranchEntry* out); 95 | void phtSetPush(SkylakeBranchTable& pht, MDInstrAddr pc, SkylakePHR phr); 96 | SkylakePHR getPHTIndex(SkylakePHR phr, int start1, int start2); 97 | unsigned long long getFootprint(MDInstrAddr branchAddr, MDInstrAddr targetAddr); 98 | SkylakePHR updatePHR(MDInstrAddr currentAddr, MDInstrAddr targetAddr); 99 | 100 | 101 | }; 102 | 103 | 104 | } // namespace mcad 105 | } // namespace llvm 106 | 107 | 108 | #endif // LLVM_MCA_HARDWAREUNITS_BRANCHUNIT_H 109 | -------------------------------------------------------------------------------- /CustomStages/MCADFetchDelayStage.cpp: -------------------------------------------------------------------------------- 1 | #include "CustomStages/MCADFetchDelayStage.h" 2 | #include "MetadataCategories.h" 3 | #include "llvm/Support/Debug.h" 4 | #define DEBUG_TYPE "llvm-mca" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace llvm { 11 | namespace mcad { 12 | 13 | struct MCADInstructionFetchedEvent {}; 14 | 15 | bool MCADFetchDelayStage::hasWorkToComplete() const { 16 | return !instrQueue.empty(); 17 | } 18 | 19 | bool MCADFetchDelayStage::isAvailable(const llvm::mca::InstRef &IR) const { 20 | return checkNextStage(IR); 21 | } 22 | 23 | llvm::Error MCADFetchDelayStage::forwardDueInstrs() { 24 | while(!instrQueue.empty() && instrQueue.front().delayCyclesLeft == 0) { 25 | llvm::mca::InstRef IR = instrQueue.front().IR; 26 | if(!checkNextStage(IR)) { 27 | // Although this instruction has completed its delay cycles and is 28 | // ready to be forwarded, we have to keep holding it since the next 29 | // stage is not ready. Break out of the loop, because instructions 30 | // must be dispatched to the next stage from this one in-order. 31 | break; 32 | } 33 | if (llvm::Error Val = moveToTheNextStage(IR)) { 34 | return Val; 35 | } 36 | instrQueue.pop_front(); 37 | } 38 | return llvm::ErrorSuccess(); 39 | } 40 | 41 | llvm::Error MCADFetchDelayStage::execute(llvm::mca::InstRef &IR) { 42 | // We (ab-)use the LastGenericEventType to create a notification when the instruction first enters this stage. 43 | // We use this elsewhere to calculate the number of cycles between when an instruction first enters the pipeline and the end of its execution. 44 | notifyEvent(llvm::mca::HWInstructionEvent(llvm::mca::HWInstructionEvent::LastGenericEventType, IR)); 45 | const llvm::mca::Instruction *I = IR.getInstruction(); 46 | const llvm::mca::InstrDesc &ID = I->getDesc(); 47 | const llvm::MCInstrDesc &MCID = MCII.get(I->getOpcode()); 48 | 49 | unsigned delayCyclesLeft = 0; 50 | std::optional instrAddr = getMDInstrAddrForInstr(MD, IR); 51 | 52 | // fetch instruction from the cache 53 | if(CU.has_value() && instrAddr.has_value()) { 54 | const unsigned loadDelay = CU->load(*instrAddr); 55 | stats.numInstrLoadCycles.inc(loadDelay); 56 | delayCyclesLeft += loadDelay; 57 | } 58 | 59 | if(BPU && enableBranchPredictorModeling) { 60 | // Check if previous instruction was a branch, and if so if the predicted 61 | // branch target matched what we ended up executing 62 | if(predictedBranchDirection.has_value() && instrAddr.has_value() && previousInstrAddr.has_value()) { 63 | bool fellThrough = instrAddr->addr == (previousInstrAddr->addr + previousInstrAddr->size); 64 | AbstractBranchPredictorUnit::BranchDirection actualBranchDirection = 65 | (fellThrough ? AbstractBranchPredictorUnit::NOT_TAKEN 66 | : AbstractBranchPredictorUnit::TAKEN); 67 | BPU->recordTakenBranch(*previousInstrAddr, actualBranchDirection); 68 | 69 | stats.numBranches.inc(1); 70 | if(actualBranchDirection != predictedBranchDirection) { 71 | // Previous prediction was wrong; this instruction will have extra 72 | // latency due to misprediction. 73 | delayCyclesLeft += BPU->getMispredictionPenalty(); 74 | stats.numMispredictions.inc(1); 75 | LLVM_DEBUG(dbgs() << "[MCAD FetchDelayStage] Previous branch at "); 76 | LLVM_DEBUG(dbgs().write_hex(instrAddr->addr)); 77 | LLVM_DEBUG(dbgs() << " mispredicted, delaying next instruction by " 78 | << delayCyclesLeft << " cycle(s).\n"); 79 | } else { 80 | LLVM_DEBUG(dbgs() << "[MCAD FetchDelayStage] Previous branch at "); 81 | LLVM_DEBUG(dbgs().write_hex(instrAddr->addr)); 82 | LLVM_DEBUG(dbgs() << " predicted correctly.\n" ); 83 | } 84 | } 85 | // Update branch prediction state 86 | if(MCID.isBranch() && instrAddr.has_value()) { 87 | predictedBranchDirection = BPU->predictBranch(*instrAddr); 88 | } else { 89 | predictedBranchDirection = std::nullopt; 90 | } 91 | } 92 | 93 | unsigned delayCyclesSkipped = 0; 94 | if(maxNumberSimulatedStallCycles > -1 && delayCyclesLeft > maxNumberSimulatedStallCycles) { 95 | delayCyclesSkipped = delayCyclesLeft - maxNumberSimulatedStallCycles; 96 | stats.numSkippedDelayCycles.inc(delayCyclesSkipped); 97 | delayCyclesLeft = maxNumberSimulatedStallCycles; 98 | } 99 | 100 | instrQueue.emplace_back(DelayedInstr { delayCyclesLeft, IR }); 101 | previousInstrAddr = instrAddr; 102 | // if the instruction is not delayed, execute it immediately (it will 103 | // have a delayCyclesLeft of 0 and be at the top of the queue) 104 | return forwardDueInstrs(); 105 | } 106 | 107 | llvm::Error MCADFetchDelayStage::cycleStart() { 108 | if(!instrQueue.empty() && instrQueue.front().delayCyclesLeft > 0) { 109 | // An instruction may be at the front of the instruction queue with no 110 | // delay cycles left but have not been forwarded to the next stage yet 111 | // if the next stage is not ready yet. 112 | instrQueue.front().delayCyclesLeft--; 113 | } 114 | return forwardDueInstrs(); 115 | } 116 | 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /CustomStages/MCADFetchDelayStage.h: -------------------------------------------------------------------------------- 1 | // This class does not model a real hardware stage. It is used to block the 2 | // pipeline for a number of cycles to prevent further instructions from being 3 | // fetched. We use this to model the cost of branch mispredictions. 4 | 5 | #ifndef LLVM_MCAD_FETCH_DELAY_STAGE_H 6 | #define LLVM_MCAD_FETCH_DELAY_STAGE_H 7 | 8 | #include "llvm/MC/MCInstrInfo.h" 9 | #include "llvm/MC/MCInstrAnalysis.h" 10 | #include "llvm/MCA/SourceMgr.h" 11 | #include "llvm/MCA/Stages/Stage.h" 12 | #include "CustomHWUnits/AbstractBranchPredictorUnit.h" 13 | #include "CustomHWUnits/Cache.h" 14 | #include "MetadataRegistry.h" 15 | #include "Statistics.h" 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | namespace llvm { 22 | namespace mcad { 23 | 24 | class MCADFetchDelayStage : public llvm::mca::Stage { 25 | 26 | struct DelayedInstr { 27 | unsigned delayCyclesLeft; 28 | llvm::mca::InstRef IR; 29 | }; 30 | 31 | const llvm::MCInstrInfo &MCII; 32 | std::deque instrQueue = {}; 33 | 34 | AbstractBranchPredictorUnit *BPU; 35 | MetadataRegistry &MD; 36 | 37 | // Whenever a branch instruction is executed, we run the branch predictor 38 | // and store the predicted branch direction here. 39 | // At the next instruction, we compare the predicted direction to the actual 40 | // direction taken (fallthrough vs. branch taken) and add a penalty if 41 | // there is a mismatch. 42 | // Non-branch instructions set this member to nullopt. 43 | std::optional predictedBranchDirection = std::nullopt; 44 | 45 | // Stores the address and size of the last executed instruction. 46 | std::optional previousInstrAddr = std::nullopt; 47 | std::optional previousInstrSize = std::nullopt; 48 | 49 | // This limits the number of cycles that the FetchDelay stage will simulate 50 | // a stalled fetch unit (-1 = unlimited, 0 = skip any cycles where fetch 51 | // stage would be stalled). This improves MCAD runtime (fewer cycles need 52 | // to be simulated) but it also reduces accuracy (other units may still 53 | // perform useful work when the fetch stage is stalled). 54 | int maxNumberSimulatedStallCycles = -1; 55 | 56 | public: 57 | 58 | // The memory cache unit. 59 | std::optional CU = std::nullopt; 60 | 61 | struct Statistics { 62 | OverflowableCount numSkippedDelayCycles = {}; 63 | OverflowableCount numBranches = {}; 64 | OverflowableCount numMispredictions = {}; 65 | OverflowableCount numInstrLoadCycles = {}; 66 | }; 67 | 68 | Statistics stats = {}; 69 | 70 | public: 71 | MCADFetchDelayStage(const llvm::MCInstrInfo &MCII, MetadataRegistry &MD, 72 | AbstractBranchPredictorUnit *BPU, 73 | std::optional CU = std::nullopt, 74 | int maxNumberSimulatedStallCycles = -1) 75 | : MCII(MCII), MD(MD), BPU(BPU), CU(std::move(CU)), maxNumberSimulatedStallCycles(maxNumberSimulatedStallCycles) {} 76 | 77 | bool hasWorkToComplete() const override; 78 | bool isAvailable(const llvm::mca::InstRef &IR) const override; 79 | llvm::Error execute(llvm::mca::InstRef &IR) override; 80 | 81 | llvm::Error cycleStart() override; 82 | 83 | llvm::Error forwardDueInstrs(); 84 | 85 | bool enableInstructionCacheModeling = true; 86 | bool enableBranchPredictorModeling = true; 87 | 88 | }; 89 | 90 | } 91 | } 92 | 93 | #endif -------------------------------------------------------------------------------- /LLVM_COMMIT_ID.txt: -------------------------------------------------------------------------------- 1 | commit 56ffbd97fda16008d02180a460211829354f1094 (upstream/main) 2 | Author: Tom Stellard 3 | Date: Fri Jul 19 14:47:35 2024 -0700 4 | 5 | [workflows] Avoid usage of access token in issue-write.yml (#94011) 6 | 7 | This adds a new composite workflow that allows you to download artifacts 8 | from other workflows without using an access token. 9 | 10 | actions/download-artifact from GitHub requires an access token in order 11 | to download artifacts from a different workflow, which is why we can't 12 | use it here if we want to avoid using a token. 13 | 14 | See 15 | https://github.com/actions/download-artifact?tab=readme-ov-file#download-artifacts-from-other-workflow-runs-or-repositories 16 | -------------------------------------------------------------------------------- /MCAViews/InstructionView.cpp: -------------------------------------------------------------------------------- 1 | //===----------------------- View.cpp ---------------------------*- C++ -*-===// 2 | // 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 | // See https://llvm.org/LICENSE.txt for license information. 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 | // 7 | //===----------------------------------------------------------------------===// 8 | /// \file 9 | /// 10 | /// This file defines the member functions of the class InstructionView. 11 | /// 12 | //===----------------------------------------------------------------------===// 13 | 14 | #include 15 | #include "InstructionView.h" 16 | #include "llvm/MC/MCInst.h" 17 | #include "llvm/MC/MCSubtargetInfo.h" 18 | 19 | namespace llvm { 20 | namespace mca { 21 | 22 | StringRef InstructionView::printInstructionString(const llvm::MCInst &MCI) const { 23 | InstructionString = ""; 24 | MCIP.printInst(&MCI, 0, "", STI, InstrStream); 25 | InstrStream.flush(); 26 | // Remove any tabs or spaces at the beginning of the instruction. 27 | return StringRef(InstructionString).ltrim(); 28 | } 29 | } // namespace mca 30 | } // namespace llvm 31 | -------------------------------------------------------------------------------- /MCAViews/InstructionView.h: -------------------------------------------------------------------------------- 1 | //===----------------------- InstrucionView.h -----------------------------*- C++ -*-===// 2 | // 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 | // See https://llvm.org/LICENSE.txt for license information. 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 | // 7 | //===----------------------------------------------------------------------===// 8 | /// \file 9 | /// 10 | /// This file defines the main interface for Views that examine and reference 11 | /// a sequence of machine instructions. 12 | /// 13 | //===----------------------------------------------------------------------===// 14 | 15 | #ifndef LLVM_TOOLS_LLVM_MCA_INSTRUCTIONVIEW_H 16 | #define LLVM_TOOLS_LLVM_MCA_INSTRUCTIONVIEW_H 17 | 18 | #include "llvm/ADT/STLExtras.h" 19 | #include "llvm/MC/MCInstPrinter.h" 20 | #include "llvm/MCA/View.h" 21 | #include "llvm/Support/JSON.h" 22 | #include "llvm/Support/raw_ostream.h" 23 | 24 | namespace llvm { 25 | namespace mca { 26 | 27 | // The base class for views that deal with individual machine instructions. 28 | struct InstructionView : public View { 29 | using GetInstCallbackTy = const llvm::MCInst*(unsigned); 30 | 31 | private: 32 | const llvm::MCSubtargetInfo &STI; 33 | llvm::MCInstPrinter &MCIP; 34 | llvm::function_ref GetInstCB; 35 | StringRef MCPU; 36 | 37 | mutable std::string InstructionString; 38 | mutable raw_string_ostream InstrStream; 39 | 40 | public: 41 | void printView(llvm::raw_ostream &) const override {} 42 | InstructionView(const llvm::MCSubtargetInfo &STI, 43 | llvm::MCInstPrinter &Printer, 44 | llvm::function_ref CB = nullptr, 45 | StringRef MCPU = StringRef()) 46 | : STI(STI), MCIP(Printer), GetInstCB(CB), MCPU(MCPU), 47 | InstrStream(InstructionString) {} 48 | 49 | virtual ~InstructionView() = default; 50 | 51 | StringRef getNameAsString() const override { 52 | return "Instructions and CPU resources"; 53 | } 54 | // Return a reference to a string representing a given machine instruction. 55 | // The result should be used or copied before the next call to 56 | // printInstructionString() as it will overwrite the previous result. 57 | StringRef printInstructionString(const llvm::MCInst &MCI) const; 58 | const llvm::MCSubtargetInfo &getSubTargetInfo() const { return STI; } 59 | 60 | llvm::MCInstPrinter &getInstPrinter() const { return MCIP; } 61 | const llvm::MCInst *getSourceInst(unsigned SourceIdx) const { 62 | if (GetInstCB) 63 | return GetInstCB(SourceIdx); 64 | else 65 | return nullptr; 66 | } 67 | json::Value toJSON() const override { return json::Object(); } 68 | virtual void printViewJSON(llvm::raw_ostream &OS) { 69 | json::Value JV = toJSON(); 70 | OS << formatv("{0:2}", JV) << "\n"; 71 | } 72 | }; 73 | } // namespace mca 74 | } // namespace llvm 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /MCAViews/SummaryView.cpp: -------------------------------------------------------------------------------- 1 | //===--------------------- SummaryView.cpp -------------------*- C++ -*-===// 2 | // 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 | // See https://llvm.org/LICENSE.txt for license information. 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 | // 7 | //===----------------------------------------------------------------------===// 8 | /// \file 9 | /// 10 | /// This file implements the functionalities used by the SummaryView to print 11 | /// the report information. 12 | /// 13 | //===----------------------------------------------------------------------===// 14 | 15 | #include "SummaryView.h" 16 | #include "llvm/ADT/SmallVector.h" 17 | #include "llvm/MCA/Support.h" 18 | #include "llvm/Support/Format.h" 19 | #include "llvm/Support/raw_ostream.h" 20 | #include "MetadataCategories.h" 21 | #include "MetadataRegistry.h" 22 | #include "RegionMarker.h" 23 | 24 | namespace llvm { 25 | namespace mca { 26 | 27 | #define DEBUG_TYPE "llvm-mca" 28 | 29 | SummaryView::SummaryView(const MCSchedModel &Model, 30 | function_ref GetSrcSize, unsigned Width, 31 | mcad::MetadataRegistry *MDRegistry, 32 | llvm::raw_ostream *OutStream) 33 | : SM(Model), GetSourceSize(GetSrcSize), 34 | DispatchWidth(Width ? Width : Model.IssueWidth), LastInstructionIdx(0), 35 | TotalCycles(0), NumMicroOps(0), MDRegistry(MDRegistry), 36 | OutStream(OutStream), 37 | ProcResourceUsage(Model.getNumProcResourceKinds(), 0), 38 | ProcResourceMasks(Model.getNumProcResourceKinds()), 39 | ResIdx2ProcResID(Model.getNumProcResourceKinds(), 0) { 40 | computeProcResourceMasks(SM, ProcResourceMasks); 41 | for (unsigned I = 1, E = SM.getNumProcResourceKinds(); I < E; ++I) { 42 | unsigned Index = getResourceStateIndex(ProcResourceMasks[I]); 43 | ResIdx2ProcResID[Index] = I; 44 | } 45 | } 46 | 47 | void SummaryView::onEvent(const HWInstructionEvent &Event) { 48 | const Instruction &Inst = *Event.IR.getInstruction(); 49 | if (Event.Type == HWInstructionEvent::Dispatched) 50 | LastInstructionIdx = Event.IR.getSourceIndex(); 51 | 52 | if (MDRegistry && Inst.getIdentifier().has_value() && OutStream) { 53 | mcad::MetadataRegistry &MDR = *MDRegistry; 54 | unsigned MDTok = *Inst.getIdentifier(); 55 | auto &MarkerCat = MDR[mcad::MD_BinaryRegionMarkers]; 56 | if (auto Marker = MarkerCat.get(MDTok)) { 57 | unsigned InstIdx = Event.IR.getSourceIndex(); 58 | bool IsBegin = Marker->isBegin(), IsEnd = Marker->isEnd(); 59 | 60 | if (Event.Type == HWInstructionEvent::Retired) { 61 | if (IsBegin && IsEnd) { 62 | (*OutStream) << "====Marker==== [" << InstIdx << "]\n"; 63 | printView(*OutStream); 64 | } else if (IsBegin) { 65 | // Normal Begin marker 66 | std::string BeginSummary; 67 | llvm::raw_string_ostream SS(BeginSummary); 68 | SS << "====Marker Begins==== [" << InstIdx << "]\n"; 69 | printView(SS); 70 | PairingStack.push_back(std::move(BeginSummary)); 71 | } else if (IsEnd) { 72 | // Normal End marker 73 | if (PairingStack.size()) 74 | (*OutStream) << "\n" << PairingStack.pop_back_val(); 75 | (*OutStream) << "====Marker Ends==== [" << InstIdx << "]\n"; 76 | printView(*OutStream); 77 | } 78 | } 79 | } 80 | } 81 | 82 | // We are only interested in the "instruction retired" events generated by 83 | // the retire stage for instructions that are part of iteration #0. 84 | if (Event.Type != HWInstructionEvent::Retired || 85 | Event.IR.getSourceIndex() >= GetSourceSize()) 86 | return; 87 | 88 | // Update the cumulative number of resource cycles based on the processor 89 | // resource usage information available from the instruction descriptor. We 90 | // need to compute the cumulative number of resource cycles for every 91 | // processor resource which is consumed by an instruction of the block. 92 | const InstrDesc &Desc = Inst.getDesc(); 93 | NumMicroOps += Desc.NumMicroOps; 94 | for (const std::pair &RU : Desc.Resources) { 95 | if (RU.second.size()) { 96 | unsigned ProcResID = ResIdx2ProcResID[getResourceStateIndex(RU.first)]; 97 | ProcResourceUsage[ProcResID] += RU.second.size(); 98 | } 99 | } 100 | } 101 | 102 | void SummaryView::printView(raw_ostream &OS) const { 103 | std::string Buffer; 104 | raw_string_ostream TempStream(Buffer); 105 | DisplayValues DV; 106 | 107 | collectData(DV); 108 | TempStream << "Iterations: " << DV.Iterations; 109 | TempStream << "\nInstructions: " << DV.TotalInstructions; 110 | TempStream << "\nTotal Cycles: " << DV.TotalCycles; 111 | TempStream << "\nTotal uOps: " << DV.TotalUOps << '\n'; 112 | TempStream << "\nDispatch Width: " << DV.DispatchWidth; 113 | TempStream << "\nuOps Per Cycle: " 114 | << format("%.2f", floor((DV.UOpsPerCycle * 100) + 0.5) / 100); 115 | TempStream << "\nIPC: " 116 | << format("%.2f", floor((DV.IPC * 100) + 0.5) / 100); 117 | TempStream << "\nBlock RThroughput: " 118 | << format("%.1f", floor((DV.BlockRThroughput * 10) + 0.5) / 10) 119 | << '\n'; 120 | TempStream.flush(); 121 | OS << Buffer; 122 | } 123 | 124 | void SummaryView::collectData(DisplayValues &DV) const { 125 | DV.Instructions = GetSourceSize(); 126 | DV.Iterations = (LastInstructionIdx / DV.Instructions) + 1; 127 | DV.TotalInstructions = DV.Instructions * DV.Iterations; 128 | DV.TotalCycles = TotalCycles; 129 | DV.DispatchWidth = DispatchWidth; 130 | DV.TotalUOps = NumMicroOps * DV.Iterations; 131 | DV.UOpsPerCycle = (double)DV.TotalUOps / TotalCycles; 132 | DV.IPC = (double)DV.TotalInstructions / TotalCycles; 133 | DV.BlockRThroughput = computeBlockRThroughput(SM, DispatchWidth, NumMicroOps, 134 | ProcResourceUsage); 135 | } 136 | 137 | json::Value SummaryView::toJSON() const { 138 | DisplayValues DV; 139 | collectData(DV); 140 | json::Object JO({{"Iterations", DV.Iterations}, 141 | {"Instructions", DV.TotalInstructions}, 142 | {"TotalCycles", DV.TotalCycles}, 143 | {"TotaluOps", DV.TotalUOps}, 144 | {"DispatchWidth", DV.DispatchWidth}, 145 | {"uOpsPerCycle", DV.UOpsPerCycle}, 146 | {"IPC", DV.IPC}, 147 | {"BlockRThroughput", DV.BlockRThroughput}}); 148 | return JO; 149 | } 150 | } // namespace mca. 151 | } // namespace llvm 152 | -------------------------------------------------------------------------------- /MCAViews/SummaryView.h: -------------------------------------------------------------------------------- 1 | //===--------------------- SummaryView.h ---------------------*- C++ -*-===// 2 | // 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 | // See https://llvm.org/LICENSE.txt for license information. 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 | // 7 | //===----------------------------------------------------------------------===// 8 | /// \file 9 | /// 10 | /// This file implements the summary view. 11 | /// 12 | /// The goal of the summary view is to give a very quick overview of the 13 | /// performance throughput. Below is an example of summary view: 14 | /// 15 | /// 16 | /// Iterations: 300 17 | /// Instructions: 900 18 | /// Total Cycles: 610 19 | /// Dispatch Width: 2 20 | /// IPC: 1.48 21 | /// Block RThroughput: 2.0 22 | /// 23 | /// The summary view collects a few performance numbers. The two main 24 | /// performance indicators are 'Total Cycles' and IPC (Instructions Per Cycle). 25 | /// 26 | //===----------------------------------------------------------------------===// 27 | 28 | #ifndef LLVM_TOOLS_LLVM_MCA_SUMMARYVIEW_H 29 | #define LLVM_TOOLS_LLVM_MCA_SUMMARYVIEW_H 30 | 31 | #include "MetadataRegistry.h" 32 | #include "llvm/ADT/DenseMap.h" 33 | #include "llvm/ADT/STLExtras.h" 34 | #include "llvm/MC/MCSchedule.h" 35 | #include "llvm/MCA/View.h" 36 | #include "llvm/Support/raw_ostream.h" 37 | 38 | namespace llvm { 39 | class raw_ostream; 40 | 41 | namespace mca { 42 | 43 | /// A view that collects and prints a few performance numbers. 44 | class SummaryView : public View { 45 | const llvm::MCSchedModel &SM; 46 | llvm::function_ref GetSourceSize; 47 | const unsigned DispatchWidth; 48 | unsigned LastInstructionIdx; 49 | unsigned TotalCycles; 50 | // The total number of micro opcodes contributed by a block of instructions. 51 | unsigned NumMicroOps; 52 | 53 | // Used for printing region markers (optional) 54 | mcad::MetadataRegistry *MDRegistry; 55 | llvm::raw_ostream *OutStream; 56 | // List of begin summary strings to be paired by end-marked 57 | // instruciton later 58 | llvm::SmallVector PairingStack; 59 | 60 | struct DisplayValues { 61 | unsigned Instructions; 62 | unsigned Iterations; 63 | unsigned TotalInstructions; 64 | unsigned TotalCycles; 65 | unsigned DispatchWidth; 66 | unsigned TotalUOps; 67 | double IPC; 68 | double UOpsPerCycle; 69 | double BlockRThroughput; 70 | }; 71 | 72 | // For each processor resource, this vector stores the cumulative number of 73 | // resource cycles consumed by the analyzed code block. 74 | llvm::SmallVector ProcResourceUsage; 75 | 76 | // Each processor resource is associated with a so-called processor resource 77 | // mask. This vector allows to correlate processor resource IDs with processor 78 | // resource masks. There is exactly one element per each processor resource 79 | // declared by the scheduling model. 80 | llvm::SmallVector ProcResourceMasks; 81 | 82 | // Used to map resource indices to actual processor resource IDs. 83 | llvm::SmallVector ResIdx2ProcResID; 84 | 85 | // Compute the reciprocal throughput for the analyzed code block. 86 | // The reciprocal block throughput is computed as the MAX between: 87 | // - NumMicroOps / DispatchWidth 88 | // - Total Resource Cycles / #Units (for every resource consumed). 89 | double getBlockRThroughput() const; 90 | 91 | /// Compute the data we want to print out in the object DV. 92 | void collectData(DisplayValues &DV) const; 93 | 94 | public: 95 | SummaryView(const llvm::MCSchedModel &Model, 96 | llvm::function_ref GetSrcSize, unsigned Width, 97 | mcad::MetadataRegistry *MDRegistry = nullptr, 98 | llvm::raw_ostream *OutStream = nullptr); 99 | 100 | void onCycleEnd() override { ++TotalCycles; } 101 | void onEvent(const HWInstructionEvent &Event) override; 102 | void printView(llvm::raw_ostream &OS) const override; 103 | StringRef getNameAsString() const override { return "SummaryView"; } 104 | json::Value toJSON() const override; 105 | }; 106 | } // namespace mca 107 | } // namespace llvm 108 | 109 | #endif 110 | -------------------------------------------------------------------------------- /MCAWorker.h: -------------------------------------------------------------------------------- 1 | #ifndef MCAD_MCAWORKER_H 2 | #define MCAD_MCAWORKER_H 3 | #include "llvm/Option/Option.h" 4 | #include "llvm/MCA/IncrementalSourceMgr.h" 5 | #include "llvm/MCA/SourceMgr.h" 6 | #include "llvm/Support/Error.h" 7 | #include "llvm/Support/Timer.h" 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "BrokerFacade.h" 15 | #include "Brokers/Broker.h" 16 | #include "CustomHWUnits/CustomSourceMgr.h" 17 | 18 | namespace llvm { 19 | class Target; 20 | class ToolOutputFile; 21 | class MCAsmInfo; 22 | class MCContext; 23 | class MCSubtargetInfo; 24 | class MCInst; 25 | class MCInstPrinter; 26 | class MCInstrInfo; 27 | namespace mca { 28 | class Context; 29 | class CustomBehaviour; 30 | class InstrBuilder; 31 | class Pipeline; 32 | class PipelineOptions; 33 | class PipelinePrinter; 34 | class InstrDesc; 35 | class Instruction; 36 | } // end namespace mca 37 | 38 | namespace mcad { 39 | class MCAWorker { 40 | friend class BrokerFacade; 41 | const Target &TheTarget; 42 | const MCSubtargetInfo &STI; 43 | mca::InstrBuilder &MCAIB; 44 | MCContext &Ctx; 45 | const MCAsmInfo &MAI; 46 | const MCInstrInfo &MCII; 47 | MCInstPrinter &MIP; 48 | mca::Context &TheMCA; 49 | const mca::PipelineOptions &MCAPO; 50 | ToolOutputFile &MCAOF; 51 | std::unique_ptr MCAPipeline; 52 | std::unique_ptr MCAPipelinePrinter; 53 | std::set Listeners; 54 | 55 | size_t NumTraceMIs; 56 | // MCAWorker is the owner of this callback. Note that 57 | // SummaryView will only take reference of it. 58 | std::function GetTraceMISize; 59 | 60 | llvm::SourceMgr &SM; 61 | mca::IncrementalSourceMgr SrcMgr; 62 | 63 | mca::CustomBehaviour *CB; 64 | 65 | std::unordered_map> RecycledInsts; 67 | std::function 68 | GetRecycledInst; 69 | std::function AddRecycledInst; 70 | 71 | TimerGroup Timers; 72 | 73 | std::unique_ptr TheBroker; 74 | 75 | MetadataRegistry &MDRegistry; 76 | 77 | std::unique_ptr createDefaultPipeline(); 78 | std::unique_ptr createInOrderPipeline(); 79 | std::unique_ptr createPipeline(); 80 | void resetPipeline(); 81 | 82 | Error runPipeline(); 83 | 84 | void printMCA(StringRef RegionDescription = ""); 85 | 86 | public: 87 | MCAWorker() = delete; 88 | 89 | MCAWorker(const Target &T, const MCSubtargetInfo &STI, mca::Context &MCA, 90 | const mca::PipelineOptions &PO, mca::InstrBuilder &IB, 91 | ToolOutputFile &OF, MCContext &Ctx, const MCAsmInfo &MAI, 92 | const MCInstrInfo &II, MCInstPrinter &IP, MetadataRegistry &MDR, 93 | llvm::SourceMgr &SM); 94 | 95 | BrokerFacade getBrokerFacade() { 96 | return BrokerFacade(*this); 97 | } 98 | 99 | Error run(); 100 | 101 | ~MCAWorker(); 102 | }; 103 | 104 | } // end namespace mcad 105 | } // end namespace llvm 106 | #endif 107 | -------------------------------------------------------------------------------- /MetadataCategories.cpp: -------------------------------------------------------------------------------- 1 | #include "llvm/MCA/Instruction.h" 2 | #include 3 | #include "MetadataRegistry.h" 4 | #include "MetadataCategories.h" 5 | 6 | namespace llvm { 7 | namespace mcad { 8 | 9 | std::optional getMDInstrAddrForInstr(MetadataRegistry &MD, const llvm::mca::InstRef &IR) { 10 | const llvm::mca::Instruction *I = IR.getInstruction(); 11 | auto instrId = I->getIdentifier(); 12 | if (instrId.has_value()) { 13 | auto &Registry = MD[llvm::mcad::MD_InstrAddr]; 14 | auto instrAddr = Registry.get(*instrId); 15 | return instrAddr; 16 | } 17 | return std::nullopt; 18 | } 19 | 20 | } 21 | } -------------------------------------------------------------------------------- /MetadataCategories.h: -------------------------------------------------------------------------------- 1 | #ifndef LLVM_MCA_METADATACATEGORIES_H 2 | #define LLVM_MCA_METADATACATEGORIES_H 3 | 4 | 5 | #include "MetadataRegistry.h" 6 | #include "llvm/MCA/Instruction.h" 7 | 8 | namespace llvm { 9 | namespace mcad { 10 | 11 | enum MetadataCategories { 12 | 13 | // Metadata for LSUnit 14 | MD_LSUnit_MemAccess = 0, 15 | 16 | // Metadata for Branch Prediction 17 | MD_FrontEnd_BranchFlow = 1, 18 | 19 | // Virtual address at which an instruction is located in memory 20 | MD_InstrAddr = 2, 21 | 22 | // Used for marking the start of custom MD Category 23 | MD_LAST, 24 | 25 | // Metadata categories (custom) 26 | MD_BinaryRegionMarkers 27 | 28 | }; 29 | 30 | struct MDInstrAddr { 31 | unsigned long long addr; 32 | unsigned size; 33 | 34 | const bool operator<(const MDInstrAddr &b) const { 35 | return addr + size < b.addr; 36 | } 37 | const bool operator==(const MDInstrAddr &b) const { 38 | return addr == b.addr && size == b.size; 39 | } 40 | const bool operator!=(const MDInstrAddr &b) const { 41 | return addr != b.addr || size != b.size; 42 | } 43 | }; 44 | 45 | std::optional getMDInstrAddrForInstr(MetadataRegistry &MD, const llvm::mca::InstRef &IR); 46 | 47 | } // end namespace mcad 48 | } // end namespace llvm 49 | #endif 50 | -------------------------------------------------------------------------------- /MetadataRegistry.h: -------------------------------------------------------------------------------- 1 | #ifndef LLVM_MCA_METADATAREGISTRY_H 2 | #define LLVM_MCA_METADATAREGISTRY_H 3 | #include "llvm/ADT/Any.h" 4 | #include "llvm/ADT/DenseMap.h" 5 | #include "llvm/Option/Option.h" 6 | #include "llvm/ADT/SmallVector.h" 7 | #include 8 | 9 | namespace llvm { 10 | namespace mcad { 11 | 12 | class MetadataCategory { 13 | // The reason we don't use IndexedMap here 14 | // is because we want an easy way to recycle the space 15 | // in case the number of instructions gets too big. 16 | // However, it is a little difficult to do that with IndexedMap. 17 | DenseMap InstEntries; 18 | 19 | public: 20 | inline bool count(unsigned Idx) const { 21 | return InstEntries.count(Idx); 22 | } 23 | 24 | template 25 | const T *getPtr(unsigned Idx) const { 26 | auto It = InstEntries.find(Idx); 27 | if (It != InstEntries.end()) { 28 | const Any *Res = &It->second; 29 | return any_cast(Res); 30 | } 31 | return nullptr; 32 | } 33 | 34 | template 35 | std::optional get(unsigned Idx) const { 36 | auto It = InstEntries.find(Idx); 37 | if (It != InstEntries.end()) { 38 | const Any &Res = It->second; 39 | return any_cast(Res); 40 | } 41 | return std::nullopt; 42 | } 43 | 44 | Any &operator[](unsigned Idx) { 45 | return InstEntries[Idx]; 46 | } 47 | 48 | inline bool erase(unsigned Idx) { 49 | return InstEntries.erase(Idx); 50 | } 51 | }; 52 | 53 | /// A registry that holds metadata for each CustomInstruction. 54 | /// Each entry is indexed by MDToken within CustomInstruction, and 55 | /// recycled upon being released. 56 | class MetadataRegistry { 57 | SmallVector, 2> Categories; 58 | 59 | public: 60 | MetadataCategory &operator[](unsigned Kind) { 61 | if (Kind >= Categories.size()) { 62 | for (int i = 0, S = Kind - Categories.size() + 1; i < S; ++i) 63 | Categories.emplace_back(nullptr); 64 | } 65 | if (!Categories[Kind]) 66 | Categories[Kind] = std::make_unique(); 67 | return *Categories[Kind]; 68 | } 69 | 70 | bool erase(unsigned Idx) { 71 | bool Success = false; 72 | // Iterate through all categories 73 | for (unsigned i = 0U, S = Categories.size(); i < S; ++i) { 74 | if (auto &Cat = Categories[i]) 75 | Success |= Cat->erase(Idx); 76 | } 77 | return Success; 78 | } 79 | }; 80 | 81 | } // end namespace mcad 82 | } // end namespace llvm 83 | #endif 84 | -------------------------------------------------------------------------------- /PipelinePrinter.cpp: -------------------------------------------------------------------------------- 1 | //===--------------------- PipelinePrinter.cpp ------------------*- C++ -*-===// 2 | // 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 | // See https://llvm.org/LICENSE.txt for license information. 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 | // 7 | //===----------------------------------------------------------------------===// 8 | /// \file 9 | /// 10 | /// This file implements the PipelinePrinter interface. 11 | /// 12 | //===----------------------------------------------------------------------===// 13 | 14 | #include "PipelinePrinter.h" 15 | 16 | namespace llvm { 17 | namespace mca { 18 | 19 | void PipelinePrinter::printReport(llvm::raw_ostream &OS) const { 20 | for (const auto &V : Views) 21 | V->printView(OS); 22 | } 23 | } // namespace mca. 24 | } // namespace llvm 25 | -------------------------------------------------------------------------------- /PipelinePrinter.h: -------------------------------------------------------------------------------- 1 | //===--------------------- PipelinePrinter.h --------------------*- C++ -*-===// 2 | // 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 | // See https://llvm.org/LICENSE.txt for license information. 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 | // 7 | //===----------------------------------------------------------------------===// 8 | /// \file 9 | /// 10 | /// This file implements class PipelinePrinter. 11 | /// 12 | /// PipelinePrinter allows the customization of the performance report. 13 | /// 14 | //===----------------------------------------------------------------------===// 15 | 16 | #ifndef LLVM_TOOLS_LLVM_MCA_PIPELINEPRINTER_H 17 | #define LLVM_TOOLS_LLVM_MCA_PIPELINEPRINTER_H 18 | 19 | #include "llvm/ADT/SmallVector.h" 20 | #include "llvm/MCA/Pipeline.h" 21 | #include "llvm/MCA/View.h" 22 | #include "llvm/Support/raw_ostream.h" 23 | 24 | namespace llvm { 25 | namespace mca { 26 | 27 | /// A printer class that knows how to collects statistics on the 28 | /// code analyzed by the llvm-mca tool. 29 | /// 30 | /// This class knows how to print out the analysis information collected 31 | /// during the execution of the code. Internally, it delegates to other 32 | /// classes the task of printing out timeline information as well as 33 | /// resource pressure. 34 | class PipelinePrinter { 35 | Pipeline &P; 36 | llvm::SmallVector, 8> Views; 37 | 38 | public: 39 | PipelinePrinter(Pipeline &pipeline) : P(pipeline) {} 40 | 41 | void addView(std::unique_ptr V) { 42 | P.addEventListener(V.get()); 43 | Views.emplace_back(std::move(V)); 44 | } 45 | 46 | void printReport(llvm::raw_ostream &OS) const; 47 | }; 48 | } // namespace mca 49 | } // namespace llvm 50 | 51 | #endif // LLVM_TOOLS_LLVM_MCA_PIPELINEPRINTER_H 52 | -------------------------------------------------------------------------------- /RegionMarker.h: -------------------------------------------------------------------------------- 1 | #ifndef MCAD_REGIONMARKER_H 2 | #define MCAD_REGIONMARKER_H 3 | namespace llvm { 4 | namespace mcad { 5 | class RegionMarker { 6 | static constexpr unsigned BeginMark = 0b01; 7 | static constexpr unsigned EndMark = 0b10; 8 | 9 | unsigned Storage : 2; 10 | 11 | explicit RegionMarker(unsigned Storage) : Storage(Storage) {} 12 | 13 | public: 14 | RegionMarker() : Storage(0) {} 15 | 16 | static RegionMarker getBegin() { 17 | return RegionMarker(BeginMark); 18 | } 19 | 20 | static RegionMarker getEnd() { 21 | return RegionMarker(EndMark); 22 | } 23 | 24 | inline bool isBegin() const { return Storage & BeginMark; } 25 | 26 | inline bool isEnd() const { return Storage & EndMark; } 27 | 28 | RegionMarker operator|(const RegionMarker &RHS) const { 29 | return RegionMarker(Storage | RHS.Storage); 30 | } 31 | 32 | RegionMarker &operator|=(const RegionMarker &RHS) { 33 | Storage |= RHS.Storage; 34 | return *this; 35 | } 36 | }; 37 | } // end namespace mcad 38 | } // end namespace llvm 39 | #endif 40 | -------------------------------------------------------------------------------- /Statistics.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef STATISTICS_H 3 | #define STATISTICS_H 4 | 5 | // Stats 6 | // TODO: Move these elsewhere, as they are useful outside of just branch 7 | // prediction or the FetchDelayStage; we could also make use of the event 8 | // infrastructure that already exists (grep for STALL event) 9 | struct OverflowableCount { 10 | unsigned long long count; 11 | bool overflowed; 12 | void inc(unsigned by) { 13 | if(count + by < count) { 14 | overflowed = true; 15 | } 16 | count++; 17 | } 18 | }; 19 | 20 | 21 | #endif -------------------------------------------------------------------------------- /ci/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Running ./docker/setup-llvm.sh" 4 | sudo ./docker/setup-llvm.sh 5 | echo "Running ./docker/setup-deps.sh" 6 | sudo ./docker/setup-deps.sh 7 | echo "Running ./docker/setup-build.sh" 8 | sudo ./docker/setup-build.sh 9 | -------------------------------------------------------------------------------- /cmake/FindLibunwind.cmake: -------------------------------------------------------------------------------- 1 | # Source: https://github.com/m-a-d-n-e-s-s/madness/blob/0b282e29b11e5d62bf2e3cf08019dbf350fb72ed/cmake/modules/FindLibunwind.cmake 2 | # - Try to find Libunwind 3 | # Input variables: 4 | # LIBUNWIND_ROOT_DIR - The libunwind install directory; 5 | # if not set the LIBUNWIND_DIR environment variable will be used 6 | # LIBUNWIND_INCLUDE_DIR - The libunwind include directory 7 | # LIBUNWIND_LIBRARY - The libunwind library directory 8 | # Output variables: 9 | # LIBUNWIND_FOUND - System has libunwind 10 | # LIBUNWIND_INCLUDE_DIRS - The libunwind include directories 11 | # LIBUNWIND_LIBRARIES - The libraries needed to use libunwind 12 | # LIBUNWIND_VERSION - The version string for libunwind 13 | 14 | include(FindPackageHandleStandardArgs) 15 | 16 | if(NOT DEFINED LIBUNWIND_FOUND) 17 | 18 | # if not set already, set LIBUNWIND_ROOT_DIR from environment 19 | if (DEFINED ENV{LIBUNWIND_DIR} AND NOT DEFINED LIBUNWIND_ROOT_DIR) 20 | set(LIBUNWIND_ROOT_DIR $ENV{LIBUNWIND_DIR}) 21 | endif() 22 | 23 | # Set default sarch paths for libunwind 24 | if(LIBUNWIND_ROOT_DIR) 25 | set(LIBUNWIND_INCLUDE_DIR ${LIBUNWIND_ROOT_DIR}/include CACHE PATH "The include directory for libunwind") 26 | if(CMAKE_SIZEOF_VOID_P EQUAL 8 AND CMAKE_SYSTEM_NAME STREQUAL "Linux") 27 | set(LIBUNWIND_LIBRARY ${LIBUNWIND_ROOT_DIR}/lib64;${LIBUNWIND_ROOT_DIR}/lib CACHE PATH "The library directory for libunwind") 28 | else() 29 | set(LIBUNWIND_LIBRARY ${LIBUNWIND_ROOT_DIR}/lib CACHE PATH "The library directory for libunwind") 30 | endif() 31 | endif() 32 | 33 | find_path(LIBUNWIND_INCLUDE_DIRS NAMES libunwind.h 34 | HINTS ${LIBUNWIND_INCLUDE_DIR}) 35 | 36 | find_library(LIBUNWIND_LIBRARIES unwind 37 | HINTS ${LIBUNWIND_LIBRARY}) 38 | 39 | # Get libunwind version 40 | if(EXISTS "${LIBUNWIND_INCLUDE_DIRS}/libunwind-common.h") 41 | file(READ "${LIBUNWIND_INCLUDE_DIRS}/libunwind-common.h" _libunwind_version_header) 42 | string(REGEX REPLACE ".*define[ \t]+UNW_VERSION_MAJOR[ \t]+([0-9]+).*" "\\1" 43 | LIBUNWIND_MAJOR_VERSION "${_libunwind_version_header}") 44 | string(REGEX REPLACE ".*define[ \t]+UNW_VERSION_MINOR[ \t]+([0-9]+).*" "\\1" 45 | LIBUNWIND_MINOR_VERSION "${_libunwind_version_header}") 46 | string(REGEX REPLACE ".*define[ \t]+UNW_VERSION_EXTRA[ \t]+([0-9]+).*" "\\1" 47 | LIBUNWIND_MICRO_VERSION "${_libunwind_version_header}") 48 | set(LIBUNWIND_VERSION "${LIBUNWIND_MAJOR_VERSION}.${LIBUNWIND_MINOR_VERSION}.${LIBUNWIND_MICRO_VERSION}") 49 | unset(_libunwind_version_header) 50 | endif() 51 | 52 | # handle the QUIETLY and REQUIRED arguments and set LIBUNWIND_FOUND to TRUE 53 | # if all listed variables are TRUE 54 | find_package_handle_standard_args(Libunwind 55 | FOUND_VAR LIBUNWIND_FOUND 56 | VERSION_VAR LIBUNWIND_VERSION 57 | REQUIRED_VARS LIBUNWIND_LIBRARIES LIBUNWIND_INCLUDE_DIRS) 58 | 59 | mark_as_advanced(LIBUNWIND_INCLUDE_DIR LIBUNWIND_LIBRARY 60 | LIBUNWIND_INCLUDE_DIRS LIBUNWIND_LIBRARIES) 61 | 62 | endif() 63 | -------------------------------------------------------------------------------- /doc/cache-simulation.md: -------------------------------------------------------------------------------- 1 | # Cache Simulation 2 | Our modified version of MCA allows users to simulate cache accesses and take into consideration of their latencies. 3 | 4 | ## Usage 5 | To use this feature, please supply the path to cache configuration file via the `-cache-sim-config=` command line option. For example: 6 | ```bash 7 | llvm-mcad ... -mcpu=skylake -cache-sim-config=skylake.cache-config.json ... 8 | ``` 9 | The following section explains the format of the configuration file. 10 | 11 | ## Config file format 12 | The configuration file is written in JSON. The top level should always be an object, which encloses fields representing a specific level of cache. For example, the following JSON presents the config for a L1 data cache: 13 | ```json 14 | { 15 | "l1d": { 16 | "size": 4096, 17 | "associate": 1, 18 | "line_size": 64 19 | } 20 | } 21 | ``` 22 | Currently we only support "l1d" and "l2d" keys for L1D and L2D caches, respectively. 23 | 24 | Each cache level entry can have the following properties: 25 | - `size`. Total size of this cache. 26 | - `associate`. Number of cache association. 27 | - `line_size`. Size of a cache line. 28 | - `penalty`. Number of penalty cycles if a cache miss happens. 29 | 30 | All of these fields are optional. 31 | 32 | ## Note 33 | LLVM _can_ provide cache configuration for each CPU (via `TargetTransformInfo`). However, in order to retrieve these number we need to import libraries for backend targets and it's not trivial to get a `TargetTransformInfo` instance either. What's worse, many mainstream targets (e.g. ARM) don't provide those information. Therefore, I found it easier to just use configuration file. -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # [2024/04/17:JCC] Force platform otherwise it does not work on Apple silicon. 2 | FROM --platform=linux/amd64 ubuntu:22.04 3 | 4 | RUN yes | unminimize \ 5 | && apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y locales && rm -rf /var/lib/apt/lists/* \ 6 | && localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 7 | ENV LANG en_US.utf8 8 | 9 | WORKDIR /work 10 | 11 | COPY . LLVM-MCA-Daemon/ 12 | 13 | COPY docker/setup-ssh.sh ./setup/ 14 | RUN setup/setup-ssh.sh 15 | 16 | COPY docker/setup-deps.sh ./setup/ 17 | RUN setup/setup-deps.sh 18 | 19 | COPY docker/setup-llvm.sh ./setup/ 20 | RUN setup/setup-llvm.sh 21 | 22 | COPY docker/setup-build.sh ./setup/ 23 | RUN setup/setup-build.sh 24 | 25 | ENV PATH="/work/mcad-build:/opt/llvm-14/bin:$PATH" 26 | RUN rm /etc/apt/apt.conf.d/docker-clean 27 | 28 | ENTRYPOINT ["llvm-mcad"] 29 | 30 | # [2024/04/17:JCC] Expose the MCAD server port. 31 | EXPOSE 50052 32 | -------------------------------------------------------------------------------- /docker/compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | dev: 3 | build: 4 | context: .. 5 | dockerfile: ./docker/Dockerfile 6 | volumes: 7 | - ../../:/work/host 8 | # [2024/04/17:JCC] No need to mount home dir, its a security risk. 9 | #- ${HOME}:${HOME} 10 | hostname: ${DOCKER_HOSTNAME}-focal 11 | # [2024/04/17:JCC] No need to set timezone 12 | #environment: 13 | # - TZ=America/Los_Angeles 14 | # [2024/04/17:JCC] No need for this, security risk. 15 | #privileged: true 16 | -------------------------------------------------------------------------------- /docker/setup-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | export DEBIAN_FRONTEND=noninteractive 6 | 7 | if [ -z "${WORKSPACE_PATH}" ]; then 8 | WORKSPACE_PATH=/work 9 | fi 10 | 11 | mkdir -p ${WORKSPACE_PATH}/mcad-build 12 | mkdir -p ${WORKSPACE_PATH}/mcad-install 13 | cd ${WORKSPACE_PATH}/mcad-build 14 | 15 | cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=${WORKSPACE_PATH}/mcad-install \ 16 | -DCMAKE_C_COMPILER=clang-14 -DCMAKE_CXX_COMPILER=clang++-14 \ 17 | -DLLVM_DIR=${WORKSPACE_PATH}/llvm-install/lib/cmake/llvm \ 18 | -DLLVM_MCAD_ENABLE_PLUGINS="tracer;binja;vivisect" \ 19 | ${WORKSPACE_PATH}/LLVM-MCA-Daemon 20 | ninja 21 | -------------------------------------------------------------------------------- /docker/setup-deps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script probably needs to run as sudo. 4 | 5 | set -e 6 | 7 | export DEBIAN_FRONTEND=noninteractive 8 | 9 | dpkg --add-architecture i386 10 | 11 | # General dependencies -- 12 | apt-get update 13 | apt-get install -y \ 14 | autoconf \ 15 | binutils-dev \ 16 | curl \ 17 | g++ \ 18 | g++-11-multilib \ 19 | gcc \ 20 | gcc-11-multilib \ 21 | gfortran \ 22 | gfortran-11-multilib \ 23 | libboost-dev \ 24 | libboost-regex-dev \ 25 | libboost-serialization-dev \ 26 | libboost-system-dev \ 27 | libbsd-dev \ 28 | libelf-dev \ 29 | libelf-dev:i386 \ 30 | libglib2.0-dev \ 31 | libglib2.0-dev:i386 \ 32 | libmemcached-dev \ 33 | libpixman-1-dev \ 34 | libpng-dev \ 35 | libtool \ 36 | mingw-w64 \ 37 | nasm \ 38 | pkg-config \ 39 | python-is-python3 \ 40 | python2 \ 41 | python3-magic \ 42 | python3-pip \ 43 | python3-pyelftools \ 44 | tar \ 45 | unzip \ 46 | wget \ 47 | zip \ 48 | zstd \ 49 | `# And some utilities` \ 50 | bash-completion \ 51 | byobu \ 52 | ccache \ 53 | htop \ 54 | vim \ 55 | nano 56 | 57 | # Dependencies to build LLVM -- 58 | apt-get update 59 | apt-get install -y software-properties-common wget 60 | wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc 61 | add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-14 main" 62 | apt-get update 63 | apt-get install -y \ 64 | build-essential \ 65 | clang-14 \ 66 | cmake \ 67 | lld-14 \ 68 | git \ 69 | ninja-build 70 | 71 | rm -rf /var/lib/apt/lists/* 72 | 73 | rm -rf /root/.cache 74 | -------------------------------------------------------------------------------- /docker/setup-llvm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | export DEBIAN_FRONTEND=noninteractive 6 | 7 | if [ -z "${WORKSPACE_PATH}" ]; then 8 | WORKSPACE_PATH=/work 9 | fi 10 | 11 | # Run ./setup-deps.sh before this to install dependencies needed to build LLVM 12 | 13 | # Applying the patches requires that our git user has an identity. 14 | git config --global user.email "workflow@example.com" 15 | git config --global user.name "Workflow" 16 | git clone https://github.com/llvm/llvm-project.git llvm 17 | cd llvm 18 | git am ${WORKSPACE_PATH}/LLVM-MCA-Daemon/patches/*.patch 19 | mkdir build && cd build 20 | cmake -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=${WORKSPACE_PATH}/llvm-install \ 21 | -DCMAKE_C_COMPILER=clang-14 -DCMAKE_CXX_COMPILER=clang++-14 \ 22 | -DLLVM_USE_LINKER=lld-14 -DLLVM_ENABLE_ASSERTIONS=ON \ 23 | -DLLVM_TOOL_LLVM_MCA_BUILD=ON \ 24 | -DLLVM_TARGETS_TO_BUILD="AArch64;ARM;X86;PowerPC" -DLLVM_DEFAULT_TARGET_TRIPLE=x86_64-pc-linux-gnu \ 25 | ../llvm 26 | ninja llvm-mca llvm-mc LLVMDebugInfoDWARF 27 | ninja install 28 | -------------------------------------------------------------------------------- /docker/setup-ssh.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | export DEBIAN_FRONTEND=noninteractive 6 | 7 | apt-get update 8 | apt-get install -y openssh-server 9 | rm -rf /var/lib/apt/lists/* 10 | service ssh start 11 | -------------------------------------------------------------------------------- /docker/up: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | DOCKER_SSH_PORT=$((DOCKER_UID + 1222)) 6 | export DOCKER_SSH_PORT 7 | DOCKER_HOSTNAME="$(hostname)-mcad" 8 | export DOCKER_HOSTNAME 9 | DOCKER_USER=$(id -un) 10 | export DOCKER_USER 11 | 12 | # [2024/04/17:JCC] We want a constant docker name for use in the PATE Binja Plugin 13 | # [2024/04/17:JCC] Don't want up, just build the container 14 | # [2024/04/18:JCC] If having cache issus add --pull --no-cache 15 | docker-compose --project-name "mcad" build 16 | -------------------------------------------------------------------------------- /eval/branch_prediction/Makefile: -------------------------------------------------------------------------------- 1 | perf?=/usr/lib/linux-tools-5.15.0-130/perf # Path to perf binary $(which perf) 2 | # May be hard-coded like this when using a modified kernel and the corresponding 3 | # linux-tools aren't installed (at your own risk) 4 | 5 | BP_TARGETS=bp_0 bp_1 bp_2 bp_3 bp_4 bp_5 bp_6 bp_7 bp_8 6 | 7 | .PHONY: all 8 | all: $(BP_TARGETS) 9 | 10 | bp_%.S: gen_bp.py 11 | ./gen_bp.py $* > $@ 12 | 13 | .NOTINTERMEDIATE: $(BP_TARGETS:%=%.S) 14 | bp_%: bp_%.S 15 | gcc -no-pie -nostdlib -O0 -o $@ $< 16 | 17 | .PHONY: eval 18 | eval: $(BP_TARGETS:%=eval_%.txt) 19 | 20 | eval_%.txt: % 21 | $(perf) stat -e cycles,branches,branch-misses ./$^ 2>&1 | tee $@ 22 | 23 | .PHONY: clean 24 | clean: 25 | rm bp_* eval_*.txt 26 | -------------------------------------------------------------------------------- /eval/branch_prediction/gen_bp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import random 4 | import sys 5 | from textwrap import dedent 6 | 7 | it=10_000 8 | inlined_iters_p2 = 3 # 2**3 = 8 9 | assert it % (2**inlined_iters_p2) == 0 10 | assert it < 2**64 11 | # If we have less randomness below than total iterations, the branch predictor 12 | # will learn form the global history: The first branch in the iteration of 8 13 | # can then be used to predict the behavior of the next 7, because they will all 14 | # use the same randomness as on an earlier iteration. It might be possible to 15 | # estimate the size of the global history by adjusting the amount of randomness. 16 | n_randbits_p2=15 # 2**12 = 4096 17 | 18 | n_random=int(sys.argv[1]) # number out of 8 that will be made random 19 | assert 0 <= n_random <= 8 20 | 21 | random.seed(99837212) 22 | randbits = random.getrandbits(2**n_randbits_p2) 23 | for i in range(2**n_randbits_p2//(2**inlined_iters_p2)): 24 | for j in range(2**inlined_iters_p2-n_random): 25 | randbits &= ~(1<<(i*2**inlined_iters_p2+j)) 26 | 27 | print(f""" 28 | .globl _start 29 | .p2align 4, 0x90 30 | _start: 31 | xorq %r8, %r8 32 | xorq %r9, %r9 33 | xorq %rax, %rax # outer loop induction variable 34 | 35 | loop_head: 36 | cmpq ${it//inlined_iters_p2}, %rax 37 | jge loop_end 38 | """); 39 | 40 | for i in range(2**inlined_iters_p2): 41 | print(f""" 42 | # The following 8 instructions load the next random bit from the randomness 43 | # byte array below, by first loading the correct byte offset 44 | # (loop_iteration/8) from randomness, then shifting (loop_iteration%8) to 45 | # the right to get the right bit out of the byte. 46 | movq %rax, %rcx # %rcx will be used to hold byte offset into array 47 | andq ${2**n_randbits_p2//2**inlined_iters_p2-1}, %rcx # %rcx mod size of the slice of randomness array for this inlined iter (in bits) 48 | addq ${(2**n_randbits_p2//2**inlined_iters_p2)*i}, %rcx 49 | shrq $3, %rcx # %rcx / 8 to get byte offset 50 | mov randomness(%rcx), %dl # load randomness into %dl 51 | mov %rax, %rcx 52 | and $7, %rcx 53 | shr %rcx, %dl 54 | and $1, %dl # take lowest bit of randomness 55 | 56 | # Actual comparison and branch; this is where we'll get our mispredicts 57 | cmpb $0, %dl 58 | je branch_{i}_true 59 | branch_{i}_false: 60 | addq $1, %r8 # keep track of not-taken branches in %r8 61 | branch_{i}_true: 62 | """) 63 | 64 | print(f""" 65 | loop_foot: 66 | addq $1, %rax 67 | jmp loop_head 68 | loop_end: 69 | xorl %eax, %eax 70 | 71 | // the following performs the exit system call with a 0 exit code 72 | movl $60, %eax 73 | xorl %ebx, %ebx 74 | syscall 75 | 76 | .data 77 | randomness: 78 | .align 8 79 | .byte {", ".join("0x{0:x}".format((randbits>>(8*i))&0xFF) for i in range(2**n_randbits_p2//8))} 80 | 81 | """) -------------------------------------------------------------------------------- /eval/branch_prediction/plot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import matplotlib.pyplot as plt 4 | import seaborn as sns 5 | 6 | # the following data was obtained with: 7 | # repository commit: 8 | # MCAD command: 9 | # ./llvm-mcad -mtriple="x86_64-unknown-linux-gnu" -mcpu="skylake" --load-broker-plugin=plugins/qemu-broker/libMCADQemuBroker.so -broker-plugin-arg-host="localhost:9487" -enable-cache=0 -enable-branch-predictor=Naive -bht-size=32 -mispredict-delay=17 10 | # MCAD (w/o BP) command: 11 | # ./llvm-mcad -mtriple="x86_64-unknown-linux-gnu" -mcpu="skylake" --load-broker-plugin=plugins/qemu-broker/libMCADQemuBroker.so -broker-plugin-arg-host="localhost:9487" -enable-cache=0 -enable-branch-predictor=None 12 | # QEMU command: 13 | # ~/mcad/qemu/build/qemu-x86_64 -plugin ~/mcad/LLVM-MCA-Daemon/build/plugins/qemu-broker/Qemu/libQemuRelay.so,arg="-addr=127.0.0.1",arg="-port=9487" -d plugin ./bp_0 through ./bp_8 14 | 15 | x_groups = ( "0%", "12%", "25%", "38%", "50%", "62%", "75%", "88%", "100%") 16 | cycle_data = { 17 | 'perf': ( 85_521, 108_893, 121_759, 141_260, 160_082, 185_161, 197_857, 227_740, 242_077), 18 | 'MCAD': ( 83_815, 126_959, 152_327, 177_008, 206_603, 228_926, 258_991, 285_141, 288_296), 19 | 'MCAD (w/o BP)': ( 83_338, 84_166, 84_528, 84_891, 85_267, 85_579, 85_920, 86_280, 86_711), 20 | } 21 | mispredict_data = { 22 | 'perf': ( 9, 821, 1_228, 1_964, 2_647, 3_548, 4_001, 5_067, 5_578), 23 | 'MCAD': ( 10, 3208, 4_898, 6_542, 8_491, 9_981, 11_845, 13_417, 13_587), 24 | } 25 | 26 | def grouped_bar_plot(ax, x_groups, data): 27 | width = 0.25 # the width of the bars 28 | multiplier = 0 29 | xs = [((len(data)+1)*width)*x for x in range(len(x_groups))] # the label locations 30 | 31 | for attribute, measurement in data.items(): 32 | offset = width * multiplier 33 | rects = ax.bar([x + offset for x in xs], measurement, width, label=attribute) 34 | #ax.bar_label(rects, padding=3) 35 | multiplier += 1 36 | 37 | ax.set_xticks([x + (width*(len(data)-1)/2.0) for x in xs], x_groups) 38 | 39 | sns.set_theme(palette="terrain") 40 | sns.set_style("whitegrid") 41 | 42 | fig, (ax1, ax2) = plt.subplots(2, layout='constrained') 43 | 44 | grouped_bar_plot(ax1, x_groups, cycle_data) 45 | ax1.set_ylabel('Cycle count') 46 | ax1.legend(loc='upper left', ncols=3) 47 | ax1.set_xlabel('Proportion of random branches in inner loop') 48 | 49 | grouped_bar_plot(ax2, x_groups, mispredict_data) 50 | ax2.set_ylabel('Branch mispredicts') 51 | ax2.set_xlabel('Proportion of random branches in inner loop') 52 | ax2.legend(loc='upper left', ncols=3) 53 | 54 | fig.suptitle('Cycle count estimation using branch predictor modeling') 55 | 56 | sns.despine() 57 | 58 | plt.savefig("plot.png") -------------------------------------------------------------------------------- /eval/cache/Makefile: -------------------------------------------------------------------------------- 1 | perf?=/usr/lib/linux-tools-5.15.0-130/perf # Path to perf binary $(which perf) 2 | # May be hard-coded like this when using a modified kernel and the corresponding 3 | # linux-tools aren't installed (at your own risk) 4 | 5 | TARGETS=x 6 | 7 | .PHONY: all 8 | all: $(TARGETS) 9 | 10 | x.S: gen_cache.py 11 | ./gen_cache.py 0 > $@ 12 | 13 | .NOTINTERMEDIATE: x.S 14 | x: x.S 15 | gcc -no-pie -nostdlib -O0 -o $@ $< 16 | 17 | .PHONY: clean 18 | clean: 19 | rm x 20 | -------------------------------------------------------------------------------- /eval/cache/gen_cache.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # blackforest (i9-9900K) stats: 4 | # L1d: 256 KiB (8 instances) -> 32 KB per core -- 8-way 5 | # L1i: 256 KiB (8 instances) -> 32 KB per core -- 8-way 6 | # L2: 2 MiB (8 instances) -- 4-way 7 | # L3: 16 MiB (1 instance) -- 16-way 8 | 9 | # source: https://en.wikichip.org/wiki/intel/microarchitectures/skylake_(client) 10 | # 11 | # perf stat -e "cache-misses,L1-dcache-load-miss,l2_rqsts.miss,offcore_requests.all_requests" 12 | # Try: 13 | # - active_set_size > 600, stride == 1: 14 | # this is a larger active set than L1 cache set, should see ~100,000 L1 cache misses 15 | # - active_set_size > 64 (e.g. 74), stride == 8: 16 | # since L1 cache is 8-way, using a stride == 8 means each access will map to a 17 | # separate set, i.e. we are only utilizing 1 way in each set, effectively 18 | # dividing L1 cache size by 8; should se ~100,000 L1 cache misses 19 | 20 | import sys 21 | 22 | n_to_read = 1<<30-1 # 1 GB 23 | 24 | it=10_000 25 | 26 | cache_line_size = 64 # in bytes 27 | active_set_size = 128 # number of cache lines that we use; once this increases past cache size, we should see a spike in cache misses 28 | 29 | # stride in units of `cache_line_size`; how many cache lines to stride between accesses; 1 means contiguous cache lines are accessed 30 | # contiguous accesses mean that the tag bits of the address are likely different between accesses; 31 | # meaning that there will be little contention / few conflict misses 32 | # making the stride large enough that only the bits outside of the tag change means that back-to-back accesses map to the 33 | # same cache set, leading to conflict misses once the active set size exceeds the number of ways of the cache 34 | stride = 24 35 | 36 | print(f""" 37 | .globl _start 38 | .p2align 4, 0x90 39 | _start: 40 | xorq %r8, %r8 41 | xorq %r9, %r9 42 | xor %ebx, %ebx # outer loop induction variable 43 | xor %edx, %edx # upper half of divison; we don't use it 44 | 45 | loop_head: 46 | cmp ${it}, %ebx 47 | jge loop_end 48 | 49 | mov %ebx, %eax # div operates on eax 50 | mov ${active_set_size}, %ecx 51 | div %ecx # div puts the remainder in edx 52 | 53 | mov %edx, %eax # mul operates on eax 54 | mov ${stride*cache_line_size}, %ecx 55 | mul %ecx 56 | 57 | # this is the read we are interested in benchmarking 58 | mov to_read(%eax), %edx 59 | 60 | # write back to memory; I believe this stops the prefetcher 61 | # without this line, we do not get L1 cache hits as expected when the 62 | # active set size is larger than L1 cache 63 | mov %edx, to_read(%eax) 64 | 65 | loop_foot: 66 | add $1, %ebx 67 | jmp loop_head 68 | loop_end: 69 | xor %eax, %eax 70 | 71 | // the following performs the exit system call with a 0 exit code 72 | movl $60, %eax 73 | xorl %ebx, %ebx 74 | syscall 75 | 76 | .lcomm to_read, {n_to_read} // 1 GB 77 | 78 | """) -------------------------------------------------------------------------------- /eval/cache/plot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import matplotlib.pyplot as plt 4 | import seaborn as sns 5 | import csv 6 | 7 | # the following data was obtained with: 8 | # - vary stride and active_set_size in gen_cache.py before compiling 9 | 10 | # repository commit: 11 | 12 | # baseline (CPU/perf): 13 | # pushd LLVM-MCA-Daemon/eval/cache/; make; popd; /usr/lib/linux-tools-5.15.0-97/perf stat -e "cache-misses,L1-dcache-load-miss,l2_rqsts.miss,offcore_requests.all_requests" ./LLVM-MCA-Daemon/eval/cache/x 14 | 15 | # MCAD command: 16 | # ./LLVM-MCA-Daemon/build/llvm-mcad -mtriple="x86_64-unknown-linux-gnu" -mcpu="skylake" --load-broker-plugin=./LLVM-MCA-Daemon/build/plugins/qemu-broker/libMCADQemuBroker.so -broker-plugin-arg-host="localhost:9487" --enable-cache=L1d --enable-cache=L2 --enable-cache=L3cat 17 | 18 | # QEMU command: 19 | # ./qemu/build/qemu-x86_64 -plugin ./LLVM-MCA-Daemon/build/plugins/qemu-broker/Qemu/libQemuRelay.so,arg="-addr=127.0.01",arg="-port=9487" -d plugin ./LLVM-MCA-Daemon/eval/cache/x 20 | 21 | data = {'perf':{}, 'mcad':{}} # map: bench -> (size -> (stride -> (l1-misses, l2-misses, l3-misses))) 22 | with open('results_10000.csv') as csvfile: 23 | reader = csv.reader(csvfile) 24 | next(reader) # skip header 25 | for bench, size, stride, l1m, l2m, l3m, _ in reader: 26 | bench=bench.strip() 27 | size=int(size) 28 | stride=int(stride) 29 | l1m=int(l1m) 30 | l2m=int(l2m) 31 | l3m=int(l3m) 32 | if size not in data[bench]: 33 | data[bench][size] = {} 34 | data[bench][size][stride] = (l1m, l2m, l3m) 35 | 36 | 37 | def grouped_bar_plot(ax, x_groups, data): 38 | assert all(len(d) == len(x_groups) for d in data.values()) 39 | width = 0.25 # the width of the bars 40 | multiplier = 0 41 | xs = [((len(data)+1)*width)*x for x in range(len(x_groups))] # the label locations 42 | 43 | for attribute, measurement in data.items(): 44 | offset = width * multiplier 45 | rects = ax.bar([x + offset for x in xs], measurement, width, label=attribute) 46 | #ax.bar_label(rects, padding=3) 47 | multiplier += 1 48 | 49 | ax.set_xticks([x + (width*(len(data)-1)/2.0) for x in xs], x_groups) 50 | 51 | 52 | x_group_sizes = (128, 256, 512, 1024, 2048, 4096) #, 8192) 53 | size_misses = { 54 | 'perf L1 Misses': [data['perf'][size][1][0] for size in x_group_sizes], 55 | 'MCAD L1 Misses': [data['mcad'][size][1][0] for size in x_group_sizes] 56 | } 57 | 58 | x_group_strides = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16) #, 24) 59 | stride_misses = { 60 | 'perf L1 Misses': [data['perf'][128][stride][0] for stride in x_group_strides], 61 | 'MCAD L1 Misses': [data['mcad'][128][stride][0] for stride in x_group_strides] 62 | } 63 | 64 | sns.set_theme(palette="terrain") 65 | sns.set_style("whitegrid") 66 | 67 | fig, (ax1, ax2) = plt.subplots(2, layout='constrained') 68 | 69 | grouped_bar_plot(ax1, list(map(str, x_group_sizes)), size_misses) 70 | ax1.set_ylabel('Cache Misses') 71 | ax1.legend(loc='upper left', ncols=3) 72 | ax1.set_xlabel('Active Set Size (# cache lines)') 73 | ax1.set_ylim([0, 13_000]) 74 | 75 | grouped_bar_plot(ax2, list(map(str, x_group_strides)), stride_misses) 76 | ax2.set_ylabel('Cache Misses') 77 | ax2.set_xlabel('Stride Between Accessed Elements (# cache lines)') 78 | ax2.legend(loc='upper left', ncols=3) 79 | ax2.set_ylim([0, 13_000]) 80 | 81 | fig.suptitle('Cache Simulation') 82 | 83 | sns.despine() 84 | 85 | plt.savefig("plot.png") -------------------------------------------------------------------------------- /eval/cache/results_10000.csv: -------------------------------------------------------------------------------- 1 | bench, size, stride, l1-misses, l2-misses, l3-misses, 2 | perf, 128, 1, 15, 38, 53, 3 | perf, 256, 1, 73, 146, 194, 4 | perf, 512, 1, 379, 462, 700, 5 | perf, 1024, 1, 9045, 3590, 6019, 6 | perf, 2048, 1, 8112, 3370, 4854, 7 | perf, 4096, 1, 6210, 6439, 9487, 8 | perf, 8192, 1, 2440, 2502, 3744, 9 | perf, 128, 2, 50, 101, 132, 10 | perf, 128, 3, 82, 206, 281, 11 | perf, 128, 4, 110, 246, 327, 12 | perf, 128, 5, 117, 309, 440, 13 | perf, 128, 6, 142, 301, 430, 14 | perf, 128, 7, 164, 378, 509, 15 | perf, 128, 8, 9953, 1734, 1891, 16 | perf, 128, 9, 175, 395, 672, 17 | perf, 128, 10, 177, 363, 597, 18 | perf, 128, 11, 196, 354, 553, 19 | perf, 128, 12, 203, 393, 620, 20 | perf, 128, 13, 204, 387, 570, 21 | perf, 128, 14, 214, 366, 584, 22 | perf, 128, 15, 245, 356, 549, 23 | perf, 128, 16, 10017, 3966, 5040, 24 | perf, 128, 24, 10025, 2536, 2803, 25 | mcad, 128, 1, 128, 128, 128, 26 | mcad, 256, 1, 256, 256, 256, 27 | mcad, 512, 1, 10000, 512, 512, 28 | mcad, 1024, 1, 10000, 1024, 1024, 29 | mcad, 2048, 1, 10000, 2048, 2048, 30 | mcad, 4096, 1, 10000, 4096, 4096, 31 | mcad, 8192, 1, 10000, 8192, 8192, 32 | mcad, 128, 2, 128, 128, 128, 33 | mcad, 128, 3, 128, 128, 128, 34 | mcad, 128, 4, 128, 128, 128, 35 | mcad, 128, 5, 128, 128, 128, 36 | mcad, 128, 6, 128, 128, 128, 37 | mcad, 128, 7, 128, 128, 128, 38 | mcad, 128, 8, 10000, 128, 128, 39 | mcad, 128, 9, 128, 128, 128, 40 | mcad, 128, 10, 128, 128, 128, 41 | mcad, 128, 11, 128, 128, 128, 42 | mcad, 128, 12, 128, 128, 128, 43 | mcad, 128, 13, 128, 128, 128, 44 | mcad, 128, 14, 128, 128, 128, 45 | mcad, 128, 15, 128, 128, 128, 46 | mcad, 128, 16, 10000, 128, 128, 47 | mcad, 128, 24, 10000, 128, 128, -------------------------------------------------------------------------------- /patches/0001-MCAD-Patch-0-Add-identifier-field-to-mca-Instruction.patch: -------------------------------------------------------------------------------- 1 | From 25275350e76ab580cb2a31d55144a07d3f7927fc Mon Sep 17 00:00:00 2001 2 | From: Chinmay Deshpande 3 | Date: Fri, 5 Jul 2024 16:05:25 -0700 4 | Subject: [PATCH 1/3] [MCAD Patch 0] Add identifier field to mca::Instruction 5 | 6 | --- 7 | llvm/include/llvm/MCA/Instruction.h | 6 ++++++ 8 | 1 file changed, 6 insertions(+) 9 | 10 | diff --git a/llvm/include/llvm/MCA/Instruction.h b/llvm/include/llvm/MCA/Instruction.h 11 | index e48a70164bec..4fb0a62ac7f8 100644 12 | --- a/llvm/include/llvm/MCA/Instruction.h 13 | +++ b/llvm/include/llvm/MCA/Instruction.h 14 | @@ -643,6 +643,9 @@ class Instruction : public InstructionBase { 15 | // True if this instruction has been optimized at register renaming stage. 16 | bool IsEliminated; 17 | 18 | + // Client-dependent identifier for this instruction. 19 | + std::optional Identifier; 20 | + 21 | public: 22 | Instruction(const InstrDesc &D, const unsigned Opcode) 23 | : InstructionBase(D, Opcode), Stage(IS_INVALID), 24 | @@ -690,6 +693,9 @@ public: 25 | bool isRetired() const { return Stage == IS_RETIRED; } 26 | bool isEliminated() const { return IsEliminated; } 27 | 28 | + std::optional getIdentifier() const { return Identifier; } 29 | + void setIdentifier(uint64_t Id) { Identifier = Id; } 30 | + 31 | // Forces a transition from state IS_DISPATCHED to state IS_EXECUTED. 32 | void forceExecuted(); 33 | void setEliminated() { IsEliminated = true; } 34 | -- 35 | 2.39.5 (Apple Git-154) 36 | 37 | -------------------------------------------------------------------------------- /patches/0002-MCAD-Patch-2-WIP-Start-mapping-e500-itinerary-model-.patch: -------------------------------------------------------------------------------- 1 | From a6d7d2bd5fe34c424f6587ef8519b471f82afa7d Mon Sep 17 00:00:00 2001 2 | From: Ayrton Munoz 3 | Date: Fri, 1 Dec 2023 16:15:29 -0500 4 | Subject: [PATCH 3/3] [MCAD Patch 2] WIP Start mapping e500 itinerary model to 5 | new scheduling model 6 | 7 | See comment on `class WriteRes` in llvm/include/llvm/Target/TargetSchedule.td 8 | for a starting point on how to map the old (itinerary) model to the new 9 | scheduling model. As far as I've seen ARMScheduleA9.td is the only cpu model 10 | currently doing this mapping so this work is based off of that and the existing 11 | info in the e500 model. 12 | --- 13 | llvm/lib/Target/PowerPC/PPCScheduleE500.td | 80 ++++++++++++++++++++++ 14 | 1 file changed, 80 insertions(+) 15 | 16 | diff --git a/llvm/lib/Target/PowerPC/PPCScheduleE500.td b/llvm/lib/Target/PowerPC/PPCScheduleE500.td 17 | index 74744dda54f7..e070130859d4 100644 18 | --- a/llvm/lib/Target/PowerPC/PPCScheduleE500.td 19 | +++ b/llvm/lib/Target/PowerPC/PPCScheduleE500.td 20 | @@ -273,7 +273,87 @@ def PPCE500Model : SchedMachineModel { 21 | // This is overriden by OperandCycles if the 22 | // Itineraries are queried instead. 23 | 24 | + let MicroOpBufferSize = 14; 25 | let CompleteModel = 0; 26 | 27 | let Itineraries = PPCE500Itineraries; 28 | } 29 | + 30 | +let SchedModel = PPCE500Model in { 31 | + 32 | + // ********** Processor Resources ********** 33 | + def DIS: ProcResource<2>; 34 | + def SU: ProcResource<2>; 35 | + def BU: ProcResource<1>; 36 | + def MU: ProcResource<1>; 37 | + def LSU: ProcResource<1>; 38 | + 39 | + // ********** SchedWriteRes Definitions ********** 40 | + def DIS_1C : SchedWriteRes<[DIS]> {} 41 | + def SU_1C : SchedWriteRes<[SU]> { } 42 | + def BU_1C : SchedWriteRes<[BU]> { } 43 | + def MU_1C : SchedWriteRes<[MU]> { } 44 | + def LSU_1C : SchedWriteRes<[LSU]> { } 45 | + def LSU_6C : SchedWriteRes<[LSU]> { 46 | + let Latency = 6; 47 | + } 48 | + 49 | + // double check these SchedWriteRes choices 50 | + def : ItinRW<[SU_1C, DIS_1C], [IIC_IntSimple]>; 51 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_LdStSTU]>; 52 | + 53 | + // using dummy values from previous entry 54 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_IntGeneral]>; 55 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_IntISEL]>; 56 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_IntCompare]>; 57 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_IntDivW]>; 58 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_IntMulHW]>; 59 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_IntMulHWU]>; 60 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_IntMulLI]>; 61 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_IntRotate]>; 62 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_IntShift]>; 63 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_IntTrapW]>; 64 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_BrB]>; 65 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_BrCR]>; 66 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_BrMCR]>; 67 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_BrMCRX]>; 68 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_LdStDCBA]>; 69 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_LdStDCBF]>; 70 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_LdStDCBI]>; 71 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_LdStLoad]>; 72 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_LdStLoadUpd]>; 73 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_LdStLoadUpdX]>; 74 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_LdStStore]>; 75 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_LdStSTUX]>; 76 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_LdStICBI]>; 77 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_LdStLHA]>; 78 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_LdStLHAU]>; 79 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_LdStLHAUX]>; 80 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_LdStLMW]>; 81 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_LdStLWARX]>; 82 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_LdStSTWCX]>; 83 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_LdStSync]>; 84 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_SprMFSR]>; 85 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_SprMTMSR]>; 86 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_SprMTSR]>; 87 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_SprTLBSYNC]>; 88 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_SprMFCR]>; 89 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_SprMFCRF]>; 90 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_SprMFPMR]>; 91 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_SprMFMSR]>; 92 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_SprMFSPR]>; 93 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_SprMTPMR]>; 94 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_SprMFTB]>; 95 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_SprMTSPR]>; 96 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_SprMTSRIN]>; 97 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_FPDGeneral]>; 98 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_FPSGeneral]>; 99 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_FPDivD]>; 100 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_FPDivS]>; 101 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_VecGeneral]>; 102 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_VecComplex]>; 103 | + 104 | + // not included as part of the original e500 itinerary-based model 105 | + def : ItinRW<[LSU_6C, SU_1C, DIS_1C], [IIC_SprISYNC]>; 106 | + 107 | +} // SchedModel = PPCE500Model 108 | -- 109 | 2.39.5 (Apple Git-154) 110 | 111 | -------------------------------------------------------------------------------- /plugins/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(LLVM_MCAD_ALL_PLUGINS "qemu;tracer;vivisect;binja") 2 | set(LLVM_MCAD_ENABLE_PLUGINS "" CACHE STRING 3 | "Semicolon-separated list of MCAD plugins to build (${LLVM_KNOWN_PROJECTS}), or \"all\". Empty string (default) builds none.") 4 | if (LLVM_MCAD_ENABLE_PLUGINS STREQUAL "all") 5 | set( LLVM_MCAD_ENABLE_PLUGINS ${LLVM_MCAD_ALL_PLUGINS}) 6 | endif() 7 | 8 | if (NOT LLVM_MCAD_ENABLE_PLUGINS STREQUAL "") 9 | 10 | include(FetchContent) 11 | 12 | set(ABSL_ENABLE_INSTALL ON) 13 | SET(ABSL_PROPAGATE_CXX_STD ON) 14 | SET(ABSL_BUILD_TESTING OFF) 15 | 16 | FetchContent_Declare( 17 | gRPC 18 | GIT_REPOSITORY https://github.com/grpc/grpc 19 | GIT_TAG v1.60.0 20 | ) 21 | set(FETCHCONTENT_QUIET OFF) 22 | FetchContent_MakeAvailable(gRPC) 23 | 24 | include(${grpc_BINARY_DIR}/third_party/protobuf/cmake/protobuf/protobuf-generate.cmake) 25 | 26 | set(grpc_cpp_plugin_location $) 27 | 28 | set(proto_include_dir "${CMAKE_CURRENT_BINARY_DIR}") 29 | message(STATUS "Protobuf include directory: ${proto_include_dir}") 30 | 31 | if ("qemu" IN_LIST LLVM_MCAD_ENABLE_PLUGINS) 32 | add_subdirectory(qemu-broker) 33 | endif () 34 | if ("tracer" IN_LIST LLVM_MCAD_ENABLE_PLUGINS) 35 | add_subdirectory(tracer-broker) 36 | endif () 37 | if ("vivisect" IN_LIST LLVM_MCAD_ENABLE_PLUGINS) 38 | add_subdirectory(vivisect-broker) 39 | endif () 40 | if ("binja" IN_LIST LLVM_MCAD_ENABLE_PLUGINS) 41 | add_subdirectory(binja-broker) 42 | endif () 43 | 44 | endif() 45 | -------------------------------------------------------------------------------- /plugins/binja-broker/Broker.exports: -------------------------------------------------------------------------------- 1 | mcadGetBrokerPluginInfo 2 | -------------------------------------------------------------------------------- /plugins/binja-broker/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_STANDARD 20) 2 | 3 | add_library(binja_bridge_proto binja.proto) 4 | target_compile_options(binja_bridge_proto PRIVATE "-fPIC") 5 | target_link_libraries(binja_bridge_proto 6 | PUBLIC 7 | grpc++_reflection 8 | grpc++ 9 | libprotobuf 10 | ) 11 | 12 | # Compile protobuf and grpc files in binja_bridge_proto target to cpp 13 | protobuf_generate(TARGET binja_bridge_proto LANGUAGE cpp OUT_VAR proto_files) 14 | protobuf_generate(TARGET binja_bridge_proto LANGUAGE grpc GENERATE_EXTENSIONS .grpc.pb.h .grpc.pb.cc PLUGIN "protoc-gen-grpc=${grpc_cpp_plugin_location}") 15 | 16 | target_include_directories(binja_bridge_proto PUBLIC ${proto_include_dir}) 17 | 18 | set(LLVM_HAVE_LINK_VERSION_SCRIPT 1) 19 | find_package (Python3 REQUIRED 20 | COMPONENTS Interpreter Development) 21 | 22 | # FIXME: We need to export llvm::Any::TypeId::Id as (weak) global symbol 23 | # or the id for each type will not be unique and break the whole llvm::Any 24 | # system. However, since llvm's symbol exporting script processor doesn't 25 | # do symbol name mangling for us, the current Broker.exports script probably 26 | # only works on Linux (and maybe MacOSX?). 27 | set(LLVM_EXPORTED_SYMBOL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/Broker.exports) 28 | 29 | add_llvm_library(MCADBinjaBroker SHARED 30 | Broker.cpp 31 | 32 | # Components like Support, MC, TargetDesc or TargetInfo 33 | # should be already available in llvm-mcad 34 | LINK_COMPONENTS 35 | DebugInfoDWARF 36 | Object 37 | ) 38 | add_dependencies(MCADBinjaBroker 39 | binja_bridge_proto) 40 | 41 | # MacOS-specific fix: 42 | # The mcadGetBrokerPluginInfo() function, which is defined by the individual 43 | # plugins, calls into functions defined in the main llvm-mcad executable into 44 | # which the plugin will be loaded at runtime. We cannot link against the main 45 | # executable; instead those calls should be resolved at runtime. We achieve this 46 | # on Linux using attribute(weak) in the source code; the MacOS linker requires 47 | # -undefied dynamic_lookup as a command line argument. 48 | if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") 49 | target_link_options(MCADBinjaBroker PUBLIC -Wl,-undefined -Wl,dynamic_lookup) 50 | endif() 51 | 52 | target_link_libraries(MCADBinjaBroker PUBLIC binja_bridge_proto) 53 | 54 | unset(LLVM_EXPORTED_SYMBOL_FILE) 55 | 56 | include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) 57 | -------------------------------------------------------------------------------- /plugins/binja-broker/README.md: -------------------------------------------------------------------------------- 1 | # Binja Broker 2 | 3 | This broker provides integration of MCAD with BinaryNinja. BN is expected to send a `trace` of instructions and MCAD will provide detailed cycle count information. 4 | 5 | ``` 6 | |------| Instructions |----------| 7 | | | ------------------> | | 8 | | BN | (grpc channel) | MCAD | 9 | | | <------------------ | | 10 | |------| Cycle Counts |----------| 11 | ``` 12 | 13 | MCAD provides information about when each instruction started executing, finished executing and whether there was pipeline pressure during its dispatch. 14 | 15 | We use this information annotate the BN UI. 16 | 17 | ## Regenerating Protobuf/gRPC code 18 | 19 | This directory already contains a copy of the protobuf/gRPC code for the C++-based server and a python client. To **update** them, edit the interface `binja.proto` and rerun the following. 20 | 21 | ```bash 22 | $ # This is only necessary after updating binja.proto. 23 | $ protoc --grpc_out=. --cpp_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` binja.proto 24 | $ cd binja-plugin 25 | $ python3 -m grpc_tools.protoc -I. --python_out=. --pyi_out=. --grpc_python_out=. ../binja.proto 26 | ``` 27 | 28 | This may require running the following if they are not already installed on your system - 29 | ``` 30 | pip install grpcio 31 | pip install protobuf 32 | ``` 33 | 34 | ## Build 35 | 36 | We assume that MCAD and `libMCADBinjaBroker.so` have already been built. 37 | 38 | To install the plugin, the easiest way is to create a symlink of the `binja-plugin` folder in the BinaryNinja plugins directory. 39 | 40 | ``` 41 | ln -s binja-plugin ~/.binaryninja/plugins/mcad 42 | ``` 43 | 44 | On MacOS, you have to fully copy the conents: 45 | ``` 46 | cp -r binja-plugin "~/Library/Application Support/Binary Ninja" 47 | ``` 48 | 49 | Update variable `MCAD_BUILD_PATH` in `MCADBridge.py` to point to your build directory. For instance, `/home/chinmay_dd/Projects/LLVM-MCA-Daemon/build`. It will pick up `llvm-mcad` from there. 50 | 51 | The Python interpreter used by Binary Ninja will have to have access to the PIP packages `grpcio` and `protobuf`. 52 | The easiest way to set this up cleanly is with a separate Binary-Ninja Python virtual environment: 53 | 54 | ``` 55 | cd ~ 56 | python3 -m venv binja_venv 57 | source binja_venv/bin/activate 58 | pip3 install grpcio protobuf 59 | ``` 60 | 61 | Then, in the Binary Ninja settings, under Python, set 62 | ``` 63 | python.binaryOverride = /path/to/binja_venv/bin/python3 64 | python.interpreter = /path/to/binja_venv/bin/python3 65 | python.virtualenv = /path/to/binja_venv/lib/python3.12/site-packages 66 | ``` 67 | 68 | ### Usage 69 | 70 | 1. Start the MCAD server in the backend - `Plugins/MCAD/1. Start Server`. 71 | 2. Navigate to any function in BinaryNinja. 72 | 3. To retrieve cycle counts for all basic blocks - `Plugins/MCAD/2. Get Cycle Counts for function`. 73 | 4. You can also choose to annotate specific basic blocks by adding Tag `Trace Member` to their first instruction. 74 | Then, `Plugins/MCAD/3. Get Cycle counts for annotated` will return cycle counts for only those functions. 75 | 5. Shut down server before closing BinaryNinja - `Plugins/MCAD/4. Stop Server`. 76 | 77 | ### Experiment Ghidra Plugin 78 | 79 | We implement experimental support for using the BinjaBroker with Ghidra. 80 | 81 | 1. Install and configure [Ghidrathon](https://github.com/mandiant/ghidrathon). 82 | 2. In `Script Manager` -> `Manage Script Directories`, add the `binja-plugin` directory. 83 | 3. The `ghidra-script.py` script should be visible under `Scripts/Python3`. Add a convenient keybind for it (Ctrl-L), if preferred. 84 | 4. Start the `llvm-mcad` server in the background. 85 | 5. Ctrl-L will now annotate all instructions of the "current function" with their corresponding cycle counts fetched from llvm-mca. 86 | -------------------------------------------------------------------------------- /plugins/binja-broker/binja-plugin/__init__.py: -------------------------------------------------------------------------------- 1 | from . import MCADBridge 2 | -------------------------------------------------------------------------------- /plugins/binja-broker/binja-plugin/binja_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # source: binja.proto 4 | # Protobuf Python Version: 5.26.1 5 | """Generated protocol buffer code.""" 6 | from google.protobuf import descriptor as _descriptor 7 | from google.protobuf import descriptor_pool as _descriptor_pool 8 | from google.protobuf import symbol_database as _symbol_database 9 | from google.protobuf.internal import builder as _builder 10 | # @@protoc_insertion_point(imports) 11 | 12 | _sym_db = _symbol_database.Default() 13 | 14 | 15 | 16 | 17 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0b\x62inja.proto\"g\n\x11\x42injaInstructions\x12\x33\n\x0binstruction\x18\x01 \x03(\x0b\x32\x1e.BinjaInstructions.Instruction\x1a\x1d\n\x0bInstruction\x12\x0e\n\x06opcode\x18\x01 \x01(\x0c\"\x85\x01\n\x0b\x43ycleCounts\x12,\n\x0b\x63ycle_count\x18\x01 \x03(\x0b\x32\x17.CycleCounts.CycleCount\x1aH\n\nCycleCount\x12\r\n\x05ready\x18\x01 \x01(\x04\x12\x10\n\x08\x65xecuted\x18\x02 \x01(\x04\x12\x19\n\x11is_under_pressure\x18\x03 \x01(\x08\x32?\n\x05\x42inja\x12\x36\n\x12RequestCycleCounts\x12\x12.BinjaInstructions\x1a\x0c.CycleCountsb\x06proto3') 18 | 19 | _globals = globals() 20 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) 21 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'binja_pb2', _globals) 22 | if not _descriptor._USE_C_DESCRIPTORS: 23 | DESCRIPTOR._loaded_options = None 24 | _globals['_BINJAINSTRUCTIONS']._serialized_start=15 25 | _globals['_BINJAINSTRUCTIONS']._serialized_end=118 26 | _globals['_BINJAINSTRUCTIONS_INSTRUCTION']._serialized_start=89 27 | _globals['_BINJAINSTRUCTIONS_INSTRUCTION']._serialized_end=118 28 | _globals['_CYCLECOUNTS']._serialized_start=121 29 | _globals['_CYCLECOUNTS']._serialized_end=254 30 | _globals['_CYCLECOUNTS_CYCLECOUNT']._serialized_start=182 31 | _globals['_CYCLECOUNTS_CYCLECOUNT']._serialized_end=254 32 | _globals['_BINJA']._serialized_start=256 33 | _globals['_BINJA']._serialized_end=319 34 | # @@protoc_insertion_point(module_scope) 35 | -------------------------------------------------------------------------------- /plugins/binja-broker/binja-plugin/binja_pb2.pyi: -------------------------------------------------------------------------------- 1 | from google.protobuf.internal import containers as _containers 2 | from google.protobuf import descriptor as _descriptor 3 | from google.protobuf import message as _message 4 | from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union 5 | 6 | DESCRIPTOR: _descriptor.FileDescriptor 7 | 8 | class BinjaInstructions(_message.Message): 9 | __slots__ = ("instruction",) 10 | class Instruction(_message.Message): 11 | __slots__ = ("opcode",) 12 | OPCODE_FIELD_NUMBER: _ClassVar[int] 13 | opcode: bytes 14 | def __init__(self, opcode: _Optional[bytes] = ...) -> None: ... 15 | INSTRUCTION_FIELD_NUMBER: _ClassVar[int] 16 | instruction: _containers.RepeatedCompositeFieldContainer[BinjaInstructions.Instruction] 17 | def __init__(self, instruction: _Optional[_Iterable[_Union[BinjaInstructions.Instruction, _Mapping]]] = ...) -> None: ... 18 | 19 | class CycleCounts(_message.Message): 20 | __slots__ = ("cycle_count",) 21 | class CycleCount(_message.Message): 22 | __slots__ = ("ready", "executed", "is_under_pressure") 23 | READY_FIELD_NUMBER: _ClassVar[int] 24 | EXECUTED_FIELD_NUMBER: _ClassVar[int] 25 | IS_UNDER_PRESSURE_FIELD_NUMBER: _ClassVar[int] 26 | ready: int 27 | executed: int 28 | is_under_pressure: bool 29 | def __init__(self, ready: _Optional[int] = ..., executed: _Optional[int] = ..., is_under_pressure: bool = ...) -> None: ... 30 | CYCLE_COUNT_FIELD_NUMBER: _ClassVar[int] 31 | cycle_count: _containers.RepeatedCompositeFieldContainer[CycleCounts.CycleCount] 32 | def __init__(self, cycle_count: _Optional[_Iterable[_Union[CycleCounts.CycleCount, _Mapping]]] = ...) -> None: ... 33 | -------------------------------------------------------------------------------- /plugins/binja-broker/binja-plugin/binja_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | """Client and server classes corresponding to protobuf-defined services.""" 3 | import grpc 4 | import warnings 5 | 6 | import binja_pb2 as binja__pb2 7 | 8 | GRPC_GENERATED_VERSION = '1.64.1' 9 | GRPC_VERSION = grpc.__version__ 10 | EXPECTED_ERROR_RELEASE = '1.65.0' 11 | SCHEDULED_RELEASE_DATE = 'June 25, 2024' 12 | _version_not_supported = False 13 | 14 | try: 15 | from grpc._utilities import first_version_is_lower 16 | _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) 17 | except ImportError: 18 | _version_not_supported = True 19 | 20 | if _version_not_supported: 21 | warnings.warn( 22 | f'The grpc package installed is at version {GRPC_VERSION},' 23 | + f' but the generated code in binja_pb2_grpc.py depends on' 24 | + f' grpcio>={GRPC_GENERATED_VERSION}.' 25 | + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' 26 | + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' 27 | + f' This warning will become an error in {EXPECTED_ERROR_RELEASE},' 28 | + f' scheduled for release on {SCHEDULED_RELEASE_DATE}.', 29 | RuntimeWarning 30 | ) 31 | 32 | 33 | class BinjaStub(object): 34 | """Missing associated documentation comment in .proto file.""" 35 | 36 | def __init__(self, channel): 37 | """Constructor. 38 | 39 | Args: 40 | channel: A grpc.Channel. 41 | """ 42 | self.RequestCycleCounts = channel.unary_unary( 43 | '/Binja/RequestCycleCounts', 44 | request_serializer=binja__pb2.BinjaInstructions.SerializeToString, 45 | response_deserializer=binja__pb2.CycleCounts.FromString, 46 | _registered_method=True) 47 | 48 | 49 | class BinjaServicer(object): 50 | """Missing associated documentation comment in .proto file.""" 51 | 52 | def RequestCycleCounts(self, request, context): 53 | """Missing associated documentation comment in .proto file.""" 54 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 55 | context.set_details('Method not implemented!') 56 | raise NotImplementedError('Method not implemented!') 57 | 58 | 59 | def add_BinjaServicer_to_server(servicer, server): 60 | rpc_method_handlers = { 61 | 'RequestCycleCounts': grpc.unary_unary_rpc_method_handler( 62 | servicer.RequestCycleCounts, 63 | request_deserializer=binja__pb2.BinjaInstructions.FromString, 64 | response_serializer=binja__pb2.CycleCounts.SerializeToString, 65 | ), 66 | } 67 | generic_handler = grpc.method_handlers_generic_handler( 68 | 'Binja', rpc_method_handlers) 69 | server.add_generic_rpc_handlers((generic_handler,)) 70 | server.add_registered_method_handlers('Binja', rpc_method_handlers) 71 | 72 | 73 | # This class is part of an EXPERIMENTAL API. 74 | class Binja(object): 75 | """Missing associated documentation comment in .proto file.""" 76 | 77 | @staticmethod 78 | def RequestCycleCounts(request, 79 | target, 80 | options=(), 81 | channel_credentials=None, 82 | call_credentials=None, 83 | insecure=False, 84 | compression=None, 85 | wait_for_ready=None, 86 | timeout=None, 87 | metadata=None): 88 | return grpc.experimental.unary_unary( 89 | request, 90 | target, 91 | '/Binja/RequestCycleCounts', 92 | binja__pb2.BinjaInstructions.SerializeToString, 93 | binja__pb2.CycleCounts.FromString, 94 | options, 95 | channel_credentials, 96 | insecure, 97 | call_credentials, 98 | compression, 99 | wait_for_ready, 100 | timeout, 101 | metadata, 102 | _registered_method=True) 103 | -------------------------------------------------------------------------------- /plugins/binja-broker/binja-plugin/ghidra-script.py: -------------------------------------------------------------------------------- 1 | #@author chinmay_dd 2 | #@category Python 3 3 | #@keybinding 4 | #@menupath 5 | #@toolbar 6 | 7 | import grpc 8 | import binja_pb2 9 | import binja_pb2_grpc 10 | import pdb 11 | 12 | from ghidra.program.model.listing import CodeUnit, Function 13 | from ghidra.app.script import GhidraScript 14 | 15 | class GRPCClient: 16 | def __init__(self): 17 | self.channel = grpc.insecure_channel("localhost:50052") 18 | self.stub = binja_pb2_grpc.BinjaStub(self.channel) 19 | 20 | def request_empty(self): 21 | self.stub.RequestCycleCounts(binja_pb2.BinjaInstructions(instructions=[])) 22 | return None 23 | 24 | def request_cycle_counts(self): 25 | response = binja_pb2.CycleCounts() 26 | 27 | function = getFunctionContaining(currentAddress()) 28 | insns = function.getProgram().getListing().getInstructions(function.getBody(), True) 29 | instructions = [] 30 | 31 | for insn in function.getProgram().getListing().getInstructions(function.getBody(), True): 32 | bytez = bytes(map(lambda b: b & 0xff, insn.getBytes())) 33 | instructions.append(binja_pb2.BinjaInstructions.Instruction(opcode=bytez)) 34 | 35 | response = self.stub.RequestCycleCounts(binja_pb2.BinjaInstructions(instruction=instructions)) 36 | 37 | for idx, insn in enumerate(function.getProgram().getListing().getInstructions(function.getBody(), True)): 38 | cycle_start = response.cycle_count[idx].ready 39 | cycle_end = response.cycle_count[idx].executed 40 | 41 | num_cycles = cycle_end - cycle_start 42 | setEOLComment(insn.getAddress(), str(num_cycles)) 43 | 44 | client = GRPCClient() 45 | client.request_cycle_counts() 46 | -------------------------------------------------------------------------------- /plugins/binja-broker/binja-plugin/grpcclient.py: -------------------------------------------------------------------------------- 1 | import grpc 2 | import binja_pb2 3 | import binja_pb2_grpc 4 | 5 | class GRPCClient: 6 | def __init__(self): 7 | self.channel = grpc.insecure_channel("localhost:50052") 8 | self.stub = binja_pb2_grpc.BinjaStub(self.channel) 9 | 10 | def request_cycle_counts(self, info_ctx): 11 | if not info_ctx: 12 | self.stub.RequestCycleCounts(binja_pb2.BinjaInstructions(instruction=[])) 13 | return None 14 | 15 | response = binja_pb2.CycleCounts() 16 | for block_addr in info_ctx.block_list: 17 | block = info_ctx.get_block_at_addr(block_addr) 18 | instructions = [] 19 | for insn in block.disassembly_text: 20 | w_i = info_ctx.get_instruction_at_addr(insn.address) 21 | instructions.append(binja_pb2.BinjaInstructions.Instruction(opcode=bytes(w_i.bytez))) 22 | 23 | curr_response = self.stub.RequestCycleCounts(binja_pb2.BinjaInstructions(instruction=instructions)) 24 | response.MergeFrom(curr_response) 25 | 26 | return response 27 | -------------------------------------------------------------------------------- /plugins/binja-broker/binja-plugin/infoctx.py: -------------------------------------------------------------------------------- 1 | class WrappedInstruction: 2 | def __init__(self, addr=0, length=0, disasm_text=None, block=None, bytez=None, opcode=None): 3 | self.addr = addr 4 | self.length = length 5 | self.block = block 6 | self.bytez = bytez 7 | self.disasm_text = disasm_text 8 | 9 | def get_wrapped_instruction(function, addr): 10 | i = WrappedInstruction() 11 | i.addr = addr 12 | i.length = function.view.get_instruction_length(addr) 13 | i.block = function.get_basic_block_at(addr) 14 | i.bytez = function.view.read(addr, i.length) 15 | i.disasm_text = function.view.get_disassembly(addr) 16 | return i 17 | 18 | class InfoCtx: 19 | def __init__(self, function): 20 | self.function = function 21 | self.view = function.view 22 | 23 | self.instructions = dict() 24 | 25 | self.blocks = dict() 26 | self.block_list = [] 27 | 28 | def add_instruction_at_addr(self, addr): 29 | self.instructions[addr] = get_wrapped_instruction(self.function, addr) 30 | 31 | def add_block(self, block): 32 | if block.start in self.blocks: 33 | return 34 | 35 | self.blocks[block.start] = block 36 | self.block_list.append(block.start) 37 | 38 | for insn in block.disassembly_text: 39 | self.add_instruction_at_addr(insn.address) 40 | 41 | def get_instruction_at_addr(self, addr): 42 | if addr in self.instructions: 43 | return self.instructions[addr] 44 | 45 | return None 46 | 47 | def get_block_at_addr(self, addr): 48 | if addr in self.blocks: 49 | return self.blocks[addr] 50 | 51 | return None 52 | 53 | def get_info_ctx_for_annotated(function): 54 | info_ctx = InfoCtx(function) 55 | view = function.view 56 | member_tag = view.get_tag_type("Trace Member") 57 | 58 | for block in function.basic_blocks: 59 | if any(tag.type == member_tag for tag in function.get_tags_at(block.start)): 60 | info_ctx.add_block(block) 61 | 62 | return info_ctx 63 | 64 | def get_info_ctx_for_function(function): 65 | info_ctx = InfoCtx(function) 66 | 67 | for block in function.basic_blocks: 68 | info_ctx.add_block(block) 69 | 70 | return info_ctx 71 | -------------------------------------------------------------------------------- /plugins/binja-broker/binja-plugin/test_script.py: -------------------------------------------------------------------------------- 1 | import grpc 2 | import binja_pb2 3 | import binja_pb2_grpc 4 | import ipdb 5 | 6 | class grpc_client: 7 | def __init__(self): 8 | self.channel = grpc.insecure_channel("localhost:50052") 9 | self.stub = binja_pb2_grpc.BinjaStub(self.channel) 10 | 11 | # mov rbx,rcx 12 | # add rax,rbx 13 | # sub rax,rcx 14 | def send_instructions(self): 15 | insn_1 = binja_pb2.BinjaInstructions.Instruction(opcode=bytes(bytearray([0x48, 0x01, 0xD8]))) 16 | insn_2 = binja_pb2.BinjaInstructions.Instruction(opcode=bytes(bytearray([0x48, 0x89, 0xCB]))) 17 | insn_3 = binja_pb2.BinjaInstructions.Instruction(opcode=bytes(bytearray([0x48, 0x29, 0xC8]))) 18 | instructions = [insn_1, insn_2, insn_3] 19 | print(instructions) 20 | return self.stub.RequestCycleCounts(binja_pb2.BinjaInstructions(instruction=instructions)) 21 | 22 | # mov rbx,rcx 23 | # add rax,rbx 24 | # cdqe / syscall 25 | def send_instructions2(self): 26 | insn_1 = binja_pb2.BinjaInstructions.Instruction(opcode=bytes(bytearray([0x48, 0x01, 0xD8]))) 27 | insn_2 = binja_pb2.BinjaInstructions.Instruction(opcode=bytes(bytearray([0x48, 0x89, 0xCB]))) 28 | insn_3 = binja_pb2.BinjaInstructions.Instruction(opcode=bytes(bytearray([0x0f, 0x05]))) 29 | instructions = [insn_1, insn_2, insn_3] 30 | print(instructions) 31 | return self.stub.RequestCycleCounts(binja_pb2.BinjaInstructions(instruction=instructions)) 32 | 33 | def send_illegal_instruction(self): 34 | instructions = [] 35 | #instructions.append(binja_pb2.BinjaInstructions.Instruction(opcode=bytes(b'\x0b\xf0\xd5\xf9'))) 36 | instructions.append(binja_pb2.BinjaInstructions.Instruction(opcode=bytes(b'\x00'))) 37 | return self.stub.RequestCycleCounts(binja_pb2.BinjaInstructions(instruction=instructions)) 38 | 39 | def send_empty(self): 40 | self.stub.RequestCycleCounts(binja_pb2.BinjaInstructions(instruction=[])) 41 | 42 | G = grpc_client() 43 | response1 = G.send_instructions2() 44 | ipdb.set_trace() 45 | response2 = G.send_instructions() 46 | ipdb.set_trace() 47 | try: 48 | response3 = G.send_illegal_instruction() 49 | except grpc._channel._InactiveRpcError as e: 50 | print("send_illegal_instruction() failed with:") 51 | print(e) 52 | ipdb.set_trace() 53 | 54 | -------------------------------------------------------------------------------- /plugins/binja-broker/binja.grpc.pb.cc: -------------------------------------------------------------------------------- 1 | // Generated by the gRPC C++ plugin. 2 | // If you make any local change, they will be lost. 3 | // source: binja.proto 4 | 5 | #include "binja.pb.h" 6 | #include "binja.grpc.pb.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | static const char* Binja_method_names[] = { 24 | "/Binja/RequestCycleCounts", 25 | }; 26 | 27 | std::unique_ptr< Binja::Stub> Binja::NewStub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options) { 28 | (void)options; 29 | std::unique_ptr< Binja::Stub> stub(new Binja::Stub(channel, options)); 30 | return stub; 31 | } 32 | 33 | Binja::Stub::Stub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options) 34 | : channel_(channel), rpcmethod_RequestCycleCounts_(Binja_method_names[0], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel) 35 | {} 36 | 37 | ::grpc::Status Binja::Stub::RequestCycleCounts(::grpc::ClientContext* context, const ::BinjaInstructions& request, ::CycleCounts* response) { 38 | return ::grpc::internal::BlockingUnaryCall< ::BinjaInstructions, ::CycleCounts, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_RequestCycleCounts_, context, request, response); 39 | } 40 | 41 | void Binja::Stub::async::RequestCycleCounts(::grpc::ClientContext* context, const ::BinjaInstructions* request, ::CycleCounts* response, std::function f) { 42 | ::grpc::internal::CallbackUnaryCall< ::BinjaInstructions, ::CycleCounts, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_RequestCycleCounts_, context, request, response, std::move(f)); 43 | } 44 | 45 | void Binja::Stub::async::RequestCycleCounts(::grpc::ClientContext* context, const ::BinjaInstructions* request, ::CycleCounts* response, ::grpc::ClientUnaryReactor* reactor) { 46 | ::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_RequestCycleCounts_, context, request, response, reactor); 47 | } 48 | 49 | ::grpc::ClientAsyncResponseReader< ::CycleCounts>* Binja::Stub::PrepareAsyncRequestCycleCountsRaw(::grpc::ClientContext* context, const ::BinjaInstructions& request, ::grpc::CompletionQueue* cq) { 50 | return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::CycleCounts, ::BinjaInstructions, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_RequestCycleCounts_, context, request); 51 | } 52 | 53 | ::grpc::ClientAsyncResponseReader< ::CycleCounts>* Binja::Stub::AsyncRequestCycleCountsRaw(::grpc::ClientContext* context, const ::BinjaInstructions& request, ::grpc::CompletionQueue* cq) { 54 | auto* result = 55 | this->PrepareAsyncRequestCycleCountsRaw(context, request, cq); 56 | result->StartCall(); 57 | return result; 58 | } 59 | 60 | Binja::Service::Service() { 61 | AddMethod(new ::grpc::internal::RpcServiceMethod( 62 | Binja_method_names[0], 63 | ::grpc::internal::RpcMethod::NORMAL_RPC, 64 | new ::grpc::internal::RpcMethodHandler< Binja::Service, ::BinjaInstructions, ::CycleCounts, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>( 65 | [](Binja::Service* service, 66 | ::grpc::ServerContext* ctx, 67 | const ::BinjaInstructions* req, 68 | ::CycleCounts* resp) { 69 | return service->RequestCycleCounts(ctx, req, resp); 70 | }, this))); 71 | } 72 | 73 | Binja::Service::~Service() { 74 | } 75 | 76 | ::grpc::Status Binja::Service::RequestCycleCounts(::grpc::ServerContext* context, const ::BinjaInstructions* request, ::CycleCounts* response) { 77 | (void) context; 78 | (void) request; 79 | (void) response; 80 | return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); 81 | } 82 | 83 | 84 | -------------------------------------------------------------------------------- /plugins/binja-broker/binja.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | service Binja { 4 | rpc RequestCycleCounts (BinjaInstructions) returns (CycleCounts); 5 | } 6 | 7 | message BinjaInstructions { 8 | repeated Instruction instruction = 1; 9 | 10 | message Instruction { 11 | bytes opcode = 1; 12 | } 13 | } 14 | 15 | message CycleCounts { 16 | repeated CycleCount cycle_count = 1; 17 | 18 | message CycleCount { 19 | uint64 ready = 1; 20 | uint64 executed = 2; 21 | bool is_under_pressure = 3; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /plugins/qemu-broker/BinaryRegions.h: -------------------------------------------------------------------------------- 1 | #ifndef LLVM_MCAD_QEMU_BROKER_BINARYREGIONS_H 2 | #define LLVM_MCAD_QEMU_BROKER_BINARYREGIONS_H 3 | #include "llvm/ADT/StringRef.h" 4 | #include "llvm/ADT/StringMap.h" 5 | #include "llvm/Support/Error.h" 6 | #include "llvm/Support/Format.h" 7 | #include "llvm/Support/raw_ostream.h" 8 | #include 9 | 10 | namespace llvm { 11 | // Forward declarations 12 | class MemoryBuffer; 13 | class raw_ostream; 14 | namespace json { 15 | class Array; 16 | class Object; 17 | } 18 | 19 | namespace mcad { 20 | namespace qemu_broker { 21 | struct BinaryRegion { 22 | std::string Description; 23 | // range: [StartAddr, EndAddr] 24 | uint64_t StartAddr, EndAddr; 25 | }; 26 | 27 | inline 28 | raw_ostream &operator<<(raw_ostream &OS, const BinaryRegion &BR) { 29 | OS << "<" << BR.Description << ">, " 30 | << "Address: [ " << format_hex(BR.StartAddr, 16) 31 | << " - " << format_hex(BR.EndAddr, 16) << " ]"; 32 | return OS; 33 | } 34 | 35 | struct BinaryRegions { 36 | enum Mode { 37 | M_Trim, 38 | M_Mark 39 | }; 40 | 41 | private: 42 | Error parseAddressBasedRegions(const json::Array &RawRegions); 43 | 44 | Error parseSymbolBasedRegions(const json::Object &RawManifest); 45 | 46 | // {start address -> BinaryRegion} 47 | std::unordered_map Regions; 48 | 49 | Mode OperationMode; 50 | 51 | BinaryRegions() : OperationMode(M_Trim) {} 52 | 53 | public: 54 | static 55 | Expected> Create(StringRef ManifestPath); 56 | 57 | size_t size() const { return Regions.size(); } 58 | 59 | void setOperationMode(Mode OpMode) { OperationMode = OpMode; } 60 | Mode getOperationMode() const { return OperationMode; } 61 | 62 | const BinaryRegion *lookup(uint64_t StartAddr) const { 63 | if (!Regions.count(StartAddr)) 64 | return nullptr; 65 | else 66 | return &Regions.at(StartAddr); 67 | } 68 | }; 69 | } // end namespace qemu_broker 70 | } // end namespace mcad 71 | } // end namespace llvm 72 | #endif 73 | -------------------------------------------------------------------------------- /plugins/qemu-broker/Broker.exports: -------------------------------------------------------------------------------- 1 | mcadGetBrokerPluginInfo 2 | _ZN4llvm3Any6TypeIdINS_3mca14MDMemoryAccessEE2IdE 3 | _ZN4llvm3Any6TypeIdINS_4mcad12RegionMarkerEE2IdE 4 | -------------------------------------------------------------------------------- /plugins/qemu-broker/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if (WIN32) 2 | message(FATAL_ERROR "This plugin does not support Windows") 3 | endif() 4 | 5 | set(LLVM_HAVE_LINK_VERSION_SCRIPT 1) 6 | find_package (Python3 REQUIRED 7 | COMPONENTS Interpreter Development) 8 | 9 | # Search for the Flatbuffers header 10 | find_path(_FLATBUFFERS_HEADER flatbuffers.h 11 | PATH_SUFFIXES flatbuffers 12 | REQUIRED) 13 | if (_FLATBUFFERS_HEADER STREQUAL "_FLATBUFFERS_HEADER-NOTFOUND") 14 | message(FATAL_ERROR "This plugin requires Flatbuffers..." 15 | "because I'm too lazy to do serialization by myself ;-)") 16 | endif() 17 | message(STATUS "Using Flatbuffers at ${_FLATBUFFERS_HEADER}") 18 | 19 | # If there is Flatbuffers compiler, re-generate the header 20 | find_program(_FLATBUFFERS_COMPILER flatc) 21 | 22 | set(_FBS_OUTPUT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/Serialization) 23 | set(_FBS_HEADER_PATH ${_FBS_OUTPUT_DIR}/mcad_generated.h) 24 | if (NOT _FLATBUFFERS_COMPILER STREQUAL "_FLATBUFFERS_COMPILER-NOTFOUND") 25 | message(STATUS "Found Flatbuffers compiler at ${_FLATBUFFERS_COMPILER}") 26 | set(_FBS_INPUT ${CMAKE_CURRENT_SOURCE_DIR}/Serialization/mcad.fbs) 27 | add_custom_command(OUTPUT ${_FBS_HEADER_PATH} 28 | COMMAND ${_FLATBUFFERS_COMPILER} 29 | ARGS --cpp -o ${_FBS_OUTPUT_DIR} ${_FBS_INPUT} 30 | DEPENDS ${_FBS_INPUT}) 31 | else() 32 | message(STATUS "Flatbuffers compiler not found, will not re-generate the header") 33 | endif() 34 | add_custom_target(generate-fbs 35 | DEPENDS ${_FBS_HEADER_PATH}) 36 | 37 | # FIXME: We need to export llvm::Any::TypeId::Id as (weak) global symbol 38 | # or the id for each type will not be unique and break the whole llvm::Any 39 | # system. However, since llvm's symbol exporting script processor doesn't 40 | # do symbol name mangling for us, the current Broker.exports script probably 41 | # only works on Linux (and maybe MacOSX?). 42 | set(LLVM_EXPORTED_SYMBOL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/Broker.exports) 43 | 44 | add_llvm_library(MCADQemuBroker SHARED 45 | BinaryRegions.cpp 46 | Broker.cpp 47 | 48 | # Components like Support, MC, TargetDesc or TargetInfo 49 | # should be already available in llvm-mcad 50 | LINK_COMPONENTS 51 | DebugInfoDWARF 52 | Object 53 | ) 54 | add_dependencies(MCADQemuBroker generate-fbs) 55 | 56 | unset(LLVM_EXPORTED_SYMBOL_FILE) 57 | 58 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}) 59 | 60 | add_subdirectory(Qemu) 61 | 62 | add_subdirectory(utils) 63 | -------------------------------------------------------------------------------- /plugins/qemu-broker/Qemu/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(QEMU_INCLUDE_DIR /usr/include CACHE PATH 2 | "Folder that contains qemu/qemu-plugin.h") 3 | 4 | include_directories(${QEMU_INCLUDE_DIR}) 5 | 6 | add_llvm_library(QemuRelay SHARED 7 | Plugin.cpp 8 | 9 | LINK_COMPONENTS 10 | Support 11 | ) 12 | -------------------------------------------------------------------------------- /plugins/qemu-broker/Serialization/mcad.fbs: -------------------------------------------------------------------------------- 1 | namespace llvm.mcad.fbs; 2 | 3 | // Some metadata 4 | table Metadata { 5 | // The address where the main executable is loaded 6 | LoadAddr: uint64; 7 | } 8 | 9 | struct MemoryAccess { 10 | // Instruction index in its enclosing TB 11 | Index: uint; 12 | // The memory address it accessed 13 | VAddr: uint64; 14 | // Size in bytes 15 | Size: uint8; 16 | IsStore: bool; 17 | } 18 | 19 | // Sent when a TB with index `Index` is executed 20 | table ExecTB { 21 | Index: uint; 22 | PC: uint64; 23 | MemAccesses: [MemoryAccess]; 24 | } 25 | 26 | table Inst { 27 | Data: [ubyte]; 28 | } 29 | 30 | // Sent when a TB is translated 31 | table TranslatedBlock { 32 | Index: uint; 33 | Instructions: [Inst]; 34 | } 35 | 36 | union Msg { 37 | Metadata, 38 | ExecTB, 39 | TranslatedBlock 40 | } 41 | 42 | table Message { 43 | Content: Msg; 44 | } 45 | 46 | root_type Message; 47 | -------------------------------------------------------------------------------- /plugins/qemu-broker/utils/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(LLVM_LINK_COMPONENTS 2 | Support) 3 | 4 | add_llvm_executable(qemu-broker-client 5 | qemu-broker-client.cpp 6 | ) 7 | -------------------------------------------------------------------------------- /plugins/qemu-broker/utils/qemu-broker-client.cpp: -------------------------------------------------------------------------------- 1 | #include "llvm/ADT/APInt.h" 2 | #include "llvm/ADT/SmallVector.h" 3 | #include "llvm/ADT/StringRef.h" 4 | #include "llvm/ADT/STLExtras.h" 5 | #include "llvm/Support/CommandLine.h" 6 | #include "llvm/Support/Debug.h" 7 | #include "llvm/Support/Format.h" 8 | #include "llvm/Support/MemoryBuffer.h" 9 | #include "llvm/Support/raw_ostream.h" 10 | #include "llvm/Support/WithColor.h" 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "Serialization/mcad_generated.h" 22 | 23 | using namespace llvm; 24 | using namespace mcad; 25 | 26 | #define DEBUG_TYPE "qemu-broker-client" 27 | 28 | static cl::opt 29 | ConnectAddr("addr", cl::Required); 30 | 31 | static cl::opt 32 | ConnectPort("port", cl::Required); 33 | 34 | static cl::opt 35 | InputFilename(cl::Positional, cl::value_desc("filename"), 36 | cl::init("-")); 37 | 38 | enum ActionKind { 39 | AK_SendTB, 40 | AK_TBExec 41 | }; 42 | 43 | static cl::opt 44 | Action("action", cl::desc("Action to perform"), 45 | cl::values( 46 | clEnumValN(AK_SendTB, "send-tb", "Send a TranslationBlock"), 47 | clEnumValN(AK_TBExec, "tb-exec", "Signal that a TB is executed") 48 | ), 49 | cl::init(AK_SendTB)); 50 | 51 | static cl::opt 52 | TBSentIdx("tb-sent-index", cl::desc("The index of the sent TB"), 53 | cl::init(0U)); 54 | 55 | static cl::opt 56 | TBExecIdx("tb-exec", cl::desc("The index of the executed TB"), 57 | cl::init(0U)); 58 | 59 | static cl::opt 60 | TBExecAddr("tb-exec-addr", cl::desc("The VAddr which TB is executed"), 61 | cl::init(0U)); 62 | 63 | static void sendTB(int Sockt) { 64 | auto ErrOrBuffer = MemoryBuffer::getFileOrSTDIN(InputFilename, /*IsText=*/true); 65 | if (!ErrOrBuffer) { 66 | WithColor::error() << "Failed to open the file: " 67 | << ErrOrBuffer.getError().message() << "\n"; 68 | return; 69 | } 70 | StringRef Buffer = (*ErrOrBuffer)->getBuffer(); 71 | 72 | SmallVector Lines; 73 | SmallVector RawBytes; 74 | std::vector Bytes; 75 | SmallVector Insts; 76 | Buffer.split(Lines, '\n', -1, false); 77 | for (const auto &Line : Lines) { 78 | if (Line.starts_with("//") || Line.starts_with("#")) 79 | // Treat as comment 80 | continue; 81 | RawBytes.clear(); 82 | Line.trim().split(RawBytes, ',', -1, false); 83 | Bytes.clear(); 84 | for (const StringRef &RawByte : RawBytes) { 85 | uint8_t Byte; 86 | if (!RawByte.trim().getAsInteger(0, Byte)) 87 | Bytes.push_back(Byte); 88 | } 89 | Insts.push_back(Bytes); 90 | } 91 | if (Insts.empty()) 92 | return; 93 | 94 | // Build the flatbuffers 95 | flatbuffers::FlatBufferBuilder Builder(1024); 96 | std::vector> FbInsts; 97 | for (const auto &Inst : Insts) { 98 | auto InstData = Builder.CreateVector(Inst); 99 | auto FbInst = fbs::CreateInst(Builder, InstData); 100 | FbInsts.push_back(FbInst); 101 | } 102 | auto FbInstructions = Builder.CreateVector(FbInsts); 103 | auto FbTB = fbs::CreateTranslatedBlock(Builder, TBSentIdx, FbInstructions); 104 | auto FbMessage = fbs::CreateMessage(Builder, fbs::Msg_TranslatedBlock, 105 | FbTB.Union()); 106 | fbs::FinishSizePrefixedMessageBuffer(Builder, FbMessage); 107 | 108 | errs() << "Sending TB with " 109 | << FbInsts.size() << " instructions " 110 | << ". Size = " << Builder.GetSize() << " bytes\n"; 111 | int NumBytesSent = write(Sockt, Builder.GetBufferPointer(), Builder.GetSize()); 112 | if (NumBytesSent < 0) { 113 | ::perror("Failed to send TB data"); 114 | } 115 | LLVM_DEBUG(dbgs() << "Sent " << NumBytesSent << " bytes\n"); 116 | } 117 | 118 | static void tbExec(int Sockt) { 119 | flatbuffers::FlatBufferBuilder Builder(128); 120 | auto FbExecTB = fbs::CreateExecTB(Builder, TBExecIdx, TBExecAddr); 121 | auto FbMessage = fbs::CreateMessage(Builder, fbs::Msg_ExecTB, 122 | FbExecTB.Union()); 123 | fbs::FinishSizePrefixedMessageBuffer(Builder, FbMessage); 124 | 125 | errs() << "Sending execution signal of TB " << TBExecIdx 126 | << " with address " << format_hex(TBExecAddr, 16) << "\n"; 127 | int NumBytesSent = write(Sockt, Builder.GetBufferPointer(), Builder.GetSize()); 128 | if (NumBytesSent < 0) { 129 | ::perror("Failed to send TB data"); 130 | } 131 | LLVM_DEBUG(dbgs() << "Sent " << NumBytesSent << " bytes\n"); 132 | } 133 | 134 | namespace { 135 | // A simple RAII to cleanup socket resources 136 | struct SocketRAII { 137 | int SocktFD; 138 | explicit SocketRAII(int FD) : SocktFD(FD) {} 139 | ~SocketRAII() { 140 | LLVM_DEBUG(dbgs() << "Cleaning up socket...\n"); 141 | ::shutdown(SocktFD, SHUT_RDWR); 142 | ::close(SocktFD); 143 | } 144 | }; 145 | } // end anonymous namespace 146 | 147 | int main(int argc, char **argv) { 148 | cl::ParseCommandLineOptions(argc, argv, ""); 149 | 150 | int Sockt = socket(AF_INET , SOCK_STREAM , 0); 151 | if (Sockt < 0) { 152 | errs() << "Failed to create socket\n"; 153 | return 1; 154 | } 155 | 156 | sockaddr_in ServAddr; 157 | ServAddr.sin_addr.s_addr = inet_addr(ConnectAddr.c_str()); 158 | ServAddr.sin_family = AF_INET; 159 | ServAddr.sin_port = htons(ConnectPort); 160 | if (connect(Sockt, (sockaddr*)&ServAddr, sizeof(ServAddr)) < 0) { 161 | errs() << "Failed to connect to " 162 | << ConnectAddr << ":" << ConnectPort << "\n"; 163 | return 1; 164 | } 165 | errs() << "Connected to " << ConnectAddr << ":" << ConnectPort << "\n"; 166 | SocketRAII SocktRAII(Sockt); 167 | 168 | switch (Action) { 169 | case AK_SendTB: 170 | sendTB(Sockt); 171 | break; 172 | case AK_TBExec: 173 | tbExec(Sockt); 174 | break; 175 | } 176 | 177 | return 0; 178 | } 179 | -------------------------------------------------------------------------------- /plugins/tracer-broker/Broker.cpp: -------------------------------------------------------------------------------- 1 | #include "BrokerFacade.h" 2 | #include "Brokers/Broker.h" 3 | #include "Brokers/BrokerPlugin.h" 4 | 5 | using namespace llvm; 6 | using namespace mcad; 7 | 8 | #define DEBUG_TYPE "mcad-tracer-broker" 9 | 10 | namespace { 11 | class TracerBroker : public Broker { 12 | unsigned getFeatures() const override { 13 | return Broker::Feature_Metadata; 14 | } 15 | }; 16 | } // end anonymous namespace 17 | 18 | extern "C" ::llvm::mcad::BrokerPluginLibraryInfo LLVM_ATTRIBUTE_WEAK 19 | mcadGetBrokerPluginInfo() { 20 | return { 21 | LLVM_MCAD_BROKER_PLUGIN_API_VERSION, "TracerBroker", "v0.1", 22 | [](int argc, const char *const *argv, BrokerFacade &BF) { 23 | BF.setBroker(std::make_unique()); 24 | } 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /plugins/tracer-broker/Broker.exports: -------------------------------------------------------------------------------- 1 | mcadGetBrokerPluginInfo 2 | -------------------------------------------------------------------------------- /plugins/tracer-broker/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | set(LLVM_HAVE_LINK_VERSION_SCRIPT 1) 3 | find_package (Python3 REQUIRED 4 | COMPONENTS Interpreter Development) 5 | 6 | # FIXME: We need to export llvm::Any::TypeId::Id as (weak) global symbol 7 | # or the id for each type will not be unique and break the whole llvm::Any 8 | # system. However, since llvm's symbol exporting script processor doesn't 9 | # do symbol name mangling for us, the current Broker.exports script probably 10 | # only works on Linux (and maybe MacOSX?). 11 | set(LLVM_EXPORTED_SYMBOL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/Broker.exports) 12 | 13 | add_llvm_library(MCADTracerBroker SHARED 14 | LocalTraceGenerator.cpp 15 | Broker.cpp 16 | 17 | # Components like Support, MC, TargetDesc or TargetInfo 18 | # should be already available in llvm-mcad 19 | LINK_COMPONENTS 20 | Object 21 | ) 22 | 23 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}) 24 | 25 | unset(LLVM_EXPORTED_SYMBOL_FILE) 26 | 27 | add_subdirectory(tools) 28 | -------------------------------------------------------------------------------- /plugins/tracer-broker/LocalTraceGenerator.cpp: -------------------------------------------------------------------------------- 1 | #include "LocalTraceGenerator.h" 2 | 3 | using namespace llvm; 4 | using namespace mcad; 5 | using namespace tracer_broker; 6 | -------------------------------------------------------------------------------- /plugins/tracer-broker/LocalTraceGenerator.h: -------------------------------------------------------------------------------- 1 | #ifndef LLVM_MCAD_TRACER_BROKER_LOCALTRACEGENERATOR_H 2 | #define LLVM_MCAD_TRACER_BROKER_LOCALTRACEGENERATOR_H 3 | namespace llvm { 4 | namespace mcad { 5 | namespace tracer_broker { 6 | /// Given an executable and a stream of trace addresses, 7 | /// LocalTraceGenerator can reconstruct the original stream of MCInst. 8 | class LocalTraceGenerator { 9 | }; 10 | } // end namespace tracer_broker 11 | } // end namespace mcad 12 | } // end namespace llvm 13 | #endif 14 | -------------------------------------------------------------------------------- /plugins/tracer-broker/README.md: -------------------------------------------------------------------------------- 1 | # Tracer Broker 2 | A Broker that interacts with the LLVM Tracer sanitizer. Which transmits a stream 3 | of starting addresses of an execution trace. 4 | 5 | -------------------------------------------------------------------------------- /plugins/tracer-broker/tools/CMakeLists.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/securesystemslab/LLVM-MCA-Daemon/10373aa4daeab1ebcc1529a15fc66cfc2688c156/plugins/tracer-broker/tools/CMakeLists.txt -------------------------------------------------------------------------------- /plugins/vivisect-broker/Broker.exports: -------------------------------------------------------------------------------- 1 | mcadGetBrokerPluginInfo 2 | _ZN4llvm3Any6TypeIdINS_3mca14MDMemoryAccessEE2IdE 3 | _ZN4llvm3Any6TypeIdINS_4mcad12RegionMarkerEE2IdE 4 | _ZN4llvm3Any6TypeIdIbE2IdE 5 | -------------------------------------------------------------------------------- /plugins/vivisect-broker/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_STANDARD 17) 2 | 3 | add_library(vivisect_service_proto emulator.proto) 4 | target_compile_options(vivisect_service_proto PRIVATE "-fPIC") 5 | target_link_libraries(vivisect_service_proto 6 | PUBLIC 7 | grpc++_reflection 8 | grpc++ 9 | libprotobuf 10 | ) 11 | 12 | # Compile protobuf and grpc files in vivisect_service_proto target to cpp 13 | protobuf_generate(TARGET vivisect_service_proto LANGUAGE cpp OUT_VAR proto_files) 14 | protobuf_generate(TARGET vivisect_service_proto LANGUAGE grpc GENERATE_EXTENSIONS .grpc.pb.h .grpc.pb.cc PLUGIN "protoc-gen-grpc=${grpc_cpp_plugin_location}") 15 | 16 | target_include_directories(vivisect_service_proto PUBLIC ${proto_include_dir}) 17 | 18 | set(LLVM_HAVE_LINK_VERSION_SCRIPT 1) 19 | find_package (Python3 REQUIRED 20 | COMPONENTS Interpreter Development) 21 | 22 | # FIXME: We need to export llvm::Any::TypeId::Id as (weak) global symbol 23 | # or the id for each type will not be unique and break the whole llvm::Any 24 | # system. However, since llvm's symbol exporting script processor doesn't 25 | # do symbol name mangling for us, the current Broker.exports script probably 26 | # only works on Linux (and maybe MacOSX?). 27 | set(LLVM_EXPORTED_SYMBOL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/Broker.exports) 28 | 29 | add_llvm_library(MCADVivisectBroker SHARED 30 | Broker.cpp 31 | 32 | # Components like Support, MC, TargetDesc or TargetInfo 33 | # should be already available in llvm-mcad 34 | LINK_COMPONENTS 35 | DebugInfoDWARF 36 | Object 37 | ) 38 | add_dependencies(MCADVivisectBroker 39 | vivisect_service_proto) 40 | 41 | # MacOS-specific fix: 42 | # The mcadGetBrokerPluginInfo() function, which is defined by the individual 43 | # plugins, calls into functions defined in the main llvm-mcad executable into 44 | # which the plugin will be loaded at runtime. We cannot link against the main 45 | # executable; instead those calls should be resolved at runtime. We achieve this 46 | # on Linux using attribute(weak) in the source code; the MacOS linker requires 47 | # -undefied dynamic_lookup as a command line argument. 48 | if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") 49 | target_link_options(MCADVivisectBroker PUBLIC -Wl,-undefined -Wl,dynamic_lookup) 50 | endif() 51 | 52 | target_link_libraries(MCADVivisectBroker PUBLIC vivisect_service_proto) 53 | 54 | unset(LLVM_EXPORTED_SYMBOL_FILE) 55 | 56 | include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) 57 | -------------------------------------------------------------------------------- /plugins/vivisect-broker/README.md: -------------------------------------------------------------------------------- 1 | # Vivisect Broker 2 | 3 | This broker works with emulators based off of the vivisect disassembler framework. 4 | 5 | ## Regenerating Protobuf/gRPC code 6 | 7 | This directory already contains a copy of the protobuf/gRPC code for the 8 | C++-based server and a python client. To update them, edit the interface 9 | `emulator.proto` and rerun the following. 10 | 11 | ``` 12 | python3 -m grpc_tools.protoc -I. --python_out=grpc_client --pyi_out=grpc_client --grpc_python_out=grpc_client emulator.proto 13 | protoc --grpc_out=. --cpp_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` emulator.proto 14 | ``` 15 | 16 | This may require running the following if they are not already installed on your system 17 | 18 | ``` 19 | sudo apt install -y protobuf-compiler python3-dev 20 | pip install grpcio grpcio-tools protobuf 21 | ``` 22 | 23 | ## Build 24 | 25 | To build MCAD plugins you must set `-DLLVM_MCAD_ENABLE_PLUGINS=vivisect` when running 26 | the CMake config step. For example 27 | 28 | ``` 29 | mkdir build && build 30 | cmake -GNinja -DCMAKE_BUILD_TYPE=Debug \ 31 | -DLLVM_DIR=/path/to/installed-llvm/lib/cmake/llvm \ 32 | -DLLVM_MCAD_ENABLE_PLUGINS=vivisect \ 33 | .. 34 | ninja llvm-mcad MCADVivisectBroker 35 | ``` 36 | 37 | This broker also requires modifying the emulator to make use of the grpc client 38 | to record instructions executed. This directory contains cm2350.patch which can 39 | be applied to the CM2350 emulator 40 | 41 | ``` 42 | git clone git@github.com:Assured-Micropatching/CM2350-Emulator 43 | cd CM2350-Emulator 44 | git checkout 3960f630a74b738e43da7c3512c5ff09ea4df441 45 | git apply /path/to/this/directory/cm2350.patch 46 | cp -r /path/to/this/repo/plugins/vivisect-broker/grpc_client /path/to/CM2350-Emulator/cm2350/ 47 | # optionally add grpcio and protobuf to that repo's requirements.txt to install in the virtualenv 48 | ``` 49 | 50 | ## Usage 51 | 52 | This directory generates 53 | `/$BUILD_DIR/plugins/vivisect-broker/libMCADVivisectBroker.so` which is the 54 | broker plugin that is loaded by MCAD. It sets up a gRPC server in MCAD to handle 55 | client requests from an emulator for recording instructions executed. See 56 | `emulator.proto` for all the actions that an emulator can record in MCAD. 57 | 58 | To use it, first start MCAD 59 | ``` 60 | ./llvm-mcad -mtriple="powerpcle-linux-gnu" -mcpu="pwr10" \ 61 | -load-broker-plugin=$PWD/plugins/vivisect-broker/libMCADVivisectBroker.so 62 | ``` 63 | 64 | Then start the emulator, for example in the case of the CM2350 emulator 65 | 66 | ``` 67 | ./ECU.py -vvv test_program.bin 68 | ``` 69 | 70 | After shutting down the emulator, the gRPC client will ensure that the 71 | connection to the server is closed and notify MCAD that the emulator has 72 | terminated. 73 | -------------------------------------------------------------------------------- /plugins/vivisect-broker/cm2350.patch: -------------------------------------------------------------------------------- 1 | diff --git a/cm2350/e200z7.py b/cm2350/e200z7.py 2 | index a0b660c..6241c73 100644 3 | --- a/cm2350/e200z7.py 4 | +++ b/cm2350/e200z7.py 5 | @@ -26,6 +26,8 @@ import envi 6 | import envi.bits as e_bits 7 | import envi.memory as e_mem 8 | 9 | +from .grpc_client import grpc_client 10 | + 11 | # PPC registers 12 | import envi.archs.ppc.regs as eapr 13 | from .ppc_vstructs import v_const, v_w1c, v_bits, BitFieldSPR, PpcSprCallbackWrapper 14 | @@ -153,6 +155,8 @@ class PPC_e200z7(mmio.ComplexMemoryMap, vimp_emu.WorkspaceEmulator, 15 | # Initialize the core PPC emualtor 16 | eape.Ppc32EmbeddedEmulator.__init__(self, endian=envi.ENDIAN_MSB) 17 | 18 | + self.grpc_client = grpc_client.grpc_client(self) 19 | + 20 | # initialize the core "helper" emulator. WorkspaceEmulator to start, as 21 | # it provides inspection hooks and tracing which is helpful for 22 | # debugging. PpcWorkspaceEmulator is the base PPC WorkspaceEmulator with 23 | @@ -841,14 +845,14 @@ class PPC_e200z7(mmio.ComplexMemoryMap, vimp_emu.WorkspaceEmulator, 24 | self.processIO() 25 | 26 | pc = self.getProgramCounter() 27 | + # do normal opcode parsing and execution 28 | + op, op_bytes = self._parseOpcode(pc) 29 | + self.grpc_client.record_instruction(op, op_bytes) 30 | 31 | try: 32 | # See if there are any exceptions that need to start being handled 33 | self.mcu_intc.checkException() 34 | 35 | - # do normal opcode parsing and execution 36 | - op = self.parseOpcode(pc) 37 | - 38 | # TODO: check MSR for FP (MSR_FP_MASK) and SPE (MSR_SPE_MASK) 39 | # support here? 40 | self.executeOpcode(op) 41 | @@ -966,7 +970,7 @@ class PPC_e200z7(mmio.ComplexMemoryMap, vimp_emu.WorkspaceEmulator, 42 | # Attempt to clear any instruction cache 43 | self.clearOpcache(ea, len(bytez)) 44 | 45 | - def parseOpcode(self, va, arch=envi.ARCH_PPC_E32, skipcache=False, skipcallbacks=False): 46 | + def _parseOpcode(self, va, arch=envi.ARCH_PPC_E32, skipcache=False, skipcallbacks=False): 47 | ''' 48 | Combination of the WorkspaceEmulator and the standard envi.memory 49 | parseOpcode functions that handles translating instruction addresses 50 | @@ -979,8 +983,8 @@ class PPC_e200z7(mmio.ComplexMemoryMap, vimp_emu.WorkspaceEmulator, 51 | else: 52 | op = self.opcache[vle].get(ea) 53 | 54 | + off, b = mmio.ComplexMemoryMap.getByteDef(self, ea) 55 | if op is None: 56 | - off, b = mmio.ComplexMemoryMap.getByteDef(self, ea) 57 | if vle: 58 | op = self._arch_vle_dis.disasm(b, off, va) 59 | else: 60 | @@ -1004,7 +1008,10 @@ class PPC_e200z7(mmio.ComplexMemoryMap, vimp_emu.WorkspaceEmulator, 61 | # handling information 62 | self._cur_instr = (op, va, va+op.size, vle) 63 | 64 | - return op 65 | + return op, b[off:off+4] 66 | + 67 | + def parseOpcode(self, va, arch=envi.ARCH_PPC_E32, skipcache=False, skipcallbacks=False): 68 | + return self._parseOpcode(va, arch, skipcache, skipcallbacks)[0] 69 | 70 | def _checkReadCallbacks(self, src, addr, data=None, size=0, instr=False): 71 | ''' 72 | diff --git a/requirements.txt b/requirements.txt 73 | index 0ddd64f..d457d92 100644 74 | --- a/requirements.txt 75 | +++ b/requirements.txt 76 | @@ -1,2 +1,5 @@ 77 | ipython>=8.0.0 78 | -e git+https://github.com/atlas0fd00m/vivisect.git@envi_ppc#egg=vivisect 79 | +grpcio 80 | +protobuf 81 | +lxml 82 | -------------------------------------------------------------------------------- /plugins/vivisect-broker/emulator.grpc.pb.cc: -------------------------------------------------------------------------------- 1 | // Generated by the gRPC C++ plugin. 2 | // If you make any local change, they will be lost. 3 | // source: emulator.proto 4 | 5 | #include "emulator.pb.h" 6 | #include "emulator.grpc.pb.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | static const char* Emulator_method_names[] = { 24 | "/Emulator/RecordEmulatorActions", 25 | }; 26 | 27 | std::unique_ptr< Emulator::Stub> Emulator::NewStub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options) { 28 | (void)options; 29 | std::unique_ptr< Emulator::Stub> stub(new Emulator::Stub(channel, options)); 30 | return stub; 31 | } 32 | 33 | Emulator::Stub::Stub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options) 34 | : channel_(channel), rpcmethod_RecordEmulatorActions_(Emulator_method_names[0], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel) 35 | {} 36 | 37 | ::grpc::Status Emulator::Stub::RecordEmulatorActions(::grpc::ClientContext* context, const ::EmulatorActions& request, ::NextAction* response) { 38 | return ::grpc::internal::BlockingUnaryCall< ::EmulatorActions, ::NextAction, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_RecordEmulatorActions_, context, request, response); 39 | } 40 | 41 | void Emulator::Stub::async::RecordEmulatorActions(::grpc::ClientContext* context, const ::EmulatorActions* request, ::NextAction* response, std::function f) { 42 | ::grpc::internal::CallbackUnaryCall< ::EmulatorActions, ::NextAction, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_RecordEmulatorActions_, context, request, response, std::move(f)); 43 | } 44 | 45 | void Emulator::Stub::async::RecordEmulatorActions(::grpc::ClientContext* context, const ::EmulatorActions* request, ::NextAction* response, ::grpc::ClientUnaryReactor* reactor) { 46 | ::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_RecordEmulatorActions_, context, request, response, reactor); 47 | } 48 | 49 | ::grpc::ClientAsyncResponseReader< ::NextAction>* Emulator::Stub::PrepareAsyncRecordEmulatorActionsRaw(::grpc::ClientContext* context, const ::EmulatorActions& request, ::grpc::CompletionQueue* cq) { 50 | return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::NextAction, ::EmulatorActions, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_RecordEmulatorActions_, context, request); 51 | } 52 | 53 | ::grpc::ClientAsyncResponseReader< ::NextAction>* Emulator::Stub::AsyncRecordEmulatorActionsRaw(::grpc::ClientContext* context, const ::EmulatorActions& request, ::grpc::CompletionQueue* cq) { 54 | auto* result = 55 | this->PrepareAsyncRecordEmulatorActionsRaw(context, request, cq); 56 | result->StartCall(); 57 | return result; 58 | } 59 | 60 | Emulator::Service::Service() { 61 | AddMethod(new ::grpc::internal::RpcServiceMethod( 62 | Emulator_method_names[0], 63 | ::grpc::internal::RpcMethod::NORMAL_RPC, 64 | new ::grpc::internal::RpcMethodHandler< Emulator::Service, ::EmulatorActions, ::NextAction, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>( 65 | [](Emulator::Service* service, 66 | ::grpc::ServerContext* ctx, 67 | const ::EmulatorActions* req, 68 | ::NextAction* resp) { 69 | return service->RecordEmulatorActions(ctx, req, resp); 70 | }, this))); 71 | } 72 | 73 | Emulator::Service::~Service() { 74 | } 75 | 76 | ::grpc::Status Emulator::Service::RecordEmulatorActions(::grpc::ServerContext* context, const ::EmulatorActions* request, ::NextAction* response) { 77 | (void) context; 78 | (void) request; 79 | (void) response; 80 | return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); 81 | } 82 | 83 | 84 | -------------------------------------------------------------------------------- /plugins/vivisect-broker/emulator.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | service Emulator { 4 | rpc RecordEmulatorActions (EmulatorActions) returns (NextAction); 5 | } 6 | 7 | message NextAction { 8 | optional int32 num_instructions = 1; 9 | } 10 | 11 | message EmulatorActions { 12 | repeated Instruction instructions = 1; 13 | 14 | message MemoryAccess { 15 | uint64 vaddr = 1; 16 | uint32 size = 2; 17 | bool is_store = 3; 18 | } 19 | 20 | message BranchFlow { 21 | bool is_mispredict = 1; 22 | } 23 | 24 | message Instruction { 25 | bytes opcode = 1; 26 | uint64 addr = 2; 27 | uint32 size = 3; 28 | optional MemoryAccess memory_access = 4; 29 | optional BranchFlow branch_flow = 5; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /plugins/vivisect-broker/generate_interface.sh: -------------------------------------------------------------------------------- 1 | if [ -z "$grpc_cpp_plugin_path" ]; then 2 | grpc_cpp_plugin_path=$(which grpc_cpp_plugin) 3 | if [ -z "$grpc_cpp_plugin_path" ]; then 4 | grpc_cpp_plugin_path=$(realpath ../../build/_deps/grpc-build/grpc_cpp_plugin) 5 | if [ -z "$grpc_cpp_plugin_path" ]; then 6 | echo "Did not find grpc_cpp_plugin. You should be able to get one by configuring CMake, which will clone gRPC into build_dir/_deps/grpc-build; then ninja grpc_cpp_plugin"; 7 | fi 8 | fi 9 | fi 10 | python3 -m grpc_tools.protoc -I. --python_out=grpc_client --pyi_out=grpc_client --grpc_python_out=grpc_client emulator.proto 11 | protoc --grpc_out=. --cpp_out=. "--plugin=protoc-gen-grpc=$grpc_cpp_plugin_path" emulator.proto --experimental_allow_proto3_optional 12 | -------------------------------------------------------------------------------- /plugins/vivisect-broker/grpc_client/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ["grpc_client", "emulator_pb2", "emulator_pb2_grpc"] 2 | -------------------------------------------------------------------------------- /plugins/vivisect-broker/grpc_client/emulator_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # NO CHECKED-IN PROTOBUF GENCODE 4 | # source: emulator.proto 5 | # Protobuf Python Version: 5.29.0 6 | """Generated protocol buffer code.""" 7 | from google.protobuf import descriptor as _descriptor 8 | from google.protobuf import descriptor_pool as _descriptor_pool 9 | from google.protobuf import runtime_version as _runtime_version 10 | from google.protobuf import symbol_database as _symbol_database 11 | from google.protobuf.internal import builder as _builder 12 | _runtime_version.ValidateProtobufRuntimeVersion( 13 | _runtime_version.Domain.PUBLIC, 14 | 5, 15 | 29, 16 | 0, 17 | '', 18 | 'emulator.proto' 19 | ) 20 | # @@protoc_insertion_point(imports) 21 | 22 | _sym_db = _symbol_database.Default() 23 | 24 | 25 | 26 | 27 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0e\x65mulator.proto\"@\n\nNextAction\x12\x1d\n\x10num_instructions\x18\x01 \x01(\x05H\x00\x88\x01\x01\x42\x13\n\x11_num_instructions\"\xf9\x02\n\x0f\x45mulatorActions\x12\x32\n\x0cinstructions\x18\x01 \x03(\x0b\x32\x1c.EmulatorActions.Instruction\x1a=\n\x0cMemoryAccess\x12\r\n\x05vaddr\x18\x01 \x01(\x04\x12\x0c\n\x04size\x18\x02 \x01(\r\x12\x10\n\x08is_store\x18\x03 \x01(\x08\x1a#\n\nBranchFlow\x12\x15\n\ris_mispredict\x18\x01 \x01(\x08\x1a\xcd\x01\n\x0bInstruction\x12\x0e\n\x06opcode\x18\x01 \x01(\x0c\x12\x0c\n\x04\x61\x64\x64r\x18\x02 \x01(\x04\x12\x0c\n\x04size\x18\x03 \x01(\r\x12\x39\n\rmemory_access\x18\x04 \x01(\x0b\x32\x1d.EmulatorActions.MemoryAccessH\x00\x88\x01\x01\x12\x35\n\x0b\x62ranch_flow\x18\x05 \x01(\x0b\x32\x1b.EmulatorActions.BranchFlowH\x01\x88\x01\x01\x42\x10\n\x0e_memory_accessB\x0e\n\x0c_branch_flow2B\n\x08\x45mulator\x12\x36\n\x15RecordEmulatorActions\x12\x10.EmulatorActions\x1a\x0b.NextActionb\x06proto3') 28 | 29 | _globals = globals() 30 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) 31 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'emulator_pb2', _globals) 32 | if not _descriptor._USE_C_DESCRIPTORS: 33 | DESCRIPTOR._loaded_options = None 34 | _globals['_NEXTACTION']._serialized_start=18 35 | _globals['_NEXTACTION']._serialized_end=82 36 | _globals['_EMULATORACTIONS']._serialized_start=85 37 | _globals['_EMULATORACTIONS']._serialized_end=462 38 | _globals['_EMULATORACTIONS_MEMORYACCESS']._serialized_start=156 39 | _globals['_EMULATORACTIONS_MEMORYACCESS']._serialized_end=217 40 | _globals['_EMULATORACTIONS_BRANCHFLOW']._serialized_start=219 41 | _globals['_EMULATORACTIONS_BRANCHFLOW']._serialized_end=254 42 | _globals['_EMULATORACTIONS_INSTRUCTION']._serialized_start=257 43 | _globals['_EMULATORACTIONS_INSTRUCTION']._serialized_end=462 44 | _globals['_EMULATOR']._serialized_start=464 45 | _globals['_EMULATOR']._serialized_end=530 46 | # @@protoc_insertion_point(module_scope) 47 | -------------------------------------------------------------------------------- /plugins/vivisect-broker/grpc_client/emulator_pb2.pyi: -------------------------------------------------------------------------------- 1 | from google.protobuf.internal import containers as _containers 2 | from google.protobuf import descriptor as _descriptor 3 | from google.protobuf import message as _message 4 | from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union 5 | 6 | DESCRIPTOR: _descriptor.FileDescriptor 7 | 8 | class NextAction(_message.Message): 9 | __slots__ = ("num_instructions",) 10 | NUM_INSTRUCTIONS_FIELD_NUMBER: _ClassVar[int] 11 | num_instructions: int 12 | def __init__(self, num_instructions: _Optional[int] = ...) -> None: ... 13 | 14 | class EmulatorActions(_message.Message): 15 | __slots__ = ("instructions",) 16 | class MemoryAccess(_message.Message): 17 | __slots__ = ("vaddr", "size", "is_store") 18 | VADDR_FIELD_NUMBER: _ClassVar[int] 19 | SIZE_FIELD_NUMBER: _ClassVar[int] 20 | IS_STORE_FIELD_NUMBER: _ClassVar[int] 21 | vaddr: int 22 | size: int 23 | is_store: bool 24 | def __init__(self, vaddr: _Optional[int] = ..., size: _Optional[int] = ..., is_store: bool = ...) -> None: ... 25 | class BranchFlow(_message.Message): 26 | __slots__ = ("is_mispredict",) 27 | IS_MISPREDICT_FIELD_NUMBER: _ClassVar[int] 28 | is_mispredict: bool 29 | def __init__(self, is_mispredict: bool = ...) -> None: ... 30 | class Instruction(_message.Message): 31 | __slots__ = ("opcode", "addr", "size", "memory_access", "branch_flow") 32 | OPCODE_FIELD_NUMBER: _ClassVar[int] 33 | ADDR_FIELD_NUMBER: _ClassVar[int] 34 | SIZE_FIELD_NUMBER: _ClassVar[int] 35 | MEMORY_ACCESS_FIELD_NUMBER: _ClassVar[int] 36 | BRANCH_FLOW_FIELD_NUMBER: _ClassVar[int] 37 | opcode: bytes 38 | addr: int 39 | size: int 40 | memory_access: EmulatorActions.MemoryAccess 41 | branch_flow: EmulatorActions.BranchFlow 42 | def __init__(self, opcode: _Optional[bytes] = ..., addr: _Optional[int] = ..., size: _Optional[int] = ..., memory_access: _Optional[_Union[EmulatorActions.MemoryAccess, _Mapping]] = ..., branch_flow: _Optional[_Union[EmulatorActions.BranchFlow, _Mapping]] = ...) -> None: ... 43 | INSTRUCTIONS_FIELD_NUMBER: _ClassVar[int] 44 | instructions: _containers.RepeatedCompositeFieldContainer[EmulatorActions.Instruction] 45 | def __init__(self, instructions: _Optional[_Iterable[_Union[EmulatorActions.Instruction, _Mapping]]] = ...) -> None: ... 46 | -------------------------------------------------------------------------------- /plugins/vivisect-broker/grpc_client/emulator_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | """Client and server classes corresponding to protobuf-defined services.""" 3 | import grpc 4 | import warnings 5 | 6 | import emulator_pb2 as emulator__pb2 7 | 8 | GRPC_GENERATED_VERSION = '1.70.0' 9 | GRPC_VERSION = grpc.__version__ 10 | _version_not_supported = False 11 | 12 | try: 13 | from grpc._utilities import first_version_is_lower 14 | _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) 15 | except ImportError: 16 | _version_not_supported = True 17 | 18 | if _version_not_supported: 19 | raise RuntimeError( 20 | f'The grpc package installed is at version {GRPC_VERSION},' 21 | + f' but the generated code in emulator_pb2_grpc.py depends on' 22 | + f' grpcio>={GRPC_GENERATED_VERSION}.' 23 | + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' 24 | + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' 25 | ) 26 | 27 | 28 | class EmulatorStub(object): 29 | """Missing associated documentation comment in .proto file.""" 30 | 31 | def __init__(self, channel): 32 | """Constructor. 33 | 34 | Args: 35 | channel: A grpc.Channel. 36 | """ 37 | self.RecordEmulatorActions = channel.unary_unary( 38 | '/Emulator/RecordEmulatorActions', 39 | request_serializer=emulator__pb2.EmulatorActions.SerializeToString, 40 | response_deserializer=emulator__pb2.NextAction.FromString, 41 | _registered_method=True) 42 | 43 | 44 | class EmulatorServicer(object): 45 | """Missing associated documentation comment in .proto file.""" 46 | 47 | def RecordEmulatorActions(self, request, context): 48 | """Missing associated documentation comment in .proto file.""" 49 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 50 | context.set_details('Method not implemented!') 51 | raise NotImplementedError('Method not implemented!') 52 | 53 | 54 | def add_EmulatorServicer_to_server(servicer, server): 55 | rpc_method_handlers = { 56 | 'RecordEmulatorActions': grpc.unary_unary_rpc_method_handler( 57 | servicer.RecordEmulatorActions, 58 | request_deserializer=emulator__pb2.EmulatorActions.FromString, 59 | response_serializer=emulator__pb2.NextAction.SerializeToString, 60 | ), 61 | } 62 | generic_handler = grpc.method_handlers_generic_handler( 63 | 'Emulator', rpc_method_handlers) 64 | server.add_generic_rpc_handlers((generic_handler,)) 65 | server.add_registered_method_handlers('Emulator', rpc_method_handlers) 66 | 67 | 68 | # This class is part of an EXPERIMENTAL API. 69 | class Emulator(object): 70 | """Missing associated documentation comment in .proto file.""" 71 | 72 | @staticmethod 73 | def RecordEmulatorActions(request, 74 | target, 75 | options=(), 76 | channel_credentials=None, 77 | call_credentials=None, 78 | insecure=False, 79 | compression=None, 80 | wait_for_ready=None, 81 | timeout=None, 82 | metadata=None): 83 | return grpc.experimental.unary_unary( 84 | request, 85 | target, 86 | '/Emulator/RecordEmulatorActions', 87 | emulator__pb2.EmulatorActions.SerializeToString, 88 | emulator__pb2.NextAction.FromString, 89 | options, 90 | channel_credentials, 91 | insecure, 92 | call_credentials, 93 | compression, 94 | wait_for_ready, 95 | timeout, 96 | metadata, 97 | _registered_method=True) 98 | -------------------------------------------------------------------------------- /plugins/vivisect-broker/grpc_client/grpc_client.py: -------------------------------------------------------------------------------- 1 | import grpc 2 | 3 | import sys 4 | import os 5 | 6 | sys.path.append(os.path.dirname(os.path.abspath(__file__))) 7 | 8 | import emulator_pb2 9 | import emulator_pb2_grpc 10 | 11 | from envi.archs.ppc.const import LOAD_INSTRS, STORE_INSTRS 12 | from envi.archs.ppc.disasm_classes import BCTR_INSTR, BLR_INSTR 13 | 14 | class grpc_client: 15 | def __init__(self, emu): 16 | self.channel = grpc.insecure_channel("localhost:50051") 17 | self.stub = emulator_pb2_grpc.EmulatorStub(self.channel) 18 | self.emu = emu 19 | 20 | def record_instruction(self, op, bytez): 21 | is_load = op.opcode in LOAD_INSTRS 22 | is_store = op.opcode in STORE_INSTRS 23 | is_branch_ctr = op.opcode in BCTR_INSTR 24 | is_branch_lr = op.opcode in BLR_INSTR 25 | insn_addr = op.va 26 | insn_size = op.size 27 | 28 | addr = None 29 | width = None 30 | if is_load or is_store: 31 | addr = [] 32 | oper = None 33 | for operand in op.opers: 34 | maybe_addr = operand.getOperAddr((), emu=self.emu) 35 | if maybe_addr is not None: 36 | oper = operand 37 | addr.append(maybe_addr) 38 | 39 | assert(len(addr) == 1) 40 | addr = addr[0] 41 | width = oper.getWidth(emu=self.emu) 42 | 43 | opcode = bytes(bytez) 44 | 45 | mem_arg = None 46 | if addr is not None and width is not None: 47 | mem_arg = emulator_pb2.EmulatorActions.MemoryAccess( 48 | vaddr=addr, 49 | size=width, 50 | is_store=is_store) 51 | 52 | branch_arg = None 53 | if is_branch_ctr or is_branch_lr: 54 | # XXX: Fix this - or make it random 55 | is_mispredict = True 56 | if is_mispredict: 57 | branch_arg = emulator_pb2.EmulatorActions.BranchFlow( 58 | is_mispredict=is_mispredict) 59 | 60 | insn_arg = emulator_pb2.EmulatorActions.Instruction(opcode=opcode, addr=insn_addr, size=insn_size) 61 | if mem_arg is not None: 62 | insn_arg = emulator_pb2.EmulatorActions.Instruction(opcode=opcode, addr=insn_addr, size=insn_size, 63 | memory_access=mem_arg) 64 | 65 | if branch_arg is not None: 66 | insn_arg = emulator_pb2.EmulatorActions.Instruction(opcode=opcode, addr=insn_addr, size=insn_size, 67 | branch_flow=branch_arg) 68 | 69 | action_arg = emulator_pb2.EmulatorActions(instructions=[insn_arg]) 70 | 71 | self.stub.RecordEmulatorActions(action_arg) 72 | 73 | def __del__(self): 74 | self.stub.RecordEmulatorActions(emulator_pb2.EmulatorActions(instructions=[])) 75 | self.channel.close() 76 | -------------------------------------------------------------------------------- /test/binja-broker/binja_pb2.py: -------------------------------------------------------------------------------- 1 | ../../plugins/binja-broker/binja-plugin/binja_pb2.py -------------------------------------------------------------------------------- /test/binja-broker/binja_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | ../../plugins/binja-broker/binja-plugin/binja_pb2_grpc.py -------------------------------------------------------------------------------- /test/binja-broker/empty_arg.py: -------------------------------------------------------------------------------- 1 | # Test if BinjaBroker shuts down reliably on sending an empty instructions list 2 | from utils import binja_client 3 | import argparse 4 | 5 | parser = argparse.ArgumentParser("MCAD testing") 6 | parser.add_argument("--port", dest="port", type=int) 7 | args = parser.parse_args() 8 | 9 | if __name__ == "__main__": 10 | G = binja_client(args.port) 11 | # Send empty instructions list 12 | response = G.send_instructions([]) 13 | if response["status"] == "failure": 14 | ex = response["exception"] 15 | print("RPC Error. Code: " + str(ex.code())) 16 | print("Details: " + ex.details()) 17 | 18 | # Close connection 19 | response = G.send_instructions([]) 20 | if response["status"] == "failure": 21 | ex = response["exception"] 22 | print("RPC Error. Code: " + str(ex.code())) 23 | print("Details: " + ex.details()) 24 | -------------------------------------------------------------------------------- /test/binja-broker/empty_arg.s: -------------------------------------------------------------------------------- 1 | # RUN: export MCAD_PORT=50055 2 | # RUN: llvm-mcad -mtriple=x86_64-unknown-unknown -mcpu=znver2 -load-broker-plugin=$MCAD_BIN_PATH/plugins/binja-broker/libMCADBinjaBroker.so --broker-plugin-arg-host=0.0.0.0:$MCAD_PORT | python3 %S/empty_arg.py --port $MCAD_PORT > %t 3 | # RUN: FileCheck --input-file=%t %s 4 | 5 | # CHECK: PC Error. Code: StatusCode.CANCELLED 6 | # CHECK-NEXT: Details: CANCELLED 7 | -------------------------------------------------------------------------------- /test/binja-broker/invalid_arg.py: -------------------------------------------------------------------------------- 1 | # Test if BinjaBroker can handle invalid instructions 2 | from utils import binja_client 3 | import argparse 4 | 5 | parser = argparse.ArgumentParser("MCAD testing") 6 | parser.add_argument("--port", dest="port", type=int) 7 | args = parser.parse_args() 8 | 9 | if __name__ == "__main__": 10 | G = binja_client(args.port) 11 | instructions = [] 12 | # Send null instruction 13 | instructions.append(G.get_instruction([0x00])) 14 | response = G.send_instructions(instructions) 15 | if response["status"] == "failure": 16 | ex = response["exception"] 17 | print("RPC Error. Code: " + str(ex.code())) 18 | print("Details: " + ex.details()) 19 | 20 | # Close connection 21 | G.send_instructions([]) 22 | -------------------------------------------------------------------------------- /test/binja-broker/invalid_arg.s: -------------------------------------------------------------------------------- 1 | # RUN: export MCAD_PORT=50053 2 | # RUN: llvm-mcad -mtriple=x86_64-unknown-unknown -mcpu=znver2 -load-broker-plugin=$MCAD_BIN_PATH/plugins/binja-broker/libMCADBinjaBroker.so --broker-plugin-arg-host=0.0.0.0:$MCAD_PORT | python3 %S/invalid_arg.py --port $MCAD_PORT > %t 3 | # RUN: FileCheck --input-file=%t %s 4 | 5 | # CHECK: PC Error. Code: StatusCode.INVALID_ARGUMENT 6 | # CHECK-NEXT: Details: Errors in instruction(s) 0 (Disassembler reported Fail). 7 | -------------------------------------------------------------------------------- /test/binja-broker/sanity-x64.py: -------------------------------------------------------------------------------- 1 | # BinjaBroker sanity test with x64 2 | from utils import binja_client 3 | import argparse 4 | 5 | parser = argparse.ArgumentParser("MCAD testing") 6 | parser.add_argument("--port", dest="port", type=int) 7 | args = parser.parse_args() 8 | 9 | if __name__ == "__main__": 10 | G = binja_client(args.port) 11 | 12 | instructions = [] 13 | # add rax, rbx 14 | instructions.append(G.get_instruction([0x48, 0x01, 0xD8])) 15 | # mov rbx, rcx 16 | instructions.append(G.get_instruction([0x48, 0x89, 0xCB])) 17 | # syscall 18 | instructions.append(G.get_instruction([0x0F, 0x05])) 19 | response = G.send_instructions(instructions) 20 | 21 | print("STATUS: {}".format(response['status'])) 22 | print(response['result']) 23 | 24 | # Close connection 25 | G.send_instructions([]) 26 | -------------------------------------------------------------------------------- /test/binja-broker/sanity-x64.s: -------------------------------------------------------------------------------- 1 | # RUN: export MCAD_PORT=50054 2 | # RUN: llvm-mcad -mtriple=x86_64-unknown-unknown -mcpu=znver2 -load-broker-plugin=$MCAD_BIN_PATH/plugins/binja-broker/libMCADBinjaBroker.so --broker-plugin-arg-host=0.0.0.0:$MCAD_PORT --mca-output=%t-mcad | python3 %S/sanity-x64.py --port $MCAD_PORT > %t-client 3 | # RUN: FileCheck --input-file=%t-mcad --check-prefix=MCAD %s 4 | # RUN: FileCheck --input-file=%t-client --check-prefix=CLIENT %s 5 | 6 | # MCAD: === Printing report for Region [0] === 7 | # MCAD-NEXT: Iterations: 1 8 | # MCAD-NEXT: Instructions: 3 9 | # MCAD-NEXT: Total Cycles: 103 10 | # MCAD-NEXT: Total uOps: 3 11 | 12 | # MCAD: Dispatch Width: 4 13 | # MCAD-NEXT: uOps Per Cycle: 0.03 14 | # MCAD-NEXT: IPC: 0.03 15 | # MCAD-NEXT: Block RThroughput: 0.8 16 | 17 | # CLIENT: STATUS: success 18 | # CLIENT-NEXT: cycle_count { 19 | # CLIENT-NEXT: executed: 2 20 | # CLIENT-NEXT: } 21 | # CLIENT-NEXT: cycle_count { 22 | # CLIENT-NEXT: executed: 2 23 | # CLIENT-NEXT: } 24 | # CLIENT-NEXT: cycle_count { 25 | # CLIENT-NEXT: executed: 101 26 | # CLIENT-NEXT: } 27 | -------------------------------------------------------------------------------- /test/binja-broker/utils.py: -------------------------------------------------------------------------------- 1 | import grpc 2 | import binja_pb2 3 | import binja_pb2_grpc 4 | 5 | class binja_client: 6 | def __init__(self, port=50052): 7 | self.channel = grpc.insecure_channel("localhost:" + str(port)) 8 | self.stub = binja_pb2_grpc.BinjaStub(self.channel) 9 | 10 | def get_instruction(self, insn_bytes): 11 | return binja_pb2.BinjaInstructions.Instruction(opcode=bytes(bytearray(insn_bytes))) 12 | 13 | def send_instructions(self, instructions): 14 | response = {} 15 | try: 16 | result = self.stub.RequestCycleCounts(binja_pb2.BinjaInstructions(instruction=instructions)) 17 | response["status"] = "success" 18 | response["result"] = result 19 | except Exception as ex: 20 | response["status"] = "failure" 21 | response["exception"] = ex 22 | return response 23 | -------------------------------------------------------------------------------- /test/lit.cfg.py: -------------------------------------------------------------------------------- 1 | import lit.formats 2 | import os 3 | 4 | config.name = "Example tests" 5 | config.test_format = lit.formats.ShTest(True) 6 | 7 | config.suffixes = [".s"] 8 | 9 | # test_source_root: The root path where tests are located. 10 | config.test_source_root = os.path.dirname(__file__) 11 | 12 | # Build directory path 13 | mcad_bin_path = os.path.realpath("../build") 14 | config.environment["MCAD_BIN_PATH"] = mcad_bin_path 15 | 16 | # test_exec_root: The root path where tests should be run. 17 | config.test_exec_root = mcad_bin_path 18 | 19 | # Tweak the PATH to include the binary dir. 20 | path = os.environ["PATH"] + os.pathsep + mcad_bin_path 21 | config.environment["PATH"] = path 22 | -------------------------------------------------------------------------------- /test/my-lit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from lit.main import main 3 | if __name__ == '__main__': 4 | main() 5 | --------------------------------------------------------------------------------