Skip to content

[flang][CUDA] Add error & warning for device argument first dimension… #136058

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 17 additions & 9 deletions flang/include/flang/Evaluate/check-expression.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,21 +113,29 @@ extern template void CheckSpecificationExpr(
// read-only data.
template <typename A>
std::optional<bool> IsContiguous(const A &, FoldingContext &,
bool namedConstantSectionsAreContiguous = true);
bool namedConstantSectionsAreContiguous = true,
bool firstDimensionStride1 = false);
extern template std::optional<bool> IsContiguous(const Expr<SomeType> &,
FoldingContext &, bool namedConstantSectionsAreContiguous);
FoldingContext &, bool namedConstantSectionsAreContiguous,
bool firstDimensionStride1);
extern template std::optional<bool> IsContiguous(const ArrayRef &,
FoldingContext &, bool namedConstantSectionsAreContiguous);
FoldingContext &, bool namedConstantSectionsAreContiguous,
bool firstDimensionStride1);
extern template std::optional<bool> IsContiguous(const Substring &,
FoldingContext &, bool namedConstantSectionsAreContiguous);
FoldingContext &, bool namedConstantSectionsAreContiguous,
bool firstDimensionStride1);
extern template std::optional<bool> IsContiguous(const Component &,
FoldingContext &, bool namedConstantSectionsAreContiguous);
FoldingContext &, bool namedConstantSectionsAreContiguous,
bool firstDimensionStride1);
extern template std::optional<bool> IsContiguous(const ComplexPart &,
FoldingContext &, bool namedConstantSectionsAreContiguous);
FoldingContext &, bool namedConstantSectionsAreContiguous,
bool firstDimensionStride1);
extern template std::optional<bool> IsContiguous(const CoarrayRef &,
FoldingContext &, bool namedConstantSectionsAreContiguous);
extern template std::optional<bool> IsContiguous(
const Symbol &, FoldingContext &, bool namedConstantSectionsAreContiguous);
FoldingContext &, bool namedConstantSectionsAreContiguous,
bool firstDimensionStride1);
extern template std::optional<bool> IsContiguous(const Symbol &,
FoldingContext &, bool namedConstantSectionsAreContiguous,
bool firstDimensionStride1);
static inline std::optional<bool> IsContiguous(const SymbolRef &s,
FoldingContext &c, bool namedConstantSectionsAreContiguous = true) {
return IsContiguous(s.get(), c, namedConstantSectionsAreContiguous);
Expand Down
87 changes: 53 additions & 34 deletions flang/lib/Evaluate/check-expression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -881,10 +881,12 @@ class IsContiguousHelper
public:
using Result = std::optional<bool>; // tri-state
using Base = AnyTraverse<IsContiguousHelper, Result>;
explicit IsContiguousHelper(
FoldingContext &c, bool namedConstantSectionsAreContiguous)
: Base{*this}, context_{c}, namedConstantSectionsAreContiguous_{
namedConstantSectionsAreContiguous} {}
explicit IsContiguousHelper(FoldingContext &c,
bool namedConstantSectionsAreContiguous,
bool firstDimensionStride1 = false)
: Base{*this}, context_{c},
namedConstantSectionsAreContiguous_{namedConstantSectionsAreContiguous},
firstDimensionStride1_{firstDimensionStride1} {}
using Base::operator();

template <typename T> Result operator()(const Constant<T> &) const {
Expand Down Expand Up @@ -956,13 +958,14 @@ class IsContiguousHelper
if (!*baseIsContiguous) {
return false;
}
// TODO could be true if base contiguous and this is only component, or
// if base has only one element?
// TODO: should be true if base is contiguous and this is only
// component, or when the base has at most one element
}
return std::nullopt;
}
}
Result operator()(const ComplexPart &x) const {
// TODO: should be true when base is empty array, too
return x.complex().Rank() == 0;
}
Result operator()(const Substring &x) const {
Expand Down Expand Up @@ -1061,6 +1064,9 @@ class IsContiguousHelper
auto dims{subscript.size()};
std::vector<bool> knownPartialSlice(dims, false);
for (auto j{dims}; j-- > 0;) {
if (j == 0 && firstDimensionStride1_ && !result.value_or(true)) {
result.reset(); // ignore problems on later dimensions
}
std::optional<ConstantSubscript> dimLbound;
std::optional<ConstantSubscript> dimUbound;
std::optional<ConstantSubscript> dimExtent;
Expand All @@ -1083,18 +1089,20 @@ class IsContiguousHelper
dimExtent = 0;
}
}

