1 //===-- lib/Evaluate/fold-integer.cpp -------------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "fold-implementation.h" 10 #include "fold-matmul.h" 11 #include "fold-reduction.h" 12 #include "flang/Evaluate/check-expression.h" 13 14 namespace Fortran::evaluate { 15 16 // Given a collection of ConstantSubscripts values, package them as a Constant. 17 // Return scalar value if asScalar == true and shape-dim array otherwise. 18 template <typename T> 19 Expr<T> PackageConstantBounds( 20 const ConstantSubscripts &&bounds, bool asScalar = false) { 21 if (asScalar) { 22 return Expr<T>{Constant<T>{bounds.at(0)}}; 23 } else { 24 // As rank-dim array 25 const int rank{GetRank(bounds)}; 26 std::vector<Scalar<T>> packed(rank); 27 std::transform(bounds.begin(), bounds.end(), packed.begin(), 28 [](ConstantSubscript x) { return Scalar<T>(x); }); 29 return Expr<T>{Constant<T>{std::move(packed), ConstantSubscripts{rank}}}; 30 } 31 } 32 33 // If a DIM= argument to LBOUND(), UBOUND(), or SIZE() exists and has a valid 34 // constant value, return in "dimVal" that value, less 1 (to make it suitable 35 // for use as a C++ vector<> index). Also check for erroneous constant values 36 // and returns false on error. 37 static bool CheckDimArg(const std::optional<ActualArgument> &dimArg, 38 const Expr<SomeType> &array, parser::ContextualMessages &messages, 39 bool isLBound, std::optional<int> &dimVal) { 40 dimVal.reset(); 41 if (int rank{array.Rank()}; rank > 0 || IsAssumedRank(array)) { 42 auto named{ExtractNamedEntity(array)}; 43 if (auto dim64{ToInt64(dimArg)}) { 44 if (*dim64 < 1) { 45 messages.Say("DIM=%jd dimension must be positive"_err_en_US, *dim64); 46 return false; 47 } else if (!IsAssumedRank(array) && *dim64 > rank) { 48 messages.Say( 49 "DIM=%jd dimension is out of range for rank-%d array"_err_en_US, 50 *dim64, rank); 51 return false; 52 } else if (!isLBound && named && 53 semantics::IsAssumedSizeArray(named->GetLastSymbol()) && 54 *dim64 == rank) { 55 messages.Say( 56 "DIM=%jd dimension is out of range for rank-%d assumed-size array"_err_en_US, 57 *dim64, rank); 58 return false; 59 } else if (IsAssumedRank(array)) { 60 if (*dim64 > common::maxRank) { 61 messages.Say( 62 "DIM=%jd dimension is too large for any array (maximum rank %d)"_err_en_US, 63 *dim64, common::maxRank); 64 return false; 65 } 66 } else { 67 dimVal = static_cast<int>(*dim64 - 1); // 1-based to 0-based 68 } 69 } 70 } 71 return true; 72 } 73 74 // Class to retrieve the constant bound of an expression which is an 75 // array that devolves to a type of Constant<T> 76 class GetConstantArrayBoundHelper { 77 public: 78 template <typename T> 79 static Expr<T> GetLbound( 80 const Expr<SomeType> &array, std::optional<int> dim) { 81 return PackageConstantBounds<T>( 82 GetConstantArrayBoundHelper(dim, /*getLbound=*/true).Get(array), 83 dim.has_value()); 84 } 85 86 template <typename T> 87 static Expr<T> GetUbound( 88 const Expr<SomeType> &array, std::optional<int> dim) { 89 return PackageConstantBounds<T>( 90 GetConstantArrayBoundHelper(dim, /*getLbound=*/false).Get(array), 91 dim.has_value()); 92 } 93 94 private: 95 GetConstantArrayBoundHelper( 96 std::optional<ConstantSubscript> dim, bool getLbound) 97 : dim_{dim}, getLbound_{getLbound} {} 98 99 template <typename T> ConstantSubscripts Get(const T &) { 100 // The method is needed for template expansion, but we should never get 101 // here in practice. 102 CHECK(false); 103 return {0}; 104 } 105 106 template <typename T> ConstantSubscripts Get(const Constant<T> &x) { 107 if (getLbound_) { 108 // Return the lower bound 109 if (dim_) { 110 return {x.lbounds().at(*dim_)}; 111 } else { 112 return x.lbounds(); 113 } 114 } else { 115 // Return the upper bound 116 if (arrayFromParenthesesExpr) { 117 // Underlying array comes from (x) expression - return shapes 118 if (dim_) { 119 return {x.shape().at(*dim_)}; 120 } else { 121 return x.shape(); 122 } 123 } else { 124 return x.ComputeUbounds(dim_); 125 } 126 } 127 } 128 129 template <typename T> ConstantSubscripts Get(const Parentheses<T> &x) { 130 // Case of temp variable inside parentheses - return [1, ... 1] for lower 131 // bounds and shape for upper bounds 132 if (getLbound_) { 133 return ConstantSubscripts(x.Rank(), ConstantSubscript{1}); 134 } else { 135 // Indicate that underlying array comes from parentheses expression. 136 // Continue to unwrap expression until we hit a constant 137 arrayFromParenthesesExpr = true; 138 return Get(x.left()); 139 } 140 } 141 142 template <typename T> ConstantSubscripts Get(const Expr<T> &x) { 143 // recurse through Expr<T>'a until we hit a constant 144 return common::visit([&](const auto &inner) { return Get(inner); }, 145 // [&](const auto &) { return 0; }, 146 x.u); 147 } 148 149 const std::optional<ConstantSubscript> dim_; 150 const bool getLbound_; 151 bool arrayFromParenthesesExpr{false}; 152 }; 153 154 template <int KIND> 155 Expr<Type<TypeCategory::Integer, KIND>> LBOUND(FoldingContext &context, 156 FunctionRef<Type<TypeCategory::Integer, KIND>> &&funcRef) { 157 using T = Type<TypeCategory::Integer, KIND>; 158 ActualArguments &args{funcRef.arguments()}; 159 if (const auto *array{UnwrapExpr<Expr<SomeType>>(args[0])}) { 160 std::optional<int> dim; 161 if (funcRef.Rank() == 0) { 162 // Optional DIM= argument is present: result is scalar. 163 if (!CheckDimArg(args[1], *array, context.messages(), true, dim)) { 164 return MakeInvalidIntrinsic<T>(std::move(funcRef)); 165 } else if (!dim) { 166 // DIM= is present but not constant, or error 167 return Expr<T>{std::move(funcRef)}; 168 } 169 } 170 if (IsAssumedRank(*array)) { 171 // Would like to return 1 if DIM=.. is present, but that would be 172 // hiding a runtime error if the DIM= were too large (including 173 // the case of an assumed-rank argument that's scalar). 174 } else if (int rank{array->Rank()}; rank > 0) { 175 bool lowerBoundsAreOne{true}; 176 if (auto named{ExtractNamedEntity(*array)}) { 177 const Symbol &symbol{named->GetLastSymbol()}; 178 if (symbol.Rank() == rank) { 179 lowerBoundsAreOne = false; 180 if (dim) { 181 if (auto lb{GetLBOUND(context, *named, *dim)}) { 182 return Fold(context, ConvertToType<T>(std::move(*lb))); 183 } 184 } else if (auto extents{ 185 AsExtentArrayExpr(GetLBOUNDs(context, *named))}) { 186 return Fold(context, 187 ConvertToType<T>(Expr<ExtentType>{std::move(*extents)})); 188 } 189 } else { 190 lowerBoundsAreOne = symbol.Rank() == 0; // LBOUND(array%component) 191 } 192 } 193 if (IsActuallyConstant(*array)) { 194 return GetConstantArrayBoundHelper::GetLbound<T>(*array, dim); 195 } 196 if (lowerBoundsAreOne) { 197 ConstantSubscripts ones(rank, ConstantSubscript{1}); 198 return PackageConstantBounds<T>(std::move(ones), dim.has_value()); 199 } 200 } 201 } 202 return Expr<T>{std::move(funcRef)}; 203 } 204 205 template <int KIND> 206 Expr<Type<TypeCategory::Integer, KIND>> UBOUND(FoldingContext &context, 207 FunctionRef<Type<TypeCategory::Integer, KIND>> &&funcRef) { 208 using T = Type<TypeCategory::Integer, KIND>; 209 ActualArguments &args{funcRef.arguments()}; 210 if (auto *array{UnwrapExpr<Expr<SomeType>>(args[0])}) { 211 std::optional<int> dim; 212 if (funcRef.Rank() == 0) { 213 // Optional DIM= argument is present: result is scalar. 214 if (!CheckDimArg(args[1], *array, context.messages(), false, dim)) { 215 return MakeInvalidIntrinsic<T>(std::move(funcRef)); 216 } else if (!dim) { 217 // DIM= is present but not constant, or error 218 return Expr<T>{std::move(funcRef)}; 219 } 220 } 221 if (IsAssumedRank(*array)) { 222 } else if (int rank{array->Rank()}; rank > 0) { 223 bool takeBoundsFromShape{true}; 224 if (auto named{ExtractNamedEntity(*array)}) { 225 const Symbol &symbol{named->GetLastSymbol()}; 226 if (symbol.Rank() == rank) { 227 takeBoundsFromShape = false; 228 if (dim) { 229 if (auto ub{GetUBOUND(context, *named, *dim)}) { 230 return Fold(context, ConvertToType<T>(std::move(*ub))); 231 } 232 } else { 233 Shape ubounds{GetUBOUNDs(context, *named)}; 234 if (semantics::IsAssumedSizeArray(symbol)) { 235 CHECK(!ubounds.back()); 236 ubounds.back() = ExtentExpr{-1}; 237 } 238 if (auto extents{AsExtentArrayExpr(ubounds)}) { 239 return Fold(context, 240 ConvertToType<T>(Expr<ExtentType>{std::move(*extents)})); 241 } 242 } 243 } else { 244 takeBoundsFromShape = symbol.Rank() == 0; // UBOUND(array%component) 245 } 246 } 247 if (IsActuallyConstant(*array)) { 248 return GetConstantArrayBoundHelper::GetUbound<T>(*array, dim); 249 } 250 if (takeBoundsFromShape) { 251 if (auto shape{GetContextFreeShape(context, *array)}) { 252 if (dim) { 253 if (auto &dimSize{shape->at(*dim)}) { 254 return Fold(context, 255 ConvertToType<T>(Expr<ExtentType>{std::move(*dimSize)})); 256 } 257 } else if (auto shapeExpr{AsExtentArrayExpr(*shape)}) { 258 return Fold(context, ConvertToType<T>(std::move(*shapeExpr))); 259 } 260 } 261 } 262 } 263 } 264 return Expr<T>{std::move(funcRef)}; 265 } 266 267 // COUNT() 268 template <typename T, int MASK_KIND> class CountAccumulator { 269 using MaskT = Type<TypeCategory::Logical, MASK_KIND>; 270 271 public: 272 CountAccumulator(const Constant<MaskT> &mask) : mask_{mask} {} 273 void operator()( 274 Scalar<T> &element, const ConstantSubscripts &at, bool /*first*/) { 275 if (mask_.At(at).IsTrue()) { 276 auto incremented{element.AddSigned(Scalar<T>{1})}; 277 overflow_ |= incremented.overflow; 278 element = incremented.value; 279 } 280 } 281 bool overflow() const { return overflow_; } 282 void Done(Scalar<T> &) const {} 283 284 private: 285 const Constant<MaskT> &mask_; 286 bool overflow_{false}; 287 }; 288 289 template <typename T, int maskKind> 290 static Expr<T> FoldCount(FoldingContext &context, FunctionRef<T> &&ref) { 291 using KindLogical = Type<TypeCategory::Logical, maskKind>; 292 static_assert(T::category == TypeCategory::Integer); 293 std::optional<int> dim; 294 if (std::optional<ArrayAndMask<KindLogical>> arrayAndMask{ 295 ProcessReductionArgs<KindLogical>( 296 context, ref.arguments(), dim, /*ARRAY=*/0, /*DIM=*/1)}) { 297 CountAccumulator<T, maskKind> accumulator{arrayAndMask->array}; 298 Constant<T> result{DoReduction<T>(arrayAndMask->array, arrayAndMask->mask, 299 dim, Scalar<T>{}, accumulator)}; 300 if (accumulator.overflow() && 301 context.languageFeatures().ShouldWarn( 302 common::UsageWarning::FoldingException)) { 303 context.messages().Say(common::UsageWarning::FoldingException, 304 "Result of intrinsic function COUNT overflows its result type"_warn_en_US); 305 } 306 return Expr<T>{std::move(result)}; 307 } 308 return Expr<T>{std::move(ref)}; 309 } 310 311 // FINDLOC(), MAXLOC(), & MINLOC() 312 enum class WhichLocation { Findloc, Maxloc, Minloc }; 313 template <WhichLocation WHICH> class LocationHelper { 314 public: 315 LocationHelper( 316 DynamicType &&type, ActualArguments &arg, FoldingContext &context) 317 : type_{type}, arg_{arg}, context_{context} {} 318 using Result = std::optional<Constant<SubscriptInteger>>; 319 using Types = std::conditional_t<WHICH == WhichLocation::Findloc, 320 AllIntrinsicTypes, RelationalTypes>; 321 322 template <typename T> Result Test() const { 323 if (T::category != type_.category() || T::kind != type_.kind()) { 324 return std::nullopt; 325 } 326 CHECK(arg_.size() == (WHICH == WhichLocation::Findloc ? 6 : 5)); 327 Folder<T> folder{context_}; 328 Constant<T> *array{folder.Folding(arg_[0])}; 329 if (!array) { 330 return std::nullopt; 331 } 332 std::optional<Constant<T>> value; 333 if constexpr (WHICH == WhichLocation::Findloc) { 334 if (const Constant<T> *p{folder.Folding(arg_[1])}) { 335 value.emplace(*p); 336 } else { 337 return std::nullopt; 338 } 339 } 340 std::optional<int> dim; 341 Constant<LogicalResult> *mask{ 342 GetReductionMASK(arg_[maskArg], array->shape(), context_)}; 343 if ((!mask && arg_[maskArg]) || 344 !CheckReductionDIM(dim, context_, arg_, dimArg, array->Rank())) { 345 return std::nullopt; 346 } 347 bool back{false}; 348 if (arg_[backArg]) { 349 const auto *backConst{ 350 Folder<LogicalResult>{context_, /*forOptionalArgument=*/true}.Folding( 351 arg_[backArg])}; 352 if (backConst) { 353 back = backConst->GetScalarValue().value().IsTrue(); 354 } else { 355 return std::nullopt; 356 } 357 } 358 const RelationalOperator relation{WHICH == WhichLocation::Findloc 359 ? RelationalOperator::EQ 360 : WHICH == WhichLocation::Maxloc 361 ? (back ? RelationalOperator::GE : RelationalOperator::GT) 362 : back ? RelationalOperator::LE 363 : RelationalOperator::LT}; 364 // Use lower bounds of 1 exclusively. 365 array->SetLowerBoundsToOne(); 366 ConstantSubscripts at{array->lbounds()}, maskAt, resultIndices, resultShape; 367 if (mask) { 368 if (auto scalarMask{mask->GetScalarValue()}) { 369 // Convert into array in case of scalar MASK= (for 370 // MAXLOC/MINLOC/FINDLOC mask should be conformable) 371 ConstantSubscript n{GetSize(array->shape())}; 372 std::vector<Scalar<LogicalResult>> mask_elements( 373 n, Scalar<LogicalResult>{scalarMask.value()}); 374 *mask = Constant<LogicalResult>{ 375 std::move(mask_elements), ConstantSubscripts{array->shape()}}; 376 } 377 mask->SetLowerBoundsToOne(); 378 maskAt = mask->lbounds(); 379 } 380 if (dim) { // DIM= 381 if (*dim < 1 || *dim > array->Rank()) { 382 context_.messages().Say("DIM=%d is out of range"_err_en_US, *dim); 383 return std::nullopt; 384 } 385 int zbDim{*dim - 1}; 386 resultShape = array->shape(); 387 resultShape.erase( 388 resultShape.begin() + zbDim); // scalar if array is vector 389 ConstantSubscript dimLength{array->shape()[zbDim]}; 390 ConstantSubscript n{GetSize(resultShape)}; 391 for (ConstantSubscript j{0}; j < n; ++j) { 392 ConstantSubscript hit{0}; 393 if constexpr (WHICH == WhichLocation::Maxloc || 394 WHICH == WhichLocation::Minloc) { 395 value.reset(); 396 } 397 for (ConstantSubscript k{0}; k < dimLength; 398 ++k, ++at[zbDim], mask && ++maskAt[zbDim]) { 399 if ((!mask || mask->At(maskAt).IsTrue()) && 400 IsHit(array->At(at), value, relation, back)) { 401 hit = at[zbDim]; 402 if constexpr (WHICH == WhichLocation::Findloc) { 403 if (!back) { 404 break; 405 } 406 } 407 } 408 } 409 resultIndices.emplace_back(hit); 410 at[zbDim] = std::max<ConstantSubscript>(dimLength, 1); 411 array->IncrementSubscripts(at); 412 at[zbDim] = 1; 413 if (mask) { 414 maskAt[zbDim] = mask->lbounds()[zbDim] + 415 std::max<ConstantSubscript>(dimLength, 1) - 1; 416 mask->IncrementSubscripts(maskAt); 417 maskAt[zbDim] = mask->lbounds()[zbDim]; 418 } 419 } 420 } else { // no DIM= 421 resultShape = ConstantSubscripts{array->Rank()}; // always a vector 422 ConstantSubscript n{GetSize(array->shape())}; 423 resultIndices = ConstantSubscripts(array->Rank(), 0); 424 for (ConstantSubscript j{0}; j < n; ++j, array->IncrementSubscripts(at), 425 mask && mask->IncrementSubscripts(maskAt)) { 426 if ((!mask || mask->At(maskAt).IsTrue()) && 427 IsHit(array->At(at), value, relation, back)) { 428 resultIndices = at; 429 if constexpr (WHICH == WhichLocation::Findloc) { 430 if (!back) { 431 break; 432 } 433 } 434 } 435 } 436 } 437 std::vector<Scalar<SubscriptInteger>> resultElements; 438 for (ConstantSubscript j : resultIndices) { 439 resultElements.emplace_back(j); 440 } 441 return Constant<SubscriptInteger>{ 442 std::move(resultElements), std::move(resultShape)}; 443 } 444 445 private: 446 template <typename T> 447 bool IsHit(typename Constant<T>::Element element, 448 std::optional<Constant<T>> &value, 449 [[maybe_unused]] RelationalOperator relation, 450 [[maybe_unused]] bool back) const { 451 std::optional<Expr<LogicalResult>> cmp; 452 bool result{true}; 453 if (value) { 454 if constexpr (T::category == TypeCategory::Logical) { 455 // array(at) .EQV. value? 456 static_assert(WHICH == WhichLocation::Findloc); 457 cmp.emplace(ConvertToType<LogicalResult>( 458 Expr<T>{LogicalOperation<T::kind>{LogicalOperator::Eqv, 459 Expr<T>{Constant<T>{element}}, Expr<T>{Constant<T>{*value}}}})); 460 } else { // compare array(at) to value 461 if constexpr (T::category == TypeCategory::Real && 462 (WHICH == WhichLocation::Maxloc || 463 WHICH == WhichLocation::Minloc)) { 464 if (value && value->GetScalarValue().value().IsNotANumber() && 465 (back || !element.IsNotANumber())) { 466 // Replace NaN 467 cmp.emplace(Constant<LogicalResult>{Scalar<LogicalResult>{true}}); 468 } 469 } 470 if (!cmp) { 471 cmp.emplace(PackageRelation(relation, Expr<T>{Constant<T>{element}}, 472 Expr<T>{Constant<T>{*value}})); 473 } 474 } 475 Expr<LogicalResult> folded{Fold(context_, std::move(*cmp))}; 476 result = GetScalarConstantValue<LogicalResult>(folded).value().IsTrue(); 477 } else { 478 // first unmasked element for MAXLOC/MINLOC - always take it 479 } 480 if constexpr (WHICH == WhichLocation::Maxloc || 481 WHICH == WhichLocation::Minloc) { 482 if (result) { 483 value.emplace(std::move(element)); 484 } 485 } 486 return result; 487 } 488 489 static constexpr int dimArg{WHICH == WhichLocation::Findloc ? 2 : 1}; 490 static constexpr int maskArg{dimArg + 1}; 491 static constexpr int backArg{maskArg + 2}; 492 493 DynamicType type_; 494 ActualArguments &arg_; 495 FoldingContext &context_; 496 }; 497 498 template <WhichLocation which> 499 static std::optional<Constant<SubscriptInteger>> FoldLocationCall( 500 ActualArguments &arg, FoldingContext &context) { 501 if (arg[0]) { 502 if (auto type{arg[0]->GetType()}) { 503 if constexpr (which == WhichLocation::Findloc) { 504 // Both ARRAY and VALUE are susceptible to conversion to a common 505 // comparison type. 506 if (arg[1]) { 507 if (auto valType{arg[1]->GetType()}) { 508 if (auto compareType{ComparisonType(*type, *valType)}) { 509 type = compareType; 510 } 511 } 512 } 513 } 514 return common::SearchTypes( 515 LocationHelper<which>{std::move(*type), arg, context}); 516 } 517 } 518 return std::nullopt; 519 } 520 521 template <WhichLocation which, typename T> 522 static Expr<T> FoldLocation(FoldingContext &context, FunctionRef<T> &&ref) { 523 static_assert(T::category == TypeCategory::Integer); 524 if (std::optional<Constant<SubscriptInteger>> found{ 525 FoldLocationCall<which>(ref.arguments(), context)}) { 526 return Expr<T>{Fold( 527 context, ConvertToType<T>(Expr<SubscriptInteger>{std::move(*found)}))}; 528 } else { 529 return Expr<T>{std::move(ref)}; 530 } 531 } 532 533 // for IALL, IANY, & IPARITY 534 template <typename T> 535 static Expr<T> FoldBitReduction(FoldingContext &context, FunctionRef<T> &&ref, 536 Scalar<T> (Scalar<T>::*operation)(const Scalar<T> &) const, 537 Scalar<T> identity) { 538 static_assert(T::category == TypeCategory::Integer || 539 T::category == TypeCategory::Unsigned); 540 std::optional<int> dim; 541 if (std::optional<ArrayAndMask<T>> arrayAndMask{ 542 ProcessReductionArgs<T>(context, ref.arguments(), dim, 543 /*ARRAY=*/0, /*DIM=*/1, /*MASK=*/2)}) { 544 OperationAccumulator<T> accumulator{arrayAndMask->array, operation}; 545 return Expr<T>{DoReduction<T>( 546 arrayAndMask->array, arrayAndMask->mask, dim, identity, accumulator)}; 547 } 548 return Expr<T>{std::move(ref)}; 549 } 550 551 // Common cases for INTEGER and UNSIGNED 552 template <typename T> 553 std::optional<Expr<T>> FoldIntrinsicFunctionCommon( 554 FoldingContext &context, FunctionRef<T> &funcRef) { 555 ActualArguments &args{funcRef.arguments()}; 556 auto *intrinsic{std::get_if<SpecificIntrinsic>(&funcRef.proc().u)}; 557 CHECK(intrinsic); 558 std::string name{intrinsic->name}; 559 using Int4 = Type<TypeCategory::Integer, 4>; 560 if (name == "bit_size") { 561 return Expr<T>{Scalar<T>::bits}; 562 } else if (name == "digits") { 563 if (const auto *cx{UnwrapExpr<Expr<SomeInteger>>(args[0])}) { 564 return Expr<T>{common::visit( 565 [](const auto &kx) { 566 return Scalar<ResultType<decltype(kx)>>::DIGITS; 567 }, 568 cx->u)}; 569 } else if (const auto *cx{UnwrapExpr<Expr<SomeUnsigned>>(args[0])}) { 570 return Expr<T>{common::visit( 571 [](const auto &kx) { 572 return Scalar<ResultType<decltype(kx)>>::DIGITS + 1; 573 }, 574 cx->u)}; 575 } else if (const auto *cx{UnwrapExpr<Expr<SomeReal>>(args[0])}) { 576 return Expr<T>{common::visit( 577 [](const auto &kx) { 578 return Scalar<ResultType<decltype(kx)>>::DIGITS; 579 }, 580 cx->u)}; 581 } else if (const auto *cx{UnwrapExpr<Expr<SomeComplex>>(args[0])}) { 582 return Expr<T>{common::visit( 583 [](const auto &kx) { 584 return Scalar<typename ResultType<decltype(kx)>::Part>::DIGITS; 585 }, 586 cx->u)}; 587 } 588 } else if (name == "dot_product") { 589 return FoldDotProduct<T>(context, std::move(funcRef)); 590 } else if (name == "dshiftl" || name == "dshiftr") { 591 const auto fptr{ 592 name == "dshiftl" ? &Scalar<T>::DSHIFTL : &Scalar<T>::DSHIFTR}; 593 // Third argument can be of any kind. However, it must be smaller or equal 594 // than BIT_SIZE. It can be converted to Int4 to simplify. 595 if (const auto *argCon{Folder<T>(context).Folding(args[0])}; 596 argCon && argCon->empty()) { 597 } else if (const auto *shiftCon{Folder<Int4>(context).Folding(args[2])}) { 598 for (const auto &scalar : shiftCon->values()) { 599 std::int64_t shiftVal{scalar.ToInt64()}; 600 if (shiftVal < 0) { 601 context.messages().Say("SHIFT=%jd count for %s is negative"_err_en_US, 602 std::intmax_t{shiftVal}, name); 603 break; 604 } else if (shiftVal > T::Scalar::bits) { 605 context.messages().Say( 606 "SHIFT=%jd count for %s is greater than %d"_err_en_US, 607 std::intmax_t{shiftVal}, name, T::Scalar::bits); 608 break; 609 } 610 } 611 } 612 return FoldElementalIntrinsic<T, T, T, Int4>(context, std::move(funcRef), 613 ScalarFunc<T, T, T, Int4>( 614 [&fptr](const Scalar<T> &i, const Scalar<T> &j, 615 const Scalar<Int4> &shift) -> Scalar<T> { 616 return std::invoke(fptr, i, j, static_cast<int>(shift.ToInt64())); 617 })); 618 } else if (name == "iand" || name == "ior" || name == "ieor") { 619 auto fptr{&Scalar<T>::IAND}; 620 if (name == "iand") { // done in fptr declaration 621 } else if (name == "ior") { 622 fptr = &Scalar<T>::IOR; 623 } else if (name == "ieor") { 624 fptr = &Scalar<T>::IEOR; 625 } else { 626 common::die("missing case to fold intrinsic function %s", name.c_str()); 627 } 628 return FoldElementalIntrinsic<T, T, T>( 629 context, std::move(funcRef), ScalarFunc<T, T, T>(fptr)); 630 } else if (name == "iall") { 631 return FoldBitReduction( 632 context, std::move(funcRef), &Scalar<T>::IAND, Scalar<T>{}.NOT()); 633 } else if (name == "iany") { 634 return FoldBitReduction( 635 context, std::move(funcRef), &Scalar<T>::IOR, Scalar<T>{}); 636 } else if (name == "ibclr" || name == "ibset") { 637 // Second argument can be of any kind. However, it must be smaller 638 // than BIT_SIZE. It can be converted to Int4 to simplify. 639 auto fptr{&Scalar<T>::IBCLR}; 640 if (name == "ibclr") { // done in fptr definition 641 } else if (name == "ibset") { 642 fptr = &Scalar<T>::IBSET; 643 } else { 644 common::die("missing case to fold intrinsic function %s", name.c_str()); 645 } 646 if (const auto *argCon{Folder<T>(context).Folding(args[0])}; 647 argCon && argCon->empty()) { 648 } else if (const auto *posCon{Folder<Int4>(context).Folding(args[1])}) { 649 for (const auto &scalar : posCon->values()) { 650 std::int64_t posVal{scalar.ToInt64()}; 651 if (posVal < 0) { 652 context.messages().Say( 653 "bit position for %s (%jd) is negative"_err_en_US, name, 654 std::intmax_t{posVal}); 655 break; 656 } else if (posVal >= T::Scalar::bits) { 657 context.messages().Say( 658 "bit position for %s (%jd) is not less than %d"_err_en_US, name, 659 std::intmax_t{posVal}, T::Scalar::bits); 660 break; 661 } 662 } 663 } 664 return FoldElementalIntrinsic<T, T, Int4>(context, std::move(funcRef), 665 ScalarFunc<T, T, Int4>( 666 [&](const Scalar<T> &i, const Scalar<Int4> &pos) -> Scalar<T> { 667 return std::invoke(fptr, i, static_cast<int>(pos.ToInt64())); 668 })); 669 } else if (name == "ibits") { 670 const auto *posCon{Folder<Int4>(context).Folding(args[1])}; 671 const auto *lenCon{Folder<Int4>(context).Folding(args[2])}; 672 if (const auto *argCon{Folder<T>(context).Folding(args[0])}; 673 argCon && argCon->empty()) { 674 } else { 675 std::size_t posCt{posCon ? posCon->size() : 0}; 676 std::size_t lenCt{lenCon ? lenCon->size() : 0}; 677 std::size_t n{std::max(posCt, lenCt)}; 678 for (std::size_t j{0}; j < n; ++j) { 679 int posVal{j < posCt || posCt == 1 680 ? static_cast<int>(posCon->values()[j % posCt].ToInt64()) 681 : 0}; 682 int lenVal{j < lenCt || lenCt == 1 683 ? static_cast<int>(lenCon->values()[j % lenCt].ToInt64()) 684 : 0}; 685 if (posVal < 0) { 686 context.messages().Say( 687 "bit position for IBITS(POS=%jd) is negative"_err_en_US, 688 std::intmax_t{posVal}); 689 break; 690 } else if (lenVal < 0) { 691 context.messages().Say( 692 "bit length for IBITS(LEN=%jd) is negative"_err_en_US, 693 std::intmax_t{lenVal}); 694 break; 695 } else if (posVal + lenVal > T::Scalar::bits) { 696 context.messages().Say( 697 "IBITS() must have POS+LEN (>=%jd) no greater than %d"_err_en_US, 698 std::intmax_t{posVal + lenVal}, T::Scalar::bits); 699 break; 700 } 701 } 702 } 703 return FoldElementalIntrinsic<T, T, Int4, Int4>(context, std::move(funcRef), 704 ScalarFunc<T, T, Int4, Int4>( 705 [&](const Scalar<T> &i, const Scalar<Int4> &pos, 706 const Scalar<Int4> &len) -> Scalar<T> { 707 return i.IBITS(static_cast<int>(pos.ToInt64()), 708 static_cast<int>(len.ToInt64())); 709 })); 710 } else if (name == "int" || name == "int2" || name == "int8" || 711 name == "uint") { 712 if (auto *expr{UnwrapExpr<Expr<SomeType>>(args[0])}) { 713 return common::visit( 714 [&](auto &&x) -> Expr<T> { 715 using From = std::decay_t<decltype(x)>; 716 if constexpr (std::is_same_v<From, BOZLiteralConstant> || 717 IsNumericCategoryExpr<From>()) { 718 return Fold(context, ConvertToType<T>(std::move(x))); 719 } 720 DIE("int() argument type not valid"); 721 }, 722 std::move(expr->u)); 723 } 724 } else if (name == "iparity") { 725 return FoldBitReduction( 726 context, std::move(funcRef), &Scalar<T>::IEOR, Scalar<T>{}); 727 } else if (name == "ishft" || name == "ishftc") { 728 const auto *argCon{Folder<T>(context).Folding(args[0])}; 729 const auto *shiftCon{Folder<Int4>(context).Folding(args[1])}; 730 const auto *shiftVals{shiftCon ? &shiftCon->values() : nullptr}; 731 const auto *sizeCon{args.size() == 3 732 ? Folder<Int4>{context, /*forOptionalArgument=*/true}.Folding( 733 args[2]) 734 : nullptr}; 735 const auto *sizeVals{sizeCon ? &sizeCon->values() : nullptr}; 736 if ((argCon && argCon->empty()) || !shiftVals || shiftVals->empty() || 737 (sizeVals && sizeVals->empty())) { 738 // size= and shift= values don't need to be checked 739 } else { 740 for (const auto &scalar : *shiftVals) { 741 std::int64_t shiftVal{scalar.ToInt64()}; 742 if (shiftVal < -T::Scalar::bits) { 743 context.messages().Say( 744 "SHIFT=%jd count for %s is less than %d"_err_en_US, 745 std::intmax_t{shiftVal}, name, -T::Scalar::bits); 746 break; 747 } else if (shiftVal > T::Scalar::bits) { 748 context.messages().Say( 749 "SHIFT=%jd count for %s is greater than %d"_err_en_US, 750 std::intmax_t{shiftVal}, name, T::Scalar::bits); 751 break; 752 } 753 } 754 if (sizeVals) { 755 for (const auto &scalar : *sizeVals) { 756 std::int64_t sizeVal{scalar.ToInt64()}; 757 if (sizeVal <= 0) { 758 context.messages().Say( 759 "SIZE=%jd count for ishftc is not positive"_err_en_US, 760 std::intmax_t{sizeVal}, name); 761 break; 762 } else if (sizeVal > T::Scalar::bits) { 763 context.messages().Say( 764 "SIZE=%jd count for ishftc is greater than %d"_err_en_US, 765 std::intmax_t{sizeVal}, T::Scalar::bits); 766 break; 767 } 768 } 769 if (shiftVals->size() == 1 || sizeVals->size() == 1 || 770 shiftVals->size() == sizeVals->size()) { 771 auto iters{std::max(shiftVals->size(), sizeVals->size())}; 772 for (std::size_t j{0}; j < iters; ++j) { 773 auto shiftVal{static_cast<int>( 774 (*shiftVals)[j % shiftVals->size()].ToInt64())}; 775 auto sizeVal{ 776 static_cast<int>((*sizeVals)[j % sizeVals->size()].ToInt64())}; 777 if (sizeVal > 0 && std::abs(shiftVal) > sizeVal) { 778 context.messages().Say( 779 "SHIFT=%jd count for ishftc is greater in magnitude than SIZE=%jd"_err_en_US, 780 std::intmax_t{shiftVal}, std::intmax_t{sizeVal}); 781 break; 782 } 783 } 784 } 785 } 786 } 787 if (name == "ishft") { 788 return FoldElementalIntrinsic<T, T, Int4>(context, std::move(funcRef), 789 ScalarFunc<T, T, Int4>( 790 [&](const Scalar<T> &i, const Scalar<Int4> &shift) -> Scalar<T> { 791 return i.ISHFT(static_cast<int>(shift.ToInt64())); 792 })); 793 } else if (!args.at(2)) { // ISHFTC(no SIZE=) 794 return FoldElementalIntrinsic<T, T, Int4>(context, std::move(funcRef), 795 ScalarFunc<T, T, Int4>( 796 [&](const Scalar<T> &i, const Scalar<Int4> &shift) -> Scalar<T> { 797 return i.ISHFTC(static_cast<int>(shift.ToInt64())); 798 })); 799 } else { // ISHFTC(with SIZE=) 800 return FoldElementalIntrinsic<T, T, Int4, Int4>(context, 801 std::move(funcRef), 802 ScalarFunc<T, T, Int4, Int4>( 803 [&](const Scalar<T> &i, const Scalar<Int4> &shift, 804 const Scalar<Int4> &size) -> Scalar<T> { 805 auto shiftVal{static_cast<int>(shift.ToInt64())}; 806 auto sizeVal{static_cast<int>(size.ToInt64())}; 807 return i.ISHFTC(shiftVal, sizeVal); 808 }), 809 /*hasOptionalArgument=*/true); 810 } 811 } else if (name == "izext" || name == "jzext") { 812 if (args.size() == 1) { 813 if (auto *expr{UnwrapExpr<Expr<SomeKind<T::category>>>(args[0])}) { 814 // Rewrite to IAND(INT(n,k),255_k) for k=KIND(T) 815 intrinsic->name = "iand"; 816 auto converted{ConvertToType<T>(std::move(*expr))}; 817 *expr = 818 Fold(context, Expr<SomeKind<T::category>>{std::move(converted)}); 819 args.emplace_back(AsGenericExpr(Expr<T>{Scalar<T>{255}})); 820 return FoldIntrinsicFunction(context, std::move(funcRef)); 821 } 822 } 823 } else if (name == "maskl" || name == "maskr" || name == "umaskl" || 824 name == "umaskr") { 825 // Argument can be of any kind but value has to be smaller than BIT_SIZE. 826 // It can be safely converted to Int4 to simplify. 827 const auto fptr{name == "maskl" || name == "umaskl" ? &Scalar<T>::MASKL 828 : &Scalar<T>::MASKR}; 829 return FoldElementalIntrinsic<T, Int4>(context, std::move(funcRef), 830 ScalarFunc<T, Int4>([&fptr](const Scalar<Int4> &places) -> Scalar<T> { 831 return fptr(static_cast<int>(places.ToInt64())); 832 })); 833 } else if (name == "matmul") { 834 return FoldMatmul(context, std::move(funcRef)); 835 } else if (name == "max") { 836 return FoldMINorMAX(context, std::move(funcRef), Ordering::Greater); 837 } else if (name == "maxval") { 838 return FoldMaxvalMinval<T>(context, std::move(funcRef), 839 RelationalOperator::GT, 840 T::category == TypeCategory::Unsigned ? typename T::Scalar{} 841 : T::Scalar::Least()); 842 } else if (name == "merge_bits") { 843 return FoldElementalIntrinsic<T, T, T, T>( 844 context, std::move(funcRef), &Scalar<T>::MERGE_BITS); 845 } else if (name == "min") { 846 return FoldMINorMAX(context, std::move(funcRef), Ordering::Less); 847 } else if (name == "minval") { 848 return FoldMaxvalMinval<T>(context, std::move(funcRef), 849 RelationalOperator::LT, 850 T::category == TypeCategory::Unsigned ? typename T::Scalar{}.NOT() 851 : T::Scalar::HUGE()); 852 } else if (name == "not") { 853 return FoldElementalIntrinsic<T, T>( 854 context, std::move(funcRef), &Scalar<T>::NOT); 855 } else if (name == "product") { 856 return FoldProduct<T>(context, std::move(funcRef), Scalar<T>{1}); 857 } else if (name == "radix") { 858 return Expr<T>{2}; 859 } else if (name == "shifta" || name == "shiftr" || name == "shiftl") { 860 // Second argument can be of any kind. However, it must be smaller or 861 // equal than BIT_SIZE. It can be converted to Int4 to simplify. 862 auto fptr{&Scalar<T>::SHIFTA}; 863 if (name == "shifta") { // done in fptr definition 864 } else if (name == "shiftr") { 865 fptr = &Scalar<T>::SHIFTR; 866 } else if (name == "shiftl") { 867 fptr = &Scalar<T>::SHIFTL; 868 } else { 869 common::die("missing case to fold intrinsic function %s", name.c_str()); 870 } 871 if (const auto *argCon{Folder<T>(context).Folding(args[0])}; 872 argCon && argCon->empty()) { 873 } else if (const auto *shiftCon{Folder<Int4>(context).Folding(args[1])}) { 874 for (const auto &scalar : shiftCon->values()) { 875 std::int64_t shiftVal{scalar.ToInt64()}; 876 if (shiftVal < 0) { 877 context.messages().Say("SHIFT=%jd count for %s is negative"_err_en_US, 878 std::intmax_t{shiftVal}, name, -T::Scalar::bits); 879 break; 880 } else if (shiftVal > T::Scalar::bits) { 881 context.messages().Say( 882 "SHIFT=%jd count for %s is greater than %d"_err_en_US, 883 std::intmax_t{shiftVal}, name, T::Scalar::bits); 884 break; 885 } 886 } 887 } 888 return FoldElementalIntrinsic<T, T, Int4>(context, std::move(funcRef), 889 ScalarFunc<T, T, Int4>( 890 [&](const Scalar<T> &i, const Scalar<Int4> &shift) -> Scalar<T> { 891 return std::invoke(fptr, i, static_cast<int>(shift.ToInt64())); 892 })); 893 } else if (name == "sum") { 894 return FoldSum<T>(context, std::move(funcRef)); 895 } 896 return std::nullopt; 897 } 898 899 template <int KIND> 900 Expr<Type<TypeCategory::Integer, KIND>> FoldIntrinsicFunction( 901 FoldingContext &context, 902 FunctionRef<Type<TypeCategory::Integer, KIND>> &&funcRef) { 903 if (auto foldedCommon{FoldIntrinsicFunctionCommon(context, funcRef)}) { 904 return std::move(*foldedCommon); 905 } 906 907 using T = Type<TypeCategory::Integer, KIND>; 908 ActualArguments &args{funcRef.arguments()}; 909 auto *intrinsic{std::get_if<SpecificIntrinsic>(&funcRef.proc().u)}; 910 CHECK(intrinsic); 911 std::string name{intrinsic->name}; 912 913 auto FromInt64{[&name, &context](std::int64_t n) { 914 Scalar<T> result{n}; 915 if (result.ToInt64() != n && 916 context.languageFeatures().ShouldWarn( 917 common::UsageWarning::FoldingException)) { 918 context.messages().Say(common::UsageWarning::FoldingException, 919 "Result of intrinsic function '%s' (%jd) overflows its result type"_warn_en_US, 920 name, std::intmax_t{n}); 921 } 922 return result; 923 }}; 924 925 if (name == "abs") { // incl. babs, iiabs, jiaabs, & kiabs 926 return FoldElementalIntrinsic<T, T>(context, std::move(funcRef), 927 ScalarFunc<T, T>([&context](const Scalar<T> &i) -> Scalar<T> { 928 typename Scalar<T>::ValueWithOverflow j{i.ABS()}; 929 if (j.overflow && 930 context.languageFeatures().ShouldWarn( 931 common::UsageWarning::FoldingException)) { 932 context.messages().Say(common::UsageWarning::FoldingException, 933 "abs(integer(kind=%d)) folding overflowed"_warn_en_US, KIND); 934 } 935 return j.value; 936 })); 937 } else if (name == "ceiling" || name == "floor" || name == "nint") { 938 if (const auto *cx{UnwrapExpr<Expr<SomeReal>>(args[0])}) { 939 // NINT rounds ties away from zero, not to even 940 common::RoundingMode mode{name == "ceiling" ? common::RoundingMode::Up 941 : name == "floor" ? common::RoundingMode::Down 942 : common::RoundingMode::TiesAwayFromZero}; 943 return common::visit( 944 [&](const auto &kx) { 945 using TR = ResultType<decltype(kx)>; 946 return FoldElementalIntrinsic<T, TR>(context, std::move(funcRef), 947 ScalarFunc<T, TR>([&](const Scalar<TR> &x) { 948 auto y{x.template ToInteger<Scalar<T>>(mode)}; 949 if (y.flags.test(RealFlag::Overflow) && 950 context.languageFeatures().ShouldWarn( 951 common::UsageWarning::FoldingException)) { 952 context.messages().Say( 953 common::UsageWarning::FoldingException, 954 "%s intrinsic folding overflow"_warn_en_US, name); 955 } 956 return y.value; 957 })); 958 }, 959 cx->u); 960 } 961 } else if (name == "count") { 962 int maskKind = args[0]->GetType()->kind(); 963 switch (maskKind) { 964 SWITCH_COVERS_ALL_CASES 965 case 1: 966 return FoldCount<T, 1>(context, std::move(funcRef)); 967 case 2: 968 return FoldCount<T, 2>(context, std::move(funcRef)); 969 case 4: 970 return FoldCount<T, 4>(context, std::move(funcRef)); 971 case 8: 972 return FoldCount<T, 8>(context, std::move(funcRef)); 973 } 974 } else if (name == "dim") { 975 return FoldElementalIntrinsic<T, T, T>(context, std::move(funcRef), 976 ScalarFunc<T, T, T>( 977 [&context](const Scalar<T> &x, const Scalar<T> &y) -> Scalar<T> { 978 auto result{x.DIM(y)}; 979 if (result.overflow && 980 context.languageFeatures().ShouldWarn( 981 common::UsageWarning::FoldingException)) { 982 context.messages().Say(common::UsageWarning::FoldingException, 983 "DIM intrinsic folding overflow"_warn_en_US); 984 } 985 return result.value; 986 })); 987 } else if (name == "exponent") { 988 if (auto *sx{UnwrapExpr<Expr<SomeReal>>(args[0])}) { 989 return common::visit( 990 [&funcRef, &context](const auto &x) -> Expr<T> { 991 using TR = typename std::decay_t<decltype(x)>::Result; 992 return FoldElementalIntrinsic<T, TR>(context, std::move(funcRef), 993 &Scalar<TR>::template EXPONENT<Scalar<T>>); 994 }, 995 sx->u); 996 } else { 997 DIE("exponent argument must be real"); 998 } 999 } else if (name == "findloc") { 1000 return FoldLocation<WhichLocation::Findloc, T>(context, std::move(funcRef)); 1001 } else if (name == "huge") { 1002 return Expr<T>{Scalar<T>::HUGE()}; 1003 } else if (name == "iachar" || name == "ichar") { 1004 auto *someChar{UnwrapExpr<Expr<SomeCharacter>>(args[0])}; 1005 CHECK(someChar); 1006 if (auto len{ToInt64(someChar->LEN())}) { 1007 if (len.value() < 1) { 1008 context.messages().Say( 1009 "Character in intrinsic function %s must have length one"_err_en_US, 1010 name); 1011 } else if (len.value() > 1 && 1012 context.languageFeatures().ShouldWarn( 1013 common::UsageWarning::Portability)) { 1014 // Do not die, this was not checked before 1015 context.messages().Say(common::UsageWarning::Portability, 1016 "Character in intrinsic function %s should have length one"_port_en_US, 1017 name); 1018 } else { 1019 return common::visit( 1020 [&funcRef, &context, &FromInt64](const auto &str) -> Expr<T> { 1021 using Char = typename std::decay_t<decltype(str)>::Result; 1022 (void)FromInt64; 1023 return FoldElementalIntrinsic<T, Char>(context, 1024 std::move(funcRef), 1025 ScalarFunc<T, Char>( 1026 #ifndef _MSC_VER 1027 [&FromInt64](const Scalar<Char> &c) { 1028 return FromInt64(CharacterUtils<Char::kind>::ICHAR( 1029 CharacterUtils<Char::kind>::Resize(c, 1))); 1030 })); 1031 #else // _MSC_VER 1032 // MSVC 14 get confused by the original code above and 1033 // ends up emitting an error about passing a std::string 1034 // to the std::u16string instantiation of 1035 // CharacterUtils<2>::ICHAR(). Can't find a work-around, 1036 // so remove the FromInt64 error checking lambda that 1037 // seems to have caused the proble. 1038 [](const Scalar<Char> &c) { 1039 return CharacterUtils<Char::kind>::ICHAR( 1040 CharacterUtils<Char::kind>::Resize(c, 1)); 1041 })); 1042 #endif // _MSC_VER 1043 }, 1044 someChar->u); 1045 } 1046 } 1047 } else if (name == "index" || name == "scan" || name == "verify") { 1048 if (auto *charExpr{UnwrapExpr<Expr<SomeCharacter>>(args[0])}) { 1049 return common::visit( 1050 [&](const auto &kch) -> Expr<T> { 1051 using TC = typename std::decay_t<decltype(kch)>::Result; 1052 if (UnwrapExpr<Expr<SomeLogical>>(args[2])) { // BACK= 1053 return FoldElementalIntrinsic<T, TC, TC, LogicalResult>(context, 1054 std::move(funcRef), 1055 ScalarFunc<T, TC, TC, LogicalResult>{ 1056 [&name, &FromInt64](const Scalar<TC> &str, 1057 const Scalar<TC> &other, 1058 const Scalar<LogicalResult> &back) { 1059 return FromInt64(name == "index" 1060 ? CharacterUtils<TC::kind>::INDEX( 1061 str, other, back.IsTrue()) 1062 : name == "scan" 1063 ? CharacterUtils<TC::kind>::SCAN( 1064 str, other, back.IsTrue()) 1065 : CharacterUtils<TC::kind>::VERIFY( 1066 str, other, back.IsTrue())); 1067 }}); 1068 } else { 1069 return FoldElementalIntrinsic<T, TC, TC>(context, 1070 std::move(funcRef), 1071 ScalarFunc<T, TC, TC>{ 1072 [&name, &FromInt64]( 1073 const Scalar<TC> &str, const Scalar<TC> &other) { 1074 return FromInt64(name == "index" 1075 ? CharacterUtils<TC::kind>::INDEX(str, other) 1076 : name == "scan" 1077 ? CharacterUtils<TC::kind>::SCAN(str, other) 1078 : CharacterUtils<TC::kind>::VERIFY(str, other)); 1079 }}); 1080 } 1081 }, 1082 charExpr->u); 1083 } else { 1084 DIE("first argument must be CHARACTER"); 1085 } 1086 } else if (name == "int_ptr_kind") { 1087 return Expr<T>{8}; 1088 } else if (name == "kind") { 1089 // FoldOperation(FunctionRef &&) in fold-implementation.h will not 1090 // have folded the argument; in the case of TypeParamInquiry, 1091 // try to get the type of the parameter itself. 1092 if (const auto *expr{args[0] ? args[0]->UnwrapExpr() : nullptr}) { 1093 if (const auto *inquiry{UnwrapExpr<TypeParamInquiry>(*expr)}) { 1094 if (const auto *typeSpec{inquiry->parameter().GetType()}) { 1095 if (const auto *intrinType{typeSpec->AsIntrinsic()}) { 1096 if (auto k{ToInt64(Fold( 1097 context, Expr<SubscriptInteger>{intrinType->kind()}))}) { 1098 return Expr<T>{*k}; 1099 } 1100 } 1101 } 1102 } else if (auto dyType{expr->GetType()}) { 1103 return Expr<T>{dyType->kind()}; 1104 } 1105 } 1106 } else if (name == "lbound") { 1107 return LBOUND(context, std::move(funcRef)); 1108 } else if (name == "leadz" || name == "trailz" || name == "poppar" || 1109 name == "popcnt") { 1110 if (auto *sn{UnwrapExpr<Expr<SomeKind<T::category>>>(args[0])}) { 1111 return common::visit( 1112 [&funcRef, &context, &name](const auto &n) -> Expr<T> { 1113 using TI = typename std::decay_t<decltype(n)>::Result; 1114 if (name == "poppar") { 1115 return FoldElementalIntrinsic<T, TI>(context, std::move(funcRef), 1116 ScalarFunc<T, TI>([](const Scalar<TI> &i) -> Scalar<T> { 1117 return Scalar<T>{i.POPPAR() ? 1 : 0}; 1118 })); 1119 } 1120 auto fptr{&Scalar<TI>::LEADZ}; 1121 if (name == "leadz") { // done in fptr definition 1122 } else if (name == "trailz") { 1123 fptr = &Scalar<TI>::TRAILZ; 1124 } else if (name == "popcnt") { 1125 fptr = &Scalar<TI>::POPCNT; 1126 } else { 1127 common::die( 1128 "missing case to fold intrinsic function %s", name.c_str()); 1129 } 1130 return FoldElementalIntrinsic<T, TI>(context, std::move(funcRef), 1131 // `i` should be declared as `const Scalar<TI>&`. 1132 // We declare it as `auto` to workaround an msvc bug: 1133 // https://developercommunity.visualstudio.com/t/Regression:-nested-closure-assumes-wrong/10130223 1134 ScalarFunc<T, TI>([&fptr](const auto &i) -> Scalar<T> { 1135 return Scalar<T>{std::invoke(fptr, i)}; 1136 })); 1137 }, 1138 sn->u); 1139 } else { 1140 DIE("leadz argument must be integer"); 1141 } 1142 } else if (name == "len") { 1143 if (auto *charExpr{UnwrapExpr<Expr<SomeCharacter>>(args[0])}) { 1144 return common::visit( 1145 [&](auto &kx) { 1146 if (auto len{kx.LEN()}) { 1147 if (IsScopeInvariantExpr(*len)) { 1148 return Fold(context, ConvertToType<T>(*std::move(len))); 1149 } else { 1150 return Expr<T>{std::move(funcRef)}; 1151 } 1152 } else { 1153 return Expr<T>{std::move(funcRef)}; 1154 } 1155 }, 1156 charExpr->u); 1157 } else { 1158 DIE("len() argument must be of character type"); 1159 } 1160 } else if (name == "len_trim") { 1161 if (auto *charExpr{UnwrapExpr<Expr<SomeCharacter>>(args[0])}) { 1162 return common::visit( 1163 [&](const auto &kch) -> Expr<T> { 1164 using TC = typename std::decay_t<decltype(kch)>::Result; 1165 return FoldElementalIntrinsic<T, TC>(context, std::move(funcRef), 1166 ScalarFunc<T, TC>{[&FromInt64](const Scalar<TC> &str) { 1167 return FromInt64(CharacterUtils<TC::kind>::LEN_TRIM(str)); 1168 }}); 1169 }, 1170 charExpr->u); 1171 } else { 1172 DIE("len_trim() argument must be of character type"); 1173 } 1174 } else if (name == "max0" || name == "max1") { 1175 return RewriteSpecificMINorMAX(context, std::move(funcRef)); 1176 } else if (name == "maxexponent") { 1177 if (auto *sx{UnwrapExpr<Expr<SomeReal>>(args[0])}) { 1178 return common::visit( 1179 [](const auto &x) { 1180 using TR = typename std::decay_t<decltype(x)>::Result; 1181 return Expr<T>{Scalar<TR>::MAXEXPONENT}; 1182 }, 1183 sx->u); 1184 } 1185 } else if (name == "maxloc") { 1186 return FoldLocation<WhichLocation::Maxloc, T>(context, std::move(funcRef)); 1187 } else if (name == "min0" || name == "min1") { 1188 return RewriteSpecificMINorMAX(context, std::move(funcRef)); 1189 } else if (name == "minexponent") { 1190 if (auto *sx{UnwrapExpr<Expr<SomeReal>>(args[0])}) { 1191 return common::visit( 1192 [](const auto &x) { 1193 using TR = typename std::decay_t<decltype(x)>::Result; 1194 return Expr<T>{Scalar<TR>::MINEXPONENT}; 1195 }, 1196 sx->u); 1197 } 1198 } else if (name == "minloc") { 1199 return FoldLocation<WhichLocation::Minloc, T>(context, std::move(funcRef)); 1200 } else if (name == "mod") { 1201 bool badPConst{false}; 1202 if (auto *pExpr{UnwrapExpr<Expr<T>>(args[1])}) { 1203 *pExpr = Fold(context, std::move(*pExpr)); 1204 if (auto pConst{GetScalarConstantValue<T>(*pExpr)}; pConst && 1205 pConst->IsZero() && 1206 context.languageFeatures().ShouldWarn( 1207 common::UsageWarning::FoldingAvoidsRuntimeCrash)) { 1208 context.messages().Say(common::UsageWarning::FoldingAvoidsRuntimeCrash, 1209 "MOD: P argument is zero"_warn_en_US); 1210 badPConst = true; 1211 } 1212 } 1213 return FoldElementalIntrinsic<T, T, T>(context, std::move(funcRef), 1214 ScalarFuncWithContext<T, T, T>( 1215 [badPConst](FoldingContext &context, const Scalar<T> &x, 1216 const Scalar<T> &y) -> Scalar<T> { 1217 auto quotRem{x.DivideSigned(y)}; 1218 if (context.languageFeatures().ShouldWarn( 1219 common::UsageWarning::FoldingAvoidsRuntimeCrash)) { 1220 if (!badPConst && quotRem.divisionByZero) { 1221 context.messages().Say( 1222 common::UsageWarning::FoldingAvoidsRuntimeCrash, 1223 "mod() by zero"_warn_en_US); 1224 } else if (quotRem.overflow) { 1225 context.messages().Say( 1226 common::UsageWarning::FoldingAvoidsRuntimeCrash, 1227 "mod() folding overflowed"_warn_en_US); 1228 } 1229 } 1230 return quotRem.remainder; 1231 })); 1232 } else if (name == "modulo") { 1233 bool badPConst{false}; 1234 if (auto *pExpr{UnwrapExpr<Expr<T>>(args[1])}) { 1235 *pExpr = Fold(context, std::move(*pExpr)); 1236 if (auto pConst{GetScalarConstantValue<T>(*pExpr)}; pConst && 1237 pConst->IsZero() && 1238 context.languageFeatures().ShouldWarn( 1239 common::UsageWarning::FoldingAvoidsRuntimeCrash)) { 1240 context.messages().Say(common::UsageWarning::FoldingAvoidsRuntimeCrash, 1241 "MODULO: P argument is zero"_warn_en_US); 1242 badPConst = true; 1243 } 1244 } 1245 return FoldElementalIntrinsic<T, T, T>(context, std::move(funcRef), 1246 ScalarFuncWithContext<T, T, T>([badPConst](FoldingContext &context, 1247 const Scalar<T> &x, 1248 const Scalar<T> &y) -> Scalar<T> { 1249 auto result{x.MODULO(y)}; 1250 if (!badPConst && result.overflow && 1251 context.languageFeatures().ShouldWarn( 1252 common::UsageWarning::FoldingException)) { 1253 context.messages().Say(common::UsageWarning::FoldingException, 1254 "modulo() folding overflowed"_warn_en_US); 1255 } 1256 return result.value; 1257 })); 1258 } else if (name == "precision") { 1259 if (const auto *cx{UnwrapExpr<Expr<SomeReal>>(args[0])}) { 1260 return Expr<T>{common::visit( 1261 [](const auto &kx) { 1262 return Scalar<ResultType<decltype(kx)>>::PRECISION; 1263 }, 1264 cx->u)}; 1265 } else if (const auto *cx{UnwrapExpr<Expr<SomeComplex>>(args[0])}) { 1266 return Expr<T>{common::visit( 1267 [](const auto &kx) { 1268 return Scalar<typename ResultType<decltype(kx)>::Part>::PRECISION; 1269 }, 1270 cx->u)}; 1271 } 1272 } else if (name == "range") { 1273 if (const auto *cx{UnwrapExpr<Expr<SomeInteger>>(args[0])}) { 1274 return Expr<T>{common::visit( 1275 [](const auto &kx) { 1276 return Scalar<ResultType<decltype(kx)>>::RANGE; 1277 }, 1278 cx->u)}; 1279 } else if (const auto *cx{UnwrapExpr<Expr<SomeUnsigned>>(args[0])}) { 1280 return Expr<T>{common::visit( 1281 [](const auto &kx) { 1282 return Scalar<ResultType<decltype(kx)>>::UnsignedRANGE; 1283 }, 1284 cx->u)}; 1285 } else if (const auto *cx{UnwrapExpr<Expr<SomeReal>>(args[0])}) { 1286 return Expr<T>{common::visit( 1287 [](const auto &kx) { 1288 return Scalar<ResultType<decltype(kx)>>::RANGE; 1289 }, 1290 cx->u)}; 1291 } else if (const auto *cx{UnwrapExpr<Expr<SomeComplex>>(args[0])}) { 1292 return Expr<T>{common::visit( 1293 [](const auto &kx) { 1294 return Scalar<typename ResultType<decltype(kx)>::Part>::RANGE; 1295 }, 1296 cx->u)}; 1297 } 1298 } else if (name == "rank") { 1299 if (args[0]) { 1300 const Symbol *symbol{nullptr}; 1301 if (auto dataRef{ExtractDataRef(args[0])}) { 1302 symbol = &dataRef->GetLastSymbol(); 1303 } else { 1304 symbol = args[0]->GetAssumedTypeDummy(); 1305 } 1306 if (symbol && IsAssumedRank(*symbol)) { 1307 // DescriptorInquiry can only be placed in expression of kind 1308 // DescriptorInquiry::Result::kind. 1309 return ConvertToType<T>( 1310 Expr<Type<TypeCategory::Integer, DescriptorInquiry::Result::kind>>{ 1311 DescriptorInquiry{ 1312 NamedEntity{*symbol}, DescriptorInquiry::Field::Rank}}); 1313 } 1314 return Expr<T>{args[0]->Rank()}; 1315 } 1316 } else if (name == "selected_char_kind") { 1317 if (const auto *chCon{UnwrapExpr<Constant<TypeOf<std::string>>>(args[0])}) { 1318 if (std::optional<std::string> value{chCon->GetScalarValue()}) { 1319 int defaultKind{ 1320 context.defaults().GetDefaultKind(TypeCategory::Character)}; 1321 return Expr<T>{SelectedCharKind(*value, defaultKind)}; 1322 } 1323 } 1324 } else if (name == "selected_int_kind" || name == "selected_unsigned_kind") { 1325 if (auto p{ToInt64(args[0])}) { 1326 return Expr<T>{context.targetCharacteristics().SelectedIntKind(*p)}; 1327 } 1328 } else if (name == "selected_logical_kind") { 1329 if (auto p{ToInt64(args[0])}) { 1330 return Expr<T>{context.targetCharacteristics().SelectedLogicalKind(*p)}; 1331 } 1332 } else if (name == "selected_real_kind" || 1333 name == "__builtin_ieee_selected_real_kind") { 1334 if (auto p{GetInt64ArgOr(args[0], 0)}) { 1335 if (auto r{GetInt64ArgOr(args[1], 0)}) { 1336 if (auto radix{GetInt64ArgOr(args[2], 2)}) { 1337 return Expr<T>{ 1338 context.targetCharacteristics().SelectedRealKind(*p, *r, *radix)}; 1339 } 1340 } 1341 } 1342 } else if (name == "shape") { 1343 if (auto shape{GetContextFreeShape(context, args[0])}) { 1344 if (auto shapeExpr{AsExtentArrayExpr(*shape)}) { 1345 return Fold(context, ConvertToType<T>(std::move(*shapeExpr))); 1346 } 1347 } 1348 } else if (name == "sign") { 1349 return FoldElementalIntrinsic<T, T, T>(context, std::move(funcRef), 1350 ScalarFunc<T, T, T>([&context](const Scalar<T> &j, 1351 const Scalar<T> &k) -> Scalar<T> { 1352 typename Scalar<T>::ValueWithOverflow result{j.SIGN(k)}; 1353 if (result.overflow && 1354 context.languageFeatures().ShouldWarn( 1355 common::UsageWarning::FoldingException)) { 1356 context.messages().Say(common::UsageWarning::FoldingException, 1357 "sign(integer(kind=%d)) folding overflowed"_warn_en_US, KIND); 1358 } 1359 return result.value; 1360 })); 1361 } else if (name == "size") { 1362 if (auto shape{GetContextFreeShape(context, args[0])}) { 1363 if (args[1]) { // DIM= is present, get one extent 1364 std::optional<int> dim; 1365 if (const auto *array{args[0].value().UnwrapExpr()}; array && 1366 !CheckDimArg(args[1], *array, context.messages(), false, dim)) { 1367 return MakeInvalidIntrinsic<T>(std::move(funcRef)); 1368 } else if (dim) { 1369 if (auto &extent{shape->at(*dim)}) { 1370 return Fold(context, ConvertToType<T>(std::move(*extent))); 1371 } 1372 } 1373 } else if (auto extents{common::AllElementsPresent(std::move(*shape))}) { 1374 // DIM= is absent; compute PRODUCT(SHAPE()) 1375 ExtentExpr product{1}; 1376 for (auto &&extent : std::move(*extents)) { 1377 product = std::move(product) * std::move(extent); 1378 } 1379 return Expr<T>{ConvertToType<T>(Fold(context, std::move(product)))}; 1380 } 1381 } 1382 } else if (name == "sizeof") { // in bytes; extension 1383 if (auto info{ 1384 characteristics::TypeAndShape::Characterize(args[0], context)}) { 1385 if (auto bytes{info->MeasureSizeInBytes(context)}) { 1386 return Expr<T>{Fold(context, ConvertToType<T>(std::move(*bytes)))}; 1387 } 1388 } 1389 } else if (name == "storage_size") { // in bits 1390 if (auto info{ 1391 characteristics::TypeAndShape::Characterize(args[0], context)}) { 1392 if (auto bytes{info->MeasureElementSizeInBytes(context, true)}) { 1393 return Expr<T>{ 1394 Fold(context, Expr<T>{8} * ConvertToType<T>(std::move(*bytes)))}; 1395 } 1396 } 1397 } else if (name == "ubound") { 1398 return UBOUND(context, std::move(funcRef)); 1399 } else if (name == "__builtin_numeric_storage_size") { 1400 if (!context.moduleFileName()) { 1401 // Don't fold this reference until it appears in the module file 1402 // for ISO_FORTRAN_ENV -- the value depends on the compiler options 1403 // that might be in force. 1404 } else { 1405 auto intBytes{ 1406 context.targetCharacteristics().GetByteSize(TypeCategory::Integer, 1407 context.defaults().GetDefaultKind(TypeCategory::Integer))}; 1408 auto realBytes{ 1409 context.targetCharacteristics().GetByteSize(TypeCategory::Real, 1410 context.defaults().GetDefaultKind(TypeCategory::Real))}; 1411 if (intBytes != realBytes && 1412 context.languageFeatures().ShouldWarn( 1413 common::UsageWarning::FoldingValueChecks)) { 1414 context.messages().Say(common::UsageWarning::FoldingValueChecks, 1415 *context.moduleFileName(), 1416 "NUMERIC_STORAGE_SIZE from ISO_FORTRAN_ENV is not well-defined when default INTEGER and REAL are not consistent due to compiler options"_warn_en_US); 1417 } 1418 return Expr<T>{8 * std::min(intBytes, realBytes)}; 1419 } 1420 } 1421 return Expr<T>{std::move(funcRef)}; 1422 } 1423 1424 template <int KIND> 1425 Expr<Type<TypeCategory::Unsigned, KIND>> FoldIntrinsicFunction( 1426 FoldingContext &context, 1427 FunctionRef<Type<TypeCategory::Unsigned, KIND>> &&funcRef) { 1428 if (auto foldedCommon{FoldIntrinsicFunctionCommon(context, funcRef)}) { 1429 return std::move(*foldedCommon); 1430 } 1431 using T = Type<TypeCategory::Unsigned, KIND>; 1432 ActualArguments &args{funcRef.arguments()}; 1433 auto *intrinsic{std::get_if<SpecificIntrinsic>(&funcRef.proc().u)}; 1434 CHECK(intrinsic); 1435 std::string name{intrinsic->name}; 1436 if (name == "huge") { 1437 return Expr<T>{Scalar<T>{}.NOT()}; 1438 } else if (name == "mod" || name == "modulo") { 1439 bool badPConst{false}; 1440 if (auto *pExpr{UnwrapExpr<Expr<T>>(args[1])}) { 1441 *pExpr = Fold(context, std::move(*pExpr)); 1442 if (auto pConst{GetScalarConstantValue<T>(*pExpr)}; pConst && 1443 pConst->IsZero() && 1444 context.languageFeatures().ShouldWarn( 1445 common::UsageWarning::FoldingAvoidsRuntimeCrash)) { 1446 context.messages().Say(common::UsageWarning::FoldingAvoidsRuntimeCrash, 1447 "%s: P argument is zero"_warn_en_US, name); 1448 badPConst = true; 1449 } 1450 } 1451 return FoldElementalIntrinsic<T, T, T>(context, std::move(funcRef), 1452 ScalarFuncWithContext<T, T, T>( 1453 [badPConst, &name](FoldingContext &context, const Scalar<T> &x, 1454 const Scalar<T> &y) -> Scalar<T> { 1455 auto quotRem{x.DivideUnsigned(y)}; 1456 if (context.languageFeatures().ShouldWarn( 1457 common::UsageWarning::FoldingAvoidsRuntimeCrash)) { 1458 if (!badPConst && quotRem.divisionByZero) { 1459 context.messages().Say( 1460 common::UsageWarning::FoldingAvoidsRuntimeCrash, 1461 "%s() by zero"_warn_en_US, name); 1462 } 1463 } 1464 return quotRem.remainder; 1465 })); 1466 } 1467 return Expr<T>{std::move(funcRef)}; 1468 } 1469 1470 // Substitutes a bare type parameter reference with its value if it has one now 1471 // in an instantiation. Bare LEN type parameters are substituted only when 1472 // the known value is constant. 1473 Expr<TypeParamInquiry::Result> FoldOperation( 1474 FoldingContext &context, TypeParamInquiry &&inquiry) { 1475 std::optional<NamedEntity> base{inquiry.base()}; 1476 parser::CharBlock parameterName{inquiry.parameter().name()}; 1477 if (base) { 1478 // Handling "designator%typeParam". Get the value of the type parameter 1479 // from the instantiation of the base 1480 if (const semantics::DeclTypeSpec * 1481 declType{base->GetLastSymbol().GetType()}) { 1482 if (const semantics::ParamValue * 1483 paramValue{ 1484 declType->derivedTypeSpec().FindParameter(parameterName)}) { 1485 const semantics::MaybeIntExpr ¶mExpr{paramValue->GetExplicit()}; 1486 if (paramExpr && IsConstantExpr(*paramExpr)) { 1487 Expr<SomeInteger> intExpr{*paramExpr}; 1488 return Fold(context, 1489 ConvertToType<TypeParamInquiry::Result>(std::move(intExpr))); 1490 } 1491 } 1492 } 1493 } else { 1494 // A "bare" type parameter: replace with its value, if that's now known 1495 // in a current derived type instantiation. 1496 if (const auto *pdt{context.pdtInstance()}) { 1497 auto restorer{context.WithoutPDTInstance()}; // don't loop 1498 bool isLen{false}; 1499 if (const semantics::Scope * scope{pdt->scope()}) { 1500 auto iter{scope->find(parameterName)}; 1501 if (iter != scope->end()) { 1502 const Symbol &symbol{*iter->second}; 1503 const auto *details{symbol.detailsIf<semantics::TypeParamDetails>()}; 1504 if (details) { 1505 isLen = details->attr() == common::TypeParamAttr::Len; 1506 const semantics::MaybeIntExpr &initExpr{details->init()}; 1507 if (initExpr && IsConstantExpr(*initExpr) && 1508 (!isLen || ToInt64(*initExpr))) { 1509 Expr<SomeInteger> expr{*initExpr}; 1510 return Fold(context, 1511 ConvertToType<TypeParamInquiry::Result>(std::move(expr))); 1512 } 1513 } 1514 } 1515 } 1516 if (const auto *value{pdt->FindParameter(parameterName)}) { 1517 if (value->isExplicit()) { 1518 auto folded{Fold(context, 1519 AsExpr(ConvertToType<TypeParamInquiry::Result>( 1520 Expr<SomeInteger>{value->GetExplicit().value()})))}; 1521 if (!isLen || ToInt64(folded)) { 1522 return folded; 1523 } 1524 } 1525 } 1526 } 1527 } 1528 return AsExpr(std::move(inquiry)); 1529 } 1530 1531 std::optional<std::int64_t> ToInt64(const Expr<SomeInteger> &expr) { 1532 return common::visit( 1533 [](const auto &kindExpr) { return ToInt64(kindExpr); }, expr.u); 1534 } 1535 1536 std::optional<std::int64_t> ToInt64(const Expr<SomeUnsigned> &expr) { 1537 return common::visit( 1538 [](const auto &kindExpr) { return ToInt64(kindExpr); }, expr.u); 1539 } 1540 1541 std::optional<std::int64_t> ToInt64(const Expr<SomeType> &expr) { 1542 if (const auto *intExpr{UnwrapExpr<Expr<SomeInteger>>(expr)}) { 1543 return ToInt64(*intExpr); 1544 } else if (const auto *unsignedExpr{UnwrapExpr<Expr<SomeUnsigned>>(expr)}) { 1545 return ToInt64(*unsignedExpr); 1546 } else { 1547 return std::nullopt; 1548 } 1549 } 1550 1551 std::optional<std::int64_t> ToInt64(const ActualArgument &arg) { 1552 return ToInt64(arg.UnwrapExpr()); 1553 } 1554 1555 #ifdef _MSC_VER // disable bogus warning about missing definitions 1556 #pragma warning(disable : 4661) 1557 #endif 1558 FOR_EACH_INTEGER_KIND(template class ExpressionBase, ) 1559 FOR_EACH_UNSIGNED_KIND(template class ExpressionBase, ) 1560 template class ExpressionBase<SomeInteger>; 1561 template class ExpressionBase<SomeUnsigned>; 1562 } // namespace Fortran::evaluate 1563