@@ -227,25 +227,44 @@ static ParseResult parseLayoutDims(AsmParser& parser,
227227 }
228228}
229229
230+ // One endpoint 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 parseRollEndpoint (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 endpoint must name a non-negative "
239+ " tensor axis"
240+ : " a piece roll endpoint must be a non-negative dims "
241+ " position (spell a whole-axis endpoint as `axis N`)" );
242+ }
243+ encoded = encodeRollEndpoint ({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 complete roll pair, either a `(from, to)` tuple or two
253+ // bare endpoints `from, to`.
235254 while (true ) {
255+ int64_t from;
256+ int64_t to;
236257 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 ())
258+ if (failed (parseRollEndpoint (parser, from)) || parser.parseComma () ||
259+ failed (parseRollEndpoint (parser, to)) || parser.parseRParen ())
241260 return failure ();
242- rolls.push_back (from);
243- rolls.push_back (to);
244261 } else {
245- int64_t value;
246- if (parser. parseInteger (value)) return failure ();
247- rolls. push_back (value );
262+ if ( failed ( parseRollEndpoint (parser, from)) || parser. parseComma () ||
263+ failed ( parseRollEndpoint (parser, to)))
264+ return failure ( );
248265 }
266+ rolls.push_back (from);
267+ rolls.push_back (to);
249268
250269 if (succeeded (parser.parseOptionalComma ())) continue ;
251270 return parser.parseRSquare ();
@@ -255,48 +274,109 @@ static ParseResult parseLayoutRolls(AsmParser& parser,
255274static LogicalResult verifyLayoutRolls (
256275 ArrayAttr dims, DenseI64ArrayAttr rolls,
257276 function_ref<InFlightDiagnostic()> emitError) {
258- if (!rolls) return success ();
277+ const bool noRolls = !rolls || rolls.empty ();
278+ if (noRolls) return success ();
259279 ArrayRef<int64_t > r = rolls.asArrayRef ();
260- if (r.empty ()) return success ();
261280 if (r.size () % 2 != 0 ) {
262- return emitError () << " rolls must contain an even number of integers "
263- " (pairs of dim indices )" ;
281+ return emitError () << " rolls must contain an even number of endpoints "
282+ " (pairs)" ;
264283 }
265284
285+ // Traversal pieces of a tensor axis: their count decides whether an `axis`
286+ // endpoint is legal (split axes only -- the piece spelling is canonical
287+ // when the axis is one piece), and their extent product is the modulus a
288+ // whole-axis rewrite reduces by.
289+ auto piecesOfAxis = [&](int64_t axis) {
290+ std::pair<int64_t , int64_t > countAndExtent{0 , 1 };
291+ for (Attribute a : dims) {
292+ auto d = dyn_cast<DimAttr>(a);
293+ if (d && !d.isGap () && !d.isReplicate () && d.getDim () == axis) {
294+ ++countAndExtent.first ;
295+ countAndExtent.second *= d.getSize ();
296+ }
297+ }
298+ return countAndExtent;
299+ };
300+
266301 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" ;
302+ const RollEndpoint from = decodeRollEndpoint (r[i]);
303+ const RollEndpoint by = decodeRollEndpoint (r[i + 1 ]);
304+
305+ // Resolve each endpoint: the piece it names (null for axis endpoints)
306+ // and the tensor axis it reads or rewrites (sentinel for gap/replication
307+ // pieces).
308+ DimAttr fromPiece;
309+ DimAttr byPiece;
310+ int64_t fromAxis = 0 ;
311+ int64_t byAxis = 0 ;
312+ auto checkEndpoint = [&](const RollEndpoint& e, DimAttr& piece,
313+ int64_t & axis) -> LogicalResult {
314+ if (e.isAxis ) {
315+ auto [count, extent] = piecesOfAxis (e.index );
316+ (void )extent;
317+ if (count == 0 ) {
318+ return emitError () << " an axis roll endpoint must name a tensor "
319+ " axis present in dims" ;
320+ }
321+ if (count == 1 ) {
322+ return emitError () << " an axis roll endpoint requires a split "
323+ " axis; spell an unsplit axis's endpoint as "
324+ " its piece position" ;
325+ }
326+ axis = e.index ;
327+ return success ();
328+ }
329+ if (e.index >= static_cast <int64_t >(dims.size ())) {
330+ return emitError () << " roll piece endpoint out of bounds for dims "
331+ " list" ;
332+ }
333+ piece = dyn_cast<DimAttr>(dims[e.index ]);
334+ if (!piece) {
335+ return emitError () << " roll endpoints must refer to #rotom.dim "
336+ " entries" ;
337+ }
338+ axis = piece.getDim ();
339+ return success ();
340+ };
341+ if (failed (checkEndpoint (from, fromPiece, fromAxis)) ||
342+ failed (checkEndpoint (by, byPiece, byAxis))) {
343+ return failure ();
271344 }
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" ;
345+
346+ // The extents need not match: a roll rewrites the from index to
347+ // (idx - shift) mod extent(from), well-defined for any partner extent (a
348+ // smaller partner covers a prefix of the rotations, a larger one wraps).
349+ // FROM is the index expression being rewritten, so it must be a
350+ // traversal piece or a whole (traversal) axis. The by endpoint may be
351+ // any kind: rolling by a replication or gap piece shifts by that piece's
352+ // block index, so each block holds a distinct cyclic rotation of the
353+ // rolled index -- the layout materializes every rotation and alignment
354+ // becomes block selection. (A rolled-by gap thus claims its blocks,
355+ // unlike a plain gap.)
356+ if (!from.isAxis && (fromPiece.isGap () || fromPiece.isReplicate ())) {
357+ return emitError () << " the rolled dim must be a traversal dim (dim >= "
358+ " 0)" ;
275359 }
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" ;
360+ // A roll may not shift an index by itself. Piece endpoints must be
361+ // distinct positions (two pieces of one axis are distinct digits); an
362+ // axis endpoint overlaps every endpoint on the same axis, because a
363+ // whole-axis rewrite touches all of its digits.
364+ if (!from.isAxis && !by.isAxis && from.index == by.index ) {
365+ return emitError () << " each roll must use two distinct endpoints" ;
280366 }
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)" ;
367+ if ((from.isAxis || by.isAxis ) && fromAxis == byAxis) {
368+ return emitError () << " a roll may not shift an axis by one of its own "
369+ " pieces" ;
292370 }
293371 // 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.
372+ // a distinct rotation of the rolled index . If the gap is larger than the
373+ // rolled extent the rotations repeat (period = the from extent), claiming
374+ // blocks the conversion/kernel accounting was never audited for.
297375 // (Replication partners of larger extent are intended -- replicate-then-
298376 // roll -- so only gaps are bounded.)
299- if (dj.isGap () && dj.getSize () > di.getSize ()) {
377+ const int64_t fromExtent =
378+ from.isAxis ? piecesOfAxis (fromAxis).second : fromPiece.getSize ();
379+ if (byPiece && byPiece.isGap () && byPiece.getSize () > fromExtent) {
300380 return emitError () << " a rolled-by gap dim must not exceed the rolled "
301381 " dim's extent" ;
302382 }
@@ -345,10 +425,19 @@ void LayoutAttr::print(AsmPrinter& printer) const {
345425 DenseI64ArrayAttr rolls = getRolls ();
346426 if (rolls && !rolls.asArrayRef ().empty ()) {
347427 ArrayRef<int64_t > values = rolls.asArrayRef ();
428+ auto printEndpoint = [&](int64_t encoded) {
429+ const RollEndpoint e = decodeRollEndpoint (encoded);
430+ if (e.isAxis ) printer << " axis " ;
431+ printer << e.index ;
432+ };
348433 printer << " , rolls = [" ;
349434 for (size_t i = 0 ; i < values.size (); i += 2 ) {
350435 if (i != 0 ) printer << " , " ;
351- printer << " (" << values[i] << " , " << values[i + 1 ] << " )" ;
436+ printer << " (" ;
437+ printEndpoint (values[i]);
438+ printer << " , " ;
439+ printEndpoint (values[i + 1 ]);
440+ printer << " )" ;
352441 }
353442 printer << " ]" ;
354443 }
@@ -436,6 +525,18 @@ Attribute LayoutAttr::parse(AsmParser& parser, Type type) {
436525 ArrayAttr::get (context, dims), n, DenseI64ArrayAttr::get (context, rolls));
437526}
438527
528+ SmallVector<RollSpec> getRollSpecs (LayoutAttr layout) {
529+ SmallVector<RollSpec> specs;
530+ DenseI64ArrayAttr rolls = layout.getRolls ();
531+ if (!rolls) return specs;
532+ ArrayRef<int64_t > r = rolls.asArrayRef ();
533+
534+ for (size_t i = 0 ; i + 1 < r.size (); i += 2 ) {
535+ specs.push_back ({decodeRollEndpoint (r[i]), decodeRollEndpoint (r[i + 1 ])});
536+ }
537+ return specs;
538+ }
539+
439540FailureOr<LayoutData> preprocessLayoutAttr (LayoutAttr layout) {
440541 return preprocessLayoutData (layout.getDims (), layout.getN (),
441542 layout.getContext ());
@@ -452,7 +553,9 @@ LogicalResult LayoutAttr::verify(function_ref<InFlightDiagnostic()> emitError,
452553 return emitError () << " `dims` must be an array of `#rotom.dim<...>`" ;
453554 }
454555
455- if (failed (verifyLayoutRolls (dims, rolls, emitError))) return failure ();
556+ if (failed (verifyLayoutRolls (dims, rolls, emitError))) {
557+ return failure ();
558+ }
456559
457560 SmallVector<DimAttr> dimVec;
458561 dimVec.reserve (dims.size ());
@@ -508,7 +611,8 @@ void canonicalizeLayoutDims(MLIRContext* ctx, SmallVector<DimAttr>& dims,
508611 if (fill <= 1 ) return ;
509612 dims.insert (dims.begin () + ctLen,
510613 DimAttr::get (ctx, /* dim=*/ -2 , fill, /* stride=*/ 1 ));
511- // Roll endpoints at or past the insertion shift right.
614+ // Piece endpoints at or past the insertion shift right; axis endpoints
615+ // (encoded negative) name axes and do not move.
512616 for (int64_t & encoded : rolls) {
513617 if (encoded >= static_cast <int64_t >(ctLen)) ++encoded;
514618 }
0 commit comments