[Mlir-commits] [mlir] [mlir][EmitC] Create a pass to add a reflection map to a class (PR #205464)

Bhavesh M llvmlistbot at llvm.org
Mon Jul 6 12:50:54 PDT 2026


https://github.com/beamandala updated https://github.com/llvm/llvm-project/pull/205464

>From ce76fb7aa44a3320fdb45acc6e011758ed3afc77 Mon Sep 17 00:00:00 2001
From: Bhavesh Mandalapu <bmandalapu at google.com>
Date: Tue, 23 Jun 2026 17:32:22 -0700
Subject: [PATCH 1/6] [mlir][EmitC] Create a pass to add a reflection map to a
 class

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)
---
 .../mlir/Dialect/EmitC/Transforms/Passes.h    |   2 +
 .../mlir/Dialect/EmitC/Transforms/Passes.td   |  52 +++++
 .../Dialect/EmitC/Transforms/Transforms.h     |   7 +
 .../EmitC/Transforms/AddReflectionMap.cpp     | 181 ++++++++++++++++++
 .../Dialect/EmitC/Transforms/CMakeLists.txt   |   1 +
 .../Dialect/EmitC/add-reflection-map.mlir     |  58 ++++++
 6 files changed, 301 insertions(+)
 create mode 100644 mlir/lib/Dialect/EmitC/Transforms/AddReflectionMap.cpp
 create mode 100644 mlir/test/Dialect/EmitC/add-reflection-map.mlir

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 c34c3303a6ab3..6da57c07a8047 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", "ModuleOp"> {
   ];
 }
 
