https://github.com/makslevental updated 
https://github.com/llvm/llvm-project/pull/208935

>From bc9d7cc86ad7bb55ee5042c8e3d604c51b3e3c67 Mon Sep 17 00:00:00 2001
From: makslevental <[email protected]>
Date: Tue, 30 Jun 2026 11:48:08 -0700
Subject: [PATCH 1/5] [mlir-c] Add 1:N TypeConverter conversion and
 materialization bindings

Builds on the source/target materialization C bindings:

- Target materialization callbacks now receive `originalType` (split from the
  previously-shared source/target callback typedef), exposing a documented C++
  capability that was otherwise unreachable from C.
- 1:N type conversion: `mlirTypeConverterAdd1ToNConversion` plus an opaque
  results accumulator (`MlirTypeConverterConversionResults` /
  `mlirTypeConverterConversionResultsAppend`). A declining callback's appended
  types are rolled back so the driver's "try the next conversion" invariant
  holds.
- 1:N target materialization: `mlirTypeConverterAdd1ToNTargetMaterialization`,
  whose callback fills a caller-allocated `outputs` buffer. A success that
  leaves any output null is treated as a decline rather than handing the driver
  a null-containing result.
- `mlirConversionPatternRewriterReplaceOpWithMultiple` for 1:N value
  replacement, which can drive a source materialization with nInputs > 1.
- An optional `matchAndRewrite1ToN` callback on `MlirConversionPatternCallbacks`
  for patterns consuming 1:N-remapped operands; when null the driver falls back
  to the 1:1 dispatch.

Adds end-to-end tests in mlir/test/CAPI/rewrite.c covering originalType
observation, source/target materialization decline paths, 1:N conversion
(including append-then-decline rollback and erasure), 1:N target
materialization (including the unfilled-outputs decline), multi-input source
materialization, multi-range replaceOpWithMultiple, and the 1:1-only-pattern
vs 1:N-operand failure.
---
 mlir/include/mlir-c/Rewrite.h        | 108 +++-
 mlir/lib/CAPI/Transforms/Rewrite.cpp | 167 +++++-
 mlir/test/CAPI/rewrite.c             | 733 ++++++++++++++++++++++++++-
 3 files changed, 987 insertions(+), 21 deletions(-)

diff --git a/mlir/include/mlir-c/Rewrite.h b/mlir/include/mlir-c/Rewrite.h
index e074fc96f2aeb..b3e3eb6e88b23 100644
--- a/mlir/include/mlir-c/Rewrite.h
+++ b/mlir/include/mlir-c/Rewrite.h
@@ -501,6 +501,17 @@ mlirConversionPatternRewriterConvertRegionTypes(
     MlirConversionPatternRewriter rewriter, MlirRegion region,
     MlirTypeConverter typeConverter);
 
+/// Replace the given operation with multiple value ranges -- one range per
+/// result of `op` -- and erase it. `nRanges` must equal the number of results
+/// of `op`. `rangeSizes[i]` is the number of values in the i-th range, and
+/// `values` is the flat concatenation of all ranges (its length is the sum of
+/// `rangeSizes[0..nRanges)`). This is the 1:N replacement form; surviving type
+/// mismatches between a result and its range are reconciled by the driver with
+/// source/target materializations.
+MLIR_CAPI_EXPORTED void mlirConversionPatternRewriterReplaceOpWithMultiple(
+    MlirConversionPatternRewriter rewriter, MlirOperation op, intptr_t nRanges,
+    intptr_t *rangeSizes, MlirValue *values);
+
 
//===----------------------------------------------------------------------===//
 /// ConversionTarget API
 
//===----------------------------------------------------------------------===//
@@ -601,32 +612,106 @@ mlirTypeConverterAddConversion(MlirTypeConverter 
typeConverter,
                                MlirTypeConverterConversionCallback convertType,
                                void *userData);
 
-/// Convert the given type using the given TypeConverter.
+/// Opaque accumulator for the result types of a 1:N type conversion. It is
+/// passed to a MlirTypeConverter1ToNConversionCallback, which appends 
converted
+/// types to it via mlirTypeConverterConversionResultsAppend.
+typedef struct MlirTypeConverterConversionResults {
+  void *ptr;
+} MlirTypeConverterConversionResults;
+
+/// Append a converted result type to the given 1:N conversion result
+/// accumulator.
+MLIR_CAPI_EXPORTED void mlirTypeConverterConversionResultsAppend(
+    MlirTypeConverterConversionResults results, MlirType type);
+
+/// Callback type for 1:N type conversion functions. For the given `type`, the
+/// callback appends zero or more converted result types to `results` (via
+/// mlirTypeConverterConversionResultsAppend) and returns success. Returning
+/// failure leaves the type unconverted and allows another conversion function
+/// to be tried; any types appended before returning failure are discarded.
+/// Appending a single type is a 1:1 conversion; appending several is a 1:N
+/// conversion; appending none (on success) erases the type.
+typedef MlirLogicalResult (*MlirTypeConverter1ToNConversionCallback)(
+    MlirType type, MlirTypeConverterConversionResults results, void *userData);
+
+/// Add a 1:N type conversion function to the given TypeConverter.
+MLIR_CAPI_EXPORTED void mlirTypeConverterAdd1ToNConversion(
+    MlirTypeConverter typeConverter,
+    MlirTypeConverter1ToNConversionCallback convertType, void *userData);
+
+/// Convert the given type using the given TypeConverter. This is the 1:1
+/// convenience form: it returns the single converted type, or a null MlirType
+/// on failure or if the type converts to anything other than exactly one type
+/// (e.g. a 1:N conversion registered via mlirTypeConverterAdd1ToNConversion, 
or
+/// an erasure to zero types).
 MLIR_CAPI_EXPORTED MlirType
 mlirTypeConverterConvertType(MlirTypeConverter typeConverter, MlirType type);
 
-/// Callback type for type materializations. Given a builder (passed as a
+/// Callback type for source materializations. Given a builder (passed as a
 /// rewriter), the desired output type, the input values, and a location, the
 /// callback must build a cast-like operation that produces a single value of
 /// `outputType` and return it. Returning a null MlirValue indicates failure, 
in
 /// which case another registered materialization may be attempted.
-typedef MlirValue (*MlirTypeConverterMaterializationCallback)(
+///
+/// Note: Source materializations are single-output -- the callback returns one
+/// value of `outputType`. (The 1:N, multiple-output form exists only for 
target
+/// materializations; see MlirTypeConverter1ToNTargetMaterializationCallback.)
+/// `nInputs` may be greater than one when several values are mapped back to a
+/// single value, e.g. after a 1:N replacement via
+/// mlirConversionPatternRewriterReplaceOpWithMultiple.
+typedef MlirValue (*MlirTypeConverterSourceMaterializationCallback)(
     MlirRewriterBase rewriter, MlirType outputType, intptr_t nInputs,
     MlirValue *inputs, MlirLocation loc, void *userData);
 
+/// Callback type for 1:1 target materializations. Behaves like
+/// MlirTypeConverterSourceMaterializationCallback, but additionally receives
+/// `originalType`: the original type of the SSA value being materialized.
+///
+/// `originalType` may differ from `outputType` and cannot, in general, be
+/// recovered from `outputType` and `inputs`, which is why it is passed
+/// explicitly (see TypeConverter::addTargetMaterialization in
+/// DialectConversion.h for the full rationale). It may be a null MlirType when
+/// no original type is available.
+///
+/// Note: This callback is single-output. For the 1:N (multiple-output) form,
+/// use MlirTypeConverter1ToNTargetMaterializationCallback.
+typedef MlirValue (*MlirTypeConverterTargetMaterializationCallback)(
+    MlirRewriterBase rewriter, MlirType outputType, intptr_t nInputs,
+    MlirValue *inputs, MlirLocation loc, MlirType originalType, void 
*userData);
+
 /// Register a source materialization with the given TypeConverter. This is
 /// invoked when a replacement value must be converted back to its original
 /// source type because some uses persist beyond the main conversion.
 MLIR_CAPI_EXPORTED void mlirTypeConverterAddSourceMaterialization(
     MlirTypeConverter typeConverter,
-    MlirTypeConverterMaterializationCallback callback, void *userData);
+    MlirTypeConverterSourceMaterializationCallback callback, void *userData);
 
 /// Register a target materialization with the given TypeConverter. This is
 /// invoked when a value must be converted to a target type according to a
 /// pattern's type converter.
 MLIR_CAPI_EXPORTED void mlirTypeConverterAddTargetMaterialization(
     MlirTypeConverter typeConverter,
-    MlirTypeConverterMaterializationCallback callback, void *userData);
+    MlirTypeConverterTargetMaterializationCallback callback, void *userData);
+
+/// Callback type for 1:N target materializations. Like
+/// MlirTypeConverterTargetMaterializationCallback, but produces a value for
+/// each of the `nOutputTypes` requested output types instead of a single 
value.
+/// The callback must fill `outputs` -- a caller-allocated array of length
+/// `nOutputTypes` -- with that many values and return success. Returning
+/// failure signals that this materialization declined (so another may be
+/// attempted); in that case `outputs` is ignored. A success that leaves any of
+/// `outputs` null is likewise treated as a decline. `originalType` carries the
+/// original type of the value being materialized and may be a null MlirType.
+typedef MlirLogicalResult 
(*MlirTypeConverter1ToNTargetMaterializationCallback)(
+    MlirRewriterBase rewriter, intptr_t nOutputTypes, MlirType *outputTypes,
+    intptr_t nInputs, MlirValue *inputs, MlirLocation loc,
+    MlirType originalType, MlirValue *outputs, void *userData);
+
+/// Register a 1:N target materialization with the given TypeConverter.
+MLIR_CAPI_EXPORTED void mlirTypeConverterAdd1ToNTargetMaterialization(
+    MlirTypeConverter typeConverter,
+    MlirTypeConverter1ToNTargetMaterializationCallback callback,
+    void *userData);
 
 
//===----------------------------------------------------------------------===//
 /// ConversionPattern API
