[Mlir-commits] [mlir] [mlir][EmitC] Create a pass to add a reflection map to a class (PR #205464)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Tue Jun 23 18:08:03 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-mlir-emitc
Author: Bhavesh M (beamandala)
<details>
<summary>Changes</summary>
This creates the `add-reflection-map` pass to add a reflection map and a helper method to EmitC classes for runtime field lookup.
Details:
- Add headers to import `map` and `string` if not already present.
- For every `ClassOp`:
- Collect all the `FieldOp`s that contain the attribute whose value we want as the reflection map key. We ignore all `FieldOp`s that contain an attribute present in `excludedFieldAttrs` and if there's a `FieldOp` that neither has the attribute we're looking for and isn't excluded by the presence of one of the `excludedFieldAttrs`, emit and error.
- Create the reflection map where the key is the value of the attribute in each applicable `FieldOp`'s attribute dictionary and the value is a pointer to the field's contents.
- Create a `getBufferForName` method which serves as a getter method for the reflection map.
Based on PR #<!-- -->150572. Key differences:
- Adds a `excluded-field-attrs` option which is a list of attributes that if present on a field exclude it from the reflection map.
- Adds a helper method called `getBufferForName` which given a string key returns a pointer to the field's buffer.
Co-authored-by: [Jaddyen](https://github.com/Jaddyen)
---
Full diff: https://github.com/llvm/llvm-project/pull/205464.diff
6 Files Affected:
- (modified) mlir/include/mlir/Dialect/EmitC/Transforms/Passes.h (+2)
- (modified) mlir/include/mlir/Dialect/EmitC/Transforms/Passes.td (+52)
- (modified) mlir/include/mlir/Dialect/EmitC/Transforms/Transforms.h (+7)
- (added) mlir/lib/Dialect/EmitC/Transforms/AddReflectionMap.cpp (+181)
- (modified) mlir/lib/Dialect/EmitC/Transforms/CMakeLists.txt (+1)
- (added) mlir/test/Dialect/EmitC/add-reflection-map.mlir (+58)
``````````diff
diff --git a/mlir/include/mlir/Dialect/EmitC/Transforms/Passes.h b/mlir/include/mlir/Dialect/EmitC/Transforms/Passes.h
index 1af4aa06fa811..56596110a1e90 100644
--- a/mlir/include/mlir/Dialect/EmitC/Transforms/Passes.h
+++ b/mlir/include/mlir/Dialect/EmitC/Transforms/Passes.h
@@ -16,6 +16,8 @@ namespace emitc {
#define GEN_PASS_DECL_FORMEXPRESSIONSPASS
#define GEN_PASS_DECL_WRAPFUNCINCLASSPASS
+#define GEN_PASS_DECL_ADDREFLECTIONMAPPASS
+
#include "mlir/Dialect/EmitC/Transforms/Passes.h.inc"
//===----------------------------------------------------------------------===//
diff --git a/mlir/include/mlir/Dialect/EmitC/Transforms/Passes.td b/mlir/include/mlir/Dialect/EmitC/Transforms/Passes.td
index 40ecef33448d7..c695d307b586a 100644
--- a/mlir/include/mlir/Dialect/EmitC/Transforms/Passes.td
+++ b/mlir/include/mlir/Dialect/EmitC/Transforms/Passes.td
@@ -60,4 +60,56 @@ def WrapFuncInClassPass : Pass<"wrap-emitc-func-in-class"> {
];
}
+def AddReflectionMapPass : Pass<"add-reflection-map", "ModuleOp"> {
+ let summary = "Add a reflection map and a helper method to EmitC classes for runtime field lookup.";
+ let description = [{
+ This pass adds a `reflectionMap` field and an accompanying `getBufferForName` method to
+ EmitC classes, enabling runtime lookup of class fields by name.
+ This requires that the class has fields with attributes.
+ Each `emitc.field` is expected to have an attribute (configured by the
+ `field-attr-name` option) that is an array containing a single string
+ attribute.
+
+
+ Example:
+
+ ```mlir
+ emitc.class @MyClass {
+ emitc.field @fieldName0 : !emitc.array<1xf32> {emitc.field_ref = ["another_feature"]}
+ emitc.field @fieldName1 : !emitc.array<1xf32> {emitc.field_ref = ["some_feature"]}
+ emitc.func @execute() { ... }
+ }
+
+ Becomes:
+
+ emitc.class @MyClass {
+ emitc.field @fieldName0 : !emitc.array<1xf32> {emitc.field_ref = ["another_feature"]}
+ emitc.field @fieldName1 : !emitc.array<1xf32> {emitc.field_ref = ["some_feature"]}
+ emitc.field @reflectionMap : !emitc.opaque<"const std::map<std::string, char*>"> =
+ #emitc.opaque<"{ { \22another_feature\22, reinterpret_cast<char*>(&fieldName0) }, { \22some_feature\22, reinterpret_cast<char*>(&fieldName1) } }">
+ emitc.func @getBufferForName(%arg0: !emitc.opaque<"std::string">)
+ -> !emitc.ptr<!emitc.opaque<"char">> {
+ %0 = get_field @reflectionMap
+ : !emitc.opaque<"const std::map<std::string, char*>">
+ %1 = member_call_opaque %0 "at"(%arg0)
+ : !emitc.opaque<"const std::map<std::string, char*>">,
+ (!emitc.opaque<"std::string">) -> !emitc.ptr<!emitc.opaque<"char">>
+ return %1 : !emitc.ptr<!emitc.opaque<"char">>
+ }
+ emitc.func @execute() { ... }
+ }
+ ```
+ }];
+ let dependentDialects = ["emitc::EmitCDialect"];
+ let options = [
+ Option<"fieldAttrName", "field-attr-name", "std::string",
+ /*default=*/"",
+ "Attribute key used to extract field names from a field's "
+ "attribute dictionary">,
+ ListOption<"excludedFieldAttrs", "excluded-field-attrs", "std::string",
+ "Attribute keys that, if present on a field, exclude it "
+ "from the reflection map">
+ ];
+}
+
#endif // MLIR_DIALECT_EMITC_TRANSFORMS_PASSES
diff --git a/mlir/include/mlir/Dialect/EmitC/Transforms/Transforms.h b/mlir/include/mlir/Dialect/EmitC/Transforms/Transforms.h
index 962bdb3c032bf..e029d27672f8b 100644
--- a/mlir/include/mlir/Dialect/EmitC/Transforms/Transforms.h
+++ b/mlir/include/mlir/Dialect/EmitC/Transforms/Transforms.h
@@ -11,6 +11,7 @@
#include "mlir/Dialect/EmitC/IR/EmitC.h"
#include "mlir/IR/PatternMatch.h"
+#include "llvm/ADT/StringRef.h"
namespace mlir {
namespace emitc {
@@ -34,6 +35,12 @@ void populateExpressionPatterns(RewritePatternSet &patterns);
void populateWrapFuncInClass(RewritePatternSet &patterns, StringRef fName);
+//===----------------------------------------------------------------------===//
+// The AddReflectionMap pass.
+//===----------------------------------------------------------------------===//
+
+void populateAddReflectionMapPatterns(RewritePatternSet &patterns, StringRef fieldAttrName, ArrayRef<std::string> excludedFieldAttrs);
+
} // namespace emitc
} // namespace mlir
diff --git a/mlir/lib/Dialect/EmitC/Transforms/AddReflectionMap.cpp b/mlir/lib/Dialect/EmitC/Transforms/AddReflectionMap.cpp
new file mode 100644
index 0000000000000..c1588463ffb7a
--- /dev/null
+++ b/mlir/lib/Dialect/EmitC/Transforms/AddReflectionMap.cpp
@@ -0,0 +1,181 @@
+//===- AddReflectionMap.cpp - Add a reflection map to a class --------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/Dialect/EmitC/IR/EmitC.h"
+#include "mlir/Dialect/EmitC/Transforms/Passes.h"
+#include "mlir/Dialect/EmitC/Transforms/Transforms.h"
+#include "mlir/IR/Attributes.h"
+#include "mlir/IR/Builders.h"
+#include "mlir/IR/BuiltinAttributes.h"
+#include "mlir/IR/PatternMatch.h"
+#include "mlir/Transforms/WalkPatternRewriteDriver.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/FormatVariadic.h"
+
+using namespace mlir;
+using namespace emitc;
+
+namespace mlir {
+namespace emitc {
+#define GEN_PASS_DEF_ADDREFLECTIONMAPPASS
+#include "mlir/Dialect/EmitC/Transforms/Passes.h.inc"
+
+namespace {
+constexpr const char *mapLibraryHeader = "map";
+constexpr const char *stringLibraryHeader = "string";
+
+IncludeOp addHeader(OpBuilder &builder, ModuleOp module, StringRef headerName) {
+ StringAttr includeAttr = builder.getStringAttr(headerName);
+ return IncludeOp::create(
+ builder, module.getLoc(), includeAttr,
+ /*is_standard_include=*/builder.getUnitAttr());
+}
+
+class AddReflectionMapPass
+ : public impl::AddReflectionMapPassBase<AddReflectionMapPass> {
+ using AddReflectionMapPassBase::AddReflectionMapPassBase;
+ void runOnOperation() override {
+ mlir::ModuleOp moduleOp = getOperation();
+
+ RewritePatternSet patterns(&getContext());
+ populateAddReflectionMapPatterns(patterns, fieldAttrName, excludedFieldAttrs);
+
+ walkAndApplyPatterns(moduleOp, std::move(patterns));
+ bool hasMapHdr = false;
+ bool hasStringHdr = false;
+ for (auto &op : *moduleOp.getBody()) {
+ IncludeOp includeOp = llvm::dyn_cast<IncludeOp>(op);
+ if (!includeOp)
+ continue;
+
+ if (includeOp.getIsStandardInclude()) {
+ auto include = includeOp.getInclude();
+
+ hasMapHdr = include == mapLibraryHeader;
+ hasStringHdr = include == stringLibraryHeader;
+ }
+
+ if (hasMapHdr && hasStringHdr)
+ return;
+ }
+
+ mlir::OpBuilder builder(moduleOp.getBody(), moduleOp.getBody()->begin());
+ if (!hasMapHdr)
+ addHeader(builder, moduleOp, mapLibraryHeader);
+
+ if (!hasStringHdr)
+ addHeader(builder, moduleOp, stringLibraryHeader);
+ }
+};
+
+} // namespace
+} // namespace emitc
+} // namespace mlir
+
+class AddReflectionMapClass : public OpRewritePattern<ClassOp> {
+public:
+ AddReflectionMapClass(MLIRContext *context, StringRef attrName,
+ llvm::ArrayRef<std::string> excludedFieldAttrs)
+ : OpRewritePattern<ClassOp>(context), fieldAttrName(attrName),
+ excludedFieldAttrs(excludedFieldAttrs.begin(), excludedFieldAttrs.end()) {}
+
+ LogicalResult matchAndRewrite(ClassOp classOp,
+ PatternRewriter &rewriter) const override {
+ MLIRContext *context = rewriter.getContext();
+
+ emitc::OpaqueType mapType = mlir::emitc::OpaqueType::get(
+ context, "const std::map<std::string, char*>");
+
+ // Collect all field names
+ std::vector<std::pair<std::string, std::string>> fieldNames;
+ classOp.walk([&](FieldOp fieldOp) {
+ if (Attribute attr = fieldOp->getAttrDictionary().get(fieldAttrName)) {
+ if (ArrayAttr arrayAttr = dyn_cast<mlir::ArrayAttr>(attr)) {
+ StringAttr stringAttr = cast<mlir::StringAttr>(arrayAttr[0]);
+ fieldNames.emplace_back(stringAttr.getValue().str(),
+ fieldOp.getName().str());
+ return;
+ }
+ }
+
+ bool shouldIgnore = false;
+ for (const std::string &ignoreAttr : excludedFieldAttrs) {
+ if (fieldOp->hasAttr(ignoreAttr)) {
+ shouldIgnore = true;
+ break;
+ }
+ }
+
+ if (shouldIgnore)
+ return;
+
+ fieldOp.emitError()
+ << "FieldOp must have a dictionary attribute named '"
+ << fieldAttrName << "'"
+ << "with an array containing a string attribute";
+ });
+
+ std::string reflectionMapContents;
+ reflectionMapContents += "{ ";
+ for (size_t i = 0, numFields = fieldNames.size(); i < numFields; ++i) {
+ reflectionMapContents += llvm::formatv(
+ "{ \"{0}\", reinterpret_cast<char*>(&{1}) }{2}", fieldNames[i].first,
+ fieldNames[i].second, (i < numFields - 1) ? ", " : "");
+ }
+ reflectionMapContents += " }";
+
+ if (FuncOp executeFunc =
+ classOp.lookupSymbol<FuncOp>("operator()"))
+ rewriter.setInsertionPoint(executeFunc);
+ else {
+ classOp.emitError() << "ClassOp must contain a function named 'operator()' "
+ "to add reflection map";
+ return failure();
+ }
+
+ FieldOp reflectionMapField = FieldOp::create(
+ rewriter, classOp.getLoc(), rewriter.getStringAttr("reflectionMap"),
+ TypeAttr::get(mapType), emitc::OpaqueAttr::get(context, reflectionMapContents));
+
+ // Create getBufferForName method
+ emitc::OpaqueType nameType = emitc::OpaqueType::get(rewriter.getContext(), "std::string");
+ emitc::OpaqueType charType = emitc::OpaqueType::get(rewriter.getContext(), "char");
+ emitc::PointerType valType = emitc::PointerType::get(rewriter.getContext(), charType);
+ FuncOp getBufferForNameFunc = FuncOp::create(
+ rewriter, reflectionMapField->getLoc(), "getBufferForName",
+ FunctionType::get(rewriter.getContext(), {nameType}, {valType}));
+
+ Block *body = rewriter.createBlock(&getBufferForNameFunc.getBody(), {}, {nameType}, {reflectionMapField->getLoc()});
+ rewriter.setInsertionPointToStart(body);
+ GetFieldOp mapField = GetFieldOp::create(
+ rewriter, reflectionMapField->getLoc(), mapType, "reflectionMap");
+ Value nameArg = body->getArgument(0);
+ MemberCallOpaqueOp lookupCall = MemberCallOpaqueOp::create(
+ rewriter, reflectionMapField->getLoc(), valType, mapField.getResult(),
+ "at", ArrayAttr{}, ArrayAttr{}, ValueRange{nameArg});
+ ReturnOp::create(rewriter, reflectionMapField->getLoc(), lookupCall.getResult(0));
+
+ return success();
+ }
+
+private:
+ /// The name of the attribute on FieldOps that contains the field name
+ /// metadata for the reflection map.
+ StringRef fieldAttrName;
+
+ /// Attributes that, if present on a field, exclude it from the
+ /// reflection map.
+ llvm::SmallVector<std::string> excludedFieldAttrs;
+};
+
+void mlir::emitc::populateAddReflectionMapPatterns(
+ RewritePatternSet &patterns, StringRef fieldAttrName,
+ llvm::ArrayRef<std::string> excludedFieldAttrs) {
+ patterns.add<AddReflectionMapClass>(patterns.getContext(), fieldAttrName,
+ excludedFieldAttrs);
+}
\ No newline at end of file
diff --git a/mlir/lib/Dialect/EmitC/Transforms/CMakeLists.txt b/mlir/lib/Dialect/EmitC/Transforms/CMakeLists.txt
index baf67afc30072..dd8f014dc4737 100644
--- a/mlir/lib/Dialect/EmitC/Transforms/CMakeLists.txt
+++ b/mlir/lib/Dialect/EmitC/Transforms/CMakeLists.txt
@@ -3,6 +3,7 @@ add_mlir_dialect_library(MLIREmitCTransforms
FormExpressions.cpp
TypeConversions.cpp
WrapFuncInClass.cpp
+ AddReflectionMap.cpp
ADDITIONAL_HEADER_DIRS
${MLIR_MAIN_INCLUDE_DIR}/mlir/Dialect/EmitC/Transforms
diff --git a/mlir/test/Dialect/EmitC/add-reflection-map.mlir b/mlir/test/Dialect/EmitC/add-reflection-map.mlir
new file mode 100644
index 0000000000000..a1ec0356e19f4
--- /dev/null
+++ b/mlir/test/Dialect/EmitC/add-reflection-map.mlir
@@ -0,0 +1,58 @@
+// RUN: mlir-opt -split-input-file --add-reflection-map="field-attr-name=emitc.field_ref excluded-field-attrs="emitc.other_field"" %s | FileCheck %s
+
+
+// Tests that a reflection map is created for fields with a certain attribute.
+
+emitc.class @actionClass {
+ emitc.field @fieldName0 : !emitc.array<1xf32> {emitc.field_ref = ["another_feature"]}
+ emitc.field @fieldName1 : !emitc.array<1xf32> {emitc.field_ref = ["some_feature"]}
+ emitc.func @"operator()"() {
+ %0 = get_field @fieldName0 : !emitc.array<1xf32>
+ return
+ }
+}
+
+// CHECK: emitc.class @actionClass {
+// CHECK-NEXT: emitc.field @fieldName0 : !emitc.array<1xf32> {emitc.field_ref = ["another_feature"]}
+// CHECK-NEXT: emitc.field @fieldName1 : !emitc.array<1xf32> {emitc.field_ref = ["some_feature"]}
+// CHECK-NEXT: emitc.field @reflectionMap : !emitc.opaque<"const std::map<std::string, char*>"> =
+// CHECK-SAME: #emitc.opaque<"{ { \22another_feature\22, reinterpret_cast<char*>(&fieldName0) }, { \22some_feature\22, reinterpret_cast<char*>(&fieldName1) } }">
+// CHECK-NEXT: emitc.func @getBufferForName(%{{.*}}: !emitc.opaque<"std::string">) -> !emitc.ptr<!emitc.opaque<"char">> {
+// CHECK-NEXT: %[[MAP0:.*]] = get_field @reflectionMap : !emitc.opaque<"const std::map<std::string, char*>">
+// CHECK-NEXT: %[[VAL0:.*]] = member_call_opaque %[[MAP0]] "at"({{.*}}) : !emitc.opaque<"const std::map<std::string, char*>">, (!emitc.opaque<"std::string">) -> !emitc.ptr<!emitc.opaque<"char">>
+// CHECK-NEXT: return %[[VAL0]] : !emitc.ptr<!emitc.opaque<"char">>
+// CHECK-NEXT: }
+// CHECK-NEXT: emitc.func @"operator()"() {
+// CHECK-NEXT: %{{.*}} = get_field @fieldName0 : !emitc.array<1xf32>
+// CHECK-NEXT: return
+// CHECK-NEXT: }
+// CHECK-NEXT: }
+
+// -----
+// Test that a reflection map is created for fields with a certain named attribute
+// but not ones with an attribute present in the ignore-attributes option.
+
+emitc.class @actionClass {
+ emitc.field @fieldName0 : !emitc.array<1xf32> {emitc.field_ref = ["another_feature"]}
+ emitc.field @fieldName1 : !emitc.array<1xf32> {emitc.other_field = ["some_feature"]}
+ emitc.func @"operator()"() {
+ %0 = get_field @fieldName0 : !emitc.array<1xf32>
+ return
+ }
+}
+
+// CHECK: emitc.class @actionClass {
+// CHECK-NEXT: emitc.field @fieldName0 : !emitc.array<1xf32> {emitc.field_ref = ["another_feature"]}
+// CHECK-NEXT: emitc.field @fieldName1 : !emitc.array<1xf32> {emitc.other_field = ["some_feature"]}
+// CHECK-NEXT: emitc.field @reflectionMap : !emitc.opaque<"const std::map<std::string, char*>"> =
+// CHECK-SAME: #emitc.opaque<"{ { \22another_feature\22, reinterpret_cast<char*>(&fieldName0) } }">
+// CHECK-NEXT: emitc.func @getBufferForName(%{{.*}}: !emitc.opaque<"std::string">) -> !emitc.ptr<!emitc.opaque<"char">> {
+// CHECK-NEXT: %[[MAP1:.*]] = get_field @reflectionMap : !emitc.opaque<"const std::map<std::string, char*>">
+// CHECK-NEXT: %[[VAL1:.*]] = member_call_opaque %[[MAP1]] "at"({{.*}}) : !emitc.opaque<"const std::map<std::string, char*>">, (!emitc.opaque<"std::string">) -> !emitc.ptr<!emitc.opaque<"char">>
+// CHECK-NEXT: return %[[VAL1]] : !emitc.ptr<!emitc.opaque<"char">>
+// CHECK-NEXT: }
+// CHECK-NEXT: emitc.func @"operator()"() {
+// CHECK-NEXT: %{{.*}} = get_field @fieldName0 : !emitc.array<1xf32>
+// CHECK-NEXT: return
+// CHECK-NEXT: }
+// CHECK-NEXT: }
\ No newline at end of file
``````````
</details>
https://github.com/llvm/llvm-project/pull/205464
More information about the Mlir-commits
mailing list