From ff01b175d08961f4936e57aa047ebf324d66ab18 Mon Sep 17 00:00:00 2001 From: AnirudhK Date: Mon, 15 Jun 2026 01:47:23 -0400 Subject: [PATCH 1/4] Apply peterc changes to polygesit for HLS pragma handling --- tools/cgeist/Lib/pragmaHandlerHLS.cc | 54 +++++++++++++++++++ tools/cgeist/Lib/pragmaHandlerHLS.h | 51 ++++++++++++++++++ .../Test/HLS/AccessMemberArrayPartition.cpp | 43 +++++++++++++++ tools/cgeist/Test/HLS/Allocation.cpp | 23 ++++++++ tools/cgeist/Test/HLS/BasicArrayPartition.cpp | 12 +++++ tools/cgeist/Test/HLS/BindOp.cpp | 11 ++++ tools/cgeist/Test/HLS/BindStorage.cpp | 23 ++++++++ tools/cgeist/Test/HLS/Pipeline.cpp | 12 +++++ tools/cgeist/Test/HLS/Unroll.cpp | 13 +++++ tools/cgeist/driver.cc | 8 +++ 10 files changed, 250 insertions(+) create mode 100644 tools/cgeist/Lib/pragmaHandlerHLS.cc create mode 100644 tools/cgeist/Lib/pragmaHandlerHLS.h create mode 100644 tools/cgeist/Test/HLS/AccessMemberArrayPartition.cpp create mode 100644 tools/cgeist/Test/HLS/Allocation.cpp create mode 100644 tools/cgeist/Test/HLS/BasicArrayPartition.cpp create mode 100644 tools/cgeist/Test/HLS/BindOp.cpp create mode 100644 tools/cgeist/Test/HLS/BindStorage.cpp create mode 100644 tools/cgeist/Test/HLS/Pipeline.cpp create mode 100644 tools/cgeist/Test/HLS/Unroll.cpp diff --git a/tools/cgeist/Lib/pragmaHandlerHLS.cc b/tools/cgeist/Lib/pragmaHandlerHLS.cc new file mode 100644 index 000000000000..f7cd1f624906 --- /dev/null +++ b/tools/cgeist/Lib/pragmaHandlerHLS.cc @@ -0,0 +1,54 @@ +#include "pragmaHandlerHLS.h" +#include "clang/Frontend/ASTConsumers.h" +#include "clang/Frontend/MultiplexConsumer.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "mlir/IR/Builders.h" + +using namespace clang; + +std::vector g_HLSMetadata; + +PragmaHLSHandler::PragmaHLSHandler(std::vector &Metadata) + : PragmaHandler("HLS"), HLSMetaData(Metadata) {} + +void PragmaHLSHandler::HandlePragma(Preprocessor &PP, + PragmaIntroducer Introducer, + Token &PragmaTok) { + SourceManager &SM = PP.getSourceManager(); + Token Tok{}; + PP.Lex(Tok); + + if (Tok.isNot(tok::identifier)) return; + HLSPragmaMetaData P; + P.Name = StringRef(PP.getSpelling(Tok)).lower(); + P.Loc = Tok.getLocation(); + P.OffsetLoc = SM.getSpellingLineNumber(P.Loc); // ← add this line + PP.Lex(Tok); + + if (Tok.is(tok::eod)) { + HLSMetaData.push_back(P); + return; + } + + SourceLocation Start = Tok.getLocation(); + SourceLocation End = Start; + while (PP.Lex(Tok), Tok.isNot(tok::eod)) + End = Tok.getEndLoc(); + + // Extract raw text from source + bool Invalid = false; + const char *StartPtr = SM.getCharacterData(Start, &Invalid); + const char *EndPtr = SM.getCharacterData(End, &Invalid); + + if (!Invalid && StartPtr && EndPtr && EndPtr > StartPtr) { + StringRef RawPragma(StartPtr, EndPtr - StartPtr); + P.Args = RawPragma.str(); + } + llvm::outs() << "[HLS] pragma name: " << P.Name << " pragma args: " + << P.Args << "\n"; + HLSMetaData.push_back(P); +} + +void addPragmaHLSHandlers(Preprocessor &PP) { + PP.AddPragmaHandler(new PragmaHLSHandler(g_HLSMetadata)); +} \ No newline at end of file diff --git a/tools/cgeist/Lib/pragmaHandlerHLS.h b/tools/cgeist/Lib/pragmaHandlerHLS.h new file mode 100644 index 000000000000..6f979e213da2 --- /dev/null +++ b/tools/cgeist/Lib/pragmaHandlerHLS.h @@ -0,0 +1,51 @@ +#ifndef MLIR_TOOLS_MLIRCLANG_LIB_PRAGMAHANDLERHLS_H +#define MLIR_TOOLS_MLIRCLANG_LIB_PRAGMAHANDLERHLS_H + +#include "clang/Frontend/FrontendAction.h" +#include "clang/Lex/Pragma.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "mlir/IR/Operation.h" +#include +#include + +enum HLSPragmaType { + ArrayPartition, + ArrayReshape, + BindStorage, + BindOp, + Allocation, + Unroll, + Pipeline + }; + +/// Holds metadata parsed from #pragma HLS +struct HLSPragmaMetaData { + std::string Name; // e.g., "pipeline" + std::string Args; // e.g., "II=1" + clang::SourceLocation Loc; + mlir::Operation *Op = nullptr; + unsigned OffsetLoc = 0; +}; + +/// Handles the transition from Preprocessor (#pragma) to AST metadata +class PragmaHLSHandler : public clang::PragmaHandler { +public: + PragmaHLSHandler(std::vector &Metadata); + + void HandlePragma(clang::Preprocessor &PP, + clang::PragmaIntroducer Introducer, + clang::Token &PragmaTok) override; +private: + std::vector &HLSMetaData; +}; + +void addPragmaHLSHandlers(clang::Preprocessor &PP); + +// Global storage for HLS pragma metadata collected during parse. +// Populated by PragmaHLSHandler::HandlePragma, consumed in driver.cc +// after the MLIR pass pipeline runs. +extern std::vector g_HLSMetadata; + + +#endif \ No newline at end of file diff --git a/tools/cgeist/Test/HLS/AccessMemberArrayPartition.cpp b/tools/cgeist/Test/HLS/AccessMemberArrayPartition.cpp new file mode 100644 index 000000000000..94b0c5dedd52 --- /dev/null +++ b/tools/cgeist/Test/HLS/AccessMemberArrayPartition.cpp @@ -0,0 +1,43 @@ +//RUN: cgeist %s --function="*" -S --hls-annotate --raise-scf-to-affine --memref-fullrank| FileCheck %s + +struct C { + int A[8]; + int B[4]; +}; + +struct S { + int A[8]; + int B[4]; + C c; +}; + +int top(int input1[8], int input2[4]) { + S s; +#pragma HLS array_partition variable=s.A complete +#pragma HLS array_partition variable=s.c.A complete +#pragma HLS array_partition variable=s.B block factor=2 + for (int i = 0; i < 8; i++) { + s.A[i] = input1[i]; + s.c.A[i] = 2 * input1[i]; + } + for (int i = 0; i < 4; i++) { + s.B[i] = input2[i]; + s.c.B[i] = 2 * input2[i]; + } + + for (int i = 0; i < 8; i++) { + s.A[i] = s.A[i] + 1; + s.c.A[i] = s.c.A[i] + 1; + } + int sum = 0; + for (int i = 0; i < 8; i++) { + sum += s.A[i]; + sum += s.c.A[i]; + } + + return sum; + +} + +// CHECK: %alloca = memref.alloca() {hls.array_partition = [{kind = "complete", variable = "s.A"}, {kind = "complete", variable = "s.c.A"}, {factor = 2 : i32, kind = "block", variable = "s.B"}], polygeist.varname = "s"} + diff --git a/tools/cgeist/Test/HLS/Allocation.cpp b/tools/cgeist/Test/HLS/Allocation.cpp new file mode 100644 index 000000000000..7bea626fad52 --- /dev/null +++ b/tools/cgeist/Test/HLS/Allocation.cpp @@ -0,0 +1,23 @@ +//RUN: cgeist %s --function="*" -S --hls-annotate --raise-scf-to-affine --memref-fullrank| FileCheck %s + +template +DT foo(DT a, DT b) { + return a + b; +} + +// ------------------------------------------------------------ +// Top-level function that calls foo and foo +// ------------------------------------------------------------ +void top(int *out_int, float *out_float, int a, int b, float x, float y) { + +#pragma HLS ALLOCATION function instances=foo limit=1 +#pragma HLS ALLOCATION function instances=foo limit=1 + + int r1 = foo(a, b); + float r2 = foo(x, y); + + *out_int = r1; + *out_float = r2; +} + +// CHECK: hls.allocation = [{instances = "foo", limit = 1 : i32, type = "function"}, {instances = "foo", limit = 1 : i32, type = "function"} \ No newline at end of file diff --git a/tools/cgeist/Test/HLS/BasicArrayPartition.cpp b/tools/cgeist/Test/HLS/BasicArrayPartition.cpp new file mode 100644 index 000000000000..b2d2c446da37 --- /dev/null +++ b/tools/cgeist/Test/HLS/BasicArrayPartition.cpp @@ -0,0 +1,12 @@ +//RUN: cgeist %s --function="*" -S --hls-annotate --raise-scf-to-affine --memref-fullrank| FileCheck %s + +void basic_array_partition() { + int arr[8]; +#pragma HLS array_partition variable=arr complete + // Simple computation to test parallel access + for(int i = 0; i < 8; i++) { + arr[i] += 1; + } +} + +// CHECK: %alloca = memref.alloca() {hls.array_partition = [{kind = "complete", variable = "arr"}], polygeist.varname = "arr"} \ No newline at end of file diff --git a/tools/cgeist/Test/HLS/BindOp.cpp b/tools/cgeist/Test/HLS/BindOp.cpp new file mode 100644 index 000000000000..ac47fc746e35 --- /dev/null +++ b/tools/cgeist/Test/HLS/BindOp.cpp @@ -0,0 +1,11 @@ +//RUN: cgeist %s --function="*" -S --hls-annotate --raise-scf-to-affine --memref-fullrank| FileCheck %s + +int foo (int a, int b) { + int c, d; +#pragma HLS BIND_OP variable=c op=mul impl=fabric latency=2 + c = a*b; + d = a*c; + return d; +} + +// CHECK: {hls.bind_op = [{impl = "fabric", latency = 2 : i32, op = "mul"}], polygeist.ssa_names = ["c"]} \ No newline at end of file diff --git a/tools/cgeist/Test/HLS/BindStorage.cpp b/tools/cgeist/Test/HLS/BindStorage.cpp new file mode 100644 index 000000000000..bcec9b5b1bfe --- /dev/null +++ b/tools/cgeist/Test/HLS/BindStorage.cpp @@ -0,0 +1,23 @@ +//RUN: cgeist %s --function="*" -S --hls-annotate --raise-scf-to-affine --memref-fullrank| FileCheck %s + +void mac_top(int input[64], int &result) { +#pragma HLS bind_storage variable=weights type=ram_1p impl=bram +#pragma HLS bind_storage variable=bias type=ram_1p impl=lutram +#pragma HLS bind_storage variable=output type=ram_2p impl=uram latency=3 + int weights[64]; + int bias[16]; + int output[16]; + + for (int i = 0; i < 16; i++) { + output[i] = bias[i]; + for (int j = 0; j < 4; j++) + output[i] += input[i*4+j] * weights[i*4+j]; + } + for (int i = 0; i < 64; i++) { + result += output[i]; + } +} + +// CHECK: %alloca = memref.alloca() {hls.bind_storage = [{impl = "uram", latency = 3 : i32, type = "ram_2p", variable = "output"}], polygeist.varname = "output"} : memref<16xi32> +// CHECK: %alloca_0 = memref.alloca() {hls.bind_storage = [{impl = "lutram", type = "ram_1p", variable = "bias"}], polygeist.varname = "bias"} : memref<16xi32> +// CHECK: %alloca_1 = memref.alloca() {hls.bind_storage = [{impl = "bram", type = "ram_1p", variable = "weights"}], polygeist.varname = "weights"} : memref<64xi32> diff --git a/tools/cgeist/Test/HLS/Pipeline.cpp b/tools/cgeist/Test/HLS/Pipeline.cpp new file mode 100644 index 000000000000..c7d2e99619c2 --- /dev/null +++ b/tools/cgeist/Test/HLS/Pipeline.cpp @@ -0,0 +1,12 @@ +//RUN: cgeist %s --function="*" -S --hls-annotate --raise-scf-to-affine --memref-fullrank| FileCheck %s + + +void my_kernel(int j) { + j = 0; + for(int i = 0; i < 10; ++i) { + #pragma HLS pipeline II=1 + j += 1; + } +} + +// CHECK: hls.pipeline = [{II = 1 : i32, rewind = false, style = "stp"}] \ No newline at end of file diff --git a/tools/cgeist/Test/HLS/Unroll.cpp b/tools/cgeist/Test/HLS/Unroll.cpp new file mode 100644 index 000000000000..785ec1138e57 --- /dev/null +++ b/tools/cgeist/Test/HLS/Unroll.cpp @@ -0,0 +1,13 @@ +//RUN: cgeist %s --function="*" -S --hls-annotate --raise-scf-to-affine --memref-fullrank| FileCheck %s + +void fir_filter(int x[16], int h[8], int y[16]) { + for (int i = 0; i < 16; i++) { +#pragma HLS unroll + int acc = 0; + for (int j = 0; j < 8; j++) + acc += x[i-j] * h[j]; + y[i] = acc; + } +} + +// CHECK: {hls.unroll = [{factor = 0 : i32, skip_exit_check = false}]} \ No newline at end of file diff --git a/tools/cgeist/driver.cc b/tools/cgeist/driver.cc index b49eecdcfec5..812ed11cb08c 100644 --- a/tools/cgeist/driver.cc +++ b/tools/cgeist/driver.cc @@ -458,6 +458,9 @@ int emitBinary(char *Argv0, const char *filename, } #include "Lib/clang-mlir.cc" +#include "Lib/HandleHLS.h" +#include "Lib/PragmaHanderHLS.h" +#include "Lib/utils.h" int main(int argc, char **argv) { if (argc >= 1) { @@ -961,6 +964,11 @@ int main(int argc, char **argv) { } while (changed); }); + if (HLSAnnotate) { + ResolveScope(module.get(), g_HLSMetadata); + AttachMetadata(g_HLSMetadata); + } + if (EmitLLVM || !EmitAssembly || EmitOpenMPIR || EmitLLVMDialect) { mlir::PassManager pm2(&context); enablePrinting(pm2); From 857598a1adc6dce2645121ca635c2d2efd8a2487 Mon Sep 17 00:00:00 2001 From: AnirudhK Date: Mon, 15 Jun 2026 21:27:59 -0400 Subject: [PATCH 2/4] Additional changes to build polygeist --- tools/cgeist/CMakeLists.txt | 2 ++ tools/cgeist/Lib/clang-mlir.cc | 3 +++ tools/cgeist/Lib/clang-mlir.h | 5 +++++ tools/cgeist/Lib/utils.h | 2 ++ tools/cgeist/driver.cc | 2 +- 5 files changed, 13 insertions(+), 1 deletion(-) diff --git a/tools/cgeist/CMakeLists.txt b/tools/cgeist/CMakeLists.txt index 84b4a739e9df..fdcfad45f222 100644 --- a/tools/cgeist/CMakeLists.txt +++ b/tools/cgeist/CMakeLists.txt @@ -24,6 +24,8 @@ add_clang_executable(cgeist "${LLVM_SOURCE_DIR}/../clang/tools/driver/cc1as_main.cpp" "${LLVM_SOURCE_DIR}/../clang/tools/driver/cc1gen_reproducer_main.cpp" Lib/pragmaHandler.cc + Lib/pragmaHandlerHLS.cc + Lib/HandleHLS.cc Lib/AffineUtils.cc Lib/ValueCategory.cc Lib/utils.cc diff --git a/tools/cgeist/Lib/clang-mlir.cc b/tools/cgeist/Lib/clang-mlir.cc index b944cbda2e04..8a694f2df874 100644 --- a/tools/cgeist/Lib/clang-mlir.cc +++ b/tools/cgeist/Lib/clang-mlir.cc @@ -60,6 +60,9 @@ static cl::opt memRefABI("memref-abi", cl::init(true), cl::opt PrefixABI("prefix-abi", cl::init(""), cl::desc("Prefix for emitted symbols")); +cl::opt HLSAnnotate("hls-annotate", cl::init(false), + cl::desc("annotate hls pragmas")); + cl::opt CStyleMemRef("c-style-memref", cl::init(true), cl::desc("Use c style memrefs when possible")); diff --git a/tools/cgeist/Lib/clang-mlir.h b/tools/cgeist/Lib/clang-mlir.h index 117bcf162557..ccc4029ef60a 100644 --- a/tools/cgeist/Lib/clang-mlir.h +++ b/tools/cgeist/Lib/clang-mlir.h @@ -26,6 +26,8 @@ #include "mlir/Target/LLVMIR/TypeToLLVM.h" #include "polygeist/Ops.h" #include "pragmaHandler.h" +#include "pragmaHandlerHLS.h" +#include "HandleHLS.h" #include "clang/AST/ASTConsumer.h" #include "clang/AST/StmtVisitor.h" #include "clang/Lex/HeaderSearch.h" @@ -44,6 +46,8 @@ using namespace mlir; extern llvm::cl::opt PrefixABI; +extern llvm::cl::opt HLSAnnotate; + struct LoopContext { mlir::Value keepRunning; mlir::Value noBreak; @@ -96,6 +100,7 @@ struct MLIRASTConsumer : public ASTConsumer { addPragmaScopHandlers(PP, scopLocList); addPragmaEndScopHandlers(PP, scopLocList); addPragmaLowerToHandlers(PP, LTInfo); + addPragmaHLSHandlers(PP); } ~MLIRASTConsumer() {} diff --git a/tools/cgeist/Lib/utils.h b/tools/cgeist/Lib/utils.h index f80132c0b65e..e8af3dc420ce 100644 --- a/tools/cgeist/Lib/utils.h +++ b/tools/cgeist/Lib/utils.h @@ -12,6 +12,8 @@ #include "mlir/IR/Builders.h" #include "llvm/ADT/ArrayRef.h" +inline bool g_HLSpragmaAnnotate = false; + namespace mlir { class Operation; namespace func { diff --git a/tools/cgeist/driver.cc b/tools/cgeist/driver.cc index 812ed11cb08c..abd10d4c0107 100644 --- a/tools/cgeist/driver.cc +++ b/tools/cgeist/driver.cc @@ -459,7 +459,7 @@ int emitBinary(char *Argv0, const char *filename, #include "Lib/clang-mlir.cc" #include "Lib/HandleHLS.h" -#include "Lib/PragmaHanderHLS.h" +#include "Lib/pragmaHandlerHLS.h" #include "Lib/utils.h" int main(int argc, char **argv) { From 2af9f0aaa263a631773e5d5cb52e862c1dba5d12 Mon Sep 17 00:00:00 2001 From: AnirudhK Date: Mon, 15 Jun 2026 21:50:25 -0400 Subject: [PATCH 3/4] Add missing handle HLS files --- tools/cgeist/Lib/HandleHLS.cc | 541 ++++++++++++++++++++++++++++++++++ tools/cgeist/Lib/HandleHLS.h | 9 + 2 files changed, 550 insertions(+) create mode 100644 tools/cgeist/Lib/HandleHLS.cc create mode 100644 tools/cgeist/Lib/HandleHLS.h diff --git a/tools/cgeist/Lib/HandleHLS.cc b/tools/cgeist/Lib/HandleHLS.cc new file mode 100644 index 000000000000..a77ba7d0e1f2 --- /dev/null +++ b/tools/cgeist/Lib/HandleHLS.cc @@ -0,0 +1,541 @@ +#include "clang/Frontend/ASTConsumers.h" +#include "clang/Frontend/MultiplexConsumer.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "mlir/IR/Builders.h" +#include "mlir/IR/BuiltinAttributes.h" +#include "mlir/IR/Location.h" +#include "mlir/Dialect/MemRef/IR/MemRef.h" +#include "mlir/Dialect/Affine/IR/AffineOps.h" +#include "mlir/Dialect/SCF/IR/SCF.h" +#include "mlir/Dialect/Func/IR/FuncOps.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringExtras.h" // for SplitString +#include "pragmaHandlerHLS.h" +#include "HandleHLS.h" + +using mlir::memref::AllocaOp; +using namespace llvm; + +unsigned getSourceInfo(mlir::Location loc) { + // 1. Base Case: Standard File/Line/Column + if (auto fileLoc = llvm::dyn_cast(loc)) { + return fileLoc.getLine(); + } + if (auto fusedLoc = llvm::dyn_cast(loc)) { + auto locations = fusedLoc.getLocations(); + if(locations.size() > 0) { + return getSourceInfo(locations[0]); + } + return 0; + } + // 3. Recursive Case: CallSite Locations (If inlining has occurred) + if (auto callLoc = llvm::dyn_cast(loc)) { + return getSourceInfo(callLoc.getCaller()); + } + // 4. Case: Name Location (e.g., loc("myVar"(...) ) + if (auto nameLoc = llvm::dyn_cast(loc)) { + return getSourceInfo(nameLoc.getChildLoc()); + } + return 0; +} + +void ResolveScope(mlir::ModuleOp MlirModule, + std::vector &HLSMetadata) { + + struct ScopeInfo { + mlir::Operation *Op; + unsigned StartLine; + unsigned EndLine; + }; + + std::vector Scopes; + + // --- Phase 1: Collect all scope ops and their line ranges --- + MlirModule.walk([&](mlir::Operation *Op) { + + if (llvm::isa(Op)) { + llvm::outs() << "[scope] op=" << Op->getName().getStringRef(); + } + + if (mlir::isa(Op)) + return; + + // Only ops that define a new scope (have regions) + if (Op->getNumRegions() == 0) + return; + + + + unsigned StartLine = getSourceInfo(Op->getLoc()); + if (StartLine == 0) + return; + // End line = max line of any nested op in this scope + unsigned EndLine = StartLine; + Op->walk([&](mlir::Operation *Nested) { + if (Nested == Op) + return; + unsigned Line = getSourceInfo(Nested->getLoc()); + if (Line > EndLine) + EndLine = Line; + }); + Scopes.push_back({Op, StartLine, EndLine}); + llvm::outs() << "[scope] op=" << Op->getName().getStringRef() + << " start=" << StartLine << " end=" << EndLine << "\n"; + }); + + + // --- Phase 1.5: Widen each func.func's StartLine to cover the gap from + // the previous function's end. This lets pragmas that appear above the + // first emitted op (between '{' and the first statement) — and pragmas + // between functions — attach to the function that follows them. + // Other scope ops (loops) keep their original tight range, so the + // innermost-loop preference still works. + std::vector Funcs; + for (auto &S : Scopes) + if (llvm::isa(S.Op)) + Funcs.push_back(&S); + + llvm::sort(Funcs, [](ScopeInfo *a, ScopeInfo *b) { + return a->EndLine < b->EndLine; + }); + + unsigned PrevEnd = 0; + for (auto *F : Funcs) { + F->StartLine = PrevEnd + 1; + PrevEnd = F->EndLine; + } + + // --- Phase 2: Match each pragma to its innermost containing scope --- + for (HLSPragmaMetaData &Meta : HLSMetadata) { + mlir::Operation *InnermostOp = nullptr; + unsigned InnermostSpan = UINT_MAX; + + for (auto &Scope : Scopes) { + // Pragma must fall within the scope's line range + if (Meta.OffsetLoc < Scope.StartLine || Meta.OffsetLoc > Scope.EndLine) + continue; + + // Prefer the tightest (innermost) enclosing scope + unsigned Span = Scope.EndLine - Scope.StartLine; + // After --raise-scf-to-affine, loops are affine.for; before that, scf.for/while. + bool CurrIsLoop = llvm::isa(Scope.Op); + bool BestIsLoop = InnermostOp && + llvm::isa(InnermostOp); + + if (Span < InnermostSpan || (CurrIsLoop && !BestIsLoop && Span == InnermostSpan)) { + InnermostSpan = Span; + InnermostOp = Scope.Op; + } + } + + if (InnermostOp) { + Meta.Op = InnermostOp; + llvm::outs() << "[HLS] pragma '" << Meta.Name + << "' at line " << Meta.OffsetLoc + << " attached to '" << InnermostOp->getName().getStringRef() + << "' (scope span " << InnermostSpan << " lines)"; + + if (auto SymAttr = InnermostOp->getAttrOfType( + mlir::SymbolTable::getSymbolAttrName())) + + llvm::outs() << " ['" << SymAttr.getValue() << "']"; + llvm::outs() << "\n"; + } else { + Meta.Op = nullptr; + llvm::outs() << "[HLS] Warning: pragma '" << Meta.Name + << "' at line " << Meta.OffsetLoc + << " could not be resolved to any scope\n"; + } + } +} + +// Helper: parse a key=value arg list into a StringMap +void ParseArgs(ArrayRef Args, + llvm::StringMap &KV, + SmallVectorImpl &Flags) { + for (StringRef Arg : Args) { + if (Arg.contains('=')) { + auto [Key, Val] = Arg.split('='); + KV[Key] = Val; + } else { + Flags.push_back(Arg); // bare keywords like "complete", "rewind", "skip_exit_check" + } + } +} + +// Helper: find a memref.alloca by base variable name inside a scope op. +// memref.alloca has no name attribute; we recover the variable name from +// either (a) a NameLoc on the op's location, or (b) a "polygeist.varname" +// string attribute attached at scan time. Either source works; the second +// is the fallback if locations have been rewritten by passes. +AllocaOp FindAlloca(mlir::Operation *ScopeOp, StringRef BaseName) { + AllocaOp Found; + ScopeOp->walk([&](AllocaOp Alloca) { + // (a) try NameLoc on the alloca's location + if (auto NL = llvm::dyn_cast(Alloca.getLoc())) { + if (NL.getName().getValue() == BaseName) { + Found = Alloca; + return; + } + } + // (b) try a polygeist.varname attribute + if (auto NameAttr = + Alloca->getAttrOfType("polygeist.varname")) { + if (NameAttr.getValue() == BaseName) { + Found = Alloca; + return; + } + } + }); + return Found; +} + + +mlir::Operation *FindOpByName(mlir::Operation *ScopeOp, llvm::StringRef Name) { + mlir::Operation *Found = nullptr; + ScopeOp->walk([&](mlir::Operation *Op) { + if (auto names = Op->getAttrOfType("polygeist.ssa_names")) { + for (auto attr : names) { + if (auto sa = llvm::dyn_cast(attr)) { + if (sa.getValue() == Name) { + Found = Op; + return mlir::WalkResult::interrupt(); + } + } + } + } + return mlir::WalkResult::advance(); + }); + return Found; +} + +// Helper: append a DictionaryAttr entry to an ArrayAttr on an op +void AppendAttrEntry(mlir::Operation *Op, StringRef AttrName, + mlir::DictionaryAttr Entry) { + mlir::Builder B(Op->getContext()); + SmallVector Entries; + if (auto Existing = Op->getAttrOfType(AttrName)) + Entries.append(Existing.begin(), Existing.end()); + Entries.push_back(Entry); + Op->setAttr(AttrName, B.getArrayAttr(Entries)); +} + +// Helper: parse integer from StringRef, returns -1 on failure +int ParseInt(StringRef S) { + int Val = -1; + S.getAsInteger(10, Val); + return Val; +} + +// ── array_partition ──────────────────────────────────────────────────────── +void HandleArrayPartition(HLSPragmaMetaData &Meta) { + SmallVector Args; + SplitString(Meta.Args, Args); + + llvm::StringMap KV; + SmallVector Flags; + ParseArgs(Args, KV, Flags); + + StringRef VarPath = KV.lookup("variable"); + if (VarPath.empty()) { + llvm::outs() << "[HLS] Warning: array_partition missing variable=\n"; + return; + } + + StringRef Base = VarPath.split('.').first; + AllocaOp AttachOp = FindAlloca(Meta.Op, Base); + if (!AttachOp) { + llvm::outs() << "[HLS] Warning: array_partition could not find alloca '" + << Base << "'\n"; + return; + } + + mlir::Builder B(AttachOp.getContext()); + SmallVector Fields; + Fields.push_back(B.getNamedAttr("variable", B.getStringAttr(VarPath))); + + // kind: explicit key or bare flag (complete/block/cyclic), default complete + StringRef Kind = KV.count("type") ? KV["type"] : StringRef("complete"); + for (StringRef F : Flags) + if (F == "complete" || F == "block" || F == "cyclic") { Kind = F; break; } + Fields.push_back(B.getNamedAttr("kind", B.getStringAttr(Kind))); + + if (KV.count("factor")) + Fields.push_back(B.getNamedAttr("factor", + B.getI32IntegerAttr(ParseInt(KV["factor"])))); + if (KV.count("dim")) + Fields.push_back(B.getNamedAttr("dim", + B.getI32IntegerAttr(ParseInt(KV["dim"])))); + + AppendAttrEntry(AttachOp, "hls.array_partition", B.getDictionaryAttr(Fields)); + llvm::outs() << "[HLS] Attached array_partition {variable=" << VarPath + << ", kind=" << Kind << "} to '" << Base << "'\n"; +} + +// ── array_reshape ────────────────────────────────────────────────────────── +// Same args as array_partition; semantics differ (word-width widening vs split) +void HandleArrayReshape(HLSPragmaMetaData &Meta) { + SmallVector Args; + SplitString(Meta.Args, Args); + + llvm::StringMap KV; + SmallVector Flags; + ParseArgs(Args, KV, Flags); + + StringRef VarPath = KV.lookup("variable"); + if (VarPath.empty()) { + llvm::outs() << "[HLS] Warning: array_reshape missing variable=\n"; + return; + } + + StringRef Base = VarPath.split('.').first; + AllocaOp AttachOp = FindAlloca(Meta.Op, Base); + if (!AttachOp) { + llvm::outs() << "[HLS] Warning: array_reshape could not find alloca '" + << Base << "'\n"; + return; + } + + mlir::Builder B(AttachOp.getContext()); + SmallVector Fields; + Fields.push_back(B.getNamedAttr("variable", B.getStringAttr(VarPath))); + + StringRef Kind = KV.count("type") ? KV["type"] : StringRef("complete"); + for (StringRef F : Flags) + if (F == "complete" || F == "block" || F == "cyclic") { Kind = F; break; } + Fields.push_back(B.getNamedAttr("kind", B.getStringAttr(Kind))); + + if (KV.count("factor")) + Fields.push_back(B.getNamedAttr("factor", + B.getI32IntegerAttr(ParseInt(KV["factor"])))); + if (KV.count("dim")) + Fields.push_back(B.getNamedAttr("dim", + B.getI32IntegerAttr(ParseInt(KV["dim"])))); + + AppendAttrEntry(AttachOp, "hls.array_reshape", B.getDictionaryAttr(Fields)); + llvm::outs() << "[HLS] Attached array_reshape {variable=" << VarPath + << ", kind=" << Kind << "} to '" << Base << "'\n"; +} + +// ── bind_storage ─────────────────────────────────────────────────────────── +// #pragma HLS bind_storage variable= type= +// impl= latency= +void HandleBindStorage(HLSPragmaMetaData &Meta) { + SmallVector Args; + SplitString(Meta.Args, Args); + + llvm::StringMap KV; + SmallVector Flags; + ParseArgs(Args, KV, Flags); + + StringRef VarPath = KV.lookup("variable"); + if (VarPath.empty()) { + llvm::outs() << "[HLS] Warning: bind_storage missing variable=\n"; + return; + } + + StringRef Base = VarPath.split('.').first; + AllocaOp AttachOp = FindAlloca(Meta.Op, Base); + if (!AttachOp) { + llvm::outs() << "[HLS] Warning: bind_storage could not find alloca '" + << Base << "'\n"; + return; + } + + mlir::Builder B(AttachOp.getContext()); + SmallVector Fields; + Fields.push_back(B.getNamedAttr("variable", B.getStringAttr(VarPath))); + + if (KV.count("type")) + Fields.push_back(B.getNamedAttr("type", B.getStringAttr(KV["type"]))); + if (KV.count("impl")) + Fields.push_back(B.getNamedAttr("impl", B.getStringAttr(KV["impl"]))); + if (KV.count("latency")) + Fields.push_back(B.getNamedAttr("latency", + B.getI32IntegerAttr(ParseInt(KV["latency"])))); + + AppendAttrEntry(AttachOp, "hls.bind_storage", B.getDictionaryAttr(Fields)); + llvm::outs() << "[HLS] Attached bind_storage {variable=" << VarPath << "} to '" + << Base << "'\n"; +} + +// ── bind_op ──────────────────────────────────────────────────────────────── +// #pragma HLS bind_op variable= op= +// impl= latency= +void HandleBindOp(HLSPragmaMetaData &Meta) { + SmallVector Args; + SplitString(Meta.Args, Args); + + llvm::StringMap KV; + SmallVector Flags; + ParseArgs(Args, KV, Flags); + + StringRef VarPath = KV.lookup("variable"); + if (VarPath.empty()) { + llvm::outs() << "[HLS] Warning: bind_op missing variable=\n"; + return; + } + + StringRef Base = VarPath.split('.').first; + mlir::Operation *AttachOp = FindOpByName(Meta.Op, Base); + + if (!AttachOp) { + llvm::outs() << "[HLS] Warning: bind_op could not find producer for '" + << Base << "'\n"; + return; + } + + // Optionally: filter by op type matching the pragma's op= field + if (KV.count("op")) { + StringRef Want = KV["op"]; + bool ok = false; + if (Want == "mul") + ok = llvm::isa(AttachOp); + else if (Want == "add") + ok = llvm::isa(AttachOp); + // ... etc + if (!ok) { + llvm::outs() << "[HLS] Warning: bind_op variable=" << Base + << " op=" << Want << " but producer is " + << AttachOp->getName().getStringRef() << "\n"; + return; + } + } + + mlir::Builder B(AttachOp->getContext()); + SmallVector Fields; + if (KV.count("op")) Fields.push_back(B.getNamedAttr("op", B.getStringAttr(KV["op"]))); + if (KV.count("impl")) Fields.push_back(B.getNamedAttr("impl", B.getStringAttr(KV["impl"]))); + if (KV.count("latency")) Fields.push_back(B.getNamedAttr("latency", B.getI32IntegerAttr(ParseInt(KV["latency"])))); + + AppendAttrEntry(AttachOp, "hls.bind_op", B.getDictionaryAttr(Fields)); + llvm::outs() << "[HLS] Attached bind_op {variable=" << VarPath << "} to '" + << Base << "'\n"; +} + +// ── allocation ───────────────────────────────────────────────────────────── +// #pragma HLS allocation instances= limit= type= +// No variable — attaches to the enclosing function/scope op directly +void HandleAllocation(HLSPragmaMetaData &Meta) { + SmallVector Args; + SplitString(Meta.Args, Args); + + llvm::StringMap KV; + SmallVector Flags; + ParseArgs(Args, KV, Flags); + + if (!KV.count("instances") || !KV.count("limit")) { + llvm::outs() << "[HLS] Warning: allocation missing instances= or limit=\n"; + return; + } + + mlir::Builder B(Meta.Op->getContext()); + SmallVector Fields; + Fields.push_back(B.getNamedAttr("instances", + B.getStringAttr(KV["instances"]))); + Fields.push_back(B.getNamedAttr("limit", + B.getI32IntegerAttr(ParseInt(KV["limit"])))); + + // type= defaults to "function" + StringRef AllocType = KV.count("type") ? KV["type"] : StringRef("function"); + Fields.push_back(B.getNamedAttr("type", B.getStringAttr(AllocType))); + + AppendAttrEntry(Meta.Op, "hls.allocation", B.getDictionaryAttr(Fields)); + llvm::outs() << "[HLS] Attached allocation {instances=" << KV["instances"] + << ", limit=" << KV["limit"] << "} to scope op\n"; +} + +// ── unroll ───────────────────────────────────────────────────────────────── +// #pragma HLS unroll [factor=] [skip_exit_check] +// No variable — attaches to the enclosing loop/scope op +void HandleUnroll(HLSPragmaMetaData &Meta) { + SmallVector Args; + SplitString(Meta.Args, Args); + + llvm::StringMap KV; + SmallVector Flags; + ParseArgs(Args, KV, Flags); + + mlir::Builder B(Meta.Op->getContext()); + SmallVector Fields; + + // factor= absent means full unroll + if (KV.count("factor")) + Fields.push_back(B.getNamedAttr("factor", + B.getI32IntegerAttr(ParseInt(KV["factor"])))); + else + Fields.push_back(B.getNamedAttr("factor", B.getI32IntegerAttr(0))); // 0 = full + + bool SkipExitCheck = llvm::is_contained(Flags, StringRef("skip_exit_check")); + Fields.push_back(B.getNamedAttr("skip_exit_check", + B.getBoolAttr(SkipExitCheck))); + + // Attach directly to the scope op (loop body) + AppendAttrEntry(Meta.Op, "hls.unroll", B.getDictionaryAttr(Fields)); + llvm::outs() << "[HLS] Attached unroll {factor=" + << (KV.count("factor") ? KV["factor"] : StringRef("full")) + << ", skip_exit_check=" << SkipExitCheck << "} to scope op\n"; +} + +// ── pipeline ─────────────────────────────────────────────────────────────── +// #pragma HLS pipeline [II=] [rewind] [style=stp|flp|frp] +// No variable — attaches to the enclosing function/loop scope op +void HandlePipeline(HLSPragmaMetaData &Meta) { + SmallVector Args; + SplitString(Meta.Args, Args); + + llvm::StringMap KV; + SmallVector Flags; + ParseArgs(Args, KV, Flags); + + mlir::Builder B(Meta.Op->getContext()); + SmallVector Fields; + + // II=0 means "let tool decide minimum" + int II = KV.count("II") ? ParseInt(KV["II"]) : 0; + Fields.push_back(B.getNamedAttr("II", B.getI32IntegerAttr(II))); + + bool Rewind = llvm::is_contained(Flags, StringRef("rewind")); + Fields.push_back(B.getNamedAttr("rewind", B.getBoolAttr(Rewind))); + + // style: stp (stall), flp (flushable), frp (free-running), default stp + StringRef Style = KV.count("style") ? KV["style"] : StringRef("stp"); + Fields.push_back(B.getNamedAttr("style", B.getStringAttr(Style))); + + // Attach directly to the scope op (function or loop) + AppendAttrEntry(Meta.Op, "hls.pipeline", B.getDictionaryAttr(Fields)); + llvm::outs() << "[HLS] Attached pipeline {II=" << II + << ", rewind=" << Rewind << ", style=" << Style + << "} to scope op\n"; +} + + + +void AttachMetadata(std::vector &HLSMetadata) { + for (HLSPragmaMetaData &Meta : HLSMetadata) { + llvm::outs() << "Arguments: " << Meta.Args << "\n"; + if (!Meta.Op) { + llvm::outs() << "[HLS] Warning: pragma '" << Meta.Name + << "' has no resolved scope, skipping\n"; + continue; + } + if (Meta.Name == "array_partition") HandleArrayPartition(Meta); + else if (Meta.Name == "array_reshape") HandleArrayReshape(Meta); + else if (Meta.Name == "bind_storage") HandleBindStorage(Meta); + else if (Meta.Name == "bind_op") HandleBindOp(Meta); + else if (Meta.Name == "allocation") HandleAllocation(Meta); + else if (Meta.Name == "unroll") HandleUnroll(Meta); + else if (Meta.Name == "pipeline") HandlePipeline(Meta); + } +} + diff --git a/tools/cgeist/Lib/HandleHLS.h b/tools/cgeist/Lib/HandleHLS.h new file mode 100644 index 000000000000..02f53a126473 --- /dev/null +++ b/tools/cgeist/Lib/HandleHLS.h @@ -0,0 +1,9 @@ +#include "clang/Frontend/ASTConsumers.h" +#include "clang/Frontend/MultiplexConsumer.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "mlir/IR/Builders.h" + +void ResolveScope(mlir::ModuleOp MlirModule, + std::vector &HLSMetadata); + +void AttachMetadata(std::vector &HLSMetadata); From 7dea8a72e76289d7a85a97e29d95d57480db5059 Mon Sep 17 00:00:00 2001 From: PeterChou1 Date: Sat, 20 Jun 2026 03:35:43 -0400 Subject: [PATCH 4/4] fix lit test --- tools/cgeist/Lib/clang-mlir.cc | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tools/cgeist/Lib/clang-mlir.cc b/tools/cgeist/Lib/clang-mlir.cc index 8a694f2df874..ef1a18fc43f7 100644 --- a/tools/cgeist/Lib/clang-mlir.cc +++ b/tools/cgeist/Lib/clang-mlir.cc @@ -584,6 +584,20 @@ mlir::Value MLIRScanner::createAllocOp(mlir::Type t, VarDecl *name, } assert(params.find(name) == params.end()); params[name] = ValueCategory(alloc, /*isReference*/ true); + + // Tag the underlying memref.alloca / llvm.alloca with the C variable name + // so HLS pragma resolution can find it later by name. + mlir::Operation *defining = alloc.getDefiningOp(); + while (auto castOp = + llvm::dyn_cast_or_null(defining)) { + defining = castOp.getSource().getDefiningOp(); + } + if (defining && (llvm::isa(defining) || + llvm::isa(defining))) { + defining->setAttr( + "polygeist.varname", + mlir::StringAttr::get(builder.getContext(), name->getName())); + } } return alloc; }