@@ -647,6 +732,19 @@ typedef struct {
                                        MlirValue *operands,
                                        MlirConversionPatternRewriter rewriter,
                                        void *userData);
+  /// Optional callback corresponding to the 1:N
+  /// ConversionPattern::matchAndRewrite(Operation *, ArrayRef<ValueRange>, 
...)
+  /// overload, used when one or more operands are remapped to several values
+  /// (e.g. under a 1:N type conversion). `operands` is the flat concatenation
+  /// of all operand ranges; there are `nRanges` ranges (one per original
+  /// operand) and `rangeSizes[i]` is the number of values in the i-th range.
+  /// When this is non-null it takes precedence; when null, the driver falls
+  /// back to the 1:1 `matchAndRewrite` above, which fails to match if any
+  /// operand was remapped 1:N.
+  MlirLogicalResult (*matchAndRewrite1ToN)(
+      MlirConversionPattern pattern, MlirOperation op, intptr_t nRanges,
+      intptr_t *rangeSizes, intptr_t nOperands, MlirValue *operands,
+      MlirConversionPatternRewriter rewriter, void *userData);
 } MlirConversionPatternCallbacks;
 
 /// Create a conversion pattern that matches the operation with the given
diff --git a/mlir/lib/CAPI/Transforms/Rewrite.cpp 
b/mlir/lib/CAPI/Transforms/Rewrite.cpp
index 92456d6d8a435..e3dcede2d42b9 100644
--- a/mlir/lib/CAPI/Transforms/Rewrite.cpp
+++ b/mlir/lib/CAPI/Transforms/Rewrite.cpp
@@ -543,6 +543,22 @@ MlirLogicalResult 
mlirConversionPatternRewriterConvertRegionTypes(
                                                    *unwrap(typeConverter)));
 }
 
+void mlirConversionPatternRewriterReplaceOpWithMultiple(
+    MlirConversionPatternRewriter rewriter, MlirOperation op, intptr_t nRanges,
+    intptr_t *rangeSizes, MlirValue *values) {
+  SmallVector<SmallVector<Value>> ranges;
+  ranges.reserve(nRanges);
+  MlirValue *cur = values;
+  for (intptr_t i = 0; i < nRanges; ++i) {
+    SmallVector<Value> range;
+    range.reserve(rangeSizes[i]);
+    for (intptr_t j = 0; j < rangeSizes[i]; ++j, ++cur)
+      range.push_back(unwrap(*cur));
+    ranges.push_back(std::move(range));
+  }
+  unwrap(rewriter)->replaceOpWithMultiple(unwrap(op), std::move(ranges));
+}
+
 
//===----------------------------------------------------------------------===//
 /// ConversionTarget API
 
//===----------------------------------------------------------------------===//
@@ -664,25 +680,59 @@ void mlirTypeConverterAddConversion(
           });
 }
 
+void mlirTypeConverterConversionResultsAppend(
+    MlirTypeConverterConversionResults results, MlirType type) {
+  static_cast<SmallVectorImpl<Type> *>(results.ptr)->push_back(unwrap(type));
+}
+
+void mlirTypeConverterAdd1ToNConversion(
+    MlirTypeConverter typeConverter,
+    MlirTypeConverter1ToNConversionCallback convertType, void *userData) {
+  unwrap(typeConverter)
+      ->addConversion(
+          [convertType, userData](Type type, SmallVectorImpl<Type> &results)
+              -> std::optional<LogicalResult> {
+            size_t numPriorResults = results.size();
+            MlirTypeConverterConversionResults wrappedResults{&results};
+            MlirLogicalResult result =
+                convertType(wrap(type), wrappedResults, userData);
+            if (mlirLogicalResultIsFailure(result)) {
+              // The callback declined. Restore any types it appended so the
+              // driver's "try the next conversion" invariant holds (a 
declining
+              // conversion function must not mutate `results`).
+              results.truncate(numPriorResults);
+              return std::nullopt;
+            }
+            return success();
+          });
+}
+
 MlirType mlirTypeConverterConvertType(MlirTypeConverter typeConverter,
                                       MlirType type) {
   return wrap(unwrap(typeConverter)->convertType(unwrap(type)));
 }
 
 namespace {
-/// Wraps a C materialization callback as a C++ materialization callback of the
-/// form `Value(OpBuilder &, Type, ValueRange, Location)`, shared by both 
source
-/// and target materializations. The builder is always a RewriterBase in the
-/// conversion driver, so it is safe to expose it as an MlirRewriterBase.
+/// Marshals the `inputs` ValueRange into wrapped MlirValues. Shared by the
+/// materialization wrappers below.
+SmallVector<MlirValue> wrapInputs(ValueRange inputs) {
+  SmallVector<MlirValue> wrappedInputs;
+  wrappedInputs.reserve(inputs.size());
+  for (Value v : inputs)
+    wrappedInputs.push_back(wrap(v));
+  return wrappedInputs;
+}
+
+/// Wraps a C source materialization callback as the C++ form
+/// `Value(OpBuilder &, Type, ValueRange, Location)`. The builder is always a
+/// RewriterBase in the conversion driver, so it is safe to expose it as an
+/// MlirRewriterBase.
 std::function<Value(OpBuilder &, Type, ValueRange, Location)>
-wrapMaterializationCallback(MlirTypeConverterMaterializationCallback callback,
-                            void *userData) {
+wrapSourceMaterializationCallback(
+    MlirTypeConverterSourceMaterializationCallback callback, void *userData) {
   return [callback, userData](OpBuilder &builder, Type type, ValueRange inputs,
                               Location loc) -> Value {
-    SmallVector<MlirValue> wrappedInputs;
-    wrappedInputs.reserve(inputs.size());
-    for (Value v : inputs)
-      wrappedInputs.push_back(wrap(v));
+    SmallVector<MlirValue> wrappedInputs = wrapInputs(inputs);
     MlirValue result =
         callback(wrap(static_cast<RewriterBase *>(&builder)), wrap(type),
                  static_cast<intptr_t>(wrappedInputs.size()),
@@ -690,24 +740,91 @@ 
wrapMaterializationCallback(MlirTypeConverterMaterializationCallback callback,
     return mlirValueIsNull(result) ? Value() : unwrap(result);
   };
 }
+
+/// Wraps a C 1:1 target materialization callback as the C++ form
+/// `Value(OpBuilder &, Type, ValueRange, Location, Type)`. Selecting the
+/// 5-argument C++ overload (the one carrying `originalType`) is what makes the
+/// original type observable from C.
+std::function<Value(OpBuilder &, Type, ValueRange, Location, Type)>
+wrapTargetMaterializationCallback(
+    MlirTypeConverterTargetMaterializationCallback callback, void *userData) {
+  return [callback, userData](OpBuilder &builder, Type type, ValueRange inputs,
+                              Location loc, Type originalType) -> Value {
+    SmallVector<MlirValue> wrappedInputs = wrapInputs(inputs);
+    MlirValue result =
+        callback(wrap(static_cast<RewriterBase *>(&builder)), wrap(type),
+                 static_cast<intptr_t>(wrappedInputs.size()),
+                 wrappedInputs.data(), wrap(loc), wrap(originalType), 
userData);
+    return mlirValueIsNull(result) ? Value() : unwrap(result);
+  };
+}
+
+/// Wraps a C 1:N target materialization callback as the C++ form
+/// `SmallVector<Value>(OpBuilder &, TypeRange, ValueRange, Location, Type)`.
+std::function<SmallVector<Value>(OpBuilder &, TypeRange, ValueRange, Location,
+                                 Type)>
+wrap1ToNTargetMaterializationCallback(
+    MlirTypeConverter1ToNTargetMaterializationCallback callback,
+    void *userData) {
+  return [callback, userData](OpBuilder &builder, TypeRange outputTypes,
+                              ValueRange inputs, Location loc,
+                              Type originalType) -> SmallVector<Value> {
+    SmallVector<MlirType> wrappedOutputTypes;
+    wrappedOutputTypes.reserve(outputTypes.size());
+    for (Type t : outputTypes)
+      wrappedOutputTypes.push_back(wrap(t));
+    SmallVector<MlirValue> wrappedInputs = wrapInputs(inputs);
+    SmallVector<MlirValue> wrappedOutputs(outputTypes.size(),
+                                          MlirValue{nullptr});
+    MlirLogicalResult result = callback(
+        wrap(static_cast<RewriterBase *>(&builder)),
+        static_cast<intptr_t>(wrappedOutputTypes.size()),
+        wrappedOutputTypes.data(), static_cast<intptr_t>(wrappedInputs.size()),
+        wrappedInputs.data(), wrap(loc), wrap(originalType),
+        wrappedOutputs.data(), userData);
+    if (mlirLogicalResultIsFailure(result))
+      return {}; // declined; another materialization may be attempted
+    SmallVector<Value> outputs;
+    outputs.reserve(wrappedOutputs.size());
+    for (MlirValue v : wrappedOutputs) {
+      // A success that left any output unfilled is an incomplete
+      // materialization. Treat it as a decline rather than returning a
+      // null-containing result (which would trip an assert in the driver).
+      if (mlirValueIsNull(v))
+        return {};
+      outputs.push_back(unwrap(v));
+    }
+    return outputs;
+  };
+}
 } // namespace
 
 void mlirTypeConverterAddSourceMaterialization(
     MlirTypeConverter typeConverter,
-    MlirTypeConverterMaterializationCallback callback, void *userData) {
+    MlirTypeConverterSourceMaterializationCallback callback, void *userData) {
   assert(callback && "expected non-null materialization callback");
   unwrap(typeConverter)
       ->addSourceMaterialization(
-          wrapMaterializationCallback(callback, userData));
+          wrapSourceMaterializationCallback(callback, userData));
 }
 
 void mlirTypeConverterAddTargetMaterialization(
     MlirTypeConverter typeConverter,
-    MlirTypeConverterMaterializationCallback callback, void *userData) {
+    MlirTypeConverterTargetMaterializationCallback callback, void *userData) {
   assert(callback && "expected non-null materialization callback");
   unwrap(typeConverter)
       ->addTargetMaterialization(
-          wrapMaterializationCallback(callback, userData));
+          wrapTargetMaterializationCallback(callback, userData));
+}
+
+void mlirTypeConverterAdd1ToNTargetMaterialization(
+    MlirTypeConverter typeConverter,
+    MlirTypeConverter1ToNTargetMaterializationCallback callback,
+    void *userData) {
+  assert(callback && "expected non-null materialization callback");
+  unwrap(typeConverter)
+      ->addTargetMaterialization(
+          wrap1ToNTargetMaterializationCallback(callback, userData));
 }
 
 
//===----------------------------------------------------------------------===//
@@ -747,6 +864,28 @@ class ExternalConversionPattern : public 
mlir::ConversionPattern {
         userData));
   }
 
