@@ -54,16 +54,27 @@ bool IsVScaleCall(const PrimExpr& expr) {
5454 return false ;
5555}
5656
57+ bool TargetHasRVV (Target target) {
58+ if (!target.defined ()) return false ;
59+ static auto target_has_feature_fn =
60+ tvm::ffi::Function::GetGlobalRequired (" target.target_has_feature" );
61+ return target_has_feature_fn (" v" , target).cast <bool >();
62+ }
63+
5764// File-local helper: true if the target supports Variable-Length Array extensions
5865// (AArch64 SVE or RISC-V V).
5966bool TargetHasVLA (Target target) {
6067 if (!target.defined ()) return false ;
6168 bool has_vla = target->GetAttr <bool >(" feature.has_sve" ).value_or (false );
62- static auto target_has_feature_fn =
63- tvm::ffi::Function::GetGlobalRequired (" target.target_has_feature" );
64- has_vla |= target_has_feature_fn (" v" , target).cast <bool >();
69+ has_vla |= TargetHasRVV (target);
6570 return has_vla;
6671}
72+
73+ bool ContainsCallNode (const Stmt& stmt) {
74+ return CheckContains::StmtContains (stmt, [](const PrimExpr& expr) {
75+ return expr.as <CallNode>() != nullptr ;
76+ });
77+ }
6778} // namespace
6879
6980inline PrimExpr CreateNewLanes (bool is_scalable, int lanes_or_vscale_factor) {
@@ -132,7 +143,8 @@ bool EnableBufferLevelPredication(Target target) {
132143 */
133144class TryPredicateBufferAccesses : public StmtExprMutator {
134145 public:
135- TryPredicateBufferAccesses () {}
146+ explicit TryPredicateBufferAccesses (bool allow_offset_predication)
147+ : allow_offset_predication_(allow_offset_predication) {}
136148
137149 /* !
138150 * \brief Run the pass to try to exact predicates.
@@ -157,7 +169,10 @@ class TryPredicateBufferAccesses : public StmtExprMutator {
157169 return {false , stmt};
158170 }
159171
160- base_ = Downcast<Ramp>(lt->a )->base ;
172+ Ramp pred_ramp = Downcast<Ramp>(lt->a );
173+ base_ = pred_ramp->base ;
174+ stride_ = pred_ramp->stride ;
175+ lanes_ = pred_ramp->lanes ;
161176 limit_ = Downcast<Broadcast>(lt->b )->value ;
162177
163178 // Now we can try to predicate
@@ -190,27 +205,49 @@ class TryPredicateBufferAccesses : public StmtExprMutator {
190205 }
191206 Ramp ramp = Downcast<Ramp>(node->indices [0 ]);
192207
193- // The vectorized access pattern must match the base of the predicate
194- if ( !ffi::StructuralEqual ()(ramp->base , base_ )) {
208+ if (! ffi::StructuralEqual ()(ramp-> stride , stride_) ||
209+ !ffi::StructuralEqual ()(ramp->lanes , lanes_ )) {
195210 return node;
196211 }
197212
213+ bool same_base = ffi::StructuralEqual ()(ramp->base , base_);
214+ if (!same_base) {
215+ // The lane mask describes which lanes are active, independent of the
216+ // memory base. This covers accesses such as A[offset + i] guarded by
217+ // a predicate over i.
218+ if (!allow_offset_predication_) {
219+ return node;
220+ }
221+ }
222+
198223 DataType buf_predicate_dtype =
199224 DataType (DataType::kUInt , 1 , ramp->dtype .get_lanes_or_vscale_factor (),
200225 ramp->dtype .is_scalable_vector ());
201226 Call lane_mask = Call (buf_predicate_dtype, builtin::get_active_lane_mask (), {base_, limit_});
202227
203228 num_accesses_rewritten_ += 1 ;
204229 auto writer = node.CopyOnWrite ();
205- writer->predicate = lane_mask;
230+ if (node->predicate .defined () && allow_offset_predication_) {
231+ // Buffer predicates are uint1 lane masks, so mask merging uses bitwise
232+ // and rather than logical &&.
233+ writer->predicate = node->predicate .value () & lane_mask;
234+ } else {
235+ writer->predicate = lane_mask;
236+ }
206237 return node;
207238 }
208239
209240 /* ! \brief The variable base expr of the predicate. */
210241 PrimExpr base_;
242+ /* ! \brief The lane stride of the predicate. */
243+ PrimExpr stride_;
244+ /* ! \brief The lane count of the predicate. */
245+ PrimExpr lanes_;
211246 /* ! \brief The limit of the predicate. The expr specifies the upper bound of the base's
212247 * evaluated value. */
213248 PrimExpr limit_;
249+ /* ! \brief Whether to predicate offset buffer accesses that use the same lane layout. */
250+ bool allow_offset_predication_;
214251 /* ! \brief The number of buffer accesses in the stmt we will analyze. */
215252 size_t num_accesses_analyzed_ = 0 ;
216253 /* ! \brief The number of buffer accesses rewritten with predicates. */
@@ -819,7 +856,7 @@ class Vectorizer : public StmtMutator, public ExprFunctor<PrimExpr(const PrimExp
819856 if (EnableBufferLevelPredication (target_) &&
820857 condition.dtype ().is_scalable_or_fixed_length_vector () && !else_case.defined ()) {
821858 std::pair<bool , Stmt> success_stmt_pair =
822- TryPredicateBufferAccesses ().Run (then_case, condition);
859+ TryPredicateBufferAccesses (TargetHasRVV (target_) ).Run (then_case, condition);
823860 bool can_remove_if_then_else = success_stmt_pair.first ;
824861 if (can_remove_if_then_else) {
825862 return success_stmt_pair.second ;
@@ -975,12 +1012,19 @@ class LoopVectorizer : public StmtMutator {
9751012 if (op->kind == ForKind::kVectorized ) {
9761013 auto * extent_as_int = op->extent .as <IntImmNode>();
9771014
1015+ TVM_FFI_ICHECK (is_zero (op->min ));
1016+ // General calls still have vectorization paths that query a compile-time
1017+ // lane count, so keep them on the existing fixed-width path for now.
1018+ if (extent_as_int && extent_as_int->value > 1 && TargetHasRVV (target_) &&
1019+ !ContainsCallNode (op->body )) {
1020+ return VectorizeFixedLoopForRVV (op, extent_as_int->value );
1021+ }
1022+
9781023 if (!extent_as_int || extent_as_int->value < 1 ) {
9791024 bool is_scalable_expr = CheckContains::ExprContains (op->extent , IsVScaleCall);
9801025 TVM_FFI_ICHECK (is_scalable_expr && TargetHasVLA (target_))
9811026 << " Failed to vectorize loop with extent " << op->extent << " for target " << target_;
9821027 }
983- TVM_FFI_ICHECK (is_zero (op->min ));
9841028 return Vectorizer (op->loop_var , op->extent , target_)(op->body );
9851029 } else {
9861030 return StmtMutator::VisitStmt_ (op);
@@ -999,6 +1043,39 @@ class LoopVectorizer : public StmtMutator {
9991043 }
10001044
10011045 private:
1046+ Stmt VectorizeFixedLoopForRVV (const ForNode* op, int64_t extent) {
1047+ // Match the existing TIRx scalable-vector convention. LLVM/RVV still
1048+ // selects the runtime vector length with vsetvli.
1049+ static constexpr int kDefaultVScaleFactor = 4 ;
1050+ DataType index_dtype = op->loop_var ->dtype ;
1051+ PrimExpr zero = make_const (index_dtype, 0 );
1052+ PrimExpr fixed_extent = make_const (index_dtype, extent);
1053+ PrimExpr scalable_lanes = CreateNewLanes (/* is_scalable=*/ true , kDefaultVScaleFactor );
1054+ DataType lane_dtype = scalable_lanes.dtype ();
1055+ PrimExpr scalable_lanes_index = scalable_lanes;
1056+ if (scalable_lanes_index.dtype () != index_dtype) {
1057+ scalable_lanes_index = Cast (index_dtype, scalable_lanes_index);
1058+ }
1059+ PrimExpr num_chunks = ceildiv (fixed_extent, scalable_lanes_index);
1060+
1061+ Var outer (op->loop_var ->name_hint + " .vla.o" , index_dtype);
1062+ Var inner (op->loop_var ->name_hint + " .vla.i" , lane_dtype);
1063+ PrimExpr inner_index = inner;
1064+ if (inner_index.dtype () != index_dtype) {
1065+ inner_index = Cast (index_dtype, inner_index);
1066+ }
1067+ PrimExpr index = outer * scalable_lanes_index + inner_index;
1068+ Stmt body = Substitute (op->body , {{op->loop_var , index}});
1069+ Stmt guarded_body = IfThenElse (index < fixed_extent, body, std::nullopt , op->span );
1070+ Stmt vector_loop =
1071+ For (inner, make_const (lane_dtype, 0 ), scalable_lanes, ForKind::kVectorized , guarded_body,
1072+ std::nullopt , op->annotations , std::nullopt , op->span );
1073+ Stmt loop = For (outer, zero, num_chunks, ForKind::kSerial , vector_loop, std::nullopt , {},
1074+ std::nullopt , op->span );
1075+
1076+ return this ->VisitStmt (loop);
1077+ }
1078+
10021079 Target target_ = Target::Current();
10031080};
10041081
0 commit comments