[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

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