Skip to content

Commit 864f583

Browse files
committed
feat(control-flow): decouple pointer descriptors across SCF
Carry complete block-pointer descriptors across supported for, while, and if boundaries as pointer-free SSA components, and rebuild pointers only at their use sites. Keep tensor-pointer bases outside loop signatures when they are invariant while carrying complete lane offsets. Teach TritonToLinalg and TritonToUnstructure to lower integer pointer carriers, scalar and opaque pointer joins, descriptor loops, lane offsets, and rebased memref layouts. Preserve exact descriptor producer slots, retain legacy conversion for mixed pointer boundaries, and keep externally typed memref boundaries layout-compatible. Remove the module-wide addptr-base restriction so unrelated and local make_tensor_ptr operations retain their previous behavior. Add one end-to-end pytest covering dynamic if/for/while block-pointer descriptors, changing bases, ordinary loop results, scalar-base tensor pointers, and opaque lane-wise tensor pointers.
1 parent e9a0f9c commit 864f583

26 files changed

Lines changed: 2327 additions & 446 deletions

third_party/ascend/include/TritonControlFlowOpt/ControlFlowAnalysis.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ struct AnalyzedComponent {
7575
struct AnalyzedValue {
7676
Type originalType;
7777
SmallVector<AnalyzedComponent> components;
78-
SmallVector<Value> invariants;
7978
SmallVector<Attribute> attributes;
8079
};
8180

third_party/ascend/include/TritonControlFlowOpt/ControlFlowRewrite.h

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,27 @@
3232

3333
#include "llvm/ADT/DenseMap.h"
3434
#include "llvm/ADT/SmallVector.h"
35+
#include "llvm/ADT/StringRef.h"
3536

3637
namespace mlir::triton::controlflow {
3738

39+
/// Handoff marker for SCF loops whose pointer slots have already been expanded
40+
/// into policy-owned descriptor components. Its DenseI32ArrayAttr value lists
41+
/// the loop-carried init/result slots occupied by pointer descriptor state.
42+
/// TritonToLinalg uses those slots to preserve only the required producer
43+
/// chains and removes the marker after conversion.
44+
inline constexpr llvm::StringLiteral kPointerDescriptorBoundaryAttr =
45+
"PointerDescriptorBoundary";
46+
3847
/// Policy-owned description of one value crossing a control-flow boundary.
3948
///
40-
/// `components` are runtime values that a policy may place in an expanded SCF
41-
/// signature. `invariants` and `attributes` are public storage whose layout is
42-
/// interpreted only by the policy that creates them. The shared rewrite treats
43-
/// those fields as opaque and only accesses `components` directly.
49+
/// `components` contain every runtime value needed to rebuild the original
50+
/// value. A policy may place a selected subset in an expanded SCF signature.
51+
/// `attributes` retain non-SSA metadata. Both layouts are private to the
52+
/// policy; the shared rewrite never interprets pointer-specific fields.
4453
struct DecomposedValue {
4554
Type originalType;
4655
SmallVector<Value> components;
47-
SmallVector<Value> invariants;
4856
SmallVector<Attribute> attributes;
4957
};
5058

@@ -71,8 +79,9 @@ class ControlFlowRewriteContext {
7179
///
7280
/// The policy decides how its value is decomposed and rebuilt, which components
7381
/// cross loop/if boundaries, and whether two decompositions share a compatible
74-
/// invariant schema. It is not an IR marker and carries no state between
75-
/// policy invocations.
82+
/// non-carried schema. It carries no mutable state between policy invocations;
83+
/// a capability hook tells the shared rewrite whether expanded loop slots must
84+
/// be recorded for downstream conversion.
7685
class ControlFlowRewritePolicy : public ControlFlowAnalysisPolicy {
7786
public:
7887
virtual ~ControlFlowRewritePolicy() = default;
@@ -81,6 +90,13 @@ class ControlFlowRewritePolicy : public ControlFlowAnalysisPolicy {
8190
/// after cloning so later operations can reuse their exact component state.
8291
virtual bool shouldDecomposeOperation(Operation *op) const = 0;
8392

93+
/// Whether rewritten loops owned by this policy must expose their descriptor
94+
/// slots to downstream conversion. The shared rewrite owns the positional
95+
/// marker because it alone knows both the previous and expanded signatures.
96+
virtual bool requiresPointerDescriptorBoundaryMarker() const {
97+
return false;
98+
}
99+
84100
virtual FailureOr<DecomposedValue>
85101
decompose(Value value, const ControlFlowRewriteContext &context,
86102
OpBuilder &builder, Location loc) const = 0;

third_party/ascend/include/TritonToLinalg/BlockPtrAnalysis.h

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,16 @@ namespace triton {
4545

4646
enum class MemAccVal { Undefined = 0, StrucMemAcc = 1, UnstrucMemAcc = 2 };
4747

48+
/// Creates a verifier-valid HIVM pointer cast for a scalar Triton pointer
49+
/// represented by an integer address. Triton scalar pointers do not carry an
50+
/// extent, while their downstream carrier is normally `memref<?xT>` and every
51+
/// dynamic memref dimension requires a size operand. Use one element as the
52+
/// conservative carrier extent; reinterpret-cast lowering replaces it with a
53+
/// precise access range when a larger descriptor is materialized.
54+
hivm::PointerCastOp createScalarPointerCast(OpBuilder &builder, Location loc,
55+
MemRefType resultType,
56+
Value address);
57+
4858
struct MemAccType {
4959

5060
MemAccVal value;
@@ -288,8 +298,8 @@ class BlockDataParser {
288298
ConversionPatternRewriter &rewriter,
289299
llvm::SmallDenseMap<Value, BlockData> &known);
290300

291-
static void
292-
rewriteMakeTensorPtrOp(triton::MakeTensorPtrOp op, Value base,
301+
static LogicalResult
302+
rewriteMakeTensorPtrOp(triton::MakeTensorPtrOp op, Value convertedBase,
293303
ConversionPatternRewriter &rewriter,
294304
llvm::SmallDenseMap<Value, BlockData> &known);
295305

@@ -312,9 +322,9 @@ class BlockDataParser {
312322

313323
/// @param known is mainly designed for `rewriteLoop`, and is just non-const
314324
/// in `rewriteLoop`, `rewriteAddPtr` and `rewriteAdvance`
315-
static void rewriteLoopOp(LoopLikeOpInterface op,
316-
ConversionPatternRewriter &rewriter,
317-
llvm::SmallDenseMap<Value, BlockData> &known);
325+
static LogicalResult
326+
rewriteLoopOp(LoopLikeOpInterface op, ConversionPatternRewriter &rewriter,
327+
llvm::SmallDenseMap<Value, BlockData> &known);
318328

319329
static void rewriteAddPtrToUnstrucMemAcc(triton::AddPtrOp op,
320330
triton::AddPtrOp::Adaptor &adaptor,

third_party/ascend/include/TritonToLinalg/TritonOpConverter.h

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include "mlir/Transforms/DialectConversion.h"
3838

3939
#include "llvm/ADT/SmallVector.h"
40+
#include "llvm/ADT/StringRef.h"
4041
#include "llvm/ADT/TypeSwitch.h"
4142
#include "llvm/Support/Debug.h"
4243

@@ -576,6 +577,66 @@ class GatherConverter : public OpConversionPattern<triton::GatherOp> {
576577
ConversionPatternRewriter &rewriter) const override;
577578
};
578579

580+
// These predicates select only scalar !tt.ptr<T> transports. A
581+
// tensor<...x!tt.ptr<T>> follows the separate tensor-pointer lowering.
582+
bool hasScalarPointerResult(scf::IfOp op);
583+
bool isScalarPointerSelect(arith::SelectOp op);
584+
585+
// Marks an scf.if temporarily rebuilt by IfConverter. Its scalar-pointer
586+
// results are represented as complete i64 addresses, so only its own yields
587+
// require the matching pointer-to-address conversion.
588+
inline constexpr llvm::StringLiteral kScalarPointerCarrierBoundaryAttr =
589+
"ScalarPointerCarrierBoundary";
590+
591+
// Rebuild an scf.if with scalar-pointer results so the boundary carries
592+
// complete i64 addresses and reconstructs memrefs only after the join.
593+
// The original branch regions are moved into the new operation, preserving
594+
// side effects and allowing the conversion driver to rewrite each scf.yield
595+
// operand in place.
596+
//
597+
// Example:
598+
// %base = scf.if %cond -> !tt.ptr<f32> {
599+
// scf.yield %lhs : !tt.ptr<f32>
600+
// } else {
601+
// scf.yield %rhs : !tt.ptr<f32>
602+
// }
603+
// %ptr = tt.make_tensor_ptr %base, ...
604+
// becomes an scf.if returning i64 plus one hivm.pointer_cast after the if.
605+
class IfConverter : public OpConversionPattern<scf::IfOp> {
606+
public:
607+
using OpConversionPattern<scf::IfOp>::OpConversionPattern;
608+
609+
LogicalResult
610+
matchAndRewrite(scf::IfOp op, OpAdaptor adaptor,
611+
ConversionPatternRewriter &rewriter) const override;
612+
};
613+
614+
// Convert a scalar-pointer select into a select over complete integer addresses
615+
// and reconstruct one memref after the selection. This handles both BlockPtr
616+
// bases and ordinary scalar pointers without asking the backend to merge two
617+
// memory objects.
618+
//
619+
// Example:
620+
// %base = arith.select %cond, %lhs, %rhs : !tt.ptr<f32>
621+
// %ptr = tt.make_tensor_ptr %base, ...
622+
// becomes:
623+
// %lhs_addr = memref.extract_aligned_pointer_as_index %lhs
624+
// %rhs_addr = memref.extract_aligned_pointer_as_index %rhs
625+
// %selected_addr = arith.select %cond, %lhs_addr, %rhs_addr : i64
626+
// %base = hivm.pointer_cast %selected_addr : i64 to memref<?xf32>
627+
class PointerSelectConverter : public OpConversionPattern<arith::SelectOp> {
628+
public:
629+
using OpConversionPattern<arith::SelectOp>::OpConversionPattern;
630+
631+
LogicalResult
632+
matchAndRewrite(arith::SelectOp op, OpAdaptor adaptor,
633+
ConversionPatternRewriter &rewriter) const override;
634+
};
635+
636+
// Convert the yields of an IfConverter-created scf.if to its carrier result
637+
// types. In particular, a yielded scalar pointer becomes its complete i64
638+
// address. Yields belonging to ordinary ifs or loops are intentionally left to
639+
// their owning conversions.
579640
class YieldConverter : public OpConversionPattern<scf::YieldOp> {
580641
public:
581642
using OpConversionPattern<scf::YieldOp>::OpConversionPattern;
@@ -596,11 +657,16 @@ class LoopConverter : public OpConversionPattern<LoopOpTy> {
596657
matchAndRewrite(LoopOpTy op,
597658
typename OpConversionPattern<LoopOpTy>::OpAdaptor adaptor,
598659
ConversionPatternRewriter &rewriter) const override {
660+
// CFO-expanded descriptor loops already carry pointer-free policy values
661+
// and remain structurally unchanged. This legacy BlockData rewrite is only
662+
// valid for explicitly marked loops.
663+
if (!op->hasAttr("UnhandledLoopOp"))
664+
return failure();
599665
llvm::SmallDenseMap<Value, BlockData> known;
600666

601-
op->removeAttr("UnhandledLoopOp");
602-
BlockDataParser::rewriteLoopOp(op, rewriter, known);
603-
return success();
667+
rewriter.modifyOpInPlace(
668+
op, [&]() { op->removeAttr("UnhandledLoopOp"); });
669+
return BlockDataParser::rewriteLoopOp(op, rewriter, known);
604670
}
605671
};
606672

@@ -736,6 +802,14 @@ class PtrToIntConverter : public OpConversionPattern<triton::PtrToIntOp> {
736802
ConversionPatternRewriter &rewriter) const override;
737803
};
738804

805+
class IntToPtrConverter : public OpConversionPattern<triton::IntToPtrOp> {
806+
public:
807+
using OpConversionPattern<triton::IntToPtrOp>::OpConversionPattern;
808+
LogicalResult
809+
matchAndRewrite(triton::IntToPtrOp op, OpAdaptor adaptor,
810+
ConversionPatternRewriter &rewriter) const override;
811+
};
812+
739813
class IndexPutConverter
740814
: public OpConversionPattern<triton::ascend::IndexPutOp> {
741815
public:

third_party/ascend/include/TritonToUnstructure/BubbleUpOperation.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ class BubbleUpExtract : public OpRewritePattern<ExtractOpTy> {
7272
PatternRewriter &rewriter) const;
7373
void bubbleUpOperation(ExtractOpTy op, arith::CmpIOp parentOp, Location loc,
7474
PatternRewriter &rewriter) const;
75+
// Pushes extract(select(condition, lhs, rhs)) through the select. A shaped
76+
// condition is extracted at the same position while a scalar condition is
77+
// reused directly.
78+
void bubbleUpOperation(ExtractOpTy op, arith::SelectOp parentOp, Location loc,
79+
PatternRewriter &rewriter) const;
7580
void bubbleUpOperation(ExtractOpTy op, arith::TruncFOp parentOp, Location loc,
7681
PatternRewriter &rewriter) const;
7782
void bubbleUpOperation(ExtractOpTy op, arith::ExtFOp parentOp, Location loc,

0 commit comments

Comments
 (0)