if (const auto *triplet{std::get_if<Triplet>(&subscript[j].u)}) {
++rank;
const Expr<SubscriptInteger> *lowerBound{triplet->GetLower()};
const Expr<SubscriptInteger> *upperBound{triplet->GetUpper()};
std::optional<ConstantSubscript> lowerVal{lowerBound
? ToInt64(Fold(context_, Expr<SubscriptInteger>{*lowerBound}))
: dimLbound};
std::optional<ConstantSubscript> upperVal{upperBound
? ToInt64(Fold(context_, Expr<SubscriptInteger>{*upperBound}))
: dimUbound};
if (auto stride{ToInt64(triplet->stride())}) {
const Expr<SubscriptInteger> *lowerBound{triplet->GetLower()};
const Expr<SubscriptInteger> *upperBound{triplet->GetUpper()};
std::optional<ConstantSubscript> lowerVal{lowerBound
? ToInt64(Fold(context_, Expr<SubscriptInteger>{*lowerBound}))
: dimLbound};
std::optional<ConstantSubscript> upperVal{upperBound
? ToInt64(Fold(context_, Expr<SubscriptInteger>{*upperBound}))
: dimUbound};
if (j == 0 && *stride == 1 && firstDimensionStride1_) {
result = *stride == 1; // contiguous or empty if so
}
if (lowerVal && upperVal) {
if (*lowerVal < *upperVal) {
if (*stride < 0) {
Expand All @@ -1110,23 +1118,31 @@ class IsContiguousHelper
*lowerVal + *stride >= *upperVal) {
result = false; // discontiguous if not empty
}
} else {
mayBeEmpty = true;
} else { // bounds known and equal
if (j == 0 && firstDimensionStride1_) {
result = true; // stride doesn't matter
}
}
} else { // bounds not both known
mayBeEmpty = true;
}
} else { // stride not known
if (lowerVal && upperVal && *lowerVal == *upperVal) {
// stride doesn't matter when bounds are equal
if (j == 0 && firstDimensionStride1_) {
result = true;
}
} else {
mayBeEmpty = true;
}
} else {
mayBeEmpty = true;
}
} else if (subscript[j].Rank() > 0) {
} else if (subscript[j].Rank() > 0) { // vector subscript
++rank;
if (!result) {
result = false; // vector subscript
result = false;
}
mayBeEmpty = true;
} else {
// Scalar subscript.
} else { // scalar subscript
if (dimExtent && *dimExtent > 1) {
knownPartialSlice[j] = true;
}
Expand All @@ -1138,7 +1154,7 @@ class IsContiguousHelper
if (result) {
return result;
}
// Not provably discontiguous at this point.
// Not provably contiguous or discontiguous at this point.
// Return "true" if simply contiguous, otherwise nullopt.
for (auto j{subscript.size()}; j-- > 0;) {
if (const auto *triplet{std::get_if<Triplet>(&subscript[j].u)}) {
Expand Down Expand Up @@ -1170,33 +1186,36 @@ class IsContiguousHelper

FoldingContext &context_;
bool namedConstantSectionsAreContiguous_{false};
bool firstDimensionStride1_{false};
};

template <typename A>
std::optional<bool> IsContiguous(const A &x, FoldingContext &context,
bool namedConstantSectionsAreContiguous) {
bool namedConstantSectionsAreContiguous, bool firstDimensionStride1) {
if (!IsVariable(x) &&
(namedConstantSectionsAreContiguous || !ExtractDataRef(x, true, true))) {
return true;
} else {
return IsContiguousHelper{context, namedConstantSectionsAreContiguous}(x);
return IsContiguousHelper{
context, namedConstantSectionsAreContiguous, firstDimensionStride1}(x);
}
}

template std::optional<bool> IsContiguous(const Expr<SomeType> &,
FoldingContext &, bool namedConstantSectionsAreContiguous);
FoldingContext &, bool namedConstantSectionsAreContiguous,
bool firstDimensionStride1);
template std::optional<bool> IsContiguous(const ArrayRef &, FoldingContext &,
bool namedConstantSectionsAreContiguous);
bool namedConstantSectionsAreContiguous, bool firstDimensionStride1);
template std::optional<bool> IsContiguous(const Substring &, FoldingContext &,
bool namedConstantSectionsAreContiguous);
bool namedConstantSectionsAreContiguous, bool firstDimensionStride1);
template std::optional<bool> IsContiguous(const Component &, FoldingContext &,
bool namedConstantSectionsAreContiguous);
bool namedConstantSectionsAreContiguous, bool firstDimensionStride1);
template std::optional<bool> IsContiguous(const ComplexPart &, FoldingContext &,
bool namedConstantSectionsAreContiguous);
bool namedConstantSectionsAreContiguous, bool firstDimensionStride1);
template std::optional<bool> IsContiguous(const CoarrayRef &, FoldingContext &,
bool namedConstantSectionsAreContiguous);
template std::optional<bool> IsContiguous(
const Symbol &, FoldingContext &, bool namedConstantSectionsAreContiguous);
bool namedConstantSectionsAreContiguous, bool firstDimensionStride1);
template std::optional<bool> IsContiguous(const Symbol &, FoldingContext &,
bool namedConstantSectionsAreContiguous, bool firstDimensionStride1);

