@@ -453,6 +453,44 @@ LogicalResult MaskState::parseLoopIterArg(Value v, const Location loc,
453453 return failure ();
454454 }
455455
456+ // This is a bit of a hack!!
457+ //
458+ // The offset (MaskState::start) of a mask can now depend on a loop's
459+ // iter-arg like the following example:
460+ //
461+ // idx = offset + tl.arange(0, 4)
462+ // for it in range(n):
463+ // mask = idx < size
464+ // x = tl.load(x_ptr + idx, mask=mask)
465+ // tl.store(y_ptr + idx, x, mask=mask)
466+ // idx += 4
467+ //
468+ // See
469+ // test/Conversion/TritonToStructured/mask_loop_iter_arg.mlir and
470+ // and
471+ // python/examples/test_mask_loop_iter_arg.py
472+ // for IR and full triton code.
473+ //
474+ // To support this case, we first make the following assumptions:
475+ // - MaskAnalysis is runs after PtrAnalysis's prepass finishes, which means
476+ // the offset for the load and store pointers have already been set up
477+ // at `argIndex + 1`
478+ // - The tensor of indices used by the load / store and the mask are the same
479+ // (see above where `idx` appears in both the mask and the pointer
480+ // arithmetic). This allows us to use the offset at `argIndex + 1` in the
481+ // above assumption. In the future, to make this more robust, we need to
482+ // verify that the offsets are indeed the same. Or alternatively, make sure
483+ // to generate a separate start and end offset for each mask that is being
484+ // updated in loops.
485+ //
486+ // Now to generate the mask state in each loop iteration, we first construct
487+ // the mask state *before* coming into the loop by parsing the init-arg. A
488+ // mask dimensions stay consistent throughout each loop iteration, but its
489+ // starting offset (`MaskState::start`) will change. So to construct the mask
490+ // state for each iteration, we need to make MaskState::state be the offset
491+ // iter-arg at `argIndex + 1`. Now for `MaskState::end`, we can first compute
492+ // the distance between `start` and `end` before coming into the loop, then
493+ // use this distance to compute the actual `end` in each loop.
456494 auto argIndex = std::distance (forOp.getRegionIterArgs ().begin (), it);
457495 auto initArg = forOp.getInitArgs ()[argIndex];
458496 if (auto getStateOp = initArg.getDefiningOp <tts::GetStructuredStateOp>()) {
@@ -461,37 +499,18 @@ LogicalResult MaskState::parseLoopIterArg(Value v, const Location loc,
461499
462500 {
463501 OpBuilder::InsertionGuard guard (builder);
502+ // Make sure all ops generated for the mask state are inserted before
503+ // the current loop
464504 builder.setInsertionPoint (forOp);
465505 if (failed (lhsState.parse (tritonValue, loc, builder))) {
466506 return failure ();
467507 }
468508 }
469509
470- // ok so now lhs state contains the dimensions, start, and end
471- // but start is the init arg
472- // how do we compute end?
473- // ok so just assume we have everything
474- // but the start is actually the iter-arg
475510 auto dist = subOFRs (lhsState.end , lhsState.start , loc, builder);
476- lhsState.start = forOp.getRegionIterArgs ()[argIndex + 1 ];
477- lhsState.end = addOFRs (lhsState.start , dist, loc, builder);
478-
479- // This is a bit of a hack!!
480- //
481- // The offsets and dimensions of a MaskState can now depend on a loop's
482- // iter-arg.
483- //
484- // Because the PtrAnalysis's pre-pass already sets up the offsets,
485- // we can create a new MaskState for each loop iteration by adding the
486- // original MaskState with the current iter-arg, which is at `argIndex +
487- // 1`.
488- //
489- // This will not work for nested loop scenarios, which would need a
490- // more robust implementation.
491- if (failed (this ->addStateScalar (
492- lhsState, builder.getIndexAttr (0 ), loc, builder))) {
493- return failure ();
494- }
511+ this ->start = forOp.getRegionIterArgs ()[argIndex + 1 ];
512+ this ->end = addOFRs (this ->start , dist, loc, builder);
513+ this ->dims = lhsState.dims ;
495514
496515 return success ();
497516 }
0 commit comments