@@ -227,25 +227,40 @@ static ParseResult parseLayoutDims(AsmParser& parser,
227227 }
228228}
229229
230+ // One argument of a roll pair: a bare non-negative integer is a dims-list
231+ // position (one piece); `axis N` names the whole tensor axis N.
232+ static ParseResult parseRollArg (AsmParser& parser, int64_t & encoded) {
233+ const bool isAxis = succeeded (parser.parseOptionalKeyword (" axis" ));
234+ int64_t value;
235+ if (parser.parseInteger (value)) return failure ();
236+ if (value < 0 ) {
237+ return parser.emitError (parser.getNameLoc ())
238+ << (isAxis ? " an axis roll argument must name a non-negative "
239+ " tensor axis"
240+ : " a piece roll argument must be a non-negative dims "
241+ " position (spell a whole-axis argument as `axis N`)" );
242+ }
243+ encoded = encodeRollArg ({isAxis, value});
244+ return success ();
245+ }
246+
230247static ParseResult parseLayoutRolls (AsmParser& parser,
231248 SmallVector<int64_t >& rolls) {
232249 if (parser.parseLSquare ()) return failure ();
233250 if (succeeded (parser.parseOptionalRSquare ())) return success ();
234251
252+ // Each entry is one `(from, by)` pair, parenthesized -- the form the
253+ // printer emits, so written and round-tripped layouts read alike.
235254 while (true ) {
236- if (succeeded (parser.parseOptionalLParen ())) {
237- int64_t from;
238- int64_t to;
239- if (parser.parseInteger (from) || parser.parseComma () ||
240- parser.parseInteger (to) || parser.parseRParen ())
241- return failure ();
242- rolls.push_back (from);
243- rolls.push_back (to);
244- } else {
245- int64_t value;
246- if (parser.parseInteger (value)) return failure ();
247- rolls.push_back (value);
255+ int64_t from;
256+ int64_t by;
257+ if (parser.parseLParen () || failed (parseRollArg (parser, from)) ||
258+ parser.parseComma () || failed (parseRollArg (parser, by)) ||
259+ parser.parseRParen ()) {
260+ return failure ();
248261 }
262+ rolls.push_back (from);
263+ rolls.push_back (by);
249264
250265 if (succeeded (parser.parseOptionalComma ())) continue ;
251266 return parser.parseRSquare ();
@@ -255,48 +270,109 @@ static ParseResult parseLayoutRolls(AsmParser& parser,
255270static LogicalResult verifyLayoutRolls (
256271 ArrayAttr dims, DenseI64ArrayAttr rolls,
257272 function_ref<InFlightDiagnostic()> emitError) {
258- if (!rolls) return success ();
273+ const bool noRolls = !rolls || rolls.empty ();
274+ if (noRolls) return success ();
259275 ArrayRef<int64_t > r = rolls.asArrayRef ();
260- if (r.empty ()) return success ();
261276 if (r.size () % 2 != 0 ) {
262- return emitError () << " rolls must contain an even number of integers "
263- " (pairs of dim indices )" ;
277+ return emitError () << " rolls must contain an even number of arguments "
278+ " (pairs)" ;
264279 }
265280
281+ // Traversal pieces of a tensor axis: their count decides whether an `axis`
282+ // argument is legal (split axes only -- the piece spelling is canonical
283+ // when the axis is one piece), and their extent product is the modulus a
284+ // whole-axis rewrite reduces by.
285+ auto piecesOfAxis = [&](int64_t axis) {
286+ std::pair<int64_t , int64_t > countAndExtent{0 , 1 };
287+ for (Attribute a : dims) {
288+ auto d = dyn_cast<DimAttr>(a);
289+ if (d && !d.isGap () && !d.isReplicate () && d.getDim () == axis) {
290+ ++countAndExtent.first ;
291+ countAndExtent.second *= d.getSize ();
292+ }
293+ }
294+ return countAndExtent;
295+ };
296+
266297 for (size_t i = 0 ; i < r.size (); i += 2 ) {
267- const int64_t ti = r[i];
268- const int64_t tj = r[i + 1 ];
269- if (ti == tj) {
270- return emitError () << " each roll must use two distinct dim indices" ;
298+ const RollArg from = decodeRollArg (r[i]);
299+ const RollArg by = decodeRollArg (r[i + 1 ]);
300+
301+ // Resolve each argument: the piece it names (null for axis arguments)
302+ // and the tensor axis it reads or rewrites (sentinel for gap/replication
303+ // pieces).
304+ DimAttr fromPiece;
305+ DimAttr byPiece;
306+ int64_t fromAxis = 0 ;
307+ int64_t byAxis = 0 ;
308+ auto checkArg = [&](const RollArg& e, DimAttr& piece,
309+ int64_t & axis) -> LogicalResult {
310+ if (e.isAxis ) {
311+ auto [count, extent] = piecesOfAxis (e.index );
312+ (void )extent;
313+ if (count == 0 ) {
314+ return emitError () << " an axis roll argument must name a tensor "
315+ " axis present in dims" ;
316+ }
317+ if (count == 1 ) {
318+ return emitError () << " an axis roll argument requires a split "
319+ " axis; spell an unsplit axis's argument as "
320+ " its piece position" ;
321+ }
322+ axis = e.index ;
323+ return success ();
324+ }
325+ if (e.index >= static_cast <int64_t >(dims.size ())) {
326+ return emitError () << " roll piece argument out of bounds for dims "
327+ " list" ;
328+ }
329+ piece = dyn_cast<DimAttr>(dims[e.index ]);
330+ if (!piece) {
331+ return emitError () << " roll arguments must refer to #rotom.dim "
332+ " entries" ;
333+ }
334+ axis = piece.getDim ();
335+ return success ();
336+ };
337+ if (failed (checkArg (from, fromPiece, fromAxis)) ||
338+ failed (checkArg (by, byPiece, byAxis))) {
339+ return failure ();
271340 }
272- if (ti < 0 || tj < 0 || ti >= static_cast <int64_t >(dims.size ()) ||
273- tj >= static_cast <int64_t >(dims.size ())) {
274- return emitError () << " roll dim index out of bounds for dims list" ;
341+
342+ // The extents need not match: a roll rewrites the from index to
343+ // (idx - shift) mod extent(from), well-defined for any partner extent (a
344+ // smaller partner covers a prefix of the rotations, a larger one wraps).
345+ // FROM is the index expression being rewritten, so it must be a
346+ // traversal piece or a whole (traversal) axis. The by argument may be
347+ // any kind: rolling by a replication or gap piece shifts by that piece's
348+ // block index, so each block holds a distinct cyclic rotation of the
349+ // rolled index -- the layout materializes every rotation and alignment
350+ // becomes block selection. (A rolled-by gap thus claims its blocks,
351+ // unlike a plain gap.)
352+ if (!from.isAxis && (fromPiece.isGap () || fromPiece.isReplicate ())) {
353+ return emitError () << " the rolled dim must be a traversal dim (dim >= "
354+ " 0)" ;
275355 }
276- auto di = dyn_cast<DimAttr>(dims[ti]);
277- auto dj = dyn_cast<DimAttr>(dims[tj]);
278- if (!di || !dj) {
279- return emitError () << " roll indices must refer to #rotom.dim entries" ;
356+ // A roll may not shift an index by itself. Piece arguments must be
357+ // distinct positions (two pieces of one axis are distinct digits); an
358+ // axis argument overlaps every argument on the same axis, because a
359+ // whole-axis rewrite touches all of its digits.
360+ if (!from.isAxis && !by.isAxis && from.index == by.index ) {
361+ return emitError () << " each roll must use two distinct arguments" ;
280362 }
281- // The extents need not match: roll(i, j) rewrites dims[i]'s index to
282- // (i_i - i_j) mod size(dims[i]), well-defined for any partner extent (a
283- // smaller partner covers a prefix of the rotations, a larger one wraps).
284- // The rolled (from) dim must be a traversal dim -- it is the index
285- // expression being rewritten. The roll-by (second) dim may be any kind:
286- // rolling by a replication or gap dim shifts by that dim's block index,
287- // so each block holds a distinct cyclic rotation of the rolled dim -- the
288- // layout materializes every rotation and alignment becomes block
289- // selection. (A rolled-by gap thus claims its blocks, unlike a plain gap.)
290- if (di.isGap () || di.isReplicate ()) {
291- return emitError () << " the rolled dim must be a traversal dim (dim >= 0)" ;
363+ if ((from.isAxis || by.isAxis ) && fromAxis == byAxis) {
364+ return emitError () << " a roll may not shift an axis by one of its own "
365+ " pieces" ;
292366 }
293367 // A rolled-by GAP claims one ciphertext block per gap index, each holding
294- // a distinct rotation of the rolled dim . If the gap is larger than the
295- // rolled dim's extent the rotations repeat (period = the from extent),
296- // claiming blocks the conversion/kernel accounting was never audited for.
368+ // a distinct rotation of the rolled index . If the gap is larger than the
369+ // rolled extent the rotations repeat (period = the from extent), claiming
370+ // blocks the conversion/kernel accounting was never audited for.
297371 // (Replication partners of larger extent are intended -- replicate-then-
298372 // roll -- so only gaps are bounded.)
299- if (dj.isGap () && dj.getSize () > di.getSize ()) {
373+ const int64_t fromExtent =
374+ from.isAxis ? piecesOfAxis (fromAxis).second : fromPiece.getSize ();
375+ if (byPiece && byPiece.isGap () && byPiece.getSize () > fromExtent) {
300376 return emitError () << " a rolled-by gap dim must not exceed the rolled "
301377 " dim's extent" ;
302378 }
@@ -345,10 +421,19 @@ void LayoutAttr::print(AsmPrinter& printer) const {
345421 DenseI64ArrayAttr rolls = getRolls ();
346422 if (rolls && !rolls.asArrayRef ().empty ()) {
347423 ArrayRef<int64_t > values = rolls.asArrayRef ();
424+ auto printArg = [&](int64_t encoded) {
425+ const RollArg e = decodeRollArg (encoded);
426+ if (e.isAxis ) printer << " axis " ;
427+ printer << e.index ;
428+ };
348429 printer << " , rolls = [" ;
349430 for (size_t i = 0 ; i < values.size (); i += 2 ) {
350431 if (i != 0 ) printer << " , " ;
351- printer << " (" << values[i] << " , " << values[i + 1 ] << " )" ;
432+ printer << " (" ;
433+ printArg (values[i]);
434+ printer << " , " ;
435+ printArg (values[i + 1 ]);
436+ printer << " )" ;
352437 }
353438 printer << " ]" ;
354439 }
@@ -436,6 +521,18 @@ Attribute LayoutAttr::parse(AsmParser& parser, Type type) {
436521 ArrayAttr::get (context, dims), n, DenseI64ArrayAttr::get (context, rolls));
437522}
438523
524+ SmallVector<RollSpec> getRollSpecs (LayoutAttr layout) {
525+ SmallVector<RollSpec> specs;
526+ DenseI64ArrayAttr rolls = layout.getRolls ();
527+ if (!rolls) return specs;
528+ ArrayRef<int64_t > r = rolls.asArrayRef ();
529+
530+ for (size_t i = 0 ; i + 1 < r.size (); i += 2 ) {
531+ specs.push_back ({decodeRollArg (r[i]), decodeRollArg (r[i + 1 ])});
532+ }
533+ return specs;
534+ }
535+
439536FailureOr<LayoutData> preprocessLayoutAttr (LayoutAttr layout) {
440537 return preprocessLayoutData (layout.getDims (), layout.getN (),
441538 layout.getContext ());
@@ -452,7 +549,9 @@ LogicalResult LayoutAttr::verify(function_ref<InFlightDiagnostic()> emitError,
452549 return emitError () << " `dims` must be an array of `#rotom.dim<...>`" ;
453550 }
454551
455- if (failed (verifyLayoutRolls (dims, rolls, emitError))) return failure ();
552+ if (failed (verifyLayoutRolls (dims, rolls, emitError))) {
553+ return failure ();
554+ }
456555
457556 SmallVector<DimAttr> dimVec;
458557 dimVec.reserve (dims.size ());
@@ -508,7 +607,8 @@ void canonicalizeLayoutDims(MLIRContext* ctx, SmallVector<DimAttr>& dims,
508607 if (fill <= 1 ) return ;
509608 dims.insert (dims.begin () + ctLen,
510609 DimAttr::get (ctx, /* dim=*/ -2 , fill, /* stride=*/ 1 ));
511- // Roll endpoints at or past the insertion shift right.
610+ // Piece arguments at or past the insertion shift right; axis arguments
611+ // (encoded negative) name axes and do not move.
512612 for (int64_t & encoded : rolls) {
513613 if (encoded >= static_cast <int64_t >(ctLen)) ++encoded;
514614 }
0 commit comments