+  LogicalResult
+  matchAndRewrite(Operation *op, ArrayRef<ValueRange> operands,
+                  ConversionPatternRewriter &rewriter) const override {
+    // Without a 1:N callback, defer to the default behavior, which dispatches
+    // to the 1:1 matchAndRewrite above or fails to match on a 1:N mapping.
+    if (!callbacks.matchAndRewrite1ToN)
+      return dispatchTo1To1(*this, op, operands, rewriter);
+    SmallVector<intptr_t> rangeSizes;
+    rangeSizes.reserve(operands.size());
+    std::vector<MlirValue> wrappedOperands;
+    for (ValueRange range : operands) {
+      rangeSizes.push_back(static_cast<intptr_t>(range.size()));
+      for (Value val : range)
+        wrappedOperands.push_back(wrap(val));
+    }
+    return unwrap(callbacks.matchAndRewrite1ToN(
+        wrap(static_cast<const mlir::ConversionPattern *>(this)), wrap(op),
+        static_cast<intptr_t>(rangeSizes.size()), rangeSizes.data(),
+        static_cast<intptr_t>(wrappedOperands.size()), wrappedOperands.data(),
+        wrap(&rewriter), userData));
+  }
+
 private:
   MlirConversionPatternCallbacks callbacks;
   void *userData;
diff --git a/mlir/test/CAPI/rewrite.c b/mlir/test/CAPI/rewrite.c
index b10afbd8b812c..082af86152c9e 100644
--- a/mlir/test/CAPI/rewrite.c
+++ b/mlir/test/CAPI/rewrite.c
@@ -833,7 +833,178 @@ static MlirValue 
buildCastMaterialization(MlirRewriterBase rewriter,
   return mlirOperationGetResult(castOp, 0);
 }
 
-// Conversion pattern for `test.source`: replaces it with a `test.source_i64`
+// Source materialization callback that always declines (returns a null value)
+// and records that it was consulted. Used to exercise the "this 
materialization
+// declined, try the next one" fallback path.
+static MlirValue
+declineSourceMaterialization(MlirRewriterBase rewriter, MlirType outputType,
+                             intptr_t nInputs, MlirValue *inputs,
+                             MlirLocation loc, void *userData) {
+  (void)rewriter;
+  (void)outputType;
+  (void)nInputs;
+  (void)inputs;
+  (void)loc;
+  intptr_t *declined = (intptr_t *)userData;
+  if (declined)
+    (*declined)++;
+  return (MlirValue){NULL};
+}
+
+// Source materialization callback that records the number of inputs it was
+// invoked with (into userData) before building the `test.cast`. Used to verify
+// that a 1:N replacement drives a source materialization with nInputs > 1.
+static MlirValue buildSourceCastRecordInputs(MlirRewriterBase rewriter,
+                                             MlirType outputType,
+                                             intptr_t nInputs,
+                                             MlirValue *inputs,
+                                             MlirLocation loc, void *userData) 
{
+  intptr_t *observedInputs = (intptr_t *)userData;
+  if (observedInputs)
+    *observedInputs = nInputs;
+  MlirOperationState state =
+      mlirOperationStateGet(mlirStringRefCreateFromCString("test.cast"), loc);
+  mlirOperationStateAddOperands(&state, nInputs, inputs);
+  mlirOperationStateAddResults(&state, 1, &outputType);
+  MlirOperation castOp = mlirOperationCreate(&state);
+  mlirRewriterBaseInsert(rewriter, castOp);
+  return mlirOperationGetResult(castOp, 0);
+}
+
+// 1:1 target materialization callback. Builds a `test.cast` like
+// buildCastMaterialization, but additionally inspects `originalType`: the
+// counter passed as userData is only bumped when `originalType` is the 
expected
+// original (i32) type, so a passing test proves the original type is 
observable
+// from C.
+static MlirValue buildTargetCast(MlirRewriterBase rewriter, MlirType 
outputType,
+                                 intptr_t nInputs, MlirValue *inputs,
+                                 MlirLocation loc, MlirType originalType,
+                                 void *userData) {
+  intptr_t *counter = (intptr_t *)userData;
+  if (counter && mlirTypeIsAInteger(originalType) &&
+      mlirIntegerTypeGetWidth(originalType) == 32)
+    (*counter)++;
+  MlirOperationState state =
+      mlirOperationStateGet(mlirStringRefCreateFromCString("test.cast"), loc);
+  mlirOperationStateAddOperands(&state, nInputs, inputs);
+  mlirOperationStateAddResults(&state, 1, &outputType);
+  MlirOperation castOp = mlirOperationCreate(&state);
+  mlirRewriterBaseInsert(rewriter, castOp);
+  return mlirOperationGetResult(castOp, 0);
+}
+
+// 1:N type conversion callback: maps i32 -> (i16, i16) and leaves every other
+// type unchanged (1:1 identity).
+static MlirLogicalResult splitI32(MlirType type,
+                                  MlirTypeConverterConversionResults results,
+                                  void *userData) {
+  (void)userData;
+  if (mlirTypeIsAInteger(type) && mlirIntegerTypeGetWidth(type) == 32) {
+    MlirType i16 = mlirIntegerTypeGet(mlirTypeGetContext(type), 16);
+    mlirTypeConverterConversionResultsAppend(results, i16);
+    mlirTypeConverterConversionResultsAppend(results, i16);
+  } else {
+    mlirTypeConverterConversionResultsAppend(results, type);
+  }
+  return mlirLogicalResultSuccess();
+}
+
+// 1:N type conversion callback: erases i32 (converts it to zero types) and
+// leaves every other type unchanged (1:1 identity).
+static MlirLogicalResult eraseI32(MlirType type,
+                                  MlirTypeConverterConversionResults results,
+                                  void *userData) {
+  (void)userData;
+  if (mlirTypeIsAInteger(type) && mlirIntegerTypeGetWidth(type) == 32)
+    return mlirLogicalResultSuccess(); // append nothing -> erase
+  mlirTypeConverterConversionResultsAppend(results, type);
+  return mlirLogicalResultSuccess();
+}
+
+// 1:N type conversion callback: builds one `test.cast` per requested
+// output type, each consuming the (single) input, and fills `outputs`
+// accordingly. Bumps the counter passed as userData.
+static MlirLogicalResult buildSplitCast(MlirRewriterBase rewriter,
+                                        intptr_t nOutputTypes,
+                                        MlirType *outputTypes, intptr_t 
nInputs,
+                                        MlirValue *inputs, MlirLocation loc,
+                                        MlirType originalType,
+                                        MlirValue *outputs, void *userData) {
+  intptr_t *counter = (intptr_t *)userData;
+  // The original type must be the i32 operand type; it cannot be recovered 
from
+  // the (i16) output types alone, so observing it here proves it is 
propagated.
+  if (counter && mlirTypeIsAInteger(originalType) &&
+      mlirIntegerTypeGetWidth(originalType) == 32)
+    (*counter)++;
+  for (intptr_t i = 0; i < nOutputTypes; ++i) {
+    MlirOperationState state =
+        mlirOperationStateGet(mlirStringRefCreateFromCString("test.cast"), 
loc);
+    mlirOperationStateAddOperands(&state, nInputs, inputs);
+    mlirOperationStateAddResults(&state, 1, &outputTypes[i]);
+    MlirOperation castOp = mlirOperationCreate(&state);
+    mlirRewriterBaseInsert(rewriter, castOp);
+    outputs[i] = mlirOperationGetResult(castOp, 0);
+  }
+  return mlirLogicalResultSuccess();
+}
+
+// 1:N type conversion callback that appends bogus result types and then
+// declines (returns failure). The binding must roll back the appended types so
+// they do not pollute a subsequently-tried conversion function.
+static MlirLogicalResult appendThenDeclineConversion(
+    MlirType type, MlirTypeConverterConversionResults results, void *userData) 
{
+  intptr_t *counter = (intptr_t *)userData;
+  if (counter)
+    (*counter)++;
+  MlirType i8 = mlirIntegerTypeGet(mlirTypeGetContext(type), 8);
+  // Append more (and differently-typed) entries than the real conversion 
would,
+  // so any leak is observable as a wrong type/arity downstream.
+  mlirTypeConverterConversionResultsAppend(results, i8);
+  mlirTypeConverterConversionResultsAppend(results, i8);
+  mlirTypeConverterConversionResultsAppend(results, i8);
+  return mlirLogicalResultFailure();
+}
+
+// 1:N target materialization that always declines (returns failure) and 
records
+// that it was consulted. Exercises the "decline, try the next" fallback.
+static MlirLogicalResult declineTargetMaterialization(
+    MlirRewriterBase rewriter, intptr_t nOutputTypes, MlirType *outputTypes,
+    intptr_t nInputs, MlirValue *inputs, MlirLocation loc,
+    MlirType originalType, MlirValue *outputs, void *userData) {
+  (void)rewriter;
+  (void)nOutputTypes;
+  (void)outputTypes;
+  (void)nInputs;
+  (void)inputs;
+  (void)loc;
+  (void)originalType;
+  (void)outputs;
+  intptr_t *counter = (intptr_t *)userData;
+  if (counter)
+    (*counter)++;
+  return mlirLogicalResultFailure();
+}
+
+// 1:N target materialization that returns success but leaves `outputs`
+// unfilled (all null). The binding must treat this incomplete fill as a 
decline
+// rather than handing the driver a null-containing result.
+static MlirLogicalResult partialTargetMaterialization(
+    MlirRewriterBase rewriter, intptr_t nOutputTypes, MlirType *outputTypes,
+    intptr_t nInputs, MlirValue *inputs, MlirLocation loc,
+    MlirType originalType, MlirValue *outputs, void *userData) {
+  (void)rewriter;
+  (void)nOutputTypes;
+  (void)outputTypes;
+  (void)nInputs;
+  (void)inputs;
+  (void)loc;
+  (void)originalType;
+  (void)outputs;
+  intptr_t *counter = (intptr_t *)userData;
+  if (counter)
+    (*counter)++;
+  return mlirLogicalResultSuccess();
+}
 // op whose result has the widened (i64) type. Because the original result type
 // (i32) differs from the replacement type (i64), persisting uses force the
 // framework to insert a source materialization.
