[Mlir-commits] [mlir] [mlir][ODS] Name obfuscation for private dialects (PR #198083)

Matthias Springer llvmlistbot at llvm.org
Wed May 20 05:39:18 PDT 2026


https://github.com/matthias-springer updated https://github.com/llvm/llvm-project/pull/198083

>From a8c65353bd14211909d01ac831d59fd6e7a0c744 Mon Sep 17 00:00:00 2001
From: Matthias Springer <me at m-sp.org>
Date: Sat, 16 May 2026 09:36:32 +0000
Subject: [PATCH] strip op and pass names

---
 mlir/CMakeLists.txt                           |  29 +++
 mlir/docs/PrivateNameObfuscation.md           | 174 +++++++++++++++++
 mlir/include/mlir/IR/DialectBase.td           |   8 +
 mlir/include/mlir/Pass/PassBase.td            |  10 +
 mlir/include/mlir/TableGen/AttrOrTypeDef.h    |   5 +
 mlir/include/mlir/TableGen/Dialect.h          |   5 +
 mlir/include/mlir/TableGen/Operator.h         |   5 +
 mlir/include/mlir/TableGen/Pass.h             |   7 +
 mlir/include/mlir/TableGen/PrivateName.h      |  59 ++++++
 mlir/lib/TableGen/AttrOrTypeDef.cpp           |   2 +
 mlir/lib/TableGen/Dialect.cpp                 |   2 +
 mlir/lib/TableGen/Operator.cpp                |   2 +
 mlir/lib/TableGen/Pass.cpp                    |   2 +
 mlir/lib/Tools/mlir-tblgen/MlirTblgenMain.cpp |   2 +
 .../mlir-tblgen/private-name-obfuscation.td   | 175 ++++++++++++++++++
 .../mlir-tblgen/private-pass-obfuscation.td   |  74 ++++++++
 mlir/tools/mlir-tblgen/AttrOrTypeDefGen.cpp   |  18 +-
 mlir/tools/mlir-tblgen/CMakeLists.txt         |   1 +
 mlir/tools/mlir-tblgen/DialectGen.cpp         |   7 +-
 mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp   |  47 ++++-
 mlir/tools/mlir-tblgen/PassGen.cpp            |  46 +++--
 mlir/tools/mlir-tblgen/PrivateName.cpp        | 139 ++++++++++++++
 mlir/tools/mlir-tblgen/RewriterGen.cpp        |  30 +--
 23 files changed, 813 insertions(+), 36 deletions(-)
 create mode 100644 mlir/docs/PrivateNameObfuscation.md
 create mode 100644 mlir/include/mlir/TableGen/PrivateName.h
 create mode 100644 mlir/test/mlir-tblgen/private-name-obfuscation.td
 create mode 100644 mlir/test/mlir-tblgen/private-pass-obfuscation.td
 create mode 100644 mlir/tools/mlir-tblgen/PrivateName.cpp

diff --git a/mlir/CMakeLists.txt b/mlir/CMakeLists.txt
index 6d05fa50ecd05..ca9c4b00926a1 100644
--- a/mlir/CMakeLists.txt
+++ b/mlir/CMakeLists.txt
@@ -152,6 +152,35 @@ set(MLIR_ENABLE_NVPTXCOMPILER 0 CACHE BOOL
 
 set(MLIR_ENABLE_PDL_IN_PATTERNMATCH 1 CACHE BOOL "Enable PDL in PatternMatch")
 
+#-------------------------------------------------------------------------------
+# Private name obfuscation
+#
+# When MLIR_PRIVATE_NAME_OBFUSCATOR is set to a non-empty shell command,
+# dialects, ops, attributes, types, and passes whose TableGen definitions
+# set `let isPrivate = 1;` have their mnemonics replaced with opaque
+# identifiers computed by that command. For each name, mlir-tblgen pipes
+# the name into `<MLIR_PRIVATE_NAME_OBFUSCATOR>` via stdin and uses the
+# first whitespace-delimited token of stdout (with a `_` prefix) as the
+# obfuscated name. Private passes additionally have their descriptions /
+# option / statistic descriptions emitted as empty strings. This makes it
+# harder to reverse engineer the resulting binary while keeping
+# registration, pattern matching, bytecode, and IR printing internally
+# consistent across translation units of one build.
+#
+# Choosing a stable obfuscator is the user's responsibility: e.g.
+# `md5sum | awk '{print $1}'`, an HMAC with a salt, or any custom script.
+# The obfuscator must produce a unique output per unique input (no
+# collisions); hashing tools usually satisfy this in practice. Bytecode
+# produced by a build with one obfuscator is generally unreadable by any
+# build that uses a different obfuscator.
+#-------------------------------------------------------------------------------
+set(MLIR_PRIVATE_NAME_OBFUSCATOR "" CACHE STRING
+    "Shell command fed to mlir-tblgen for private-name obfuscation; empty disables obfuscation.")
+
+if(MLIR_PRIVATE_NAME_OBFUSCATOR)
+  list(APPEND LLVM_TABLEGEN_FLAGS "--mlir-private-name-obfuscator=${MLIR_PRIVATE_NAME_OBFUSCATOR}")
+endif()
+
 option(MLIR_INCLUDE_TESTS
        "Generate build targets for the MLIR unit tests."
        ${LLVM_INCLUDE_TESTS})
diff --git a/mlir/docs/PrivateNameObfuscation.md b/mlir/docs/PrivateNameObfuscation.md
new file mode 100644
index 0000000000000..497d36220c494
--- /dev/null
+++ b/mlir/docs/PrivateNameObfuscation.md
@@ -0,0 +1,174 @@
+# Private Name Obfuscation
+
+[TOC]
+
+This page documents an opt-in TableGen + CMake mechanism for replacing the
+human-readable names of dialects, operations, attributes, types, and passes
+in a built MLIR binary with opaque identifiers. The intent is to make it
+harder to reverse-engineer a release binary while preserving a fully-readable
+internal/test build from the same source tree.
+
+The whole obfuscation logic is delegated to an external shell command
+supplied by the build, so MLIR itself does not embed any particular hash
+function or secret material.
+
+## What gets obfuscated
+
+When `-DMLIR_PRIVATE_NAME_OBFUSCATOR=<shell-cmd>` is set and a TableGen
+definition declares `let isPrivate = 1;`, mlir-tblgen emits the following
+literals using `<shell-cmd>` to obfuscate the original identifier instead
+of the original spelling:
+
+| Source                     | Affected literal(s)                                          |
+| -------------------------- | ------------------------------------------------------------ |
+| `Dialect`                  | `getDialectNamespace()`, dialect constructor                 |
+| `Op`                       | `getOperationName()`, adaptor `odsOpName`, error prefixes |
+| `AttrDef`, `TypeDef`       | `getMnemonic()`, `name`, `dialectName`, alias `getAlias`     |
+| `PassBase` / `Pass`        | `getArgument()`, `getArgumentName()`, `getName()`, `getPassName()` |
+
+For each unique name `N`, mlir-tblgen runs roughly:
+
+```sh
+printf '%s' 'N' | <shell-cmd>
+```
+
+It then takes the first whitespace-delimited token from stdout, prefixes
+it with `_` (so the result is always a valid MLIR mnemonic / C++ identifier),
+and uses that as the obfuscated name. Results are cached, so the command is
+invoked at most once per unique name within a single mlir-tblgen invocation.
+
+For op / attribute / type names, the obfuscator is invoked separately on
+the dialect prefix and on the mnemonic, and the results are rejoined with
+a dot, so the runtime helper `OperationName::getDialectNamespace()` (which
+splits at the first `.`) keeps working.
+
+Obfuscated names are **deterministic** as long as the configured command
+is deterministic, so all translation units within one build agree on the
+obfuscated spelling. Pattern matching, `ConversionTarget`, the bytecode
+reader/writer (which uses whatever `getDialectNamespace()` /
+`getOperationName()` return), and dialect registration all keep working
+without source changes.
+
+Private ops also behave as if no `assemblyFormat` or
+`hasCustomAssemblyFormat` was specified when private-name obfuscation is
+enabled. ODS does not generate the custom/declarative `parse` and `print`
+methods for those ops. They still print in generic form, using the
+obfuscated operation name, and their custom textual syntax is rejected.
+
+Op argument names (the keys returned by `getAttributeNames()` / used by
+generated `StringAttr` accessors) are **not** obfuscated. They are dictionary
+keys with established meaning across patterns, properties, and serialization,
+so they remain in their original form even on private ops. Raw string lookups
+such as `op->getAttr("foo")` therefore keep working unchanged.
+
+For private passes, the descriptions returned by `getDescription()`, the
+per-option `cl::desc(...)` text, and the per-statistic description text are
+also emitted as empty strings (since plaintext descriptions would otherwise
+leak the meaning of the obfuscated name). Per-pass `register{PassName}()`
+helpers and the `mlirCreate{Group}{PassName}` / `mlirRegister{Group}{PassName}`
+C-API entries keep being emitted under their original C++ class names; they
+register and invoke the pass under its obfuscated textual argument and
+display name.
+
+## Build configuration
+
+```cmake
+# Public/test/dev build: no obfuscation (default).
+cmake -G Ninja path/to/llvm \
+  -DLLVM_ENABLE_PROJECTS=mlir
+
+# Release build: obfuscate names of every TableGen item marked `isPrivate`
+# using an external obfuscator command.
+cmake -G Ninja path/to/llvm \
+  -DLLVM_ENABLE_PROJECTS=mlir \
+  -DMLIR_PRIVATE_NAME_OBFUSCATOR="md5sum | awk '{print \$1}'"
+```
+
+`MLIR_PRIVATE_NAME_OBFUSCATOR` controls private-name obfuscation: leave it
+empty to disable obfuscation, or set it to a shell command. The command
+receives each name on stdin (no trailing newline) and should write the
+obfuscated form to stdout. mlir-tblgen uses the first whitespace-delimited
+token of that output as the obfuscated name, so simple tools that emit
+extra fields (such as `md5sum`, which prints `<hash>  -`) work out of the
+box.
+
+Choose any deterministic, build-stable obfuscator for production builds.
+The obfuscator must produce a unique output per unique input (no
+collisions); hashing tools usually satisfy this in practice but it is the
+user's responsibility to pick something appropriate. Examples:
+
+* `md5sum`
+* `sha256sum`
+* An HMAC pipeline with a build-private key:
+  `openssl dgst -sha256 -hmac "$RELEASE_SALT" -hex | awk '{print $2}'`
+* A custom script that mixes in a vendor-specific salt before hashing.
+
+The obfuscator command is fed directly to the `mlir-tblgen` command line as
+`--mlir-private-name-obfuscator=<cmd>` and is **not** embedded in the
+resulting binary. Bytecode produced by a build with one obfuscator is
+generally unreadable by a build that uses a different obfuscator, so pin
+the obfuscator across releases that need binary compatibility.
+
+## Marking items as private
+
+```tablegen
+def MyDialect : Dialect {
+  let name = "mydialect";
+  let cppNamespace = "::my";
+  let isPrivate = 1;        // dialect, plus all of its ops/attrs/types
+}
+
+def MyOp : Op<MyDialect, "do_thing", []>;   // inherits isPrivate from MyDialect
+
+def MyPass : Pass<"my-pass"> {
+  let isPrivate = 1;        // obfuscate the argument and name, empty out
+                            // the description / per-option / per-statistic
+                            // text
+}
+```
+
+`Op`, `AttrDef`, and `TypeDef` all derive their `isPrivate` bit from the
+owning dialect; there is no per-op or per-def override. To make an
+operation, attribute, or type "private", mark its dialect `isPrivate`.
+Passes do not belong to a dialect and are toggled individually.
+
+## Caveats
+
+* Hand-written code that compares `op->getName().getStringRef()` against a
+  spelled-out op name (e.g. `== "mydialect.do_thing"`) breaks under
+  obfuscation. Migrate such checks to `isa<my::DoThingOp>()` (which uses
+  TypeID and is unaffected) or compare against
+  `my::DoThingOp::getOperationName()` (which is itself obfuscated, so the
+  comparison still works).
+* Diagnostics and verifier messages naturally print the obfuscated names
+  because they use runtime `getName()` / `getDialect()->getNamespace()`
+  calls. The English skeleton text around the names is not stripped.
+* Python bindings emitted by `gen-python-op-bindings` are not adjusted by
+  this mechanism. Do not generate Python bindings for private dialects or
+  ops.
+* `LLVM_DEBUG`, `LDBG`, statistics (`LLVM_ENABLE_STATS`), and `--debug`
+  paths are already removed from a release build (`NDEBUG`); they don't
+  need separate handling.
+* The obfuscator is spawned via `popen` (or `_popen` on Windows), so the
+  command must be runnable by the host's default shell, and must be on
+  `PATH` when mlir-tblgen runs.
+
+## Audit checklist for downstream trees
+
+Before enabling private-name obfuscation in a downstream compiler, audit for
+hand-written string comparisons and textual pipeline dependencies. These
+patterns should generally be rewritten to use TypeID-based APIs, concrete op
+classes, or the generated `::getOperationName()` accessors:
+
+```sh
+rg 'getName\(\)\.getStringRef\(\).*==|==.*getName\(\)\.getStringRef\(\)' path/to/downstream
+rg 'OperationName\("[^"]+"' path/to/downstream
+rg 'RegisteredOperationName::lookup\("[^"]+"' path/to/downstream
+rg 'getOrLoadDialect\("[^"]+"' path/to/downstream
+rg 'PassInfo::lookup\("[^"]+"' path/to/downstream
+rg 'parsePassPipeline|--pass-pipeline|register.*Passes' path/to/downstream
+```
+
+Comparisons against generated names, such as
+`my::DoThingOp::getOperationName()`, remain valid because the generated
+method returns the obfuscated spelling in release builds.
diff --git a/mlir/include/mlir/IR/DialectBase.td b/mlir/include/mlir/IR/DialectBase.td
index efa09a43ec581..8211bcf629ef9 100644
--- a/mlir/include/mlir/IR/DialectBase.td
+++ b/mlir/include/mlir/IR/DialectBase.td
@@ -92,6 +92,14 @@ class Dialect {
 
   // If this dialect can be extended at runtime with new operations or types.
   bit isExtensible = 0;
+
+  // Marks the dialect as "private". When private-name obfuscation is enabled
+  // (see `MLIR_PRIVATE_NAME_OBFUSCATOR`), mlir-tblgen replaces the dialect's
+  // namespace and the mnemonics of all of its ops, AttrDefs, and TypeDefs
+  // with opaque identifiers produced by the configured obfuscator command.
+  // Ops, AttrDefs, and TypeDefs inherit this bit from their owning dialect;
+  // there is no per-item override.
+  bit isPrivate = 0;
 }
 
 #endif // DIALECTBASE_TD
diff --git a/mlir/include/mlir/Pass/PassBase.td b/mlir/include/mlir/Pass/PassBase.td
index e37f9735e2241..7b61dbbe96677 100644
--- a/mlir/include/mlir/Pass/PassBase.td
+++ b/mlir/include/mlir/Pass/PassBase.td
@@ -89,6 +89,16 @@ class PassBase<string passArg, string base> {
 
   // A set of statistics provided by this pass.
   list<Statistic> statistics = [];
+
+  // Marks this pass as "private". When private-name obfuscation is enabled
+  // (see `MLIR_PRIVATE_NAME_OBFUSCATOR`), the pass argument and pass name
+  // are replaced with opaque identifiers produced by the configured
+  // obfuscator command, and the pass description, the per-option
+  // descriptions, and the per-statistic descriptions are emitted as empty
+  // strings. The per-pass `register*Pass()` helpers and the `mlirCreate*` /
+  // `mlirRegister*` C-API entries are still emitted using the pass's C++
+  // class name and register the pass under its obfuscated textual argument.
+  bit isPrivate = 0;
 }
 
 // This class represents an mlir::OperationPass.
diff --git a/mlir/include/mlir/TableGen/AttrOrTypeDef.h b/mlir/include/mlir/TableGen/AttrOrTypeDef.h
index 65992f9fef5e9..cd449cc26fa38 100644
--- a/mlir/include/mlir/TableGen/AttrOrTypeDef.h
+++ b/mlir/include/mlir/TableGen/AttrOrTypeDef.h
@@ -220,6 +220,11 @@ class AttrOrTypeDef {
   /// using the mnemonic.
   bool genMnemonicAlias() const;
 
+  /// Returns true if this attribute or type belongs to a dialect marked
+  /// `isPrivate`. Private def names are eligible for obfuscation when
+  /// private-name obfuscation is enabled in mlir-tblgen.
+  bool isPrivate() const;
+
   /// Get the code location (for error printing).
   ArrayRef<SMLoc> getLoc() const;
 
diff --git a/mlir/include/mlir/TableGen/Dialect.h b/mlir/include/mlir/TableGen/Dialect.h
index 30f9d690b678d..232d3e58d4f0a 100644
--- a/mlir/include/mlir/TableGen/Dialect.h
+++ b/mlir/include/mlir/TableGen/Dialect.h
@@ -88,6 +88,11 @@ class Dialect {
   /// operations or types.
   bool isExtensible() const;
 
+  /// Returns true if this dialect has been marked private. Private dialect,
+  /// op, attribute, and type names are eligible for obfuscation when
+  /// private-name obfuscation is enabled in mlir-tblgen.
+  bool isPrivate() const;
+
   const llvm::DagInit *getDiscardableAttributes() const;
 
   const llvm::Record *getDef() const { return def; }
diff --git a/mlir/include/mlir/TableGen/Operator.h b/mlir/include/mlir/TableGen/Operator.h
index f0514d8e61748..9d7bc2a08050d 100644
--- a/mlir/include/mlir/TableGen/Operator.h
+++ b/mlir/include/mlir/TableGen/Operator.h
@@ -86,6 +86,11 @@ class Operator {
   /// format if its dialect name is not empty.
   std::string getOperationName() const;
 
+  /// Returns true if this op belongs to a dialect marked `isPrivate`. When
+  /// private-name obfuscation is enabled in mlir-tblgen, the op's dialect
+  /// prefix and mnemonic in its registered name are obfuscated.
+  bool isPrivate() const;
+
   /// Returns this op's C++ class name.
   StringRef getCppClassName() const;
 
diff --git a/mlir/include/mlir/TableGen/Pass.h b/mlir/include/mlir/TableGen/Pass.h
index d2bb6e5304ee3..81aa6d5c089a7 100644
--- a/mlir/include/mlir/TableGen/Pass.h
+++ b/mlir/include/mlir/TableGen/Pass.h
@@ -103,6 +103,13 @@ class Pass {
   /// Return the statistics provided by this pass.
   ArrayRef<PassStatistic> getStatistics() const;
 
+  /// Return true if this pass has been marked private. When private-name
+  /// obfuscation is enabled in mlir-tblgen, the pass argument and pass name
+  /// are obfuscated and the pass description / per-option / per-statistic
+  /// description strings are emitted as empty strings. Registration and
+  /// C-API helpers are still emitted using the pass's C++ class name.
+  bool isPrivate() const;
+
   const llvm::Record *getDef() const { return def; }
 
 private:
diff --git a/mlir/include/mlir/TableGen/PrivateName.h b/mlir/include/mlir/TableGen/PrivateName.h
new file mode 100644
index 0000000000000..407dc959372df
--- /dev/null
+++ b/mlir/include/mlir/TableGen/PrivateName.h
@@ -0,0 +1,59 @@
+//===- PrivateName.h - Private name obfuscation for ODS ---------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Public API for ODS-driven obfuscation of "private" dialect namespaces,
+// op mnemonics, AttrDef/TypeDef mnemonics, and pass arguments and names.
+// Op argument attribute keys are intentionally not obfuscated. For private
+// passes, descriptions / option / statistic descriptions are also emitted
+// as empty strings when obfuscation is enabled.
+//
+// All of the configuration / plumbing (the `--mlir-private-name-obfuscator`
+// `cl::opt`, the obfuscator-subprocess invocation, and the name cache)
+// lives in `mlir/tools/mlir-tblgen/PrivateName.cpp` so that callers of this
+// header only see the pure query API.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef MLIR_TABLEGEN_PRIVATENAME_H_
+#define MLIR_TABLEGEN_PRIVATENAME_H_
+
+#include "mlir/Support/LLVM.h"
+#include "llvm/ADT/StringRef.h"
+
+#include <string>
+
+namespace mlir {
+namespace tblgen {
+
+/// Returns true if private-name obfuscation is enabled, i.e., a non-empty
+/// obfuscator command has been configured via `--mlir-private-name-obfuscator`.
+bool obfuscatePrivateNamesEnabled();
+
+/// Returns the obfuscated form of `name`. The returned StringRef is stable
+/// for the lifetime of the process. The configured obfuscator is invoked at
+/// most once per distinct name.
+StringRef obfuscatePrivateName(StringRef name);
+
+/// Returns either `name` (when not private or obfuscation is disabled) or
+/// `obfuscatePrivateName(name)`.
+inline StringRef maybeObfuscate(StringRef name, bool isPrivate) {
+  if (!isPrivate || !obfuscatePrivateNamesEnabled())
+    return name;
+  return obfuscatePrivateName(name);
+}
+
+/// For a dotted name "dialect.mnemonic", obfuscates the dialect prefix and
+/// the mnemonic suffix independently and rejoins them with a dot. This keeps
+/// runtime parsing of the dialect prefix in `OperationName` working. Names
+/// without a '.' are obfuscated as-is.
+std::string maybeObfuscateDotted(StringRef name, bool isPrivate);
+
+} // namespace tblgen
+} // namespace mlir
+
+#endif // MLIR_TABLEGEN_PRIVATENAME_H_
diff --git a/mlir/lib/TableGen/AttrOrTypeDef.cpp b/mlir/lib/TableGen/AttrOrTypeDef.cpp
index bf835a860cd5b..6c3d31267e1c1 100644
--- a/mlir/lib/TableGen/AttrOrTypeDef.cpp
+++ b/mlir/lib/TableGen/AttrOrTypeDef.cpp
@@ -211,6 +211,8 @@ bool AttrOrTypeDef::genMnemonicAlias() const {
   return def->getValueAsBit("genMnemonicAlias");
 }
 
+bool AttrOrTypeDef::isPrivate() const { return getDialect().isPrivate(); }
+
 ArrayRef<SMLoc> AttrOrTypeDef::getLoc() const { return def->getLoc(); }
 
 bool AttrOrTypeDef::skipDefaultBuilders() const {
diff --git a/mlir/lib/TableGen/Dialect.cpp b/mlir/lib/TableGen/Dialect.cpp
index 7aaf4dd57c50e..9957ff0e3e6c5 100644
--- a/mlir/lib/TableGen/Dialect.cpp
+++ b/mlir/lib/TableGen/Dialect.cpp
@@ -102,6 +102,8 @@ bool Dialect::isExtensible() const {
   return def->getValueAsBit("isExtensible");
 }
 
+bool Dialect::isPrivate() const { return def->getValueAsBit("isPrivate"); }
+
 const llvm::DagInit *Dialect::getDiscardableAttributes() const {
   return def->getValueAsDag("discardableAttrs");
 }
diff --git a/mlir/lib/TableGen/Operator.cpp b/mlir/lib/TableGen/Operator.cpp
index 82dfbcbfa4d4f..bf6836f546a23 100644
--- a/mlir/lib/TableGen/Operator.cpp
+++ b/mlir/lib/TableGen/Operator.cpp
@@ -68,6 +68,8 @@ std::string Operator::getOperationName() const {
   return std::string(llvm::formatv("{0}.{1}", prefix, opName));
 }
 
+bool Operator::isPrivate() const { return dialect.isPrivate(); }
+
 std::string Operator::getAdaptorName() const {
   return std::string(llvm::formatv("{0}Adaptor", getCppClassName()));
 }
diff --git a/mlir/lib/TableGen/Pass.cpp b/mlir/lib/TableGen/Pass.cpp
index 271332d1125e5..bb0b2250760e8 100644
--- a/mlir/lib/TableGen/Pass.cpp
+++ b/mlir/lib/TableGen/Pass.cpp
@@ -98,3 +98,5 @@ ArrayRef<StringRef> Pass::getDependentDialects() const {
 ArrayRef<PassOption> Pass::getOptions() const { return options; }
 
 ArrayRef<PassStatistic> Pass::getStatistics() const { return statistics; }
+
+bool Pass::isPrivate() const { return def->getValueAsBit("isPrivate"); }
diff --git a/mlir/lib/Tools/mlir-tblgen/MlirTblgenMain.cpp b/mlir/lib/Tools/mlir-tblgen/MlirTblgenMain.cpp
index 64e86f2a62073..a84571c4b2177 100644
--- a/mlir/lib/Tools/mlir-tblgen/MlirTblgenMain.cpp
+++ b/mlir/lib/Tools/mlir-tblgen/MlirTblgenMain.cpp
@@ -151,6 +151,8 @@ int mlir::MlirTblgenMain(int argc, char **argv) {
   llvm::cl::opt<const mlir::GenInfo *, true, mlir::GenNameParser> generator(
       "", llvm::cl::desc("Generator to run"), cl::location(::generator));
 
+  // Private-name obfuscation and pass-metadata stripping are configured via
+  // `cl::opt`s registered in `PrivateName.cpp`.
   cl::ParseCommandLineOptions(argc, argv);
 
   return TableGenMain(
diff --git a/mlir/test/mlir-tblgen/private-name-obfuscation.td b/mlir/test/mlir-tblgen/private-name-obfuscation.td
new file mode 100644
index 0000000000000..f98a4f4fee649
--- /dev/null
+++ b/mlir/test/mlir-tblgen/private-name-obfuscation.td
@@ -0,0 +1,175 @@
+// Verifies that --mlir-private-name-obfuscator replaces names of TableGen
+// items marked `let isPrivate = 1;` with deterministic opaque identifiers,
+// and leaves public items unchanged.
+//
+// RUN: mlir-tblgen -gen-dialect-decls -dialect=publicd -I %S/../../include %s \
+// RUN:   | FileCheck %s --check-prefix=PUBDD-OFF
+// RUN: mlir-tblgen -gen-dialect-decls -dialect=publicd -I %S/../../include %s \
+// RUN:     --mlir-private-name-obfuscator=md5sum \
+// RUN:   | FileCheck %s --check-prefix=PUBDD-ON
+// RUN: mlir-tblgen -gen-dialect-decls -dialect=privd -I %S/../../include %s \
+// RUN:   | FileCheck %s --check-prefix=PRIVDD-OFF
+// RUN: mlir-tblgen -gen-dialect-decls -dialect=privd -I %S/../../include %s \
+// RUN:     --mlir-private-name-obfuscator=md5sum \
+// RUN:   | FileCheck %s --check-prefix=PRIVDD-ON
+// RUN: mlir-tblgen -gen-op-decls -dialect=privd -I %S/../../include %s \
+// RUN:     --mlir-private-name-obfuscator=md5sum \
+// RUN:   | FileCheck %s --check-prefix=PRIVOP-ON
+// RUN: mlir-tblgen -gen-op-decls -dialect=publicd -I %S/../../include %s \
+// RUN:     --mlir-private-name-obfuscator=md5sum \
+// RUN:   | FileCheck %s --check-prefix=PUBOP-ON
+// RUN: mlir-tblgen -gen-op-decls -I %S/../../include %s \
+// RUN:     --mlir-private-name-obfuscator=md5sum \
+// RUN:   | FileCheck %s --check-prefix=PRIVFORMAT-ON
+// RUN: mlir-tblgen -gen-attrdef-decls -attrdefs-dialect=privd -I %S/../../include %s \
+// RUN:     --mlir-private-name-obfuscator=md5sum \
+// RUN:   | FileCheck %s --check-prefix=ATTR-PRIV-ON
+// RUN: mlir-tblgen -gen-attrdef-decls -attrdefs-dialect=publicd -I %S/../../include %s \
+// RUN:     --mlir-private-name-obfuscator=md5sum \
+// RUN:   | FileCheck %s --check-prefix=ATTR-PUB-ON
+// RUN: mlir-tblgen -gen-attrdef-defs -attrdefs-dialect=privd -I %S/../../include %s \
+// RUN:     --mlir-private-name-obfuscator=md5sum \
+// RUN:   | FileCheck %s --check-prefix=ATTR-PRIV-DEF-ON
+// RUN: mlir-tblgen -gen-typedef-decls -typedefs-dialect=privd -I %S/../../include %s \
+// RUN:     --mlir-private-name-obfuscator=md5sum \
+// RUN:   | FileCheck %s --check-prefix=TYPE-PRIV-ON
+// RUN: mlir-tblgen -gen-typedef-decls -typedefs-dialect=publicd -I %S/../../include %s \
+// RUN:     --mlir-private-name-obfuscator=md5sum \
+// RUN:   | FileCheck %s --check-prefix=TYPE-PUB-ON
+// RUN: mlir-tblgen -gen-typedef-defs -typedefs-dialect=privd -I %S/../../include %s \
+// RUN:     --mlir-private-name-obfuscator=md5sum \
+// RUN:   | FileCheck %s --check-prefix=TYPE-PRIV-DEF-ON
+//
+// Determinism: rerunning with the same obfuscator produces identical output.
+// RUN: mlir-tblgen -gen-dialect-decls -dialect=privd -I %S/../../include %s \
+// RUN:     --mlir-private-name-obfuscator=md5sum > %t.first
+// RUN: mlir-tblgen -gen-dialect-decls -dialect=privd -I %S/../../include %s \
+// RUN:     --mlir-private-name-obfuscator=md5sum > %t.second
+// RUN: cmp %t.first %t.second
+//
+// A different obfuscator produces different output.
+// RUN: mlir-tblgen -gen-dialect-decls -dialect=privd -I %S/../../include %s \
+// RUN:     --mlir-private-name-obfuscator=sha1sum > %t.alt
+// RUN: not cmp %t.first %t.alt
+
+include "mlir/IR/OpBase.td"
+include "mlir/IR/AttrTypeBase.td"
+
+def PublicDialect : Dialect {
+  let name = "publicd";
+  let cppNamespace = "::publicd";
+}
+
+def PrivateDialect : Dialect {
+  let name = "privd";
+  let cppNamespace = "::privd";
+  let isPrivate = 1;
+}
+
+def Public_AddOp : Op<PublicDialect, "add", []>;
+def Public_PublicFormatOp : Op<PublicDialect, "format", []> {
+  let assemblyFormat = "attr-dict";
+}
+def Private_MulOp : Op<PrivateDialect, "mul", []>;
+def Private_PrivateFormatOp : Op<PrivateDialect, "format", []> {
+  let assemblyFormat = "attr-dict";
+}
+
+def Public_PublicAttr : AttrDef<PublicDialect, "Public"> {
+  let mnemonic = "public_attr";
+  let genMnemonicAlias = 1;
+}
+def Private_PrivateAttr : AttrDef<PrivateDialect, "Private"> {
+  let mnemonic = "private_attr";
+  let genMnemonicAlias = 1;
+}
+
+def Public_PublicType : TypeDef<PublicDialect, "Public"> {
+  let mnemonic = "public_type";
+  let genMnemonicAlias = 1;
+}
+def Private_PrivateType : TypeDef<PrivateDialect, "Private"> {
+  let mnemonic = "private_type";
+  let genMnemonicAlias = 1;
+}
+
+// The obfuscated form of each private TableGen name in this file is the
+// md5 hash of the name, prefixed with `_`:
+//
+//   printf %s privd        | md5sum  -> 1e8d1fee65b16e634839dea9f85d3429
+//   printf %s mul          | md5sum  -> 353942263d1bedfbe06b7bfa78226253
+//   printf %s format       | md5sum  -> 1ddcb92ade31c8fbd370001f9b29a7d9
+//   printf %s private_attr | md5sum  -> d0f28b2f0516a6ff810258f8d1b6899d
+//   printf %s private_type | md5sum  -> ff03a49c6c57d69cd51f5420a768f9ef
+
+// Public dialect, obfuscation off: original namespace.
+// PUBDD-OFF: getDialectNamespace()
+// PUBDD-OFF: StringLiteral("publicd")
+
+// Public dialect, obfuscation on: still original namespace.
+// PUBDD-ON: getDialectNamespace()
+// PUBDD-ON: StringLiteral("publicd")
+
+// Private dialect, obfuscation off: original namespace.
+// PRIVDD-OFF: getDialectNamespace()
+// PRIVDD-OFF: StringLiteral("privd")
+
+// Private dialect, obfuscation on: md5(privd) with `_` prefix.
+// PRIVDD-ON: getDialectNamespace()
+// PRIVDD-ON: StringLiteral("_1e8d1fee65b16e634839dea9f85d3429")
+// PRIVDD-ON-NOT: StringLiteral("privd")
+
+// Op of private dialect, obfuscation on: both halves obfuscated. The first
+// private op encountered (alphabetically) is MulOp.
+// PRIVOP-ON: getOperationName()
+// PRIVOP-ON: StringLiteral("_1e8d1fee65b16e634839dea9f85d3429._353942263d1bedfbe06b7bfa78226253")
+// PRIVOP-ON-NOT: StringLiteral("privd.mul")
+// PRIVOP-ON-NOT: StringLiteral("privd.format")
+
+// Op of public dialect, obfuscation on: unchanged.
+// PUBOP-ON: getOperationName()
+// PUBOP-ON: StringLiteral("publicd.add")
+
+// Declarative assembly formats of private ops are not generated under private
+// obfuscation. They therefore fall back to generic printing and reject the
+// custom textual syntax.
+// PRIVFORMAT-ON-LABEL: class PrivateFormatOp : public
+// PRIVFORMAT-ON: _1e8d1fee65b16e634839dea9f85d3429._1ddcb92ade31c8fbd370001f9b29a7d9
+// PRIVFORMAT-ON-NOT: static ::mlir::ParseResult parse
+// PRIVFORMAT-ON-NOT: void print
+// PRIVFORMAT-ON-LABEL: class PublicFormatOp : public
+// PRIVFORMAT-ON: publicd.format
+// PRIVFORMAT-ON: static ::mlir::ParseResult parse
+// PRIVFORMAT-ON: void print
+
+// Attribute names and mnemonics of private dialects are obfuscated, while
+// public attributes remain unchanged.
+// ATTR-PRIV-ON-LABEL: class PrivateAttr : public
+// ATTR-PRIV-ON: static constexpr ::llvm::StringLiteral name = "_1e8d1fee65b16e634839dea9f85d3429._d0f28b2f0516a6ff810258f8d1b6899d";
+// ATTR-PRIV-ON: static constexpr ::llvm::StringLiteral dialectName = "_1e8d1fee65b16e634839dea9f85d3429";
+// ATTR-PRIV-ON: getMnemonic()
+// ATTR-PRIV-ON: return {"_d0f28b2f0516a6ff810258f8d1b6899d"};
+// ATTR-PRIV-ON-NOT: private_attr
+// ATTR-PRIV-DEF-ON: os << "_d0f28b2f0516a6ff810258f8d1b6899d";
+// ATTR-PRIV-DEF-ON-NOT: private_attr
+// ATTR-PUB-ON-LABEL: class PublicAttr : public
+// ATTR-PUB-ON: static constexpr ::llvm::StringLiteral name = "publicd.public_attr";
+// ATTR-PUB-ON: static constexpr ::llvm::StringLiteral dialectName = "publicd";
+// ATTR-PUB-ON: getMnemonic()
+// ATTR-PUB-ON: return {"public_attr"};
+
+// Type names and mnemonics of private dialects are obfuscated, while public
+// types remain unchanged.
+// TYPE-PRIV-ON-LABEL: class PrivateType : public
+// TYPE-PRIV-ON: static constexpr ::llvm::StringLiteral name = "_1e8d1fee65b16e634839dea9f85d3429._ff03a49c6c57d69cd51f5420a768f9ef";
+// TYPE-PRIV-ON: static constexpr ::llvm::StringLiteral dialectName = "_1e8d1fee65b16e634839dea9f85d3429";
+// TYPE-PRIV-ON: getMnemonic()
+// TYPE-PRIV-ON: return {"_ff03a49c6c57d69cd51f5420a768f9ef"};
+// TYPE-PRIV-ON-NOT: private_type
+// TYPE-PRIV-DEF-ON: os << "_ff03a49c6c57d69cd51f5420a768f9ef";
+// TYPE-PRIV-DEF-ON-NOT: private_type
+// TYPE-PUB-ON-LABEL: class PublicType : public
+// TYPE-PUB-ON: static constexpr ::llvm::StringLiteral name = "publicd.public_type";
+// TYPE-PUB-ON: static constexpr ::llvm::StringLiteral dialectName = "publicd";
+// TYPE-PUB-ON: getMnemonic()
+// TYPE-PUB-ON: return {"public_type"};
diff --git a/mlir/test/mlir-tblgen/private-pass-obfuscation.td b/mlir/test/mlir-tblgen/private-pass-obfuscation.td
new file mode 100644
index 0000000000000..169c89459807a
--- /dev/null
+++ b/mlir/test/mlir-tblgen/private-pass-obfuscation.td
@@ -0,0 +1,74 @@
+// Verifies that --mlir-private-name-obfuscator rewrites pass arguments and names
+// for passes marked `let isPrivate = 1;`, empties out their descriptions,
+// and keeps emitting registration / C-API helpers under the pass's C++ class
+// name.
+//
+// RUN: mlir-tblgen -gen-pass-decls -name=Test -I %S/../../include %s \
+// RUN:   | FileCheck %s --check-prefix=BASELINE
+// RUN: mlir-tblgen -gen-pass-decls -name=Test -I %S/../../include %s \
+// RUN:     --mlir-private-name-obfuscator=md5sum \
+// RUN:   | FileCheck %s --check-prefix=OBFUSCATED
+// RUN: mlir-tblgen -gen-pass-capi-header -prefix=Test -I %S/../../include %s \
+// RUN:     --mlir-private-name-obfuscator=md5sum \
+// RUN:   | FileCheck %s --check-prefix=CAPI
+
+include "mlir/Pass/PassBase.td"
+
+def PublicTestPass : Pass<"public-test-pass"> {
+  let summary = "Public pass summary";
+}
+
+def PrivateTestPass : Pass<"private-test-pass"> {
+  let summary = "Private pass summary";
+  let isPrivate = 1;
+  let options = [
+    Option<"opt", "private-opt", "int", "0", "Private option description.">
+  ];
+}
+
+// Per-pass blocks emerge in alphabetical order (Private then Public), and
+// registration helpers follow the same order.
+//
+// The obfuscated forms used below are the md5 hashes of the original names,
+// prefixed with `_`:
+//
+//   printf %s private-test-pass | md5sum  -> afa72118186dea351b01e8f88a1c2481
+//   printf %s PrivateTestPass   | md5sum  -> fde0780582f53ca7f57c4453a391ef18
+//
+// Baseline (no obfuscator): names and descriptions are emitted verbatim,
+// both passes are registered.
+// BASELINE: getArgument() const override { return "private-test-pass"; }
+// BASELINE: getDescription() const override { return R"PD(Private pass summary)PD"; }
+// BASELINE: getName() const override { return "PrivateTestPass"; }
+// BASELINE: ::llvm::cl::desc(R"PO(Private option description.)PO")
+// BASELINE: getArgument() const override { return "public-test-pass"; }
+// BASELINE: getDescription() const override { return R"PD(Public pass summary)PD"; }
+// BASELINE: getName() const override { return "PublicTestPass"; }
+// BASELINE: inline void registerPrivateTestPass()
+// BASELINE: inline void registerPublicTestPass()
+
+// Obfuscated: public pass unchanged. Private pass's argument and name are
+// hashed; its description, per-option `cl::desc`, and (per the BASELINE list)
+// summary text are emitted as empty strings. The C++ class name -- and the
+// per-pass registration helper that uses it -- is unchanged.
+// OBFUSCATED: getArgument() const override { return "_afa72118186dea351b01e8f88a1c2481"; }
+// OBFUSCATED: getDescription() const override { return R"PD()PD"; }
+// OBFUSCATED: getName() const override { return "_fde0780582f53ca7f57c4453a391ef18"; }
+// OBFUSCATED: ::llvm::cl::desc(R"PO()PO")
+// OBFUSCATED: getArgument() const override { return "public-test-pass"; }
+// OBFUSCATED: getDescription() const override { return R"PD(Public pass summary)PD"; }
+// OBFUSCATED: getName() const override { return "PublicTestPass"; }
+// OBFUSCATED-NOT: Private pass summary
+// OBFUSCATED-NOT: Private option description.
+// OBFUSCATED-NOT: return "private-test-pass"
+// OBFUSCATED-NOT: return "PrivateTestPass"
+// OBFUSCATED: inline void registerPrivateTestPass()
+// OBFUSCATED: inline void registerPublicTestPass()
+
+// C-API entry points are emitted for both public and private passes; the
+// C symbol uses the C++ class name (unaffected by obfuscation) so downstream
+// linkage is stable across builds with different obfuscators.
+// CAPI: mlirCreateTestPrivateTestPass
+// CAPI: mlirRegisterTestPrivateTestPass
+// CAPI: mlirCreateTestPublicTestPass
+// CAPI: mlirRegisterTestPublicTestPass
diff --git a/mlir/tools/mlir-tblgen/AttrOrTypeDefGen.cpp b/mlir/tools/mlir-tblgen/AttrOrTypeDefGen.cpp
index 64f35e7fef6d3..a30aadc4aee38 100644
--- a/mlir/tools/mlir-tblgen/AttrOrTypeDefGen.cpp
+++ b/mlir/tools/mlir-tblgen/AttrOrTypeDefGen.cpp
@@ -13,6 +13,7 @@
 #include "mlir/TableGen/Format.h"
 #include "mlir/TableGen/GenInfo.h"
 #include "mlir/TableGen/Interfaces.h"
+#include "mlir/TableGen/PrivateName.h"
 #include "llvm/ADT/SmallVectorExtras.h"
 #include "llvm/ADT/StringSet.h"
 #include "llvm/Support/CommandLine.h"
@@ -328,15 +329,20 @@ void DefGen::emitName() {
     auto *typeDef = cast<TypeDef>(&def);
     name = typeDef->getTypeName();
   }
+  // The full name is "dialect.mnemonic"; obfuscate dialect and mnemonic
+  // halves independently so that runtime parsing of the dialect prefix still
+  // works.
   std::string nameDecl =
-      strfmt("static constexpr ::llvm::StringLiteral name = \"{0}\";\n", name);
+      strfmt("static constexpr ::llvm::StringLiteral name = \"{0}\";\n",
+             tblgen::maybeObfuscateDotted(name, def.isPrivate()));
   defCls.declare<ExtraClassDeclaration>(std::move(nameDecl));
 }
 
 void DefGen::emitDialectName() {
   std::string decl =
       strfmt("static constexpr ::llvm::StringLiteral dialectName = \"{0}\";\n",
-             def.getDialect().getName());
+             tblgen::maybeObfuscate(def.getDialect().getName(),
+                                    def.getDialect().isPrivate()));
   defCls.declare<ExtraClassDeclaration>(std::move(decl));
 }
 
@@ -448,7 +454,9 @@ void DefGen::emitInvariantsVerifier(bool hasImpl, bool hasCustomVerifier) {
 void DefGen::emitParserPrinter() {
   auto *mnemonic = defCls.addStaticMethod<Method::Constexpr>(
       "::llvm::StringLiteral", "getMnemonic");
-  mnemonic->body().indent() << strfmt("return {\"{0}\"};", *def.getMnemonic());
+  mnemonic->body().indent()
+      << strfmt("return {\"{0}\"};",
+                tblgen::maybeObfuscate(*def.getMnemonic(), def.isPrivate()));
 
   // Declare the parser and printer, if needed.
   bool hasAssemblyFormat = def.getAssemblyFormat().has_value();
@@ -705,7 +713,9 @@ void DefGen::emitMnemonicAliasMethod() {
   SmallVector<MethodParameter> params{{"::llvm::raw_ostream &", "os"}};
   Method *m = defCls.addMethod<Method::Const>("::mlir::OpAsmAliasResult",
                                               "getAlias", std::move(params));
-  m->body().indent() << strfmt("os << \"{0}\";\n", *def.getMnemonic())
+  m->body().indent() << strfmt("os << \"{0}\";\n",
+                               tblgen::maybeObfuscate(*def.getMnemonic(),
+                                                      def.isPrivate()))
                      << "return ::mlir::OpAsmAliasResult::OverridableAlias;\n";
 }
 
diff --git a/mlir/tools/mlir-tblgen/CMakeLists.txt b/mlir/tools/mlir-tblgen/CMakeLists.txt
index d7087cba3c874..7d37b3554f137 100644
--- a/mlir/tools/mlir-tblgen/CMakeLists.txt
+++ b/mlir/tools/mlir-tblgen/CMakeLists.txt
@@ -31,6 +31,7 @@ add_tablegen(mlir-tblgen MLIR
   PassCAPIGen.cpp
   PassDocGen.cpp
   PassGen.cpp
+  PrivateName.cpp
   RewriterGen.cpp
   SPIRVUtilsGen.cpp
   TosaUtilsGen.cpp
diff --git a/mlir/tools/mlir-tblgen/DialectGen.cpp b/mlir/tools/mlir-tblgen/DialectGen.cpp
index 8eecad39f49f3..dd90efc731aeb 100644
--- a/mlir/tools/mlir-tblgen/DialectGen.cpp
+++ b/mlir/tools/mlir-tblgen/DialectGen.cpp
@@ -18,6 +18,7 @@
 #include "mlir/TableGen/GenInfo.h"
 #include "mlir/TableGen/Interfaces.h"
 #include "mlir/TableGen/Operator.h"
+#include "mlir/TableGen/PrivateName.h"
 #include "mlir/TableGen/Trait.h"
 #include "llvm/ADT/Sequence.h"
 #include "llvm/ADT/StringExtras.h"
@@ -250,7 +251,9 @@ static void emitDialectDecl(Dialect &dialect, raw_ostream &os) {
     tblgen::emitSummaryAndDescComments(os, dialect.getSummary(),
                                        dialect.getDescription(),
                                        /*terminateCmment=*/false);
-    os << llvm::formatv(dialectDeclBeginStr, cppName, dialect.getName(),
+    StringRef emittedDialectName =
+        maybeObfuscate(dialect.getName(), dialect.isPrivate());
+    os << llvm::formatv(dialectDeclBeginStr, cppName, emittedDialectName,
                         superClassName);
 
     // If the dialect requested the default attribute printer and parser, emit
@@ -289,7 +292,7 @@ static void emitDialectDecl(Dialect &dialect, raw_ostream &os) {
           attrPair.first, /*capitalizeFirst=*/false);
       os << llvm::formatv(discardableAttrHelperDecl, camelNameUpper,
                           attrPair.first, attrPair.second, camelName,
-                          dialect.getName());
+                          emittedDialectName);
     }
 
     if (std::optional<StringRef> extraDecl = dialect.getExtraClassDeclaration())
diff --git a/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp b/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp
index 2cb47d084ce69..5672991e0df97 100644
--- a/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp
+++ b/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp
@@ -24,6 +24,7 @@
 #include "mlir/TableGen/GenInfo.h"
 #include "mlir/TableGen/Interfaces.h"
 #include "mlir/TableGen/Operator.h"
+#include "mlir/TableGen/PrivateName.h"
 #include "mlir/TableGen/Property.h"
 #include "mlir/TableGen/Region.h"
 #include "mlir/TableGen/SideEffects.h"
@@ -273,6 +274,23 @@ static std::string getArgumentName(const Operator &op, int index) {
   return std::string(formatv("{0}_{1}", generatedArgName, index));
 }
 
+/// Returns true if a private op should behave as if it did not specify any
+/// custom or declarative assembly format. The op remains registered and can
+/// still be printed in generic form, but its custom textual syntax is not
+/// accepted in private-name obfuscation builds.
+static bool shouldStripPrivateAssemblyFormat(const Operator &op) {
+  return op.isPrivate() && tblgen::obfuscatePrivateNamesEnabled();
+}
+
+/// Returns a diagnostic label for generated verifier text. For private ops in
+/// obfuscation builds, avoid emitting user-authored operand/result/region/etc.
+/// names into release binaries.
+static StringRef getEmittedDiagnosticName(const Operator &op, StringRef name) {
+  if (op.isPrivate() && tblgen::obfuscatePrivateNamesEnabled())
+    return "";
+  return name;
+}
+
 // Returns true if we can use unwrapped value for the given `attr` in builders.
 static bool canUseUnwrappedRawValue(const tblgen::Attribute &attr) {
   return attr.getReturnType() != attr.getStorageType() &&
@@ -381,7 +399,9 @@ class OpOrAdaptorHelper {
     return [this](raw_ostream &os) -> raw_ostream & {
       if (emitForOp)
         return os << "emitOpError(\"";
-      return os << formatv("emitError(loc, \"'{0}' op ", op.getOperationName());
+      return os << formatv("emitError(loc, \"'{0}' op ",
+                           tblgen::maybeObfuscateDotted(op.getOperationName(),
+                                                        op.isPrivate()));
     };
   }
 
@@ -1168,7 +1188,8 @@ OpEmitter::OpEmitter(const Operator &op,
   genFolderDecls();
   genTypeInterfaceMethods();
   genOpInterfaceMethods();
-  generateOpFormat(op, opClass, emitHelper.hasProperties());
+  if (!shouldStripPrivateAssemblyFormat(op))
+    generateOpFormat(op, opClass, emitHelper.hasProperties());
   genSideEffectInterfaceMethods();
 }
 void OpEmitter::emitDecl(
@@ -1210,6 +1231,7 @@ void OpEmitter::genAttrNameGetters() {
       emitHelper.getAttrMetadata();
   bool hasOperandSegmentsSize =
       op.getTrait("::mlir::OpTrait::AttrSizedOperandSegments");
+
   // Emit the getAttributeNames method.
   {
     auto *method = opClass.addStaticInlineMethod(
@@ -3835,6 +3857,9 @@ void OpEmitter::genTypeInterfaceMethods() {
 }
 
 void OpEmitter::genParser() {
+  if (shouldStripPrivateAssemblyFormat(op))
+    return;
+
   if (hasStringAttribute(def, "assemblyFormat"))
     return;
 
@@ -3851,6 +3876,9 @@ void OpEmitter::genParser() {
 }
 
 void OpEmitter::genPrinter() {
+  if (shouldStripPrivateAssemblyFormat(op))
+    return;
+
   if (hasStringAttribute(def, "assemblyFormat"))
     return;
 
@@ -3978,8 +4006,8 @@ void OpEmitter::genOperandResultVerifier(MethodBody &body,
           "    if (::mlir::failed(::mlir::OpTrait::impl::verifyValueSizeAttr("
           "*this, \"{0}\", \"{1}\", valueGroup{2}.size())))\n"
           "      return ::mlir::failure();\n",
-          value.constraint.getVariadicOfVariadicSegmentSizeAttr(), value.name,
-          staticValue.index());
+          value.constraint.getVariadicOfVariadicSegmentSizeAttr(),
+          getEmittedDiagnosticName(op, value.name), staticValue.index());
     }
 
     // Otherwise, if there is no predicate there is nothing left to do.
@@ -4031,7 +4059,8 @@ void OpEmitter::genRegionVerifier(MethodBody &body) {
                          : formatv(getSingleRegion, it.index()).str();
     auto constraintFn =
         staticVerifierEmitter.getRegionConstraintFn(region.constraint);
-    body << formatv(verifyRegion, getRegion, constraintFn, region.name);
+    body << formatv(verifyRegion, getRegion, constraintFn,
+                    getEmittedDiagnosticName(op, region.name));
   }
   body << "  }\n";
 }
@@ -4069,7 +4098,7 @@ void OpEmitter::genSuccessorVerifier(MethodBody &body) {
     auto constraintFn =
         staticVerifierEmitter.getSuccessorConstraintFn(successor.constraint);
     body << formatv(verifySuccessor, getSuccessor, constraintFn,
-                    successor.name);
+                    getEmittedDiagnosticName(op, successor.name));
   }
   body << "  }\n";
 }
@@ -4160,7 +4189,9 @@ void OpEmitter::genOpNameGetter() {
   auto *method = opClass.addStaticMethod<Method::Constexpr>(
       "::llvm::StringLiteral", "getOperationName");
   ERROR_IF_PRUNED(method, "getOperationName", op);
-  method->body() << "  return ::llvm::StringLiteral(\"" << op.getOperationName()
+  method->body() << "  return ::llvm::StringLiteral(\""
+                 << tblgen::maybeObfuscateDotted(op.getOperationName(),
+                                                 op.isPrivate())
                  << "\");";
 }
 
@@ -4427,7 +4458,7 @@ OpOperandAdaptorEmitter::OpOperandAdaptorEmitter(
     body.indent() << "if (odsAttrs)\n";
     body.indent() << formatv(
         "odsOpName.emplace(\"{0}\", odsAttrs.getContext());\n",
-        op.getOperationName());
+        tblgen::maybeObfuscateDotted(op.getOperationName(), op.isPrivate()));
 
     paramList.insert(paramList.begin(), MethodParameter("RangeT", "values"));
     auto *constructor = genericAdaptor.addConstructor(paramList);
diff --git a/mlir/tools/mlir-tblgen/PassGen.cpp b/mlir/tools/mlir-tblgen/PassGen.cpp
index e4ae78f022405..69cc988657d94 100644
--- a/mlir/tools/mlir-tblgen/PassGen.cpp
+++ b/mlir/tools/mlir-tblgen/PassGen.cpp
@@ -13,6 +13,7 @@
 
 #include "mlir/TableGen/GenInfo.h"
 #include "mlir/TableGen/Pass.h"
+#include "mlir/TableGen/PrivateName.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Support/FormatVariadic.h"
@@ -186,11 +187,14 @@ static void emitRegistrations(llvm::ArrayRef<Pass> passes, raw_ostream &os) {
 
 /// The code snippet used to generate the start of a pass base class.
 ///
-/// {0}: The def name of the pass record.
+/// {0}: The def name of the pass record (used as the C++ class identifier).
 /// {1}: The base class for the pass.
-/// {2): The command line argument for the pass.
-/// {3}: The summary for the pass.
+/// {2}: The command line argument for the pass (possibly obfuscated).
+/// {3}: The summary for the pass (possibly emptied for private passes).
 /// {4}: The dependent dialects registration.
+/// {5}: The display name returned by `getName()` / `getPassName()` (possibly
+///      obfuscated). Distinct from {0} so the C++ class identifier remains
+///      stable when the displayed name is obfuscated.
 const char *const baseClassBegin = R"(
 template <typename DerivedT>
 class {0}Base : public {1} {
@@ -214,9 +218,9 @@ class {0}Base : public {1} {
 
   /// Returns the derived pass name.
   static constexpr ::llvm::StringLiteral getPassName() {
-    return ::llvm::StringLiteral("{0}");
+    return ::llvm::StringLiteral("{5}");
   }
-  ::llvm::StringRef getName() const override { return "{0}"; }
+  ::llvm::StringRef getName() const override { return "{5}"; }
 
   /// Support isa/dyn_cast functionality for the derived pass class.
   static bool classof(const ::mlir::Pass *pass) {{
@@ -282,13 +286,16 @@ std::unique_ptr<::mlir::Pass> create{0}({0}Options options) {{
 
 /// Emit the declarations for each of the pass options.
 static void emitPassOptionDecls(const Pass &pass, raw_ostream &os) {
+  bool stripDescriptions =
+      pass.isPrivate() && tblgen::obfuscatePrivateNamesEnabled();
   for (const PassOption &opt : pass.getOptions()) {
     os.indent(2) << "::mlir::Pass::"
                  << (opt.isListOption() ? "ListOption" : "Option");
 
+    StringRef desc = stripDescriptions ? StringRef("") : opt.getDescription();
     os << formatv(R"(<{0}> {1}{{*this, "{2}", ::llvm::cl::desc(R"PO({3})PO"))",
                   opt.getType(), opt.getCppVariableName(), opt.getArgument(),
-                  opt.getDescription().trim());
+                  desc.trim());
     if (std::optional<StringRef> defaultVal = opt.getDefaultValue())
       os << ", ::llvm::cl::init(" << defaultVal << ")";
     if (std::optional<StringRef> additionalFlags = opt.getAdditionalFlags())
@@ -299,11 +306,13 @@ static void emitPassOptionDecls(const Pass &pass, raw_ostream &os) {
 
 /// Emit the declarations for each of the pass statistics.
 static void emitPassStatisticDecls(const Pass &pass, raw_ostream &os) {
+  bool stripDescriptions =
+      pass.isPrivate() && tblgen::obfuscatePrivateNamesEnabled();
   for (const PassStatistic &stat : pass.getStatistics()) {
+    StringRef desc = stripDescriptions ? StringRef("") : stat.getDescription();
     os << formatv(
         "  ::mlir::Pass::Statistic {0}{{this, \"{1}\", R\"PS({2})PS\"};\n",
-        stat.getCppVariableName(), stat.getName(),
-        stat.getDescription().trim());
+        stat.getCppVariableName(), stat.getName(), desc.trim());
   }
 }
 
@@ -334,10 +343,25 @@ static void emitPassDefs(const Pass &pass, raw_ostream &os) {
         "\n    ");
   }
 
+  // Privacy-aware substitutions for the base class template. When
+  // `--mlir-private-name-obfuscator=<cmd>` is set and the pass is marked
+  // `isPrivate`, the display name returned by `getName()` / `getPassName()`
+  // and the CLI argument returned by `getArgument()` are obfuscated, and
+  // the summary returned by `getDescription()` is emitted as the empty
+  // string. The per-pass C++ class name and the generated
+  // `register*Pass()` / `mlirCreate*` / `mlirRegister*` helpers are
+  // unaffected.
+  std::string displayName =
+      tblgen::maybeObfuscate(passName, pass.isPrivate()).str();
+  std::string argument =
+      tblgen::maybeObfuscate(pass.getArgument(), pass.isPrivate()).str();
+  std::string summary = pass.getSummary().trim().str();
+  if (pass.isPrivate() && tblgen::obfuscatePrivateNamesEnabled())
+    summary.clear();
+
   os << "namespace impl {\n";
-  os << formatv(baseClassBegin, passName, pass.getBaseClass(),
-                pass.getArgument(), pass.getSummary().trim(),
-                dependentDialectRegistrations);
+  os << formatv(baseClassBegin, passName, pass.getBaseClass(), argument,
+                summary, dependentDialectRegistrations, displayName);
 
   if (ArrayRef<PassOption> options = pass.getOptions(); !options.empty()) {
     os.indent(2) << formatv("{0}Base({0}Options options) : {0}Base() {{\n",
diff --git a/mlir/tools/mlir-tblgen/PrivateName.cpp b/mlir/tools/mlir-tblgen/PrivateName.cpp
new file mode 100644
index 0000000000000..add91ff3885ad
--- /dev/null
+++ b/mlir/tools/mlir-tblgen/PrivateName.cpp
@@ -0,0 +1,139 @@
+//===- PrivateName.cpp - Private name obfuscation for mlir-tblgen ---------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Plumbing for the `--mlir-private-name-obfuscator` mlir-tblgen flag. This
+// is opt-in release-engineering tooling for downstream MLIR builds; the
+// rest of the mlir-tblgen code generator does not need to know about it.
+//
+// When the user passes `--mlir-private-name-obfuscator=<cmd>`, mlir-tblgen
+// pipes each name marked `isPrivate` into `<cmd>` via stdin, reads the
+// first whitespace-delimited token of stdout, and uses that token (with a
+// leading `_`) as the obfuscated form of the name. Results are cached for
+// the lifetime of the process.
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/TableGen/PrivateName.h"
+
+#include "llvm/ADT/StringMap.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/ErrorHandling.h"
+
+#include <cstdio>
+#include <string>
+
+#ifdef _WIN32
+#define MLIR_TBLGEN_POPEN _popen
+#define MLIR_TBLGEN_PCLOSE _pclose
+#else
+#define MLIR_TBLGEN_POPEN popen
+#define MLIR_TBLGEN_PCLOSE pclose
+#endif
+
+using namespace mlir;
+using namespace llvm;
+
+namespace {
+llvm::cl::opt<std::string> privateNameObfuscator(
+    "mlir-private-name-obfuscator",
+    llvm::cl::desc(
+        "Shell command used to obfuscate the dialect namespace, op mnemonic, "
+        "AttrDef/TypeDef mnemonic, and pass argument/name of TableGen items "
+        "marked `isPrivate`. Pass descriptions are also emitted as empty "
+        "strings for private passes when this flag is set. Op argument "
+        "attribute keys are not obfuscated. For each name, mlir-tblgen runs "
+        "`printf %s <name> | <cmd>` and uses the first whitespace-delimited "
+        "token of stdout (prefixed with `_`) as the obfuscated form. An "
+        "empty value disables obfuscation."),
+    llvm::cl::init(""));
+
+/// Process-wide cache of `name -> obfuscated(name)`.
+llvm::StringMap<std::string> &obfuscationCache() {
+  static llvm::StringMap<std::string> cache;
+  return cache;
+}
+
+/// Appends `s` to `out`, single-quoted with embedded single quotes escaped
+/// using the POSIX `'\''` idiom.
+void appendShellSingleQuoted(std::string &out, StringRef s) {
+  out.push_back('\'');
+  for (char c : s) {
+    if (c == '\'')
+      out.append("'\\''");
+    else
+      out.push_back(c);
+  }
+  out.push_back('\'');
+}
+
+/// Invokes `printf '%s' <name> | <obfuscator>` and returns `_` + first
+/// whitespace-delimited token of stdout.
+std::string runPrivateNameObfuscator(StringRef name) {
+  std::string cmd = "printf '%s' ";
+  appendShellSingleQuoted(cmd, name);
+  cmd.append(" | ");
+  cmd.append(privateNameObfuscator);
+
+  FILE *f = MLIR_TBLGEN_POPEN(cmd.c_str(), "r");
+  if (!f)
+    llvm::report_fatal_error(
+        "--mlir-private-name-obfuscator: failed to spawn obfuscator command");
+
+  std::string out;
+  char buf[256];
+  while (size_t n = std::fread(buf, 1, sizeof(buf), f))
+    out.append(buf, n);
+  int rc = MLIR_TBLGEN_PCLOSE(f);
+  if (rc != 0)
+    llvm::report_fatal_error("--mlir-private-name-obfuscator: obfuscator "
+                             "command exited with non-zero status");
+
+  StringRef rest = StringRef(out).ltrim();
+  size_t end = rest.find_first_of(" \t\r\n");
+  StringRef token = (end == StringRef::npos) ? rest : rest.substr(0, end);
+  if (token.empty())
+    llvm::report_fatal_error("--mlir-private-name-obfuscator: obfuscator "
+                             "command produced empty output");
+
+  std::string result;
+  result.reserve(1 + token.size());
+  result.push_back('_');
+  result.append(token.data(), token.size());
+  return result;
+}
+} // namespace
+
+bool mlir::tblgen::obfuscatePrivateNamesEnabled() {
+  return !privateNameObfuscator.empty();
+}
+
+StringRef mlir::tblgen::obfuscatePrivateName(StringRef name) {
+  if (name.empty())
+    return name;
+  auto &cache = obfuscationCache();
+  if (auto it = cache.find(name); it != cache.end())
+    return it->second;
+  std::string obf = runPrivateNameObfuscator(name);
+  auto inserted = cache.try_emplace(name, std::move(obf));
+  return inserted.first->second;
+}
+
+std::string mlir::tblgen::maybeObfuscateDotted(StringRef name, bool isPrivate) {
+  if (!isPrivate || !obfuscatePrivateNamesEnabled())
+    return std::string(name);
+  size_t dot = name.find('.');
+  if (dot == StringRef::npos)
+    return obfuscatePrivateName(name).str();
+  StringRef dialect = name.substr(0, dot);
+  StringRef rest = name.substr(dot + 1);
+  std::string result = obfuscatePrivateName(dialect).str();
+  result.push_back('.');
+  result += obfuscatePrivateName(rest).str();
+  return result;
+}
diff --git a/mlir/tools/mlir-tblgen/RewriterGen.cpp b/mlir/tools/mlir-tblgen/RewriterGen.cpp
index e3043708a46d1..f63c0257ec17e 100644
--- a/mlir/tools/mlir-tblgen/RewriterGen.cpp
+++ b/mlir/tools/mlir-tblgen/RewriterGen.cpp
@@ -19,6 +19,7 @@
 #include "mlir/TableGen/Operator.h"
 #include "mlir/TableGen/Pattern.h"
 #include "mlir/TableGen/Predicate.h"
+#include "mlir/TableGen/PrivateName.h"
 #include "mlir/TableGen/Property.h"
 #include "mlir/TableGen/Type.h"
 #include "llvm/ADT/FunctionExtras.h"
@@ -727,7 +728,9 @@ void PatternEmitter::emitOperandMatch(DagNode tree, StringRef opName,
           verifier, opName, self.str(),
           formatv(
               "\"operand {0} of op '{1}' failed to satisfy constraint: '{2}'\"",
-              operandIndex, op.getOperationName(),
+              operandIndex,
+              tblgen::maybeObfuscateDotted(op.getOperationName(),
+                                           op.isPrivate()),
               escapeString(constraint.getSummary()))
               .str());
     }
@@ -911,8 +914,9 @@ void PatternEmitter::emitAttributeMatch(DagNode tree, StringRef castedName,
     emitMatchCheck(castedName, tgfmt("tblgen_attr", &fmtCtx),
                    formatv("\"expected op '{0}' to have attribute '{1}' "
                            "of type '{2}'\"",
-                           op.getOperationName(), namedAttr->name,
-                           attr.getStorageType()));
+                           tblgen::maybeObfuscateDotted(op.getOperationName(),
+                                                        op.isPrivate()),
+                           namedAttr->name, attr.getStorageType()));
   }
 
   auto matcher = tree.getArgAsLeaf(argIndex);
@@ -940,10 +944,12 @@ void PatternEmitter::emitAttributeMatch(DagNode tree, StringRef castedName,
     }
     emitStaticVerifierCall(
         verifier, castedName, "tblgen_attr",
-        formatv("\"op '{0}' attribute '{1}' failed to satisfy constraint: "
-                "'{2}'\"",
-                op.getOperationName(), namedAttr->name,
-                escapeString(matcher.getAsConstraint().getSummary()))
+        formatv(
+            "\"op '{0}' attribute '{1}' failed to satisfy constraint: "
+            "'{2}'\"",
+            tblgen::maybeObfuscateDotted(op.getOperationName(), op.isPrivate()),
+            namedAttr->name,
+            escapeString(matcher.getAsConstraint().getSummary()))
             .str());
   }
 
@@ -980,10 +986,12 @@ void PatternEmitter::emitPropertyMatch(DagNode tree, StringRef castedName,
     StringRef verifier = staticMatcherHelper.getVerifierName(matcher);
     emitStaticVerifierCall(
         verifier, castedName, "tblgen_prop",
-        formatv("\"op '{0}' property '{1}' failed to satisfy constraint: "
-                "'{2}'\"",
-                op.getOperationName(), namedProp->name,
-                escapeString(matcher.getAsConstraint().getSummary()))
+        formatv(
+            "\"op '{0}' property '{1}' failed to satisfy constraint: "
+            "'{2}'\"",
+            tblgen::maybeObfuscateDotted(op.getOperationName(), op.isPrivate()),
+            namedProp->name,
+            escapeString(matcher.getAsConstraint().getSummary()))
             .str());
   }
 



More information about the Mlir-commits mailing list