+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 791e545a8edcf..8eadfe9a2460d 100644
--- a/mlir/include/mlir/Dialect/EmitC/Transforms/Transforms.h
+++ b/mlir/include/mlir/Dialect/EmitC/Transforms/Transforms.h
@@ -12,6 +12,7 @@
 #include "mlir/Dialect/EmitC/IR/EmitC.h"
 #include "mlir/IR/PatternMatch.h"
 #include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/StringRef.h"
 
 namespace mlir {
 namespace emitc {
@@ -37,6 +38,12 @@ void populateWrapFuncInClass(
     RewritePatternSet &patterns, StringRef funcName,
     DenseMap<FuncOp, llvm::DenseSet<GlobalOp>> &globalsToMove);
 
+//===----------------------------------------------------------------------===//
+// 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

>From 60d4d88576ecc713db0a2cf7ae184308c3cd745f Mon Sep 17 00:00:00 2001
From: Bhavesh M <85327930+beamandala at users.noreply.github.com>
Date: Tue, 23 Jun 2026 02:37:42 +0530
Subject: [PATCH 2/6] [mlir][EmitC] Make `GlobalOps` `FieldOps` in
 wrap-emitc-func-in-class pass (#203641)

Update the `WrapFuncInClassPass` pass so that `GlobalOp`s are moved into
the `ClassOp` as `FieldOps`. This respects MLIR's behavior of resolving
references to the closest parent operation that defines a symbol table
which is the `ClassOp` that we are creating in this pass.

Without this change, references to a `GlobalOp` in `GetGlobalOp` are
failing to resolve.

Details:
- Identify `GlobalOp`s
- Create a `FieldOp` within the `ClassOp` for each `GlobalOp`
- Delete the `GlobalOp`s after all functions have been wrapped in a
class. Doing this after every function can cause an error when multiple
functions refer to the same `GlobalOp`(s) which would be deleted after
the first function is wrapped in a class.

Also renamed `fName` parameter in `populateWrapFuncInClass` to
`funcName` to match naming in `WrapFuncInClass`.

Based on PR #153452. Key differences:
- No size is set for the `globalsToMove` `SmallVector` type because I'm
not sure if the number of global variables is consistent across
different models.
- `GlobalOp`s are deleted after all functions have been processed.
- Instead of directly cloning the `GlobalOp`, an equivalent `FieldOp` is
created
- `GetGlobalOp`s are translated to `GetFieldOp`s

Co-authored-by: [Jaddyen](https://github.com/Jaddyen)
---
 mlir/include/mlir/Dialect/EmitC/Transforms/Transforms.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/mlir/include/mlir/Dialect/EmitC/Transforms/Transforms.h b/mlir/include/mlir/Dialect/EmitC/Transforms/Transforms.h
index 8eadfe9a2460d..4c8d3978af0f1 100644
--- a/mlir/include/mlir/Dialect/EmitC/Transforms/Transforms.h
+++ b/mlir/include/mlir/Dialect/EmitC/Transforms/Transforms.h
@@ -13,6 +13,7 @@
 #include "mlir/IR/PatternMatch.h"
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/DenseMap.h"
 
 namespace mlir {
 namespace emitc {

>From b6af183f868036d825f4133a2671b67e5a9b643e Mon Sep 17 00:00:00 2001
From: Bhavesh Mandalapu <bmandalapu at google.com>
Date: Tue, 23 Jun 2026 17:32:22 -0700
Subject: [PATCH 3/6] [mlir][EmitC] Create a pass to add a reflection map to a
 class

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)
---
 mlir/include/mlir/Dialect/EmitC/Transforms/Transforms.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/mlir/include/mlir/Dialect/EmitC/Transforms/Transforms.h b/mlir/include/mlir/Dialect/EmitC/Transforms/Transforms.h
index 4c8d3978af0f1..5a4781dc31ea8 100644
--- a/mlir/include/mlir/Dialect/EmitC/Transforms/Transforms.h
+++ b/mlir/include/mlir/Dialect/EmitC/Transforms/Transforms.h
@@ -14,6 +14,7 @@
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/StringRef.h"
 
 namespace mlir {
 namespace emitc {

>From 8d7429a032c6e0d91d1857ff5ef5c324ac35999f Mon Sep 17 00:00:00 2001
From: Bhavesh Mandalapu <bmandalapu at google.com>
Date: Wed, 24 Jun 2026 10:31:17 -0700
Subject: [PATCH 4/6] Formatting

---
 .../Dialect/EmitC/Transforms/Transforms.h     |  6 +--
 .../EmitC/Transforms/AddReflectionMap.cpp     | 47 +++++++++++--------
 2 files changed, 30 insertions(+), 23 deletions(-)

diff --git a/mlir/include/mlir/Dialect/EmitC/Transforms/Transforms.h b/mlir/include/mlir/Dialect/EmitC/Transforms/Transforms.h
index 5a4781dc31ea8..8f1784f61f540 100644
--- a/mlir/include/mlir/Dialect/EmitC/Transforms/Transforms.h
+++ b/mlir/include/mlir/Dialect/EmitC/Transforms/Transforms.h
@@ -13,8 +13,6 @@
 #include "mlir/IR/PatternMatch.h"
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/StringRef.h"
-#include "llvm/ADT/DenseMap.h"
-#include "llvm/ADT/StringRef.h"
 
 namespace mlir {
 namespace emitc {
@@ -44,7 +42,9 @@ void populateWrapFuncInClass(
 // The AddReflectionMap pass.
 //===----------------------------------------------------------------------===//
 
-void populateAddReflectionMapPatterns(RewritePatternSet &patterns, StringRef fieldAttrName, ArrayRef<std::string> excludedFieldAttrs);
+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
index c1588463ffb7a..d444f3abbf4b1 100644
--- a/mlir/lib/Dialect/EmitC/Transforms/AddReflectionMap.cpp
+++ b/mlir/lib/Dialect/EmitC/Transforms/AddReflectionMap.cpp
@@ -31,9 +31,8 @@ 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());
+  return IncludeOp::create(builder, module.getLoc(), includeAttr,
+                           /*is_standard_include=*/builder.getUnitAttr());
 }
 
 class AddReflectionMapPass
@@ -43,7 +42,8 @@ class AddReflectionMapPass
     mlir::ModuleOp moduleOp = getOperation();
 
     RewritePatternSet patterns(&getContext());
-    populateAddReflectionMapPatterns(patterns, fieldAttrName, excludedFieldAttrs);
+    populateAddReflectionMapPatterns(patterns, fieldAttrName,
+                                     excludedFieldAttrs);
 
     walkAndApplyPatterns(moduleOp, std::move(patterns));
     bool hasMapHdr = false;
@@ -55,7 +55,7 @@ class AddReflectionMapPass
 
       if (includeOp.getIsStandardInclude()) {
         auto include = includeOp.getInclude();
-        
+
         hasMapHdr = include == mapLibraryHeader;
         hasStringHdr = include == stringLibraryHeader;
       }
@@ -82,7 +82,8 @@ class AddReflectionMapClass : public OpRewritePattern<ClassOp> {
   AddReflectionMapClass(MLIRContext *context, StringRef attrName,
                         llvm::ArrayRef<std::string> excludedFieldAttrs)
       : OpRewritePattern<ClassOp>(context), fieldAttrName(attrName),
-        excludedFieldAttrs(excludedFieldAttrs.begin(), excludedFieldAttrs.end()) {}
+        excludedFieldAttrs(excludedFieldAttrs.begin(),
+                           excludedFieldAttrs.end()) {}
 
   LogicalResult matchAndRewrite(ClassOp classOp,
                                 PatternRewriter &rewriter) const override {
@@ -114,10 +115,9 @@ class AddReflectionMapClass : public OpRewritePattern<ClassOp> {
       if (shouldIgnore)
         return;
 
-      fieldOp.emitError()
-          << "FieldOp must have a dictionary attribute named '"
-          << fieldAttrName << "'"
-          << "with an array containing a string attribute";
+      fieldOp.emitError() << "FieldOp must have a dictionary attribute named '"
+                          << fieldAttrName << "'"
+                          << "with an array containing a string attribute";
     });
 
     std::string reflectionMapContents;
@@ -129,28 +129,34 @@ class AddReflectionMapClass : public OpRewritePattern<ClassOp> {
     }
     reflectionMapContents += " }";
 
-    if (FuncOp executeFunc =
-            classOp.lookupSymbol<FuncOp>("operator()"))
+    if (FuncOp executeFunc = classOp.lookupSymbol<FuncOp>("operator()"))
       rewriter.setInsertionPoint(executeFunc);
     else {
-      classOp.emitError() << "ClassOp must contain a function named 'operator()' "
-                             "to add reflection map";
+      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));
+        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);
+    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()});
+    Block *body =
+        rewriter.createBlock(&getBufferForNameFunc.getBody(), {}, {nameType},
+                             {reflectionMapField->getLoc()});
     rewriter.setInsertionPointToStart(body);
     GetFieldOp mapField = GetFieldOp::create(
         rewriter, reflectionMapField->getLoc(), mapType, "reflectionMap");
@@ -158,7 +164,8 @@ class AddReflectionMapClass : public OpRewritePattern<ClassOp> {
     MemberCallOpaqueOp lookupCall = MemberCallOpaqueOp::create(
         rewriter, reflectionMapField->getLoc(), valType, mapField.getResult(),
         "at", ArrayAttr{}, ArrayAttr{}, ValueRange{nameArg});
-    ReturnOp::create(rewriter, reflectionMapField->getLoc(), lookupCall.getResult(0));
+    ReturnOp::create(rewriter, reflectionMapField->getLoc(),
+                     lookupCall.getResult(0));
 
     return success();
   }

>From 266f21b78f0534b8bdf8354587cc883a81464a48 Mon Sep 17 00:00:00 2001
From: Bhavesh Mandalapu <bmandalapu at google.com>
Date: Fri, 26 Jun 2026 16:04:13 -0700
Subject: [PATCH 5/6] Address review comments

---
 .../mlir/Dialect/EmitC/Transforms/Passes.h    |  2 +-
 .../mlir/Dialect/EmitC/Transforms/Passes.td   | 10 ++-
 .../Dialect/EmitC/Transforms/Transforms.h     |  6 +-
 .../Dialect/EmitC/Transforms/CMakeLists.txt   |  2 +-
 ...ectionMap.cpp => MLGOAddReflectionMap.cpp} | 76 +++++++++--------
 ...-map.mlir => mlgo-add-reflection-map.mlir} | 84 +++++++++++++++++--
 .../Target/Cpp/mlgo-add-reflection-map.mlir   | 57 +++++++++++++
 7 files changed, 188 insertions(+), 49 deletions(-)
 rename mlir/lib/Dialect/EmitC/Transforms/{AddReflectionMap.cpp => MLGOAddReflectionMap.cpp} (71%)
 rename mlir/test/Dialect/EmitC/{add-reflection-map.mlir => mlgo-add-reflection-map.mlir} (52%)
 create mode 100644 mlir/test/Target/Cpp/mlgo-add-reflection-map.mlir

diff --git a/mlir/include/mlir/Dialect/EmitC/Transforms/Passes.h b/mlir/include/mlir/Dialect/EmitC/Transforms/Passes.h
index 56596110a1e90..4f46d63774625 100644
--- a/mlir/include/mlir/Dialect/EmitC/Transforms/Passes.h
+++ b/mlir/include/mlir/Dialect/EmitC/Transforms/Passes.h
@@ -16,7 +16,7 @@ namespace emitc {
 
 #define GEN_PASS_DECL_FORMEXPRESSIONSPASS
 #define GEN_PASS_DECL_WRAPFUNCINCLASSPASS
-#define GEN_PASS_DECL_ADDREFLECTIONMAPPASS
+#define GEN_PASS_DECL_MLGOADDREFLECTIONMAPPASS
 
 #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 6da57c07a8047..3d17f53032a7a 100644
--- a/mlir/include/mlir/Dialect/EmitC/Transforms/Passes.td
+++ b/mlir/include/mlir/Dialect/EmitC/Transforms/Passes.td
@@ -60,7 +60,7 @@ def WrapFuncInClassPass : Pass<"wrap-emitc-func-in-class", "ModuleOp"> {
   ];
 }
 
-def AddReflectionMapPass : Pass<"add-reflection-map", "ModuleOp"> {
+def MLGOAddReflectionMapPass : Pass<"mlgo-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 
@@ -68,9 +68,11 @@ def AddReflectionMapPass : Pass<"add-reflection-map", "ModuleOp"> {
     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.
-
-
+    attribute. If a field possesses both the attribute specified by
+    `field-attr-name` and an attribute specified in `excluded-field-attrs`,
+    the `field-attr-name` attribute takes precedence and the field will be
+    included in the reflection map.
+    
     Example:
 
     ```mlir
diff --git a/mlir/include/mlir/Dialect/EmitC/Transforms/Transforms.h b/mlir/include/mlir/Dialect/EmitC/Transforms/Transforms.h
index 8f1784f61f540..6425614e1fbcd 100644
--- a/mlir/include/mlir/Dialect/EmitC/Transforms/Transforms.h
+++ b/mlir/include/mlir/Dialect/EmitC/Transforms/Transforms.h
@@ -42,9 +42,9 @@ void populateWrapFuncInClass(
 // The AddReflectionMap pass.
 //===----------------------------------------------------------------------===//
 
-void populateAddReflectionMapPatterns(RewritePatternSet &patterns,
-                                      StringRef fieldAttrName,
-                                      ArrayRef<std::string> excludedFieldAttrs);
+void populateMLGOAddReflectionMapPatterns(
+    RewritePatternSet &patterns, StringRef fieldAttrName,
+    ArrayRef<std::string> excludedFieldAttrs);
 
 } // namespace emitc
 } // namespace mlir
diff --git a/mlir/lib/Dialect/EmitC/Transforms/CMakeLists.txt b/mlir/lib/Dialect/EmitC/Transforms/CMakeLists.txt
index dd8f014dc4737..ede2cb5e208f0 100644
--- a/mlir/lib/Dialect/EmitC/Transforms/CMakeLists.txt
+++ b/mlir/lib/Dialect/EmitC/Transforms/CMakeLists.txt
@@ -3,7 +3,7 @@ add_mlir_dialect_library(MLIREmitCTransforms
   FormExpressions.cpp
   TypeConversions.cpp
   WrapFuncInClass.cpp
-  AddReflectionMap.cpp
+  MLGOAddReflectionMap.cpp
 
   ADDITIONAL_HEADER_DIRS
   ${MLIR_MAIN_INCLUDE_DIR}/mlir/Dialect/EmitC/Transforms
diff --git a/mlir/lib/Dialect/EmitC/Transforms/AddReflectionMap.cpp b/mlir/lib/Dialect/EmitC/Transforms/MLGOAddReflectionMap.cpp
similarity index 71%
rename from mlir/lib/Dialect/EmitC/Transforms/AddReflectionMap.cpp
rename to mlir/lib/Dialect/EmitC/Transforms/MLGOAddReflectionMap.cpp
index d444f3abbf4b1..9f506b10c722a 100644
--- a/mlir/lib/Dialect/EmitC/Transforms/AddReflectionMap.cpp
+++ b/mlir/lib/Dialect/EmitC/Transforms/MLGOAddReflectionMap.cpp
@@ -1,7 +1,7 @@
-//===- 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.
+// See https://llvm.org/LICENSE.txt for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 //
 //===----------------------------------------------------------------------===//
@@ -22,7 +22,7 @@ using namespace emitc;
 
 namespace mlir {
 namespace emitc {
-#define GEN_PASS_DEF_ADDREFLECTIONMAPPASS
+#define GEN_PASS_DEF_MLGOADDREFLECTIONMAPPASS
 #include "mlir/Dialect/EmitC/Transforms/Passes.h.inc"
 
 namespace {
@@ -35,15 +35,15 @@ IncludeOp addHeader(OpBuilder &builder, ModuleOp module, StringRef headerName) {
                            /*is_standard_include=*/builder.getUnitAttr());
 }
 
-class AddReflectionMapPass
-    : public impl::AddReflectionMapPassBase<AddReflectionMapPass> {
-  using AddReflectionMapPassBase::AddReflectionMapPassBase;
+class MLGOAddReflectionMapPass
+    : public impl::MLGOAddReflectionMapPassBase<MLGOAddReflectionMapPass> {
+  using MLGOAddReflectionMapPassBase::MLGOAddReflectionMapPassBase;
   void runOnOperation() override {
     mlir::ModuleOp moduleOp = getOperation();
 
     RewritePatternSet patterns(&getContext());
-    populateAddReflectionMapPatterns(patterns, fieldAttrName,
-                                     excludedFieldAttrs);
+    populateMLGOAddReflectionMapPatterns(patterns, fieldAttrName,
+                                         excludedFieldAttrs);
 
     walkAndApplyPatterns(moduleOp, std::move(patterns));
     bool hasMapHdr = false;
@@ -56,8 +56,8 @@ class AddReflectionMapPass
       if (includeOp.getIsStandardInclude()) {
         auto include = includeOp.getInclude();
 
-        hasMapHdr = include == mapLibraryHeader;
-        hasStringHdr = include == stringLibraryHeader;
+        hasMapHdr |= include == mapLibraryHeader;
+        hasStringHdr |= include == stringLibraryHeader;
       }
 
       if (hasMapHdr && hasStringHdr)
@@ -77,10 +77,10 @@ class AddReflectionMapPass
 } // namespace emitc
 } // namespace mlir
 
-class AddReflectionMapClass : public OpRewritePattern<ClassOp> {
+class MLGOAddReflectionMapClass : public OpRewritePattern<ClassOp> {
 public:
-  AddReflectionMapClass(MLIRContext *context, StringRef attrName,
-                        llvm::ArrayRef<std::string> excludedFieldAttrs)
+  MLGOAddReflectionMapClass(MLIRContext *context, StringRef attrName,
+                            llvm::ArrayRef<std::string> excludedFieldAttrs)
       : OpRewritePattern<ClassOp>(context), fieldAttrName(attrName),
         excludedFieldAttrs(excludedFieldAttrs.begin(),
                            excludedFieldAttrs.end()) {}
@@ -93,33 +93,35 @@ class AddReflectionMapClass : public OpRewritePattern<ClassOp> {
         context, "const std::map<std::string, char*>");
 
     // Collect all field names
-    std::vector<std::pair<std::string, std::string>> fieldNames;
+    std::vector<std::pair<StringRef, StringRef>> fieldNames;
+    bool hasError = false;
     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 (auto arrayAttr =
+              dyn_cast_if_present<ArrayAttr>(fieldOp->getAttr(fieldAttrName))) {
+        if (!arrayAttr.empty()) {
+          if (auto stringAttr = dyn_cast<StringAttr>(arrayAttr[0])) {
+            fieldNames.emplace_back(stringAttr.getValue(), fieldOp.getName());
+            return;
+          }
         }
       }
 
+      bool shouldIgnore =
+          llvm::any_of(excludedFieldAttrs, [&fieldOp](StringRef ignoreAttr) {
+            return fieldOp->hasAttr(ignoreAttr);
+          });
       if (shouldIgnore)
         return;
 
       fieldOp.emitError() << "FieldOp must have a dictionary attribute named '"
-                          << fieldAttrName << "'"
+                          << fieldAttrName << "' "
                           << "with an array containing a string attribute";
+      hasError = true;
     });
 
+    if (hasError || fieldNames.empty())
+      return failure();
+
     std::string reflectionMapContents;
     reflectionMapContents += "{ ";
     for (size_t i = 0, numFields = fieldNames.size(); i < numFields; ++i) {
@@ -138,6 +140,14 @@ class AddReflectionMapClass : public OpRewritePattern<ClassOp> {
       return failure();
     }
 
+    // To generate the following C++ code
+    // const std::map<std::string, char*> reflectionMap = {
+    //  { "another_feature", reinterpret_cast<char*>(&fieldName0) },
+    //  { "some_feature", reinterpret_cast<char*>(&fieldName1) },
+    //  ...
+    // };
+    // This can be used to retrieve a pointer to the field's contents given the
+    // attribute string identifying the field
     FieldOp reflectionMapField = FieldOp::create(
         rewriter, classOp.getLoc(), rewriter.getStringAttr("reflectionMap"),
         TypeAttr::get(mapType),
@@ -180,9 +190,9 @@ class AddReflectionMapClass : public OpRewritePattern<ClassOp> {
   llvm::SmallVector<std::string> excludedFieldAttrs;
 };
 
-void mlir::emitc::populateAddReflectionMapPatterns(
+void mlir::emitc::populateMLGOAddReflectionMapPatterns(
     RewritePatternSet &patterns, StringRef fieldAttrName,
     llvm::ArrayRef<std::string> excludedFieldAttrs) {
-  patterns.add<AddReflectionMapClass>(patterns.getContext(), fieldAttrName,
-                                      excludedFieldAttrs);
-}
\ No newline at end of file
+  patterns.add<MLGOAddReflectionMapClass>(patterns.getContext(), fieldAttrName,
+                                          excludedFieldAttrs);
+}
diff --git a/mlir/test/Dialect/EmitC/add-reflection-map.mlir b/mlir/test/Dialect/EmitC/mlgo-add-reflection-map.mlir
similarity index 52%
rename from mlir/test/Dialect/EmitC/add-reflection-map.mlir
rename to mlir/test/Dialect/EmitC/mlgo-add-reflection-map.mlir
index a1ec0356e19f4..2bea6cfaf2677 100644
--- a/mlir/test/Dialect/EmitC/add-reflection-map.mlir
+++ b/mlir/test/Dialect/EmitC/mlgo-add-reflection-map.mlir
@@ -1,7 +1,7 @@
-// RUN: mlir-opt -split-input-file --add-reflection-map="field-attr-name=emitc.field_ref excluded-field-attrs="emitc.other_field"" %s | FileCheck %s
+// RUN: mlir-opt -split-input-file --mlgo-add-reflection-map="field-attr-name=emitc.field_ref \
+// RUN: excluded-field-attrs="emitc.other_field"" -verify-diagnostics %s | FileCheck %s
 
-
-// Tests that a reflection map is created for fields with a certain attribute.
+/// 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"]}
@@ -29,10 +29,11 @@ emitc.class @actionClass {
 // 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 {
+/// 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 @actionClassExcluded {
   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()"() {
@@ -41,7 +42,7 @@ emitc.class @actionClass {
   }
 }
 
-// CHECK:       emitc.class @actionClass {
+// CHECK:       emitc.class @actionClassExcluded {
 // 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*>"> = 
@@ -55,4 +56,73 @@ emitc.class @actionClass {
 // CHECK-NEXT:      %{{.*}} = get_field @fieldName0 : !emitc.array<1xf32>
 // CHECK-NEXT:      return
 // CHECK-NEXT:    }
+// CHECK-NEXT:  }
+
+// -----
+
+/// Test that the pass leaves IR unchanged if fields don't have any attributes
+
+emitc.class @actionClassNoAttrs {
+  // expected-error @below {{FieldOp must have a dictionary attribute named 'emitc.field_ref' with an array containing a string attribute}}
+  emitc.field @fieldName0 : !emitc.array<1xf32>
+  emitc.func @"operator()"() {
+    return
+  }
+}
+
+// CHECK-LABEL: emitc.class @actionClassNoAttrs {
+// CHECK-NEXT:    emitc.field @fieldName0 : !emitc.array<1xf32>
+// CHECK-NEXT:    emitc.func @"operator()"() {
+// CHECK-NEXT:      return
+// CHECK-NEXT:    }
+// CHECK-NEXT:  }
+
+// -----
+
+/// Test that the pass leaves IR unchanged if the ClassOp doesn't have any fields
+
+emitc.class @actionClassNoFields {
+  emitc.func @"operator()"() {
+    return
+  }
+}
+
+// CHECK-LABEL: emitc.class @actionClassNoFields {
+// CHECK-NEXT:    emitc.func @"operator()"() {
+// CHECK-NEXT:      return
+// CHECK-NEXT:    }
+// CHECK-NEXT:  }
+
+// -----
+
+/// Test that the pass returns with a match failure if the ClassOp doesn't have
+/// a FunctionOp named operator()
+
+// expected-error @below {{ClassOp must contain a function named 'operator()' to add reflection map}}
+emitc.class @actionClassNoOperator {
+  emitc.field @fieldName0 : !emitc.array<1xf32> {emitc.field_ref = ["another_feature"]}
+}
+
+// CHECK-LABEL: emitc.class @actionClassNoOperator {
+// CHECK-NEXT:    emitc.field @fieldName0 : !emitc.array<1xf32> {emitc.field_ref = ["another_feature"]}
+// CHECK-NEXT:  }
+
+// -----
+
+/// Test that the pass returns with a match failure if a FieldOp has the specified
+/// dictionary attribute with an array containing a type other than string
+
+emitc.class @actionClassNonStringAttr {
+  // expected-error @below {{FieldOp must have a dictionary attribute named 'emitc.field_ref' with an array containing a string attribute}}
+  emitc.field @fieldName0 : !emitc.array<1xf32> {emitc.field_ref = [1]}
+  emitc.func @"operator()"() {
+    return
+  }
+}
+
+// CHECK-LABEL: emitc.class @actionClassNonStringAttr {
+// CHECK-NEXT:    emitc.field @fieldName0 : !emitc.array<1xf32> {emitc.field_ref = [1]}
+// CHECK-NEXT:    emitc.func @"operator()"() {
+// CHECK-NEXT:      return
+// CHECK-NEXT:    }
 // CHECK-NEXT:  }
\ No newline at end of file
diff --git a/mlir/test/Target/Cpp/mlgo-add-reflection-map.mlir b/mlir/test/Target/Cpp/mlgo-add-reflection-map.mlir
new file mode 100644
index 0000000000000..eb8a8e58c8b77
--- /dev/null
+++ b/mlir/test/Target/Cpp/mlgo-add-reflection-map.mlir
@@ -0,0 +1,57 @@
+// RUN: mlir-opt -split-input-file --mlgo-add-reflection-map="field-attr-name=emitc.field_ref excluded-field-attrs=emitc.other_field" %s | mlir-translate -mlir-to-cpp | FileCheck %s
+
+// Test that a reflection map and lookup function are generated in the class.
+
+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:       #include <map>
+// CHECK-NEXT:  #include <string>
+// CHECK-NEXT:  class actionClass {
+// CHECK-NEXT:   public:
+// CHECK-NEXT:    float fieldName0[1];
+// CHECK-NEXT:    float fieldName1[1];
+// CHECK-NEXT:    const std::map<std::string, char*> reflectionMap = { { "another_feature", reinterpret_cast<char*>(&fieldName0) }, { "some_feature", reinterpret_cast<char*>(&fieldName1) } };
+// CHECK-NEXT:    char* getBufferForName(std::string [[VAL_1:v[0-9]+]]) {
+// CHECK-NEXT:      char* [[VAL_2:v[0-9]+]] = reflectionMap.at([[VAL_1]]);
+// CHECK-NEXT:      return [[VAL_2]];
+// CHECK-NEXT:    }
+// CHECK-NEXT:    void operator()() {
+// CHECK-NEXT:      return;
+// CHECK-NEXT:    }
+// CHECK-NEXT:  };
+
+// -----
+
+// Test that fields with excluded attributes are ignored.
+
+emitc.class @actionClassExcluded {
+  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:       #include <map>
+// CHECK-NEXT:  #include <string>
+// CHECK-NEXT:  class actionClassExcluded {
+// CHECK-NEXT:   public:
+// CHECK-NEXT:    float fieldName0[1];
+// CHECK-NEXT:    float fieldName1[1];
+// CHECK-NEXT:    const std::map<std::string, char*> reflectionMap = { { "another_feature", reinterpret_cast<char*>(&fieldName0) } };
+// CHECK-NEXT:    char* getBufferForName(std::string [[VAL_1:v[0-9]+]]) {
+// CHECK-NEXT:      char* [[VAL_2:v[0-9]+]] = reflectionMap.at([[VAL_1]]);
+// CHECK-NEXT:      return [[VAL_2]];
+// CHECK-NEXT:    }
+// CHECK-NEXT:    void operator()() {
+// CHECK-NEXT:      return;
+// CHECK-NEXT:    }
+// CHECK-NEXT:  };

>From 044316b2985c962d2a4b29fa700cf6974b5b7d60 Mon Sep 17 00:00:00 2001
From: Bhavesh Mandalapu <bmandalapu at google.com>
Date: Mon, 6 Jul 2026 12:50:33 -0700
Subject: [PATCH 6/6] update MLGOAddReflectionMap to use notifyMatchFailure,
 update example to be more minimal and clear about the transformation, use
 range based for loop in reflection map construction

---
 .../mlir/Dialect/EmitC/Transforms/Passes.td   | 24 ++++++------------
 .../EmitC/Transforms/MLGOAddReflectionMap.cpp | 25 +++++++++++--------
 .../EmitC/mlgo-add-reflection-map.mlir        |  5 +---
 3 files changed, 24 insertions(+), 30 deletions(-)

diff --git a/mlir/include/mlir/Dialect/EmitC/Transforms/Passes.td b/mlir/include/mlir/Dialect/EmitC/Transforms/Passes.td
index 3d17f53032a7a..29b053f3469c7 100644
--- a/mlir/include/mlir/Dialect/EmitC/Transforms/Passes.td
+++ b/mlir/include/mlir/Dialect/EmitC/Transforms/Passes.td
@@ -77,28 +77,20 @@ def MLGOAddReflectionMapPass : Pass<"mlgo-add-reflection-map", "ModuleOp"> {
 
     ```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() { ... }
+      emitc.field @field0 : !emitc.array<1xf32> {emitc.field_ref = ["feature0"]}
+      emitc.func @operator()() { ... }
     }
 
     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.field @field0 : !emitc.array<1xf32> {emitc.field_ref = ["feature0"]}
+      // reflectionMap maps the string "feature0" to the address of @field0
+      emitc.field @reflectionMap : const std::map<string, char*> = {{"feature0", &field0}}
+      emitc.func @getBufferForName(%name: string) -> char* {
+        return reflectionMap.at(%name)
       }
-      emitc.func @execute() { ... }
+      emitc.func @operator()() { ... }
     }
     ```
   }];
diff --git a/mlir/lib/Dialect/EmitC/Transforms/MLGOAddReflectionMap.cpp b/mlir/lib/Dialect/EmitC/Transforms/MLGOAddReflectionMap.cpp
index 9f506b10c722a..b93138987719b 100644
--- a/mlir/lib/Dialect/EmitC/Transforms/MLGOAddReflectionMap.cpp
+++ b/mlir/lib/Dialect/EmitC/Transforms/MLGOAddReflectionMap.cpp
@@ -113,9 +113,11 @@ class MLGOAddReflectionMapClass : public OpRewritePattern<ClassOp> {
       if (shouldIgnore)
         return;
 
-      fieldOp.emitError() << "FieldOp must have a dictionary attribute named '"
-                          << fieldAttrName << "' "
-                          << "with an array containing a string attribute";
+      (void)rewriter.notifyMatchFailure(fieldOp, [&](Diagnostic &diag) {
+        diag << "FieldOp must have a dictionary attribute named '"
+             << fieldAttrName << "' "
+             << "with an array containing a string attribute";
+      });
       hasError = true;
     });
 
@@ -124,20 +126,23 @@ class MLGOAddReflectionMapClass : public OpRewritePattern<ClassOp> {
 
     std::string reflectionMapContents;
     reflectionMapContents += "{ ";
-    for (size_t i = 0, numFields = fieldNames.size(); i < numFields; ++i) {
+    bool first = true;
+    for (const auto &[name, value] : fieldNames) {
+      if (!first)
+        reflectionMapContents += ", ";
+      
+      first = false;
       reflectionMapContents += llvm::formatv(
-          "{ \"{0}\", reinterpret_cast<char*>(&{1}) }{2}", fieldNames[i].first,
-          fieldNames[i].second, (i < numFields - 1) ? ", " : "");
+          "{ \"{0}\", reinterpret_cast<char*>(&{1}) }", name, value);
     }
     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();
+      return rewriter.notifyMatchFailure(
+          classOp, "ClassOp must contain a function named 'operator()' "
+                   "to add reflection map");
     }
 
     // To generate the following C++ code
diff --git a/mlir/test/Dialect/EmitC/mlgo-add-reflection-map.mlir b/mlir/test/Dialect/EmitC/mlgo-add-reflection-map.mlir
index 2bea6cfaf2677..1f8647910ada9 100644
--- a/mlir/test/Dialect/EmitC/mlgo-add-reflection-map.mlir
+++ b/mlir/test/Dialect/EmitC/mlgo-add-reflection-map.mlir
@@ -1,5 +1,5 @@
 // RUN: mlir-opt -split-input-file --mlgo-add-reflection-map="field-attr-name=emitc.field_ref \
-// RUN: excluded-field-attrs="emitc.other_field"" -verify-diagnostics %s | FileCheck %s
+// RUN: excluded-field-attrs="emitc.other_field"" %s | FileCheck %s
 
 /// Tests that a reflection map is created for fields with a certain attribute.
 
@@ -63,7 +63,6 @@ emitc.class @actionClassExcluded {
 /// Test that the pass leaves IR unchanged if fields don't have any attributes
 
 emitc.class @actionClassNoAttrs {
-  // expected-error @below {{FieldOp must have a dictionary attribute named 'emitc.field_ref' with an array containing a string attribute}}
   emitc.field @fieldName0 : !emitc.array<1xf32>
   emitc.func @"operator()"() {
     return
@@ -98,7 +97,6 @@ emitc.class @actionClassNoFields {
 /// Test that the pass returns with a match failure if the ClassOp doesn't have
 /// a FunctionOp named operator()
 
-// expected-error @below {{ClassOp must contain a function named 'operator()' to add reflection map}}
 emitc.class @actionClassNoOperator {
   emitc.field @fieldName0 : !emitc.array<1xf32> {emitc.field_ref = ["another_feature"]}
 }
@@ -113,7 +111,6 @@ emitc.class @actionClassNoOperator {
 /// dictionary attribute with an array containing a type other than string
 
 emitc.class @actionClassNonStringAttr {
-  // expected-error @below {{FieldOp must have a dictionary attribute named 'emitc.field_ref' with an array containing a string attribute}}
   emitc.field @fieldName0 : !emitc.array<1xf32> {emitc.field_ref = [1]}
   emitc.func @"operator()"() {
     return



More information about the Mlir-commits mailing list