4949#include " mlir/include/mlir/IR/BuiltinAttributes.h" // from @llvm-project
5050#include " mlir/include/mlir/IR/BuiltinTypes.h" // from @llvm-project
5151#include " mlir/include/mlir/IR/Diagnostics.h" // from @llvm-project
52+ #include " mlir/include/mlir/IR/Matchers.h" // from @llvm-project
5253#include " mlir/include/mlir/IR/Operation.h" // from @llvm-project
5354#include " mlir/include/mlir/IR/PatternMatch.h" // from @llvm-project
5455#include " mlir/include/mlir/IR/Types.h" // from @llvm-project
@@ -134,9 +135,95 @@ std::pair<Value, LayoutAttr> convertToLayout(
134135 return std::make_pair (toReplace, layoutAttr);
135136}
136137
138+ // The outcome of folding a zero `tensor.pad` on the width dim into a 1-D
139+ // conv's own `padding` parameter.
140+ struct FoldedConvPadding {
141+ // What the Toeplitz matrix must be built against.
142+ ConvMatrixOperand matrixOperand;
143+ // The layout the padded operand must already carry for the fold to be valid.
144+ IntegerRelation targetRelation;
145+ };
146+
147+ // Try to fold a zero `tensor.pad` on the width dim of a conv's `data` operand
148+ // into the conv's own `padding` parameter.
149+ //
150+ // Returns nullopt when the pattern does not apply, in which case the caller
151+ // keeps the unfolded path. `dataType` must be rank 3 with N=1.
152+ std::optional<FoldedConvPadding> tryFoldPadIntoConvPadding (
153+ Value data, RankedTensorType dataType, LayoutAttr dataLayout,
154+ int64_t ciphertextSize) {
155+ assert (dataType.getRank () == 3 && dataType.getDimSize (0 ) == 1 &&
156+ " expected a rank-3 N=1 conv data operand" );
157+
158+ // DropUnitDims rewrites a rank-3 pad into
159+ // collapse_shape -> tensor.pad (rank 2) -> expand_shape, so peel any
160+ // reshape/cast chain to find the pad.
161+ Value cursor = data;
162+ tensor::PadOp padOp;
163+ while (Operation* def = cursor.getDefiningOp ()) {
164+ if (auto p = dyn_cast<tensor::PadOp>(def)) {
165+ padOp = p;
166+ break ;
167+ }
168+ if (isa<tensor::ExpandShapeOp, tensor::CollapseShapeOp, tensor::CastOp>(
169+ def)) {
170+ cursor = def->getOperand (0 );
171+ continue ;
172+ }
173+ break ;
174+ }
175+ if (!padOp) return std::nullopt ;
176+
177+ Value padValue = padOp.getConstantPaddingValue ();
178+ bool zeroPad = padValue && (matchPattern (padValue, m_AnyZeroFloat ()) ||
179+ matchPattern (padValue, m_Zero ()));
180+ if (!zeroPad) return std::nullopt ;
181+
182+ // Only a symmetric pad on the trailing (width) dim is expressible as the
183+ // conv's `padding` parameter, and only when the bounds are static. The pad
184+ // may be rank 2 (unit batch dim dropped) or rank 3.
185+ if (!padOp.getLow ().empty () || !padOp.getHigh ().empty ()) return std::nullopt ;
186+ ArrayRef<int64_t > low = padOp.getStaticLow ();
187+ ArrayRef<int64_t > high = padOp.getStaticHigh ();
188+ bool widthOnly = !low.empty () && low.size () == high.size () &&
189+ low.back () == high.back () && low.back () > 0 ;
190+ for (size_t i = 0 ; widthOnly && i + 1 < low.size (); ++i) {
191+ widthOnly &= low[i] == 0 && high[i] == 0 ;
192+ }
193+ if (!widthOnly) return std::nullopt ;
194+
195+ // The reshape chain above means the pad's width dim is not guaranteed to be
196+ // this conv operand's width dim, so validate before building a type from it.
197+ int64_t p = low.back ();
198+ std::optional<ConvMatrixOperand> matrixOperand =
199+ foldConvWidthPadding (dataType, p);
200+ if (!matrixOperand) return std::nullopt ;
201+
202+ // The layout we expect on the padded value: the unpadded row-major layout
203+ // with the width index shifted by `p`. If the actual layout is anything else
204+ // (a conversion intervened, a non-row-major producer, reshapes that did not
205+ // cancel) do not fold, rather than silently mis-indexing the matrix.
206+ IntegerRelation expected =
207+ getRowMajorLayoutRelation (matrixOperand->dataType , ciphertextSize);
208+ expected = shiftVar (
209+ expected, expected.getVarKindOffset (presburger::VarKind::Domain) + 2 , p);
210+ if (!dataLayout.getIntegerRelation ().isEqual (expected)) {
211+ LLVM_DEBUG (llvm::dbgs ()
212+ << " conv_1d found a pad of " << p
213+ << " but the operand layout does not match the shifted "
214+ " unpadded row-major layout; not folding\n " );
215+ return std::nullopt ;
216+ }
217+
218+ LLVM_DEBUG (llvm::dbgs () << " conv_1d folding tensor.pad of " << p
219+ << " into the conv padding parameter\n " );
220+ return FoldedConvPadding{*matrixOperand, expected};
221+ }
222+
137223// Return a copy of the kernel info associated with the value and update the
138224// result shape to the new result shape. If the value does not have a kernel
139225// info, return an empty Attribute.
226+
140227Attribute cloneKernelInfoWithResultShape (Value value,
141228 ArrayRef<int64_t > resultShape) {
142229 auto kernelInfo = findAttributeAssociatedWith (value, kKernelInfoAttrName );
@@ -179,6 +266,7 @@ struct LayoutPropagation : impl::LayoutPropagationBase<LayoutPropagation> {
179266 LogicalResult visitOperation (tensor::InsertOp op);
180267 LogicalResult visitOperation (tensor::InsertSliceOp op);
181268 LogicalResult visitOperation (tensor::ExtractSliceOp op);
269+ LogicalResult visitOperation (tensor::PadOp op);
182270
183271 // Determine if the operation arguments have compatible layouts for the
184272 // given op. If the check fails, the CompatibilityResult::compatible field
@@ -353,8 +441,8 @@ LogicalResult LayoutPropagation::visitOperation(Operation* op) {
353441 .Case <affine::AffineForOp>([&](auto op) { return visitOperation (op); })
354442 // tensor ops
355443 .Case <tensor::ExtractOp, tensor::InsertOp, tensor::InsertSliceOp,
356- tensor::ExtractSliceOp, CollapseShapeOp, ExpandShapeOp>(
357- [&](auto op) { return visitOperation (op); })
444+ tensor::ExtractSliceOp, tensor::PadOp, CollapseShapeOp,
445+ ExpandShapeOp>( [&](auto op) { return visitOperation (op); })
358446 // AddI, AddF, mgmt.* all pass the layout through unchanged.
359447 .Default ([&](Operation* op) {
360448 passLayoutThroughOp (op);
@@ -907,8 +995,15 @@ LogicalResult LayoutPropagation::visitOperation(Conv1DNcwFcwOp op) {
907995 }
908996
909997 LayoutAttr dataLayout = getComposedLayoutAttr (data);
998+
999+ ConvMatrixOperand matrixOperand{dataType};
9101000 IntegerRelation targetDataRelation =
9111001 getRowMajorLayoutRelation (dataType, minSlotCount);
1002+ if (auto folded =
1003+ tryFoldPadIntoConvPadding (data, dataType, dataLayout, minSlotCount)) {
1004+ matrixOperand = folded->matrixOperand ;
1005+ targetDataRelation = folded->targetRelation ;
1006+ }
9121007
9131008 if (!isRelationEqual (dataLayout.getIntegerRelation (), targetDataRelation)) {
9141009 LLVM_DEBUG (llvm::dbgs () << " conv_1d data input is not row major, "
@@ -923,8 +1018,8 @@ LogicalResult LayoutPropagation::visitOperation(Conv1DNcwFcwOp op) {
9231018 // into a larger matrix and then diagonalizing.
9241019 LayoutAttr filterLayout = getComposedLayoutAttr (filter);
9251020 auto convRelation = get1dConvCwFcwFilterDiagonalizedRelation (
926- filterType, dataType, stride, /* padding= */ 0 , minSlotCount ,
927- /* interchangeRows=*/ interchangeRows);
1021+ filterType, matrixOperand. dataType , stride, matrixOperand. padding ,
1022+ minSlotCount, /* interchangeRows=*/ interchangeRows);
9281023 if (failed (convRelation)) {
9291024 return failure ();
9301025 }
@@ -956,6 +1051,10 @@ LogicalResult LayoutPropagation::visitOperation(Conv1DNcwFcwOp op) {
9561051 Attribute kernelInfoAttr =
9571052 cloneKernelInfoWithResultShape (data, outputType.getShape ());
9581053
1054+ // Record what the filter was diagonalized against, so that
1055+ // ConvertToCiphertextSemantics rebuilds the same expanded matrix shape.
1056+ setConvFoldedPadding (op, matrixOperand.padding );
1057+
9591058 assignedLayouts.insert ({result, resultLayoutAttr});
9601059 setResultLayoutAttr (op, kernelInfoAttr);
9611060 auto kernelAttr =
@@ -1395,6 +1494,48 @@ LogicalResult LayoutPropagation::visitOperation(tensor::InsertSliceOp op) {
13951494 return success ();
13961495}
13971496
1497+ LogicalResult LayoutPropagation::visitOperation (tensor::PadOp op) {
1498+ // A zero-pad does not move any data: result[i + low] = source[i], so the
1499+ // result layout is the source layout with each domain index shifted by the
1500+ // low padding. Pad positions stay unmapped in the relation; unmapped points
1501+ // are zero-filled when a layout is materialized, which matches the
1502+ // zero-fill pad body. (The default layout passthrough would instead map
1503+ // result[i] to source[i]'s slots — off by `low`, shifting every downstream
1504+ // consumer's reads by one stride per pad.)
1505+ if (!assignedLayouts.contains (op.getSource ())) {
1506+ return op->emitError () << " Source tensor has no assigned layout" ;
1507+ }
1508+ Value padValue = op.getConstantPaddingValue ();
1509+ if (!padValue || !(matchPattern (padValue, m_AnyZeroFloat ()) ||
1510+ matchPattern (padValue, m_Zero ()))) {
1511+ return op->emitError ()
1512+ << " layout propagation only supports zero-padding tensor.pad" ;
1513+ }
1514+ if (!op.getLow ().empty () || !op.getHigh ().empty ()) {
1515+ return op->emitError ()
1516+ << " layout propagation requires static tensor.pad bounds" ;
1517+ }
1518+
1519+ IntegerRelation padRelation =
1520+ getComposedLayoutAttr (op.getSource ()).getIntegerRelation ();
1521+ auto domainVarOffset =
1522+ padRelation.getVarKindOffset (presburger::VarKind::Domain);
1523+ for (auto [dim, low] : llvm::enumerate (op.getStaticLow ())) {
1524+ if (low != 0 ) {
1525+ padRelation = shiftVar (padRelation, domainVarOffset + dim, low);
1526+ }
1527+ }
1528+
1529+ LayoutAttr outputLayout =
1530+ LayoutAttr::getFromIntegerRelation (op.getContext (), padRelation);
1531+ Attribute kernelInfoAttr = cloneKernelInfoWithResultShape (
1532+ op.getSource (), op.getResultType ().getShape ());
1533+ assignedLayouts.insert ({op.getResult (), outputLayout});
1534+ debugAssignLayout (op.getResult (), outputLayout);
1535+ setResultLayoutAttr (op, kernelInfoAttr);
1536+ return success ();
1537+ }
1538+
13981539LogicalResult LayoutPropagation::visitOperation (tensor::ExtractSliceOp op) {
13991540 // Assign the induced layout from extracting a slice from the source tensor.
14001541 if (!assignedLayouts.contains (op.getSource ())) {
0 commit comments