// IsErrorExpr()
struct IsErrorExprHelper : public AnyTraverse<IsErrorExprHelper, bool> {
Expand Down
20 changes: 18 additions & 2 deletions flang/lib/Semantics/check-call.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1015,10 +1015,26 @@ static void CheckExplicitDataArg(const characteristics::DummyDataObject &dummy,
actualDataAttr = common::CUDADataAttr::Device;
}
}
if (dummyDataAttr == common::CUDADataAttr::Device &&
(dummyIsAssumedShape || dummyIsAssumedRank)) {
if (auto contig{evaluate::IsContiguous(actual, foldingContext,
/*namedConstantSectionsAreContiguous=*/true,
/*firstDimensionStride1=*/true)}) {
if (!*contig) {
messages.Say(
"actual argument associated with assumed shape/rank device %s is known to be discontiguous on its first dimension"_err_en_US,
dummyName);
}
} else {
messages.Say(
"actual argument associated with assumed shape/rank device %s is not known to be contiguous on its first dimension"_warn_en_US,
dummyName);
}
}
std::optional<std::string> warning;
bool isHostDeviceProc = procedure.cudaSubprogramAttrs &&
bool isHostDeviceProc{procedure.cudaSubprogramAttrs &&
*procedure.cudaSubprogramAttrs ==
common::CUDASubprogramAttrs::HostDevice;
common::CUDASubprogramAttrs::HostDevice};
if (!common::AreCompatibleCUDADataAttrs(dummyDataAttr, actualDataAttr,
dummy.ignoreTKR, &warning, /*allowUnifiedMatchingRule=*/true,
isHostDeviceProc, &context.languageFeatures())) {
Expand Down
30 changes: 30 additions & 0 deletions flang/test/Semantics/cuf19.cuf
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
! RUN: %python %S/test_errors.py %s %flang_fc1
interface
subroutine foo(a)
real, device, dimension(:,:) :: a
end
end interface

real, device, allocatable :: a(:,:)
complex, device, allocatable :: z(:,:)
integer :: i = 2, j = 3
allocate(a(10,10))
allocate(z(10,10))
call foo(a) ! ok
call foo(a(:,:)) ! ok
call foo(a(1:10,1:10)) ! ok
!ERROR: actual argument associated with assumed shape/rank device dummy argument 'a=' is known to be discontiguous on its first dimension
call foo(a(1:10:2,1:10))
call foo(a(1:0:2,1:10)) ! empty dimension is ok
call foo(a(1:10:2,1:0)) ! any empty dimension is ok
call foo(a(1:10,1:10:2)) ! discontiguous second dimension is ok
!WARNING: actual argument associated with assumed shape/rank device dummy argument 'a=' is not known to be contiguous on its first dimension
call foo(a(1:10:i,1:10))
!WARNING: actual argument associated with assumed shape/rank device dummy argument 'a=' is not known to be contiguous on its first dimension
call foo(a(1:i:2,1:10))
call foo(a(i:j:1,1:10)) ! stride 1, okay, despite unknown bounds
!WARNING: actual argument associated with assumed shape/rank device dummy argument 'a=' is not known to be contiguous on its first dimension
call foo(a(i:j:-1,1:10))
!ERROR: actual argument associated with assumed shape/rank device dummy argument 'a=' is known to be discontiguous on its first dimension
call foo(z%re)
end
Loading