@@ -880,6 +1051,12 @@ void testTypeConverterSourceMaterialization(MlirContext 
ctx) {
   intptr_t materializationCounter = 0;
   mlirTypeConverterAddSourceMaterialization(converter, 
buildCastMaterialization,
                                             &materializationCounter);
+  // Register a second materialization that always declines. Because
+  // materializations are tried most-recently-added first, this one runs first,
+  // returns null, and the framework must fall back to 
buildCastMaterialization.
+  intptr_t declinedCounter = 0;
+  mlirTypeConverterAddSourceMaterialization(
+      converter, declineSourceMaterialization, &declinedCounter);
 
   MlirRewritePatternSet patterns = mlirRewritePatternSetCreate(ctx);
   MlirConversionPatternCallbacks callbacks = {NULL, NULL, convertSource};
@@ -908,6 +1085,8 @@ void testTypeConverterSourceMaterialization(MlirContext 
ctx) {
   assert(mlirLogicalResultIsSuccess(result));
   assert(materializationCounter > 0 &&
          "source materialization callback must be invoked");
+  assert(declinedCounter > 0 &&
+         "declining materialization must be consulted before the fallback");
 
   mlirOperationDump(moduleOp);
   // clang-format off
@@ -953,6 +1132,32 @@ static MlirLogicalResult 
convertConsumer(MlirConversionPattern pattern,
   return mlirLogicalResultSuccess();
 }
 
+// 1:N conversion pattern for `test.consumer`: builds `test.consumer_legal` 
from
+// the flattened remapped operands. Under the i32 -> (i16, i16) conversion the
+// single operand is remapped to two values, so this is invoked via the 1:N
+// matchAndRewrite hook with nOperands == 2.
+static MlirLogicalResult
+convertConsumer1ToN(MlirConversionPattern pattern, MlirOperation op,
+                    intptr_t nRanges, intptr_t *rangeSizes, intptr_t nOperands,
+                    MlirValue *operands, MlirConversionPatternRewriter 
rewriter,
+                    void *userData) {
+  (void)pattern;
+  (void)nRanges;
+  (void)rangeSizes;
+  (void)userData;
+  MlirLocation loc = mlirOperationGetLocation(op);
+  MlirOperationState state = mlirOperationStateGet(
+      mlirStringRefCreateFromCString("test.consumer_legal"), loc);
+  mlirOperationStateAddOperands(&state, nOperands, operands);
+  MlirOperation newOp = mlirOperationCreate(&state);
+
+  MlirRewriterBase base = mlirPatternRewriterAsBase(
+      mlirConversionPatternRewriterAsPatternRewriter(rewriter));
+  mlirRewriterBaseInsert(base, newOp);
+  mlirRewriterBaseEraseOp(base, op);
+  return mlirLogicalResultSuccess();
+}
+
 void testTypeConverterTargetMaterialization(MlirContext ctx) {
   // CHECK-LABEL: @testTypeConverterTargetMaterialization
   fprintf(stderr, "@testTypeConverterTargetMaterialization\n");
@@ -969,7 +1174,7 @@ void testTypeConverterTargetMaterialization(MlirContext 
ctx) {
   MlirTypeConverter converter = mlirTypeConverterCreate();
   mlirTypeConverterAddConversion(converter, widenI32ToI64, NULL);
   intptr_t materializationCounter = 0;
-  mlirTypeConverterAddTargetMaterialization(converter, 
buildCastMaterialization,
+  mlirTypeConverterAddTargetMaterialization(converter, buildTargetCast,
                                             &materializationCounter);
 
   MlirRewritePatternSet patterns = mlirRewritePatternSetCreate(ctx);
@@ -1019,6 +1224,523 @@ void testTypeConverterTargetMaterialization(MlirContext 
ctx) {
   fprintf(stderr, "testTypeConverterTargetMaterialization: PASSED\n");
 }
 
+void testTypeConverter1ToNTargetMaterialization(MlirContext ctx) {
+  // CHECK-LABEL: @testTypeConverter1ToNTargetMaterialization
+  fprintf(stderr, "@testTypeConverter1ToNTargetMaterialization\n");
+
+  // `test.consumer` takes an i32 from the (legal, unconverted) 
`test.producer`.
+  // The 1:N conversion maps i32 -> (i16, i16), so converting `test.consumer`
+  // requires its operand as two i16 values, which triggers a 1:N target
+  // materialization producing two values from the single i32.
+  const char *moduleString = "%0 = \"test.producer\"() : () -> i32\n"
+                             "\"test.consumer\"(%0) : (i32) -> ()\n";
+  MlirModule module =
+      mlirModuleCreateParse(ctx, mlirStringRefCreateFromCString(moduleString));
+  MlirOperation moduleOp = mlirModuleGetOperation(module);
+
+  MlirTypeConverter converter = mlirTypeConverterCreate();
+  mlirTypeConverterAdd1ToNConversion(converter, splitI32, NULL);
+  intptr_t materializationCounter = 0;
+  mlirTypeConverterAdd1ToNTargetMaterialization(converter, buildSplitCast,
+                                                &materializationCounter);
+
+  MlirRewritePatternSet patterns = mlirRewritePatternSetCreate(ctx);
+  MlirConversionPatternCallbacks callbacks = {NULL, NULL, NULL,
+                                              convertConsumer1ToN};
+  MlirConversionPattern pattern = mlirOpConversionPatternCreate(
+      mlirStringRefCreateFromCString("test.consumer"), 1, ctx, converter,
+      callbacks, NULL, 0, NULL);
+  mlirRewritePatternSetAdd(patterns,
+                           mlirConversionPatternAsRewritePattern(pattern));
+  MlirFrozenRewritePatternSet frozen = mlirFreezeRewritePattern(patterns);
+
+  MlirConversionTarget target = mlirConversionTargetCreate(ctx);
+  mlirConversionTargetAddIllegalOp(
+      target, mlirStringRefCreateFromCString("test.consumer"));
+  mlirConversionTargetAddLegalOp(
+      target, mlirStringRefCreateFromCString("test.producer"));
+  mlirConversionTargetAddLegalOp(
+      target, mlirStringRefCreateFromCString("test.consumer_legal"));
+  mlirConversionTargetAddLegalOp(target,
+                                 mlirStringRefCreateFromCString("test.cast"));
+  mlirConversionTargetAddLegalOp(
+      target, mlirStringRefCreateFromCString("builtin.module"));
+
+  MlirConversionConfig config = mlirConversionConfigCreate();
+  MlirLogicalResult result =
+      mlirApplyPartialConversion(moduleOp, target, frozen, config);
+  assert(mlirLogicalResultIsSuccess(result));
+  assert(materializationCounter > 0 &&
+         "1:N target materialization callback must be invoked");
+
+  mlirOperationDump(moduleOp);
+  // clang-format off
+  // CHECK: %[[v:.*]] = "test.producer"() : () -> i32
+  // CHECK: %[[c0:.*]] = "test.cast"(%[[v]]) : (i32) -> i16
+  // CHECK: %[[c1:.*]] = "test.cast"(%[[v]]) : (i32) -> i16
+  // CHECK: "test.consumer_legal"(%[[c0]], %[[c1]]) : (i16, i16) -> ()
+  // clang-format on
+
+  mlirConversionConfigDestroy(config);
+  mlirConversionTargetDestroy(target);
+  mlirFrozenRewritePatternSetDestroy(frozen);
+  mlirTypeConverterDestroy(converter);
+  mlirModuleDestroy(module);
+
+  // CHECK: testTypeConverter1ToNTargetMaterialization: PASSED
+  fprintf(stderr, "testTypeConverter1ToNTargetMaterialization: PASSED\n");
+}
+
+void testTypeConverter1ToNConversionDeclineRollback(MlirContext ctx) {
+  // CHECK-LABEL: @testTypeConverter1ToNConversionDeclineRollback
+  fprintf(stderr, "@testTypeConverter1ToNConversionDeclineRollback\n");
+
+  // Same setup as the 1:N target materialization test, but with an extra
+  // conversion that appends bogus types and then declines. It is registered
+  // last, so it is tried first; the binding must roll back its appended types
+  // so the i32 -> (i16, i16) conversion still produces exactly two i16 values
+  // (not i8s, and not three of them).
+  const char *moduleString = "%0 = \"test.producer\"() : () -> i32\n"
+                             "\"test.consumer\"(%0) : (i32) -> ()\n";
+  MlirModule module =
+      mlirModuleCreateParse(ctx, mlirStringRefCreateFromCString(moduleString));
+  MlirOperation moduleOp = mlirModuleGetOperation(module);
+
+  MlirTypeConverter converter = mlirTypeConverterCreate();
+  mlirTypeConverterAdd1ToNConversion(converter, splitI32, NULL);
+  intptr_t declineCounter = 0;
+  mlirTypeConverterAdd1ToNConversion(converter, appendThenDeclineConversion,
+                                     &declineCounter);
+  intptr_t materializationCounter = 0;
+  mlirTypeConverterAdd1ToNTargetMaterialization(converter, buildSplitCast,
+                                                &materializationCounter);
+
+  MlirRewritePatternSet patterns = mlirRewritePatternSetCreate(ctx);
+  MlirConversionPatternCallbacks callbacks = {NULL, NULL, NULL,
+                                              convertConsumer1ToN};
+  MlirConversionPattern pattern = mlirOpConversionPatternCreate(
+      mlirStringRefCreateFromCString("test.consumer"), 1, ctx, converter,
+      callbacks, NULL, 0, NULL);
+  mlirRewritePatternSetAdd(patterns,
+                           mlirConversionPatternAsRewritePattern(pattern));
+  MlirFrozenRewritePatternSet frozen = mlirFreezeRewritePattern(patterns);
+
+  MlirConversionTarget target = mlirConversionTargetCreate(ctx);
+  mlirConversionTargetAddIllegalOp(
+      target, mlirStringRefCreateFromCString("test.consumer"));
+  mlirConversionTargetAddLegalOp(
+      target, mlirStringRefCreateFromCString("test.producer"));
+  mlirConversionTargetAddLegalOp(
+      target, mlirStringRefCreateFromCString("test.consumer_legal"));
+  mlirConversionTargetAddLegalOp(target,
+                                 mlirStringRefCreateFromCString("test.cast"));
+  mlirConversionTargetAddLegalOp(
+      target, mlirStringRefCreateFromCString("builtin.module"));
+
+  MlirConversionConfig config = mlirConversionConfigCreate();
+  MlirLogicalResult result =
+      mlirApplyPartialConversion(moduleOp, target, frozen, config);
+  assert(mlirLogicalResultIsSuccess(result));
+  assert(declineCounter > 0 && "declining conversion must be consulted");
+
+  mlirOperationDump(moduleOp);
+  // clang-format off
+  // CHECK: %[[v:.*]] = "test.producer"() : () -> i32
+  // CHECK: %[[c0:.*]] = "test.cast"(%[[v]]) : (i32) -> i16
+  // CHECK: %[[c1:.*]] = "test.cast"(%[[v]]) : (i32) -> i16
+  // CHECK: "test.consumer_legal"(%[[c0]], %[[c1]]) : (i16, i16) -> ()
+  // clang-format on
+
+  mlirConversionConfigDestroy(config);
+  mlirConversionTargetDestroy(target);
+  mlirFrozenRewritePatternSetDestroy(frozen);
+  mlirTypeConverterDestroy(converter);
+  mlirModuleDestroy(module);
+
+  // CHECK: testTypeConverter1ToNConversionDeclineRollback: PASSED
+  fprintf(stderr, "testTypeConverter1ToNConversionDeclineRollback: PASSED\n");
+}
+
+void testTypeConverter1ToNTargetMaterializationDecline(MlirContext ctx) {
+  // CHECK-LABEL: @testTypeConverter1ToNTargetMaterializationDecline
+  fprintf(stderr, "@testTypeConverter1ToNTargetMaterializationDecline\n");
+
+  // Register three 1:N target materializations. Tried most-recently-added
+  // first: one that returns failure, one that returns success but leaves the
+  // outputs unfilled (must be treated as a decline), and finally the real one.
+  // The conversion must still succeed via the last, and all three must run.
+  const char *moduleString = "%0 = \"test.producer\"() : () -> i32\n"
+                             "\"test.consumer\"(%0) : (i32) -> ()\n";
+  MlirModule module =
+      mlirModuleCreateParse(ctx, mlirStringRefCreateFromCString(moduleString));
+  MlirOperation moduleOp = mlirModuleGetOperation(module);
+
+  MlirTypeConverter converter = mlirTypeConverterCreate();
+  mlirTypeConverterAdd1ToNConversion(converter, splitI32, NULL);
+  intptr_t buildCounter = 0, partialCounter = 0, declineCounter = 0;
+  mlirTypeConverterAdd1ToNTargetMaterialization(converter, buildSplitCast,
+                                                &buildCounter);
+  mlirTypeConverterAdd1ToNTargetMaterialization(
+      converter, partialTargetMaterialization, &partialCounter);
+  mlirTypeConverterAdd1ToNTargetMaterialization(
+      converter, declineTargetMaterialization, &declineCounter);
+
+  MlirRewritePatternSet patterns = mlirRewritePatternSetCreate(ctx);
+  MlirConversionPatternCallbacks callbacks = {NULL, NULL, NULL,
+                                              convertConsumer1ToN};
+  MlirConversionPattern pattern = mlirOpConversionPatternCreate(
+      mlirStringRefCreateFromCString("test.consumer"), 1, ctx, converter,
+      callbacks, NULL, 0, NULL);
+  mlirRewritePatternSetAdd(patterns,
+                           mlirConversionPatternAsRewritePattern(pattern));
+  MlirFrozenRewritePatternSet frozen = mlirFreezeRewritePattern(patterns);
+
+  MlirConversionTarget target = mlirConversionTargetCreate(ctx);
+  mlirConversionTargetAddIllegalOp(
+      target, mlirStringRefCreateFromCString("test.consumer"));
+  mlirConversionTargetAddLegalOp(
+      target, mlirStringRefCreateFromCString("test.producer"));
+  mlirConversionTargetAddLegalOp(
+      target, mlirStringRefCreateFromCString("test.consumer_legal"));
+  mlirConversionTargetAddLegalOp(target,
+                                 mlirStringRefCreateFromCString("test.cast"));
+  mlirConversionTargetAddLegalOp(
+      target, mlirStringRefCreateFromCString("builtin.module"));
+
+  MlirConversionConfig config = mlirConversionConfigCreate();
+  MlirLogicalResult result =
+      mlirApplyPartialConversion(moduleOp, target, frozen, config);
+  assert(mlirLogicalResultIsSuccess(result));
+  assert(declineCounter > 0 && "failing materialization must be consulted");
+  assert(partialCounter > 0 &&
+         "unfilled (incomplete) materialization must be consulted");
+  assert(buildCounter > 0 && "fallback materialization must run");
+
+  mlirOperationDump(moduleOp);
+  // clang-format off
+  // CHECK: %[[v:.*]] = "test.producer"() : () -> i32
+  // CHECK: %[[c0:.*]] = "test.cast"(%[[v]]) : (i32) -> i16
+  // CHECK: %[[c1:.*]] = "test.cast"(%[[v]]) : (i32) -> i16
+  // CHECK: "test.consumer_legal"(%[[c0]], %[[c1]]) : (i16, i16) -> ()
+  // clang-format on
+
+  mlirConversionConfigDestroy(config);
+  mlirConversionTargetDestroy(target);
+  mlirFrozenRewritePatternSetDestroy(frozen);
+  mlirTypeConverterDestroy(converter);
+  mlirModuleDestroy(module);
+
+  // CHECK: testTypeConverter1ToNTargetMaterializationDecline: PASSED
+  fprintf(stderr,
+          "testTypeConverter1ToNTargetMaterializationDecline: PASSED\n");
+}
+
+// Conversion pattern for `test.producer`: replaces its single i32 result with
+// two i16 values (built by two `test.piece` ops) via replaceOpWithMultiple.
+// Because a legal use (`test.user`) still wants the original i32, the 
framework
+// must insert a source materialization with two inputs to reconcile the 1:N
+// replacement.
+static MlirLogicalResult
+convertProducerToMultiple(MlirConversionPattern pattern, MlirOperation op,
+                          intptr_t nOperands, MlirValue *operands,
+                          MlirConversionPatternRewriter rewriter,
+                          void *userData) {
+  (void)pattern;
+  (void)nOperands;
+  (void)operands;
+  (void)userData;
+  MlirContext ctx = mlirOperationGetContext(op);
+  MlirLocation loc = mlirOperationGetLocation(op);
+  MlirType i16 = mlirIntegerTypeGet(ctx, 16);
+
+  MlirRewriterBase base = mlirPatternRewriterAsBase(
+      mlirConversionPatternRewriterAsPatternRewriter(rewriter));
+
+  MlirValue pieces[2];
+  for (int i = 0; i < 2; ++i) {
+    MlirOperationState state = mlirOperationStateGet(
+        mlirStringRefCreateFromCString("test.piece"), loc);
+    mlirOperationStateAddResults(&state, 1, &i16);
+    MlirOperation pieceOp = mlirOperationCreate(&state);
+    mlirRewriterBaseInsert(base, pieceOp);
+    pieces[i] = mlirOperationGetResult(pieceOp, 0);
+  }
+
+  // The op has a single result, so there is one range, carrying two values.
+  intptr_t rangeSizes[1] = {2};
+  mlirConversionPatternRewriterReplaceOpWithMultiple(rewriter, op, 1,
+                                                     rangeSizes, pieces);
+  return mlirLogicalResultSuccess();
+}
+
+void testTypeConverterMultiInputSourceMaterialization(MlirContext ctx) {
+  // CHECK-LABEL: @testTypeConverterMultiInputSourceMaterialization
+  fprintf(stderr, "@testTypeConverterMultiInputSourceMaterialization\n");
+
+  // `test.producer` produces an i32 consumed by the (legal) `test.user`.
+  // The pattern replaces the producer's result with two i16 values, so the
+  // i32-wanting `test.user` forces a source materialization that receives both
+  // values as inputs (nInputs == 2).
+  const char *moduleString = "%0 = \"test.producer\"() : () -> i32\n"
+                             "\"test.user\"(%0) : (i32) -> ()\n";
+  MlirModule module =
+      mlirModuleCreateParse(ctx, mlirStringRefCreateFromCString(moduleString));
+  MlirOperation moduleOp = mlirModuleGetOperation(module);
+
+  MlirTypeConverter converter = mlirTypeConverterCreate();
+  intptr_t observedInputs = 0;
+  mlirTypeConverterAddSourceMaterialization(
+      converter, buildSourceCastRecordInputs, &observedInputs);
+
+  MlirRewritePatternSet patterns = mlirRewritePatternSetCreate(ctx);
+  MlirConversionPatternCallbacks callbacks = {NULL, NULL,
+                                              convertProducerToMultiple};
+  MlirConversionPattern pattern = mlirOpConversionPatternCreate(
+      mlirStringRefCreateFromCString("test.producer"), 1, ctx, converter,
+      callbacks, NULL, 0, NULL);
+  mlirRewritePatternSetAdd(patterns,
+                           mlirConversionPatternAsRewritePattern(pattern));
+  MlirFrozenRewritePatternSet frozen = mlirFreezeRewritePattern(patterns);
+
+  MlirConversionTarget target = mlirConversionTargetCreate(ctx);
+  mlirConversionTargetAddIllegalOp(
+      target, mlirStringRefCreateFromCString("test.producer"));
+  mlirConversionTargetAddLegalOp(target,
+                                 mlirStringRefCreateFromCString("test.piece"));
+  mlirConversionTargetAddLegalOp(target,
+                                 mlirStringRefCreateFromCString("test.cast"));
+  mlirConversionTargetAddLegalOp(target,
+                                 mlirStringRefCreateFromCString("test.user"));
+  mlirConversionTargetAddLegalOp(
+      target, mlirStringRefCreateFromCString("builtin.module"));
+
+  MlirConversionConfig config = mlirConversionConfigCreate();
+  MlirLogicalResult result =
+      mlirApplyPartialConversion(moduleOp, target, frozen, config);
+  assert(mlirLogicalResultIsSuccess(result));
+  assert(observedInputs == 2 &&
+         "source materialization must receive both replacement values");
+
+  mlirOperationDump(moduleOp);
+  // clang-format off
+  // CHECK: %[[a:.*]] = "test.piece"() : () -> i16
+  // CHECK: %[[b:.*]] = "test.piece"() : () -> i16
+  // CHECK: %[[c:.*]] = "test.cast"(%[[a]], %[[b]]) : (i16, i16) -> i32
+  // CHECK: "test.user"(%[[c]]) : (i32) -> ()
+  // clang-format on
+
+  mlirConversionConfigDestroy(config);
+  mlirConversionTargetDestroy(target);
+  mlirFrozenRewritePatternSetDestroy(frozen);
+  mlirTypeConverterDestroy(converter);
+  mlirModuleDestroy(module);
+
+  // CHECK: testTypeConverterMultiInputSourceMaterialization: PASSED
+  fprintf(stderr, "testTypeConverterMultiInputSourceMaterialization: 
PASSED\n");
+}
+
+// Conversion pattern for a two-result `test.multi`: replaces it via
+// replaceOpWithMultiple with two ranges (one per result), exercising the
+// nRanges > 1 marshalling path. Each result is replaced 1:1 with a same-typed
+// value so no materialization is needed.
+static MlirLogicalResult
+convertMultiResult(MlirConversionPattern pattern, MlirOperation op,
+                   intptr_t nOperands, MlirValue *operands,
+                   MlirConversionPatternRewriter rewriter, void *userData) {
+  (void)pattern;
+  (void)nOperands;
+  (void)operands;
+  (void)userData;
+  MlirContext ctx = mlirOperationGetContext(op);
+  MlirLocation loc = mlirOperationGetLocation(op);
+  MlirType i32 = mlirIntegerTypeGet(ctx, 32);
+  MlirType i64 = mlirIntegerTypeGet(ctx, 64);
+  MlirRewriterBase base = mlirPatternRewriterAsBase(
+      mlirConversionPatternRewriterAsPatternRewriter(rewriter));
+
+  MlirValue vals[2];
+  MlirType types[2] = {i32, i64};
+  const char *names[2] = {"test.r0", "test.r1"};
+  for (int i = 0; i < 2; ++i) {
+    MlirOperationState state =
+        mlirOperationStateGet(mlirStringRefCreateFromCString(names[i]), loc);
+    mlirOperationStateAddResults(&state, 1, &types[i]);
+    MlirOperation newOp = mlirOperationCreate(&state);
+    mlirRewriterBaseInsert(base, newOp);
+    vals[i] = mlirOperationGetResult(newOp, 0);
+  }
+
+  // Two ranges (one per result), each carrying a single value.
+  intptr_t rangeSizes[2] = {1, 1};
+  mlirConversionPatternRewriterReplaceOpWithMultiple(rewriter, op, 2,
+                                                     rangeSizes, vals);
+  return mlirLogicalResultSuccess();
+}
+
+void testConversionReplaceOpWithMultipleRanges(MlirContext ctx) {
+  // CHECK-LABEL: @testConversionReplaceOpWithMultipleRanges
+  fprintf(stderr, "@testConversionReplaceOpWithMultipleRanges\n");
+
+  const char *moduleString = "%0:2 = \"test.multi\"() : () -> (i32, i64)\n"
+                             "\"test.use\"(%0#0, %0#1) : (i32, i64) -> ()\n";
+  MlirModule module =
+      mlirModuleCreateParse(ctx, mlirStringRefCreateFromCString(moduleString));
+  MlirOperation moduleOp = mlirModuleGetOperation(module);
+
+  MlirTypeConverter converter = mlirTypeConverterCreate();
+
+  MlirRewritePatternSet patterns = mlirRewritePatternSetCreate(ctx);
+  MlirConversionPatternCallbacks callbacks = {NULL, NULL, convertMultiResult,
+                                              NULL};
+  MlirConversionPattern pattern = mlirOpConversionPatternCreate(
+      mlirStringRefCreateFromCString("test.multi"), 1, ctx, converter,
+      callbacks, NULL, 0, NULL);
+  mlirRewritePatternSetAdd(patterns,
+                           mlirConversionPatternAsRewritePattern(pattern));
+  MlirFrozenRewritePatternSet frozen = mlirFreezeRewritePattern(patterns);
+
+  MlirConversionTarget target = mlirConversionTargetCreate(ctx);
+  mlirConversionTargetAddIllegalOp(
+      target, mlirStringRefCreateFromCString("test.multi"));
+  mlirConversionTargetAddLegalOp(target,
+                                 mlirStringRefCreateFromCString("test.r0"));
+  mlirConversionTargetAddLegalOp(target,
+                                 mlirStringRefCreateFromCString("test.r1"));
+  mlirConversionTargetAddLegalOp(target,
+                                 mlirStringRefCreateFromCString("test.use"));
+  mlirConversionTargetAddLegalOp(
+      target, mlirStringRefCreateFromCString("builtin.module"));
+
+  MlirConversionConfig config = mlirConversionConfigCreate();
+  MlirLogicalResult result =
+      mlirApplyPartialConversion(moduleOp, target, frozen, config);
+  assert(mlirLogicalResultIsSuccess(result));
+
+  mlirOperationDump(moduleOp);
+  // clang-format off
+  // CHECK: %[[a:.*]] = "test.r0"() : () -> i32
+  // CHECK: %[[b:.*]] = "test.r1"() : () -> i64
+  // CHECK: "test.use"(%[[a]], %[[b]]) : (i32, i64) -> ()
+  // clang-format on
+
+  mlirConversionConfigDestroy(config);
+  mlirConversionTargetDestroy(target);
+  mlirFrozenRewritePatternSetDestroy(frozen);
+  mlirTypeConverterDestroy(converter);
+  mlirModuleDestroy(module);
+
+  // CHECK: testConversionReplaceOpWithMultipleRanges: PASSED
+  fprintf(stderr, "testConversionReplaceOpWithMultipleRanges: PASSED\n");
+}
+
+void testTypeConverter1ToNOperandRequires1ToNCallback(MlirContext ctx) {
+  // CHECK-LABEL: @testTypeConverter1ToNOperandRequires1ToNCallback
+  fprintf(stderr, "@testTypeConverter1ToNOperandRequires1ToNCallback\n");
+
+  // The operand of `test.consumer` converts 1:N (i32 -> (i16, i16)), but the
+  // pattern only provides the 1:1 matchAndRewrite (matchAndRewrite1ToN is
+  // null). The driver's 1:1 dispatch cannot represent a 1:N-remapped operand,
+  // so the pattern fails to match and the conversion fails.
+  const char *moduleString = "%0 = \"test.producer\"() : () -> i32\n"
+                             "\"test.consumer\"(%0) : (i32) -> ()\n";
+  MlirModule module =
+      mlirModuleCreateParse(ctx, mlirStringRefCreateFromCString(moduleString));
+  MlirOperation moduleOp = mlirModuleGetOperation(module);
+
+  MlirTypeConverter converter = mlirTypeConverterCreate();
+  mlirTypeConverterAdd1ToNConversion(converter, splitI32, NULL);
+
+  MlirRewritePatternSet patterns = mlirRewritePatternSetCreate(ctx);
+  // Only the 1:1 matchAndRewrite is set; matchAndRewrite1ToN is null.
+  MlirConversionPatternCallbacks callbacks = {NULL, NULL, convertConsumer,
+                                              NULL};
+  MlirConversionPattern pattern = mlirOpConversionPatternCreate(
+      mlirStringRefCreateFromCString("test.consumer"), 1, ctx, converter,
+      callbacks, NULL, 0, NULL);
+  mlirRewritePatternSetAdd(patterns,
+                           mlirConversionPatternAsRewritePattern(pattern));
+  MlirFrozenRewritePatternSet frozen = mlirFreezeRewritePattern(patterns);
+
+  MlirConversionTarget target = mlirConversionTargetCreate(ctx);
+  mlirConversionTargetAddIllegalOp(
+      target, mlirStringRefCreateFromCString("test.consumer"));
+  mlirConversionTargetAddLegalOp(
+      target, mlirStringRefCreateFromCString("test.producer"));
+  mlirConversionTargetAddLegalOp(
+      target, mlirStringRefCreateFromCString("builtin.module"));
+
+  MlirConversionConfig config = mlirConversionConfigCreate();
+  MlirLogicalResult result =
+      mlirApplyPartialConversion(moduleOp, target, frozen, config);
+  assert(mlirLogicalResultIsFailure(result) &&
+         "1:1-only pattern must fail to legalize a 1:N-remapped operand");
+
+  mlirConversionConfigDestroy(config);
+  mlirConversionTargetDestroy(target);
+  mlirFrozenRewritePatternSetDestroy(frozen);
+  mlirTypeConverterDestroy(converter);
+  mlirModuleDestroy(module);
+
+  // CHECK: testTypeConverter1ToNOperandRequires1ToNCallback: PASSED
+  fprintf(stderr, "testTypeConverter1ToNOperandRequires1ToNCallback: 
PASSED\n");
+}
+
+void testTypeConverter1ToNConversionErasure(MlirContext ctx) {
+  // CHECK-LABEL: @testTypeConverter1ToNConversionErasure
+  fprintf(stderr, "@testTypeConverter1ToNConversionErasure\n");
+
+  // The i32 operand of `test.consumer` is erased (converted to zero types), so
+  // the 1:N pattern is invoked with no operands and builds a nullary
+  // `test.consumer_legal`.
+  const char *moduleString = "%0 = \"test.producer\"() : () -> i32\n"
+                             "\"test.consumer\"(%0) : (i32) -> ()\n";
+  MlirModule module =
+      mlirModuleCreateParse(ctx, mlirStringRefCreateFromCString(moduleString));
+  MlirOperation moduleOp = mlirModuleGetOperation(module);
+
+  MlirTypeConverter converter = mlirTypeConverterCreate();
+  mlirTypeConverterAdd1ToNConversion(converter, eraseI32, NULL);
+
+  MlirRewritePatternSet patterns = mlirRewritePatternSetCreate(ctx);
+  MlirConversionPatternCallbacks callbacks = {NULL, NULL, NULL,
+                                              convertConsumer1ToN};
+  MlirConversionPattern pattern = mlirOpConversionPatternCreate(
+      mlirStringRefCreateFromCString("test.consumer"), 1, ctx, converter,
+      callbacks, NULL, 0, NULL);
+  mlirRewritePatternSetAdd(patterns,
+                           mlirConversionPatternAsRewritePattern(pattern));
+  MlirFrozenRewritePatternSet frozen = mlirFreezeRewritePattern(patterns);
+
+  MlirConversionTarget target = mlirConversionTargetCreate(ctx);
+  mlirConversionTargetAddIllegalOp(
+      target, mlirStringRefCreateFromCString("test.consumer"));
+  mlirConversionTargetAddLegalOp(
+      target, mlirStringRefCreateFromCString("test.producer"));
+  mlirConversionTargetAddLegalOp(
+      target, mlirStringRefCreateFromCString("test.consumer_legal"));
+  mlirConversionTargetAddLegalOp(
+      target, mlirStringRefCreateFromCString("builtin.module"));
+
+  MlirConversionConfig config = mlirConversionConfigCreate();
+  MlirLogicalResult result =
+      mlirApplyPartialConversion(moduleOp, target, frozen, config);
+  assert(mlirLogicalResultIsSuccess(result));
+
+  mlirOperationDump(moduleOp);
+  // clang-format off
+  // CHECK: "test.consumer_legal"() : () -> ()
+  // clang-format on
+
+  mlirConversionConfigDestroy(config);
+  mlirConversionTargetDestroy(target);
+  mlirFrozenRewritePatternSetDestroy(frozen);
+  mlirTypeConverterDestroy(converter);
+  mlirModuleDestroy(module);
+
+  // CHECK: testTypeConverter1ToNConversionErasure: PASSED
+  fprintf(stderr, "testTypeConverter1ToNConversionErasure: PASSED\n");
+}
+
 int main(void) {
   MlirContext ctx = mlirContextCreate();
   mlirContextSetAllowUnregisteredDialects(ctx, true);
@@ -1037,6 +1759,13 @@ int main(void) {
   testConversionTargetDynamicLegality(ctx);
   testTypeConverterSourceMaterialization(ctx);
   testTypeConverterTargetMaterialization(ctx);
+  testTypeConverter1ToNTargetMaterialization(ctx);
+  testTypeConverter1ToNConversionDeclineRollback(ctx);
+  testTypeConverter1ToNTargetMaterializationDecline(ctx);
+  testTypeConverterMultiInputSourceMaterialization(ctx);
+  testConversionReplaceOpWithMultipleRanges(ctx);
+  testTypeConverter1ToNOperandRequires1ToNCallback(ctx);
+  testTypeConverter1ToNConversionErasure(ctx);
 
   mlirContextDestroy(ctx);
   return 0;

>From 185b7b747d8a497204f5cc6fcaf159c32734e18d Mon Sep 17 00:00:00 2001
From: makslevental <[email protected]>
Date: Tue, 30 Jun 2026 12:15:17 -0700
Subject: [PATCH 2/5] [mlir-c] Fix -Wmissing-field-initializers in rewrite.c
 test

The new matchAndRewrite1ToN field left three existing
MlirConversionPatternCallbacks initializers under-initialized, which
fails the CI build under -Werror=-Wmissing-field-initializers.
---
 mlir/test/CAPI/rewrite.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/mlir/test/CAPI/rewrite.c b/mlir/test/CAPI/rewrite.c
index 082af86152c9e..b850d1056f1a3 100644
--- a/mlir/test/CAPI/rewrite.c
+++ b/mlir/test/CAPI/rewrite.c
@@ -1059,7 +1059,7 @@ void testTypeConverterSourceMaterialization(MlirContext 
ctx) {
       converter, declineSourceMaterialization, &declinedCounter);
 
   MlirRewritePatternSet patterns = mlirRewritePatternSetCreate(ctx);
-  MlirConversionPatternCallbacks callbacks = {NULL, NULL, convertSource};
+  MlirConversionPatternCallbacks callbacks = {NULL, NULL, convertSource, NULL};
   MlirConversionPattern pattern = mlirOpConversionPatternCreate(
       mlirStringRefCreateFromCString("test.source"), 1, ctx, converter,
       callbacks, NULL, 0, NULL);
@@ -1178,7 +1178,8 @@ void testTypeConverterTargetMaterialization(MlirContext 
ctx) {
                                             &materializationCounter);
 
   MlirRewritePatternSet patterns = mlirRewritePatternSetCreate(ctx);
-  MlirConversionPatternCallbacks callbacks = {NULL, NULL, convertConsumer};
+  MlirConversionPatternCallbacks callbacks = {NULL, NULL, convertConsumer,
+                                              NULL};
   MlirConversionPattern pattern = mlirOpConversionPatternCreate(
       mlirStringRefCreateFromCString("test.consumer"), 1, ctx, converter,
       callbacks, NULL, 0, NULL);
@@ -1494,7 +1495,7 @@ void 
testTypeConverterMultiInputSourceMaterialization(MlirContext ctx) {
 
   MlirRewritePatternSet patterns = mlirRewritePatternSetCreate(ctx);
   MlirConversionPatternCallbacks callbacks = {NULL, NULL,
-                                              convertProducerToMultiple};
+                                              convertProducerToMultiple, NULL};
   MlirConversionPattern pattern = mlirOpConversionPatternCreate(
       mlirStringRefCreateFromCString("test.producer"), 1, ctx, converter,
       callbacks, NULL, 0, NULL);

>From 3a029cd0dd427826c5ed1125fd21f007a3c98493 Mon Sep 17 00:00:00 2001
From: makslevental <[email protected]>
Date: Tue, 30 Jun 2026 12:45:23 -0700
Subject: [PATCH 3/5] [mlir-c] Value-initialize MlirConversionPatternCallbacks
 in Python bindings

The Python conversion-pattern binding left the struct default-initialized,
so the newly-added optional matchAndRewrite1ToN field held an indeterminate
pointer. The driver's null check then read garbage and jumped into it,
segfaulting mlir/test/python/rewrite.py. Value-initialize the struct so
optional callbacks default to null.
---
 mlir/lib/Bindings/Python/Rewrite.cpp | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/mlir/lib/Bindings/Python/Rewrite.cpp 
b/mlir/lib/Bindings/Python/Rewrite.cpp
index 750c7b1e4b1d8..2a9b0ea1c218d 100644
--- a/mlir/lib/Bindings/Python/Rewrite.cpp
+++ b/mlir/lib/Bindings/Python/Rewrite.cpp
@@ -226,7 +226,9 @@ void PyRewritePatternSet::addConversion(nb::handle root,
   std::string opName = operationNameFromObject(root);
   MlirStringRef rootName = mlirStringRefCreate(opName.data(), opName.size());
 
-  MlirConversionPatternCallbacks callbacks;
+  // Value-initialize so optional callbacks (e.g. matchAndRewrite1ToN) default
+  // to null rather than an indeterminate pointer.
+  MlirConversionPatternCallbacks callbacks{};
   callbacks.construct = [](void *userData) {
     nb::handle(static_cast<PyObject *>(userData)).inc_ref();
   };

>From f698e79019a9b89f2e944b4540947f861dc3b32d Mon Sep 17 00:00:00 2001
From: makslevental <[email protected]>
Date: Fri, 17 Jul 2026 12:23:37 -0700
Subject: [PATCH 4/5] pre-increment

---
 mlir/test/CAPI/rewrite.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/mlir/test/CAPI/rewrite.c b/mlir/test/CAPI/rewrite.c
index b850d1056f1a3..16e40753c59dc 100644
--- a/mlir/test/CAPI/rewrite.c
+++ b/mlir/test/CAPI/rewrite.c
@@ -847,7 +847,7 @@ declineSourceMaterialization(MlirRewriterBase rewriter, 
MlirType outputType,
   (void)loc;
   intptr_t *declined = (intptr_t *)userData;
   if (declined)
-    (*declined)++;
+    ++(*declined);
   return (MlirValue){NULL};
 }
 
@@ -883,7 +883,7 @@ static MlirValue buildTargetCast(MlirRewriterBase rewriter, 
MlirType outputType,
   intptr_t *counter = (intptr_t *)userData;
   if (counter && mlirTypeIsAInteger(originalType) &&
       mlirIntegerTypeGetWidth(originalType) == 32)
-    (*counter)++;
+    ++(*counter);
   MlirOperationState state =
       mlirOperationStateGet(mlirStringRefCreateFromCString("test.cast"), loc);
   mlirOperationStateAddOperands(&state, nInputs, inputs);
@@ -935,7 +935,7 @@ static MlirLogicalResult buildSplitCast(MlirRewriterBase 
rewriter,
   // the (i16) output types alone, so observing it here proves it is 
propagated.
   if (counter && mlirTypeIsAInteger(originalType) &&
       mlirIntegerTypeGetWidth(originalType) == 32)
-    (*counter)++;
+    ++(*counter);
   for (intptr_t i = 0; i < nOutputTypes; ++i) {
     MlirOperationState state =
         mlirOperationStateGet(mlirStringRefCreateFromCString("test.cast"), 
loc);
@@ -955,7 +955,7 @@ static MlirLogicalResult appendThenDeclineConversion(
     MlirType type, MlirTypeConverterConversionResults results, void *userData) 
{
   intptr_t *counter = (intptr_t *)userData;
   if (counter)
-    (*counter)++;
+    ++(*counter);
   MlirType i8 = mlirIntegerTypeGet(mlirTypeGetContext(type), 8);
   // Append more (and differently-typed) entries than the real conversion 
would,
   // so any leak is observable as a wrong type/arity downstream.
@@ -981,7 +981,7 @@ static MlirLogicalResult declineTargetMaterialization(
   (void)outputs;
   intptr_t *counter = (intptr_t *)userData;
   if (counter)
-    (*counter)++;
+    ++(*counter);
   return mlirLogicalResultFailure();
 }
 
@@ -1002,7 +1002,7 @@ static MlirLogicalResult partialTargetMaterialization(
   (void)outputs;
   intptr_t *counter = (intptr_t *)userData;
   if (counter)
-    (*counter)++;
+    ++(*counter);
   return mlirLogicalResultSuccess();
 }
 // op whose result has the widened (i64) type. Because the original result type

>From e4b62b0279fad81a1d2969822e9f009f31d8f04d Mon Sep 17 00:00:00 2001
From: makslevental <[email protected]>
Date: Fri, 17 Jul 2026 13:08:44 -0700
Subject: [PATCH 5/5] cleanup comments

---
 mlir/include/mlir-c/Rewrite.h        | 20 ++------------------
 mlir/lib/CAPI/Transforms/Rewrite.cpp | 12 ------------
 2 files changed, 2 insertions(+), 30 deletions(-)

diff --git a/mlir/include/mlir-c/Rewrite.h b/mlir/include/mlir-c/Rewrite.h
index b3e3eb6e88b23..968690f79c321 100644
--- a/mlir/include/mlir-c/Rewrite.h
+++ b/mlir/include/mlir-c/Rewrite.h
@@ -505,9 +505,7 @@ mlirConversionPatternRewriterConvertRegionTypes(
 /// result of `op` -- and erase it. `nRanges` must equal the number of results
 /// of `op`. `rangeSizes[i]` is the number of values in the i-th range, and
 /// `values` is the flat concatenation of all ranges (its length is the sum of
-/// `rangeSizes[0..nRanges)`). This is the 1:N replacement form; surviving type
-/// mismatches between a result and its range are reconciled by the driver with
-/// source/target materializations.
+/// `rangeSizes[0..nRanges)`).
 MLIR_CAPI_EXPORTED void mlirConversionPatternRewriterReplaceOpWithMultiple(
     MlirConversionPatternRewriter rewriter, MlirOperation op, intptr_t nRanges,
     intptr_t *rangeSizes, MlirValue *values);
@@ -652,13 +650,6 @@ mlirTypeConverterConvertType(MlirTypeConverter 
typeConverter, MlirType type);
 /// callback must build a cast-like operation that produces a single value of
 /// `outputType` and return it. Returning a null MlirValue indicates failure, 
in
 /// which case another registered materialization may be attempted.
-///
-/// Note: Source materializations are single-output -- the callback returns one
-/// value of `outputType`. (The 1:N, multiple-output form exists only for 
target
-/// materializations; see MlirTypeConverter1ToNTargetMaterializationCallback.)
-/// `nInputs` may be greater than one when several values are mapped back to a
-/// single value, e.g. after a 1:N replacement via
-/// mlirConversionPatternRewriterReplaceOpWithMultiple.
 typedef MlirValue (*MlirTypeConverterSourceMaterializationCallback)(
     MlirRewriterBase rewriter, MlirType outputType, intptr_t nInputs,
     MlirValue *inputs, MlirLocation loc, void *userData);
@@ -667,12 +658,6 @@ typedef MlirValue 
(*MlirTypeConverterSourceMaterializationCallback)(
 /// MlirTypeConverterSourceMaterializationCallback, but additionally receives
 /// `originalType`: the original type of the SSA value being materialized.
 ///
-/// `originalType` may differ from `outputType` and cannot, in general, be
-/// recovered from `outputType` and `inputs`, which is why it is passed
-/// explicitly (see TypeConverter::addTargetMaterialization in
-/// DialectConversion.h for the full rationale). It may be a null MlirType when
-/// no original type is available.
-///
 /// Note: This callback is single-output. For the 1:N (multiple-output) form,
 /// use MlirTypeConverter1ToNTargetMaterializationCallback.
 typedef MlirValue (*MlirTypeConverterTargetMaterializationCallback)(
@@ -739,8 +724,7 @@ typedef struct {
   /// of all operand ranges; there are `nRanges` ranges (one per original
   /// operand) and `rangeSizes[i]` is the number of values in the i-th range.
   /// When this is non-null it takes precedence; when null, the driver falls
-  /// back to the 1:1 `matchAndRewrite` above, which fails to match if any
-  /// operand was remapped 1:N.
+  /// back to the 1:1 `matchAndRewrite` above.
   MlirLogicalResult (*matchAndRewrite1ToN)(
       MlirConversionPattern pattern, MlirOperation op, intptr_t nRanges,
       intptr_t *rangeSizes, intptr_t nOperands, MlirValue *operands,
diff --git a/mlir/lib/CAPI/Transforms/Rewrite.cpp 
b/mlir/lib/CAPI/Transforms/Rewrite.cpp
index e3dcede2d42b9..5f0da4776e7bd 100644
--- a/mlir/lib/CAPI/Transforms/Rewrite.cpp
+++ b/mlir/lib/CAPI/Transforms/Rewrite.cpp
@@ -713,8 +713,6 @@ MlirType mlirTypeConverterConvertType(MlirTypeConverter 
typeConverter,
 }
 
 namespace {
-/// Marshals the `inputs` ValueRange into wrapped MlirValues. Shared by the
-/// materialization wrappers below.
 SmallVector<MlirValue> wrapInputs(ValueRange inputs) {
   SmallVector<MlirValue> wrappedInputs;
   wrappedInputs.reserve(inputs.size());
@@ -723,10 +721,6 @@ SmallVector<MlirValue> wrapInputs(ValueRange inputs) {
   return wrappedInputs;
 }
 
-/// Wraps a C source materialization callback as the C++ form
-/// `Value(OpBuilder &, Type, ValueRange, Location)`. The builder is always a
-/// RewriterBase in the conversion driver, so it is safe to expose it as an
-/// MlirRewriterBase.
 std::function<Value(OpBuilder &, Type, ValueRange, Location)>
 wrapSourceMaterializationCallback(
     MlirTypeConverterSourceMaterializationCallback callback, void *userData) {
@@ -741,10 +735,6 @@ wrapSourceMaterializationCallback(
   };
 }
 
-/// Wraps a C 1:1 target materialization callback as the C++ form
-/// `Value(OpBuilder &, Type, ValueRange, Location, Type)`. Selecting the
-/// 5-argument C++ overload (the one carrying `originalType`) is what makes the
-/// original type observable from C.
 std::function<Value(OpBuilder &, Type, ValueRange, Location, Type)>
 wrapTargetMaterializationCallback(
     MlirTypeConverterTargetMaterializationCallback callback, void *userData) {
@@ -759,8 +749,6 @@ wrapTargetMaterializationCallback(
   };
 }
 
-/// Wraps a C 1:N target materialization callback as the C++ form
-/// `SmallVector<Value>(OpBuilder &, TypeRange, ValueRange, Location, Type)`.
 std::function<SmallVector<Value>(OpBuilder &, TypeRange, ValueRange, Location,
                                  Type)>
 wrap1ToNTargetMaterializationCallback(

_______________________________________________
llvm-branch-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits

Reply via email to