[Mlir-commits] [mlir] [mlir][bytecode] Fix clang 21/22 crash in getChecked<T> for types with only inherited getChecked (PR #189172)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Sat Mar 28 08:46:37 PDT 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-mlir-core
Author: Mehdi Amini (joker-eph)
<details>
<summary>Changes</summary>
## Summary
The generic `getChecked<T>` helper in `BytecodeImplementation.h` uses `llvm::is_detected` to probe whether `T::getChecked(emitError, params...)` is callable without a `MLIRContext*` argument. For types that only inherit the base `StorageUserBase::getChecked(emitError, MLIRContext*, Args...)` template (e.g. `ArrayAttr`, `FusedLoc`), this SFINAE probe triggers a clang 21/22 internal assertion (`isInitialized() && "querying uninitialized conversion"`) during overload resolution.
Fix the root cause by ensuring `getChecked<T>` is only called for types that have an explicit, non-template no-context `getChecked(emitError, params...)` overload:
- Change the default `cBuilder` in `BytecodeBase.td` from `getChecked<T>` to `get<T>` for both `DialectAttribute` and `DialectType`. Types that need graceful failure (verifyInvariants returning null instead of asserting) override `cBuilder` explicitly.
- In `BuiltinDialectBytecode.td`: switch `FusedLoc` and `DenseResourceElementsAttr` from `getChecked<>` to `get<>` (these types have no explicit no-context getChecked); add explicit `getChecked<>` builders for `ComplexType`, `MemRefType`, `RankedTensorType`, and `UnrankedTensorType` (which do have explicit no-context overloads and can fail gracefully on invalid element types).
- In `BytecodeDialectGen.cpp`: generate `get<T>(context)` instead of `getChecked<T>(emitError, context)` for the zero-argument case.
- Add a note to `BytecodeImplementation.h` documenting the clang 21/22 limitation for future callers.
- Add a test for `ComplexType` with unsupported element type to `invalid-type-remapping.mlir`.
Fixes #<!-- -->188249
## Test plan
- [ ] Existing bytecode tests pass: `mlir/test/Bytecode/`
- [ ] New test in `mlir/test/Bytecode/invalid/invalid-type-remapping.mlir` covers `ComplexType` with unsupported element type
- [ ] Verify generated `.inc` file uses `get<T>` for types with only inherited getChecked and `getChecked<T>` for types with explicit no-context overloads
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---
Full diff: https://github.com/llvm/llvm-project/pull/189172.diff
8 Files Affected:
- (modified) mlir/include/mlir/Bytecode/BytecodeImplementation.h (+8)
- (modified) mlir/include/mlir/IR/BuiltinDialectBytecode.td (+18-4)
- (modified) mlir/include/mlir/IR/BytecodeBase.td (+14-2)
- (modified) mlir/lib/Dialect/SPIRV/Transforms/SPIRVConversion.cpp (+2-2)
- (modified) mlir/lib/Transforms/RemoveDeadValues.cpp (+11)
- (modified) mlir/test/Bytecode/invalid/invalid-type-remapping.mlir (+12)
- (modified) mlir/test/Transforms/remove-dead-values.mlir (+26)
- (modified) mlir/tools/mlir-tblgen/BytecodeDialectGen.cpp (+1-4)
``````````diff
diff --git a/mlir/include/mlir/Bytecode/BytecodeImplementation.h b/mlir/include/mlir/Bytecode/BytecodeImplementation.h
index fe85908e476ff..8e0a2e2efa7e2 100644
--- a/mlir/include/mlir/Bytecode/BytecodeImplementation.h
+++ b/mlir/include/mlir/Bytecode/BytecodeImplementation.h
@@ -543,6 +543,14 @@ using has_get_checked_method = decltype(T::getChecked(std::declval<Ts>()...));
/// the base `StorageUserBase::getChecked` template (e.g. ArrayAttr), that
/// template instantiation requires a complete storage type which may not be
/// available in the bytecode reading TU.
+///
+/// NOTE: callers must ensure T has an explicit no-context
+/// `getChecked(emitError, params...)` overload (i.e. the overload set does not
+/// consist solely of inherited StorageUserBase templates). Calling this helper
+/// for types that only inherit the base template triggers a compiler bug in
+/// clang 21/22 (assertion `isInitialized()` in ImplicitConversionSequence)
+/// during SFINAE evaluation. The generated bytecode read functions use
+/// `get<T>` for such types instead of this helper.
template <typename T, typename... Ts>
auto getChecked(function_ref<InFlightDiagnostic()> emitError,
MLIRContext *context, Ts &&...params) {
diff --git a/mlir/include/mlir/IR/BuiltinDialectBytecode.td b/mlir/include/mlir/IR/BuiltinDialectBytecode.td
index 8a1f3d5e5b2e0..d0b13a20a9040 100644
--- a/mlir/include/mlir/IR/BuiltinDialectBytecode.td
+++ b/mlir/include/mlir/IR/BuiltinDialectBytecode.td
@@ -117,7 +117,7 @@ def FileLineColLoc : DialectAttribute<(attr
}
let cType = "FusedLoc",
- cBuilder = "cast<FusedLoc>(getChecked<FusedLoc>([&]() { return reader.emitError(); }, context, $_args))" in {
+ cBuilder = "cast<FusedLoc>(get<FusedLoc>(context, $_args))" in {
def FusedLoc : DialectAttribute<(attr
Array<Location>:$locations
)> {
@@ -144,7 +144,7 @@ def DenseResourceElementsAttr : DialectAttribute<(attr
ResourceHandle<"DenseResourceElementsHandle">:$rawHandle
)> {
// Note: order of serialization does not match order of builder.
- let cBuilder = "getChecked<$_resultType>([&]() { return reader.emitError(); }, context, type, *rawHandle)";
+ let cBuilder = "get<$_resultType>(context, type, *rawHandle)";
}
let cType = "RankedTensorType" in {
@@ -153,6 +153,9 @@ def RankedTensorType : DialectType<(type
Type:$elementType
)> {
let printerPredicate = "!$_val.getEncoding()";
+ // Use getChecked to return null (and emit a diagnostic) instead of asserting
+ // when the element type is invalid.
+ let cBuilder = "getChecked<$_resultType>([&]() { return reader.emitError(); }, context, $_args)";
}
def RankedTensorTypeWithEncoding : DialectType<(type
@@ -237,7 +240,11 @@ def Float128Type : DialectType<(type)>;
def ComplexType : DialectType<(type
Type:$elementType
-)>;
+)> {
+ // Use getChecked to return null (and emit a diagnostic) instead of asserting
+ // when the element type is not a valid complex element type.
+ let cBuilder = "getChecked<$_resultType>([&]() { return reader.emitError(); }, context, $_args)";
+}
def MemRefLayout: WithType<"MemRefLayoutAttrInterface", Attribute>;
@@ -248,6 +255,9 @@ def MemRefType : DialectType<(type
MemRefLayout:$layout
)> {
let printerPredicate = "!$_val.getMemorySpace()";
+ // Use getChecked to return null (and emit a diagnostic) instead of asserting
+ // when element type or layout is invalid.
+ let cBuilder = "getChecked<$_resultType>([&]() { return reader.emitError(); }, context, $_args)";
}
def MemRefTypeWithMemSpace : DialectType<(type
@@ -288,7 +298,11 @@ def UnrankedMemRefTypeWithMemSpace : DialectType<(type
def UnrankedTensorType : DialectType<(type
Type:$elementType
-)>;
+)> {
+ // Use getChecked to return null (and emit a diagnostic) instead of asserting
+ // when the element type is invalid.
+ let cBuilder = "getChecked<$_resultType>([&]() { return reader.emitError(); }, context, $_args)";
+}
let cType = "VectorType" in {
def VectorType : DialectType<(type
diff --git a/mlir/include/mlir/IR/BytecodeBase.td b/mlir/include/mlir/IR/BytecodeBase.td
index 184c81e6a5f7d..2e31c97f11f91 100644
--- a/mlir/include/mlir/IR/BytecodeBase.td
+++ b/mlir/include/mlir/IR/BytecodeBase.td
@@ -147,11 +147,23 @@ class DialectAttrOrType<dag d> {
class DialectAttribute<dag d> : DialectAttrOrType<d>, AttributeKind {
let cParser = "succeeded($_reader.readAttribute<$_resultType>($_var))";
- let cBuilder = "getChecked<$_resultType>([&]() { return reader.emitError(); }, context, $_args)";
+ // Use get<> by default. Types with an explicit no-context getChecked
+ // (verifyInvariants that should return null instead of asserting) should
+ // override cBuilder to call T::getChecked or getChecked<T> explicitly.
+ // Avoid using the generic getChecked<T> helper for types that only inherit
+ // StorageUserBase::getChecked, because the SFINAE probe in that helper
+ // triggers a clang 21/22 compiler crash for such types.
+ let cBuilder = "get<$_resultType>(context, $_args)";
}
class DialectType<dag d> : DialectAttrOrType<d>, TypeKind {
let cParser = "succeeded($_reader.readType<$_resultType>($_var))";
- let cBuilder = "getChecked<$_resultType>([&]() { return reader.emitError(); }, context, $_args)";
+ // Use get<> by default. Types with an explicit no-context getChecked
+ // (verifyInvariants that should return null instead of asserting) should
+ // override cBuilder to call T::getChecked or getChecked<T> explicitly.
+ // Avoid using the generic getChecked<T> helper for types that only inherit
+ // StorageUserBase::getChecked, because the SFINAE probe in that helper
+ // triggers a clang 21/22 compiler crash for such types.
+ let cBuilder = "get<$_resultType>(context, $_args)";
}
class DialectAttributes<string d> {
diff --git a/mlir/lib/Dialect/SPIRV/Transforms/SPIRVConversion.cpp b/mlir/lib/Dialect/SPIRV/Transforms/SPIRVConversion.cpp
index 2c9e9c040d460..8769f0655759f 100644
--- a/mlir/lib/Dialect/SPIRV/Transforms/SPIRVConversion.cpp
+++ b/mlir/lib/Dialect/SPIRV/Transforms/SPIRVConversion.cpp
@@ -1320,8 +1320,8 @@ Value spirv::getPushConstantValue(Operation *op, unsigned elementCount,
loc, parent->getRegion(0).front(), elementCount, builder, integerType);
Value zeroOp = spirv::ConstantOp::getZero(integerType, loc, builder);
- Value offsetOp = spirv::ConstantOp::create(builder, loc, integerType,
- builder.getI32IntegerAttr(offset));
+ Value offsetOp = spirv::ConstantOp::create(
+ builder, loc, integerType, builder.getIntegerAttr(integerType, offset));
auto addrOp = spirv::AddressOfOp::create(builder, loc, varOp);
auto acOp = spirv::AccessChainOp::create(builder, loc, addrOp,
llvm::ArrayRef({zeroOp, offsetOp}));
diff --git a/mlir/lib/Transforms/RemoveDeadValues.cpp b/mlir/lib/Transforms/RemoveDeadValues.cpp
index f0a210a2ededb..3edb9af671150 100644
--- a/mlir/lib/Transforms/RemoveDeadValues.cpp
+++ b/mlir/lib/Transforms/RemoveDeadValues.cpp
@@ -511,6 +511,17 @@ static void processBranchOp(BranchOpInterface branchOp, RunLivenessAnalysis &la,
// Do (2)
BitVector successorNonLive =
markLives(operandValues, nonLiveSet, la).flip();
+ // A block argument should not be considered dead if the liveness analysis
+ // determines it is live. This can happen when this branch is in a
+ // statically unreachable (dead) block: the forwarded operand appears dead
+ // because it is in the dead block, but the successor block argument may
+ // still be live because it is also forwarded from other live predecessor
+ // branches.
+ for (auto [index, blockArg] :
+ llvm::enumerate(successorBlock->getArguments())) {
+ if (successorNonLive[index] && hasLive({blockArg}, nonLiveSet, la))
+ successorNonLive.reset(index);
+ }
collectNonLiveValues(nonLiveSet, successorBlock->getArguments(),
successorNonLive);
diff --git a/mlir/test/Bytecode/invalid/invalid-type-remapping.mlir b/mlir/test/Bytecode/invalid/invalid-type-remapping.mlir
index 44d0a4eb8bb4a..cfe6f79bc29d5 100644
--- a/mlir/test/Bytecode/invalid/invalid-type-remapping.mlir
+++ b/mlir/test/Bytecode/invalid/invalid-type-remapping.mlir
@@ -43,6 +43,18 @@ module {
// -----
+// CHECK: invalid element type for complex
+// CHECK: failed to read bytecode
+// ComplexType whose element type is replaced by one that is neither int nor
+// float — previously crashed with an assertion when using get<ComplexType>.
+module {
+ func.func @complex_unsupported_elem_type(%arg0: complex<i32>) {
+ return
+ }
+}
+
+// -----
+
// CHECK: DenseTypedElementsAttr element type must implement DenseElementTypeInterface, but got: '!test.i32'
// CHECK: failed to read bytecode
// DenseTypedElementsAttr whose element type is replaced by one that does not
diff --git a/mlir/test/Transforms/remove-dead-values.mlir b/mlir/test/Transforms/remove-dead-values.mlir
index 64088ce15cd48..d2f5c4ab95574 100644
--- a/mlir/test/Transforms/remove-dead-values.mlir
+++ b/mlir/test/Transforms/remove-dead-values.mlir
@@ -868,3 +868,29 @@ module @func_with_non_call_users {
}
spirv.EntryPoint "GLCompute" @callee
}
+
+// -----
+
+// Regression test: verify that the pass does not crash when a branch op
+// forwards a value through intermediate block arguments to a join block.
+// Previously, processBranchOp incorrectly used forwarded operand liveness
+// to determine successor block argument liveness, causing incorrect marking
+// of live values as dead. (https://github.com/llvm/llvm-project/issues/182263)
+//
+// CHECK-LABEL: func.func @branch_forwarded_block_arg_liveness
+// CHECK-CANONICALIZE-LABEL: func.func @branch_forwarded_block_arg_liveness
+func.func @branch_forwarded_block_arg_liveness(%x: i64) -> i64 {
+ %c1 = arith.constant 1 : i64
+ %c2 = arith.constant 2 : i64
+ %cmp = arith.cmpi slt, %c1, %c2 : i64
+ cf.cond_br %cmp, ^bb1(%c1 : i64), ^bb2(%c1 : i64)
+^bb1(%a: i64):
+ cf.br ^bb3(%a : i64)
+^bb2(%b: i64):
+ cf.br ^bb3(%b : i64)
+^bb3(%arg0: i64):
+ // CHECK: arith.addi
+ // CHECK-CANONICALIZE: arith.addi
+ %final = arith.addi %c1, %arg0 : i64
+ func.return %final : i64
+}
diff --git a/mlir/tools/mlir-tblgen/BytecodeDialectGen.cpp b/mlir/tools/mlir-tblgen/BytecodeDialectGen.cpp
index dd178b5e5d232..d8004a663aaee 100644
--- a/mlir/tools/mlir-tblgen/BytecodeDialectGen.cpp
+++ b/mlir/tools/mlir-tblgen/BytecodeDialectGen.cpp
@@ -206,10 +206,7 @@ void Generator::emitParseHelper(StringRef kind, StringRef returnType,
auto funScope = ios.scope("{\n", "}");
if (args.empty()) {
- ios << formatv(
- "return getChecked<{0}>([&]() {{ return reader.emitError(); }, "
- "context);\n",
- returnType);
+ ios << formatv("return get<{0}>(context);\n", returnType);
return;
}
``````````
</details>
https://github.com/llvm/llvm-project/pull/189172
More information about the Mlir-commits
mailing list