[Mlir-commits] [mlir] [MLIR][MLProgram] Fix crash in mlprogram-pipeline-globals on unresolvable callees (PR #189244)
Mehdi Amini
llvmlistbot at llvm.org
Tue Apr 14 09:04:52 PDT 2026
https://github.com/joker-eph updated https://github.com/llvm/llvm-project/pull/189244
>From 2f5711834c6509caad800849e08ddbb2cf66f863 Mon Sep 17 00:00:00 2001
From: Mehdi Amini <joker.eph at gmail.com>
Date: Sat, 28 Mar 2026 19:58:50 -0700
Subject: [PATCH] [MLIR][MLProgram] Fix crash in mlprogram-pipeline-globals on
unresolvable callees
The `MLProgramPipelineGlobals` pass crashed with a null pointer dereference
when a `CallOpInterface` operation referred to a callee symbol that could not
be resolved in the IR (e.g. an external function with no definition).
`getFromSymbol` returned nullptr in that case, which was stored in `callableMap`
and then unconditionally dereferenced via `->walk()`. Two fixes are applied:
1. In `buildGlobalMap`, interrupt the walk (and bail out) when a callee symbol
cannot be resolved, consistent with how Value-based callees are handled.
2. In the transitive-closure loop, use `find` instead of `operator[]` when
looking up entries in `callableMap` to avoid creating null entries for
symbols not present in the map.
Fixes #109649
Assisted-by: Claude Code
---
.../Transforms/PipelineGlobalOps.cpp | 14 +++++++++---
.../Dialect/MLProgram/pipeline-globals.mlir | 22 +++++++++++++++++++
2 files changed, 33 insertions(+), 3 deletions(-)
diff --git a/mlir/lib/Dialect/MLProgram/Transforms/PipelineGlobalOps.cpp b/mlir/lib/Dialect/MLProgram/Transforms/PipelineGlobalOps.cpp
index 54fa15722febe..db06d2beed94a 100644
--- a/mlir/lib/Dialect/MLProgram/Transforms/PipelineGlobalOps.cpp
+++ b/mlir/lib/Dialect/MLProgram/Transforms/PipelineGlobalOps.cpp
@@ -33,7 +33,7 @@ class MLProgramPipelineGlobals
llvm::DenseMap<SymbolRefAttr, llvm::DenseSet<SymbolRefAttr>> storeSymbolsMap;
};
-// Traverses upwards searchign for the operation mapped by the symbol.
+// Traverses upwards searching for the operation mapped by the symbol.
static Operation *getFromSymbol(Operation *baseOp, SymbolRefAttr symbol) {
for (auto *op = baseOp; op; op = op->getParentOp()) {
auto *lookup = SymbolTable::lookupNearestSymbolFrom(op, symbol);
@@ -57,6 +57,9 @@ LogicalResult MLProgramPipelineGlobals::buildGlobalMap(ModuleOp module) {
auto symbol = mlir::dyn_cast<SymbolRefAttr>(callable);
auto *func = getFromSymbol(op, symbol);
+ // If the callee cannot be resolved, we cannot safely analyze the IR.
+ if (!func)
+ return WalkResult::interrupt();
callableMap[symbol] = func;
}
return WalkResult::advance();
@@ -95,8 +98,13 @@ LogicalResult MLProgramPipelineGlobals::buildGlobalMap(ModuleOp module) {
llvm::DenseSet<SymbolRefAttr> storeSymbols;
for (size_t i = 0; i < work.size(); ++i) {
- callableMap[work[i]]->walk([&](CallOpInterface call) {
- auto symbol = dyn_cast<SymbolRefAttr>(call.getCallableForCallee());
+ // Defensive: symbols in `work` should always be in `callableMap` since
+ // buildGlobalMap interrupted on any unresolvable callee, but use find to
+ // avoid inserting null entries via operator[].
+ auto it = callableMap.find(work[i]);
+ assert(it != callableMap.end() && "Expected callable in callableMap");
+ it->second->walk([&](CallOpInterface call) {
+ auto symbol = cast<SymbolRefAttr>(call.getCallableForCallee());
if (visited.insert(symbol).second)
work.push_back(symbol);
});
diff --git a/mlir/test/Dialect/MLProgram/pipeline-globals.mlir b/mlir/test/Dialect/MLProgram/pipeline-globals.mlir
index a5c9b3e890558..a7b52efe08ca3 100644
--- a/mlir/test/Dialect/MLProgram/pipeline-globals.mlir
+++ b/mlir/test/Dialect/MLProgram/pipeline-globals.mlir
@@ -219,6 +219,28 @@ func.func @call_indirect_load() {
// -----
+// Calling via a CallOpInterface op whose callee symbol is not defined in this
+// module should not crash - the pass should bail out gracefully.
+// See https://github.com/llvm/llvm-project/issues/109649
+
+// CHECK-LABEL: @global_variable
+ml_program.global private mutable @global_variable(dense<4> : tensor<4xi32>) : tensor<4xi32>
+
+// CHECK-LABEL: @call_with_unresolvable_callee
+func.func @call_with_unresolvable_callee(%arg0: memref<f32>) {
+ // Both loads must be preserved; the pass conservatively bails out when it
+ // encounters a call whose callee symbol cannot be resolved.
+ // CHECK: ml_program.global_load @global_variable
+ %0 = ml_program.global_load @global_variable : tensor<4xi32>
+ // @callee is not defined anywhere in this module.
+ test.call_and_store @callee(%arg0), %arg0 {store_before_call = false} : (memref<f32>, memref<f32>) -> ()
+ // CHECK: ml_program.global_load @global_variable
+ %1 = ml_program.global_load @global_variable : tensor<4xi32>
+ func.return
+}
+
+// -----
+
// CHECK-LABEL: @global_variable
ml_program.global private mutable @global_variable(dense<4> : tensor<4xi32>) : tensor<4xi32>
More information about the Mlir-commits
mailing list