| 1 | //===-- lib/runtime/character.cpp -------------------------------*- C++ -*-===// |
| 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 "flang/Runtime/character.h" |
| 10 | #include "flang-rt/runtime/descriptor.h" |
| 11 | #include "flang-rt/runtime/terminator.h" |
| 12 | #include "flang-rt/runtime/tools.h" |
| 13 | #include "flang/Common/bit-population-count.h" |
| 14 | #include "flang/Common/uint128.h" |
| 15 | #include "flang/Runtime/character.h" |
| 16 | #include "flang/Runtime/cpp-type.h" |
| 17 | #include "flang/Runtime/freestanding-tools.h" |
| 18 | #include <algorithm> |
| 19 | #include <cstring> |
| 20 | |
| 21 | namespace Fortran::runtime { |
| 22 | |
| 23 | template <typename CHAR> |
| 24 | inline RT_API_ATTRS int CompareToBlankPadding( |
| 25 | const CHAR *x, std::size_t chars) { |
| 26 | using UNSIGNED_CHAR = std::make_unsigned_t<CHAR>; |
| 27 | const auto blank{static_cast<UNSIGNED_CHAR>(' ')}; |
| 28 | for (; chars-- > 0; ++x) { |
| 29 | const UNSIGNED_CHAR ux{*reinterpret_cast<const UNSIGNED_CHAR *>(x)}; |
| 30 | if (ux < blank) { |
| 31 | return -1; |
| 32 | } |
| 33 | if (ux > blank) { |
| 34 | return 1; |
| 35 | } |
| 36 | } |
| 37 | return 0; |
| 38 | } |
| 39 | |
| 40 | RT_OFFLOAD_API_GROUP_BEGIN |
| 41 | |
| 42 | template <typename CHAR> |
| 43 | RT_API_ATTRS int CharacterScalarCompare( |
| 44 | const CHAR *x, const CHAR *y, std::size_t xChars, std::size_t yChars) { |
| 45 | auto minChars{std::min(xChars, yChars)}; |
| 46 | if constexpr (sizeof(CHAR) == 1) { |
| 47 | // don't use for kind=2 or =4, that would fail on little-endian machines |
| 48 | int cmp{Fortran::runtime::memcmp(x, y, minChars)}; |
| 49 | if (cmp < 0) { |
| 50 | return -1; |
| 51 | } |
| 52 | if (cmp > 0) { |
| 53 | return 1; |
| 54 | } |
| 55 | if (xChars == yChars) { |
| 56 | return 0; |
| 57 | } |
| 58 | x += minChars; |
| 59 | y += minChars; |
| 60 | } else { |
| 61 | for (std::size_t n{minChars}; n-- > 0; ++x, ++y) { |
| 62 | if (*x < *y) { |
| 63 | return -1; |
| 64 | } |
| 65 | if (*x > *y) { |
| 66 | return 1; |
| 67 | } |
| 68 | } |
| 69 | } |
| 70 | if (int cmp{CompareToBlankPadding(x, xChars - minChars)}) { |
| 71 | return cmp; |
| 72 | } |
| 73 | return -CompareToBlankPadding(y, yChars - minChars); |
| 74 | } |
| 75 | |
| 76 | template RT_API_ATTRS int CharacterScalarCompare<char>( |
| 77 | const char *x, const char *y, std::size_t xChars, std::size_t yChars); |
| 78 | template RT_API_ATTRS int CharacterScalarCompare<char16_t>(const char16_t *x, |
| 79 | const char16_t *y, std::size_t xChars, std::size_t yChars); |
| 80 | template RT_API_ATTRS int CharacterScalarCompare<char32_t>(const char32_t *x, |
| 81 | const char32_t *y, std::size_t xChars, std::size_t yChars); |
| 82 | |
| 83 | RT_OFFLOAD_API_GROUP_END |
| 84 | |
| 85 | // Shift count to use when converting between character lengths |
| 86 | // and byte counts. |
| 87 | template <typename CHAR> |
| 88 | constexpr int shift{common::TrailingZeroBitCount(sizeof(CHAR))}; |
| 89 | |
| 90 | template <typename CHAR> |
| 91 | static RT_API_ATTRS void Compare(Descriptor &result, const Descriptor &x, |
| 92 | const Descriptor &y, const Terminator &terminator) { |
| 93 | RUNTIME_CHECK( |
| 94 | terminator, x.rank() == y.rank() || x.rank() == 0 || y.rank() == 0); |
| 95 | int rank{std::max(x.rank(), y.rank())}; |
| 96 | SubscriptValue ub[maxRank], xAt[maxRank], yAt[maxRank]; |
| 97 | SubscriptValue elements{1}; |
| 98 | for (int j{0}; j < rank; ++j) { |
| 99 | if (x.rank() > 0 && y.rank() > 0) { |
| 100 | SubscriptValue xUB{x.GetDimension(j).Extent()}; |
| 101 | SubscriptValue yUB{y.GetDimension(j).Extent()}; |
| 102 | if (xUB != yUB) { |
| 103 | terminator.Crash("Character array comparison: operands are not " |
| 104 | "conforming on dimension %d (%jd != %jd)" , |
| 105 | j + 1, static_cast<std::intmax_t>(xUB), |
| 106 | static_cast<std::intmax_t>(yUB)); |
| 107 | } |
| 108 | ub[j] = xUB; |
| 109 | } else { |
| 110 | ub[j] = (x.rank() ? x : y).GetDimension(j).Extent(); |
| 111 | } |
| 112 | elements *= ub[j]; |
| 113 | } |
| 114 | x.GetLowerBounds(xAt); |
| 115 | y.GetLowerBounds(yAt); |
| 116 | result.Establish( |
| 117 | TypeCategory::Logical, 1, nullptr, rank, ub, CFI_attribute_allocatable); |
| 118 | for (int j{0}; j < rank; ++j) { |
| 119 | result.GetDimension(j).SetBounds(1, ub[j]); |
| 120 | } |
| 121 | if (result.Allocate(kNoAsyncObject) != CFI_SUCCESS) { |
| 122 | terminator.Crash("Compare: could not allocate storage for result" ); |
| 123 | } |
| 124 | std::size_t xChars{x.ElementBytes() >> shift<CHAR>}; |
| 125 | std::size_t yChars{y.ElementBytes() >> shift<char>}; |
| 126 | for (SubscriptValue resultAt{0}; elements-- > 0; |
| 127 | ++resultAt, x.IncrementSubscripts(xAt), y.IncrementSubscripts(yAt)) { |
| 128 | *result.OffsetElement<char>(resultAt) = CharacterScalarCompare<CHAR>( |
| 129 | x.Element<CHAR>(xAt), y.Element<CHAR>(yAt), xChars, yChars); |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | template <typename CHAR, bool ADJUSTR> |
| 134 | static RT_API_ATTRS void Adjust(CHAR *to, const CHAR *from, std::size_t chars) { |
| 135 | if constexpr (ADJUSTR) { |
| 136 | std::size_t j{chars}, k{chars}; |
| 137 | for (; k > 0 && from[k - 1] == ' '; --k) { |
| 138 | } |
| 139 | while (k > 0) { |
| 140 | to[--j] = from[--k]; |
| 141 | } |
| 142 | while (j > 0) { |
| 143 | to[--j] = ' '; |
| 144 | } |
| 145 | } else { // ADJUSTL |
| 146 | std::size_t j{0}, k{0}; |
| 147 | for (; k < chars && from[k] == ' '; ++k) { |
| 148 | } |
| 149 | while (k < chars) { |
| 150 | to[j++] = from[k++]; |
| 151 | } |
| 152 | while (j < chars) { |
| 153 | to[j++] = ' '; |
| 154 | } |
| 155 | } |
| 156 | } |
| 157 | |
| 158 | template <typename CHAR, bool ADJUSTR> |
| 159 | static RT_API_ATTRS void AdjustLRHelper(Descriptor &result, |
| 160 | const Descriptor &string, const Terminator &terminator) { |
| 161 | int rank{string.rank()}; |
| 162 | SubscriptValue ub[maxRank], stringAt[maxRank]; |
| 163 | SubscriptValue elements{1}; |
| 164 | for (int j{0}; j < rank; ++j) { |
| 165 | ub[j] = string.GetDimension(j).Extent(); |
| 166 | elements *= ub[j]; |
| 167 | stringAt[j] = 1; |
| 168 | } |
| 169 | string.GetLowerBounds(stringAt); |
| 170 | std::size_t elementBytes{string.ElementBytes()}; |
| 171 | result.Establish(string.type(), elementBytes, nullptr, rank, ub, |
| 172 | CFI_attribute_allocatable); |
| 173 | for (int j{0}; j < rank; ++j) { |
| 174 | result.GetDimension(j).SetBounds(1, ub[j]); |
| 175 | } |
| 176 | if (result.Allocate(kNoAsyncObject) != CFI_SUCCESS) { |
| 177 | terminator.Crash("ADJUSTL/R: could not allocate storage for result" ); |
| 178 | } |
| 179 | for (SubscriptValue resultAt{0}; elements-- > 0; |
| 180 | resultAt += elementBytes, string.IncrementSubscripts(stringAt)) { |
| 181 | Adjust<CHAR, ADJUSTR>(result.OffsetElement<CHAR>(resultAt), |
| 182 | string.Element<const CHAR>(stringAt), elementBytes >> shift<CHAR>); |
| 183 | } |
| 184 | } |
| 185 | |
| 186 | template <bool ADJUSTR> |
| 187 | RT_API_ATTRS void AdjustLR(Descriptor &result, const Descriptor &string, |
| 188 | const char *sourceFile, int sourceLine) { |
| 189 | Terminator terminator{sourceFile, sourceLine}; |
| 190 | switch (string.raw().type) { |
| 191 | case CFI_type_char: |
| 192 | AdjustLRHelper<char, ADJUSTR>(result, string, terminator); |
| 193 | break; |
| 194 | case CFI_type_char16_t: |
| 195 | AdjustLRHelper<char16_t, ADJUSTR>(result, string, terminator); |
| 196 | break; |
| 197 | case CFI_type_char32_t: |
| 198 | AdjustLRHelper<char32_t, ADJUSTR>(result, string, terminator); |
| 199 | break; |
| 200 | default: |
| 201 | terminator.Crash("ADJUSTL/R: bad string type code %d" , |
| 202 | static_cast<int>(string.raw().type)); |
| 203 | } |
| 204 | } |
| 205 | |
| 206 | template <typename CHAR> |
| 207 | inline RT_API_ATTRS std::size_t LenTrim(const CHAR *x, std::size_t chars) { |
| 208 | while (chars > 0 && x[chars - 1] == ' ') { |
| 209 | --chars; |
| 210 | } |
| 211 | return chars; |
| 212 | } |
| 213 | |
| 214 | template <typename INT, typename CHAR> |
| 215 | static RT_API_ATTRS void LenTrim(Descriptor &result, const Descriptor &string, |
| 216 | const Terminator &terminator) { |
| 217 | int rank{string.rank()}; |
| 218 | SubscriptValue ub[maxRank], stringAt[maxRank]; |
| 219 | SubscriptValue elements{1}; |
| 220 | for (int j{0}; j < rank; ++j) { |
| 221 | ub[j] = string.GetDimension(j).Extent(); |
| 222 | elements *= ub[j]; |
| 223 | } |
| 224 | string.GetLowerBounds(stringAt); |
| 225 | result.Establish(TypeCategory::Integer, sizeof(INT), nullptr, rank, ub, |
| 226 | CFI_attribute_allocatable); |
| 227 | for (int j{0}; j < rank; ++j) { |
| 228 | result.GetDimension(j).SetBounds(1, ub[j]); |
| 229 | } |
| 230 | if (result.Allocate(kNoAsyncObject) != CFI_SUCCESS) { |
| 231 | terminator.Crash("LEN_TRIM: could not allocate storage for result" ); |
| 232 | } |
| 233 | std::size_t stringElementChars{string.ElementBytes() >> shift<CHAR>}; |
| 234 | for (SubscriptValue resultAt{0}; elements-- > 0; |
| 235 | resultAt += sizeof(INT), string.IncrementSubscripts(stringAt)) { |
| 236 | *result.OffsetElement<INT>(resultAt) = |
| 237 | LenTrim(string.Element<CHAR>(stringAt), stringElementChars); |
| 238 | } |
| 239 | } |
| 240 | |
| 241 | template <typename CHAR> |
| 242 | static RT_API_ATTRS void LenTrimKind(Descriptor &result, |
| 243 | const Descriptor &string, int kind, const Terminator &terminator) { |
| 244 | switch (kind) { |
| 245 | case 1: |
| 246 | LenTrim<CppTypeFor<TypeCategory::Integer, 1>, CHAR>( |
| 247 | result, string, terminator); |
| 248 | break; |
| 249 | case 2: |
| 250 | LenTrim<CppTypeFor<TypeCategory::Integer, 2>, CHAR>( |
| 251 | result, string, terminator); |
| 252 | break; |
| 253 | case 4: |
| 254 | LenTrim<CppTypeFor<TypeCategory::Integer, 4>, CHAR>( |
| 255 | result, string, terminator); |
| 256 | break; |
| 257 | case 8: |
| 258 | LenTrim<CppTypeFor<TypeCategory::Integer, 8>, CHAR>( |
| 259 | result, string, terminator); |
| 260 | break; |
| 261 | case 16: |
| 262 | LenTrim<CppTypeFor<TypeCategory::Integer, 16>, CHAR>( |
| 263 | result, string, terminator); |
| 264 | break; |
| 265 | default: |
| 266 | terminator.Crash( |
| 267 | "not yet implemented: CHARACTER(KIND=%d) in LEN_TRIM intrinsic" , kind); |
| 268 | } |
| 269 | } |
| 270 | |
| 271 | // INDEX implementation |
| 272 | template <typename CHAR> |
| 273 | inline RT_API_ATTRS std::size_t Index(const CHAR *x, std::size_t xLen, |
| 274 | const CHAR *want, std::size_t wantLen, bool back) { |
| 275 | if (xLen < wantLen) { |
| 276 | return 0; |
| 277 | } |
| 278 | if (xLen == 0) { |
| 279 | return 1; // wantLen is also 0, so trivial match |
| 280 | } |
| 281 | if (back) { |
| 282 | // If wantLen==0, returns xLen + 1 per standard (and all other compilers) |
| 283 | std::size_t at{xLen - wantLen + 1}; |
| 284 | for (; at > 0; --at) { |
| 285 | std::size_t j{1}; |
| 286 | for (; j <= wantLen; ++j) { |
| 287 | if (x[at + j - 2] != want[j - 1]) { |
| 288 | break; |
| 289 | } |
| 290 | } |
| 291 | if (j > wantLen) { |
| 292 | return at; |
| 293 | } |
| 294 | } |
| 295 | return 0; |
| 296 | } |
| 297 | if (wantLen == 1) { |
| 298 | // Trivial case for single character lookup. |
| 299 | // We can use simple forward search. |
| 300 | CHAR ch{want[0]}; |
| 301 | if constexpr (std::is_same_v<CHAR, char>) { |
| 302 | if (auto pos{reinterpret_cast<const CHAR *>( |
| 303 | Fortran::runtime::memchr(x, ch, xLen))}) { |
| 304 | return pos - x + 1; |
| 305 | } |
| 306 | } else { |
| 307 | for (std::size_t at{0}; at < xLen; ++at) { |
| 308 | if (x[at] == ch) { |
| 309 | return at + 1; |
| 310 | } |
| 311 | } |
| 312 | } |
| 313 | return 0; |
| 314 | } |
| 315 | // Non-trivial forward substring search: use a simplified form of |
| 316 | // Boyer-Moore substring searching. |
| 317 | for (std::size_t at{1}; at + wantLen - 1 <= xLen;) { |
| 318 | // Compare x(at:at+wantLen-1) with want(1:wantLen). |
| 319 | // The comparison proceeds from the ends of the substrings forward |
| 320 | // so that we can skip ahead by multiple positions on a miss. |
| 321 | std::size_t j{wantLen}; |
| 322 | CHAR ch; |
| 323 | for (; j > 0; --j) { |
| 324 | ch = x[at + j - 2]; |
| 325 | if (ch != want[j - 1]) { |
| 326 | break; |
| 327 | } |
| 328 | } |
| 329 | if (j == 0) { |
| 330 | return at; // found a match |
| 331 | } |
| 332 | // Suppose we have at==2: |
| 333 | // "THAT FORTRAN THAT I RAN" <- the string (x) in which we search |
| 334 | // "THAT I RAN" <- the string (want) for which we search |
| 335 | // ^------------------ j==7, ch=='T' |
| 336 | // We can shift ahead 3 positions to at==5 to align the 'T's: |
| 337 | // "THAT FORTRAN THAT I RAN" |
| 338 | // "THAT I RAN" |
| 339 | std::size_t shift{1}; |
| 340 | for (; shift < j; ++shift) { |
| 341 | if (want[j - shift - 1] == ch) { |
| 342 | break; |
| 343 | } |
| 344 | } |
| 345 | at += shift; |
| 346 | } |
| 347 | return 0; |
| 348 | } |
| 349 | |
| 350 | // SCAN and VERIFY implementation help. These intrinsic functions |
| 351 | // do pretty much the same thing, so they're templatized with a |
| 352 | // distinguishing flag. |
| 353 | |
| 354 | enum class CharFunc { Index, Scan, Verify }; |
| 355 | |
| 356 | template <typename CHAR, CharFunc FUNC> |
| 357 | inline RT_API_ATTRS std::size_t ScanVerify(const CHAR *x, std::size_t xLen, |
| 358 | const CHAR *set, std::size_t setLen, bool back) { |
| 359 | std::size_t at{back ? xLen : 1}; |
| 360 | int increment{back ? -1 : 1}; |
| 361 | for (; xLen-- > 0; at += increment) { |
| 362 | CHAR ch{x[at - 1]}; |
| 363 | bool inSet{false}; |
| 364 | // TODO: If set is sorted, could use binary search |
| 365 | for (std::size_t j{0}; j < setLen; ++j) { |
| 366 | if (set[j] == ch) { |
| 367 | inSet = true; |
| 368 | break; |
| 369 | } |
| 370 | } |
| 371 | if (inSet != (FUNC == CharFunc::Verify)) { |
| 372 | return at; |
| 373 | } |
| 374 | } |
| 375 | return 0; |
| 376 | } |
| 377 | |
| 378 | // Specialization for one-byte characters |
| 379 | template <bool IS_VERIFY = false> |
| 380 | inline RT_API_ATTRS std::size_t ScanVerify(const char *x, std::size_t xLen, |
| 381 | const char *set, std::size_t setLen, bool back) { |
| 382 | std::size_t at{back ? xLen : 1}; |
| 383 | int increment{back ? -1 : 1}; |
| 384 | if (xLen > 0) { |
| 385 | std::uint64_t bitSet[256 / 64]{0}; |
| 386 | std::uint64_t one{1}; |
| 387 | for (std::size_t j{0}; j < setLen; ++j) { |
| 388 | unsigned setCh{static_cast<unsigned char>(set[j])}; |
| 389 | bitSet[setCh / 64] |= one << (setCh % 64); |
| 390 | } |
| 391 | for (; xLen-- > 0; at += increment) { |
| 392 | unsigned ch{static_cast<unsigned char>(x[at - 1])}; |
| 393 | bool inSet{((bitSet[ch / 64] >> (ch % 64)) & 1) != 0}; |
| 394 | if (inSet != IS_VERIFY) { |
| 395 | return at; |
| 396 | } |
| 397 | } |
| 398 | } |
| 399 | return 0; |
| 400 | } |
| 401 | |
| 402 | template <typename INT, typename CHAR, CharFunc FUNC> |
| 403 | static RT_API_ATTRS void GeneralCharFunc(Descriptor &result, |
| 404 | const Descriptor &string, const Descriptor &arg, const Descriptor *back, |
| 405 | const Terminator &terminator) { |
| 406 | int rank{string.rank() ? string.rank() |
| 407 | : arg.rank() ? arg.rank() |
| 408 | : back ? back->rank() |
| 409 | : 0}; |
| 410 | SubscriptValue ub[maxRank], stringAt[maxRank], argAt[maxRank], |
| 411 | backAt[maxRank]; |
| 412 | SubscriptValue elements{1}; |
| 413 | for (int j{0}; j < rank; ++j) { |
| 414 | ub[j] = string.rank() ? string.GetDimension(j).Extent() |
| 415 | : arg.rank() ? arg.GetDimension(j).Extent() |
| 416 | : back ? back->GetDimension(j).Extent() |
| 417 | : 1; |
| 418 | elements *= ub[j]; |
| 419 | } |
| 420 | string.GetLowerBounds(stringAt); |
| 421 | arg.GetLowerBounds(argAt); |
| 422 | if (back) { |
| 423 | back->GetLowerBounds(backAt); |
| 424 | } |
| 425 | result.Establish(TypeCategory::Integer, sizeof(INT), nullptr, rank, ub, |
| 426 | CFI_attribute_allocatable); |
| 427 | for (int j{0}; j < rank; ++j) { |
| 428 | result.GetDimension(j).SetBounds(1, ub[j]); |
| 429 | } |
| 430 | if (result.Allocate(kNoAsyncObject) != CFI_SUCCESS) { |
| 431 | terminator.Crash("SCAN/VERIFY: could not allocate storage for result" ); |
| 432 | } |
| 433 | std::size_t stringElementChars{string.ElementBytes() >> shift<CHAR>}; |
| 434 | std::size_t argElementChars{arg.ElementBytes() >> shift<CHAR>}; |
| 435 | for (SubscriptValue resultAt{0}; elements-- > 0; resultAt += sizeof(INT), |
| 436 | string.IncrementSubscripts(stringAt), arg.IncrementSubscripts(argAt), |
| 437 | back && back->IncrementSubscripts(backAt)) { |
| 438 | if constexpr (FUNC == CharFunc::Index) { |
| 439 | *result.OffsetElement<INT>(resultAt) = |
| 440 | Index<CHAR>(string.Element<CHAR>(stringAt), stringElementChars, |
| 441 | arg.Element<CHAR>(argAt), argElementChars, |
| 442 | back && IsLogicalElementTrue(*back, backAt)); |
| 443 | } else if constexpr (FUNC == CharFunc::Scan) { |
| 444 | *result.OffsetElement<INT>(resultAt) = |
| 445 | ScanVerify<CHAR, CharFunc::Scan>(string.Element<CHAR>(stringAt), |
| 446 | stringElementChars, arg.Element<CHAR>(argAt), argElementChars, |
| 447 | back && IsLogicalElementTrue(*back, backAt)); |
| 448 | } else if constexpr (FUNC == CharFunc::Verify) { |
| 449 | *result.OffsetElement<INT>(resultAt) = |
| 450 | ScanVerify<CHAR, CharFunc::Verify>(string.Element<CHAR>(stringAt), |
| 451 | stringElementChars, arg.Element<CHAR>(argAt), argElementChars, |
| 452 | back && IsLogicalElementTrue(*back, backAt)); |
| 453 | } else { |
| 454 | static_assert(FUNC == CharFunc::Index || FUNC == CharFunc::Scan || |
| 455 | FUNC == CharFunc::Verify); |
| 456 | } |
| 457 | } |
| 458 | } |
| 459 | |
| 460 | template <typename CHAR, CharFunc FUNC> |
| 461 | static RT_API_ATTRS void GeneralCharFuncKind(Descriptor &result, |
| 462 | const Descriptor &string, const Descriptor &arg, const Descriptor *back, |
| 463 | int kind, const Terminator &terminator) { |
| 464 | switch (kind) { |
| 465 | case 1: |
| 466 | GeneralCharFunc<CppTypeFor<TypeCategory::Integer, 1>, CHAR, FUNC>( |
| 467 | result, string, arg, back, terminator); |
| 468 | break; |
| 469 | case 2: |
| 470 | GeneralCharFunc<CppTypeFor<TypeCategory::Integer, 2>, CHAR, FUNC>( |
| 471 | result, string, arg, back, terminator); |
| 472 | break; |
| 473 | case 4: |
| 474 | GeneralCharFunc<CppTypeFor<TypeCategory::Integer, 4>, CHAR, FUNC>( |
| 475 | result, string, arg, back, terminator); |
| 476 | break; |
| 477 | case 8: |
| 478 | GeneralCharFunc<CppTypeFor<TypeCategory::Integer, 8>, CHAR, FUNC>( |
| 479 | result, string, arg, back, terminator); |
| 480 | break; |
| 481 | case 16: |
| 482 | GeneralCharFunc<CppTypeFor<TypeCategory::Integer, 16>, CHAR, FUNC>( |
| 483 | result, string, arg, back, terminator); |
| 484 | break; |
| 485 | default: |
| 486 | terminator.Crash("not yet implemented: CHARACTER(KIND=%d) in " |
| 487 | "INDEX/SCAN/VERIFY intrinsic" , |
| 488 | kind); |
| 489 | } |
| 490 | } |
| 491 | |
| 492 | template <typename CHAR, bool ISMIN> |
| 493 | static RT_API_ATTRS void MaxMinHelper(Descriptor &accumulator, |
| 494 | const Descriptor &x, const Terminator &terminator) { |
| 495 | RUNTIME_CHECK(terminator, |
| 496 | accumulator.rank() == 0 || x.rank() == 0 || |
| 497 | accumulator.rank() == x.rank()); |
| 498 | SubscriptValue ub[maxRank], xAt[maxRank]; |
| 499 | SubscriptValue elements{1}; |
| 500 | std::size_t accumChars{accumulator.ElementBytes() >> shift<CHAR>}; |
| 501 | std::size_t xChars{x.ElementBytes() >> shift<CHAR>}; |
| 502 | std::size_t chars{std::max(a: accumChars, b: xChars)}; |
| 503 | bool reallocate{accumulator.raw().base_addr == nullptr || |
| 504 | accumChars != chars || (accumulator.rank() == 0 && x.rank() > 0)}; |
| 505 | int rank{std::max(accumulator.rank(), x.rank())}; |
| 506 | for (int j{0}; j < rank; ++j) { |
| 507 | if (x.rank() > 0) { |
| 508 | ub[j] = x.GetDimension(j).Extent(); |
| 509 | if (accumulator.rank() > 0) { |
| 510 | SubscriptValue accumExt{accumulator.GetDimension(j).Extent()}; |
| 511 | if (accumExt != ub[j]) { |
| 512 | terminator.Crash("Character MAX/MIN: operands are not " |
| 513 | "conforming on dimension %d (%jd != %jd)" , |
| 514 | j + 1, static_cast<std::intmax_t>(accumExt), |
| 515 | static_cast<std::intmax_t>(ub[j])); |
| 516 | } |
| 517 | } |
| 518 | } else { |
| 519 | ub[j] = accumulator.GetDimension(j).Extent(); |
| 520 | } |
| 521 | elements *= ub[j]; |
| 522 | } |
| 523 | x.GetLowerBounds(xAt); |
| 524 | void *old{nullptr}; |
| 525 | const CHAR *accumData{accumulator.OffsetElement<CHAR>()}; |
| 526 | if (reallocate) { |
| 527 | old = accumulator.raw().base_addr; |
| 528 | accumulator.set_base_addr(nullptr); |
| 529 | accumulator.raw().elem_len = chars << shift<CHAR>; |
| 530 | for (int j{0}; j < rank; ++j) { |
| 531 | accumulator.GetDimension(j).SetBounds(1, ub[j]); |
| 532 | } |
| 533 | RUNTIME_CHECK( |
| 534 | terminator, accumulator.Allocate(kNoAsyncObject) == CFI_SUCCESS); |
| 535 | } |
| 536 | for (CHAR *result{accumulator.OffsetElement<CHAR>()}; elements-- > 0; |
| 537 | accumData += accumChars, result += chars, x.IncrementSubscripts(xAt)) { |
| 538 | const CHAR *xData{x.Element<CHAR>(xAt)}; |
| 539 | int cmp{CharacterScalarCompare(accumData, xData, accumChars, xChars)}; |
| 540 | if constexpr (ISMIN) { |
| 541 | cmp = -cmp; |
| 542 | } |
| 543 | if (cmp < 0) { |
| 544 | CopyAndPad(result, xData, chars, xChars); |
| 545 | } else if (result != accumData) { |
| 546 | CopyAndPad(result, accumData, chars, accumChars); |
| 547 | } |
| 548 | } |
| 549 | FreeMemory(old); |
| 550 | } |
| 551 | |
| 552 | template <bool ISMIN> |
| 553 | static RT_API_ATTRS void MaxMin(Descriptor &accumulator, const Descriptor &x, |
| 554 | const char *sourceFile, int sourceLine) { |
| 555 | Terminator terminator{sourceFile, sourceLine}; |
| 556 | RUNTIME_CHECK(terminator, accumulator.raw().type == x.raw().type); |
| 557 | switch (accumulator.raw().type) { |
| 558 | case CFI_type_char: |
| 559 | MaxMinHelper<char, ISMIN>(accumulator, x, terminator); |
| 560 | break; |
| 561 | case CFI_type_char16_t: |
| 562 | MaxMinHelper<char16_t, ISMIN>(accumulator, x, terminator); |
| 563 | break; |
| 564 | case CFI_type_char32_t: |
| 565 | MaxMinHelper<char32_t, ISMIN>(accumulator, x, terminator); |
| 566 | break; |
| 567 | default: |
| 568 | terminator.Crash( |
| 569 | "Character MAX/MIN: result does not have a character type" ); |
| 570 | } |
| 571 | } |
| 572 | |
| 573 | extern "C" { |
| 574 | RT_EXT_API_GROUP_BEGIN |
| 575 | |
| 576 | void RTDEF(CharacterConcatenate)(Descriptor &accumulator, |
| 577 | const Descriptor &from, const char *sourceFile, int sourceLine) { |
| 578 | Terminator terminator{sourceFile, sourceLine}; |
| 579 | RUNTIME_CHECK(terminator, |
| 580 | accumulator.rank() == 0 || from.rank() == 0 || |
| 581 | accumulator.rank() == from.rank()); |
| 582 | int rank{std::max(accumulator.rank(), from.rank())}; |
| 583 | SubscriptValue ub[maxRank], fromAt[maxRank]; |
| 584 | SubscriptValue elements{1}; |
| 585 | for (int j{0}; j < rank; ++j) { |
| 586 | if (accumulator.rank() > 0 && from.rank() > 0) { |
| 587 | ub[j] = accumulator.GetDimension(j).Extent(); |
| 588 | SubscriptValue fromUB{from.GetDimension(j).Extent()}; |
| 589 | if (ub[j] != fromUB) { |
| 590 | terminator.Crash("Character array concatenation: operands are not " |
| 591 | "conforming on dimension %d (%jd != %jd)" , |
| 592 | j + 1, static_cast<std::intmax_t>(ub[j]), |
| 593 | static_cast<std::intmax_t>(fromUB)); |
| 594 | } |
| 595 | } else { |
| 596 | ub[j] = |
| 597 | (accumulator.rank() ? accumulator : from).GetDimension(j).Extent(); |
| 598 | } |
| 599 | elements *= ub[j]; |
| 600 | } |
| 601 | std::size_t oldBytes{accumulator.ElementBytes()}; |
| 602 | void *old{accumulator.raw().base_addr}; |
| 603 | accumulator.set_base_addr(nullptr); |
| 604 | std::size_t fromBytes{from.ElementBytes()}; |
| 605 | accumulator.raw().elem_len += fromBytes; |
| 606 | std::size_t newBytes{accumulator.ElementBytes()}; |
| 607 | for (int j{0}; j < rank; ++j) { |
| 608 | accumulator.GetDimension(j).SetBounds(1, ub[j]); |
| 609 | } |
| 610 | if (accumulator.Allocate(kNoAsyncObject) != CFI_SUCCESS) { |
| 611 | terminator.Crash( |
| 612 | "CharacterConcatenate: could not allocate storage for result" ); |
| 613 | } |
| 614 | const char *p{static_cast<const char *>(old)}; |
| 615 | char *to{static_cast<char *>(accumulator.raw().base_addr)}; |
| 616 | from.GetLowerBounds(fromAt); |
| 617 | for (; elements-- > 0; |
| 618 | to += newBytes, p += oldBytes, from.IncrementSubscripts(fromAt)) { |
| 619 | std::memcpy(dest: to, src: p, n: oldBytes); |
| 620 | std::memcpy(to + oldBytes, from.Element<char>(fromAt), fromBytes); |
| 621 | } |
| 622 | FreeMemory(old); |
| 623 | } |
| 624 | |
| 625 | void RTDEF(CharacterConcatenateScalar1)( |
| 626 | Descriptor &accumulator, const char *from, std::size_t chars) { |
| 627 | Terminator terminator{__FILE__, __LINE__}; |
| 628 | RUNTIME_CHECK(terminator, accumulator.rank() == 0); |
| 629 | void *old{accumulator.raw().base_addr}; |
| 630 | accumulator.set_base_addr(nullptr); |
| 631 | std::size_t oldLen{accumulator.ElementBytes()}; |
| 632 | accumulator.raw().elem_len += chars; |
| 633 | RUNTIME_CHECK( |
| 634 | terminator, accumulator.Allocate(kNoAsyncObject) == CFI_SUCCESS); |
| 635 | std::memcpy(accumulator.OffsetElement<char>(oldLen), from, chars); |
| 636 | FreeMemory(old); |
| 637 | } |
| 638 | |
| 639 | int RTDEF(CharacterCompareScalar)(const Descriptor &x, const Descriptor &y) { |
| 640 | Terminator terminator{__FILE__, __LINE__}; |
| 641 | RUNTIME_CHECK(terminator, x.rank() == 0); |
| 642 | RUNTIME_CHECK(terminator, y.rank() == 0); |
| 643 | RUNTIME_CHECK(terminator, x.raw().type == y.raw().type); |
| 644 | switch (x.raw().type) { |
| 645 | case CFI_type_char: |
| 646 | return CharacterScalarCompare<char>(x.OffsetElement<char>(), |
| 647 | y.OffsetElement<char>(), x.ElementBytes(), y.ElementBytes()); |
| 648 | case CFI_type_char16_t: |
| 649 | return CharacterScalarCompare<char16_t>(x.OffsetElement<char16_t>(), |
| 650 | y.OffsetElement<char16_t>(), x.ElementBytes() >> 1, |
| 651 | y.ElementBytes() >> 1); |
| 652 | case CFI_type_char32_t: |
| 653 | return CharacterScalarCompare<char32_t>(x.OffsetElement<char32_t>(), |
| 654 | y.OffsetElement<char32_t>(), x.ElementBytes() >> 2, |
| 655 | y.ElementBytes() >> 2); |
| 656 | default: |
| 657 | terminator.Crash("CharacterCompareScalar: bad string type code %d" , |
| 658 | static_cast<int>(x.raw().type)); |
| 659 | } |
| 660 | return 0; |
| 661 | } |
| 662 | |
| 663 | int RTDEF(CharacterCompareScalar1)( |
| 664 | const char *x, const char *y, std::size_t xChars, std::size_t yChars) { |
| 665 | return CharacterScalarCompare(x, y, xChars, yChars); |
| 666 | } |
| 667 | |
| 668 | int RTDEF(CharacterCompareScalar2)(const char16_t *x, const char16_t *y, |
| 669 | std::size_t xChars, std::size_t yChars) { |
| 670 | return CharacterScalarCompare(x, y, xChars, yChars); |
| 671 | } |
| 672 | |
| 673 | int RTDEF(CharacterCompareScalar4)(const char32_t *x, const char32_t *y, |
| 674 | std::size_t xChars, std::size_t yChars) { |
| 675 | return CharacterScalarCompare(x, y, xChars, yChars); |
| 676 | } |
| 677 | |
| 678 | void RTDEF(CharacterCompare)( |
| 679 | Descriptor &result, const Descriptor &x, const Descriptor &y) { |
| 680 | Terminator terminator{__FILE__, __LINE__}; |
| 681 | RUNTIME_CHECK(terminator, x.raw().type == y.raw().type); |
| 682 | switch (x.raw().type) { |
| 683 | case CFI_type_char: |
| 684 | Compare<char>(result, x, y, terminator); |
| 685 | break; |
| 686 | case CFI_type_char16_t: |
| 687 | Compare<char16_t>(result, x, y, terminator); |
| 688 | break; |
| 689 | case CFI_type_char32_t: |
| 690 | Compare<char32_t>(result, x, y, terminator); |
| 691 | break; |
| 692 | default: |
| 693 | terminator.Crash("CharacterCompareScalar: bad string type code %d" , |
| 694 | static_cast<int>(x.raw().type)); |
| 695 | } |
| 696 | } |
| 697 | |
| 698 | std::size_t RTDEF(CharacterAppend1)(char *lhs, std::size_t lhsBytes, |
| 699 | std::size_t offset, const char *rhs, std::size_t rhsBytes) { |
| 700 | if (auto n{std::min(lhsBytes - offset, rhsBytes)}) { |
| 701 | std::memcpy(lhs + offset, rhs, n); |
| 702 | offset += n; |
| 703 | } |
| 704 | return offset; |
| 705 | } |
| 706 | |
| 707 | void RTDEF(CharacterPad1)(char *lhs, std::size_t bytes, std::size_t offset) { |
| 708 | if (bytes > offset) { |
| 709 | std::memset(lhs + offset, ' ', bytes - offset); |
| 710 | } |
| 711 | } |
| 712 | |
| 713 | // Intrinsic function entry points |
| 714 | |
| 715 | void RTDEF(Adjustl)(Descriptor &result, const Descriptor &string, |
| 716 | const char *sourceFile, int sourceLine) { |
| 717 | AdjustLR<false>(result, string, sourceFile, sourceLine); |
| 718 | } |
| 719 | |
| 720 | void RTDEF(Adjustr)(Descriptor &result, const Descriptor &string, |
| 721 | const char *sourceFile, int sourceLine) { |
| 722 | AdjustLR<true>(result, string, sourceFile, sourceLine); |
| 723 | } |
| 724 | |
| 725 | std::size_t RTDEF(Index1)(const char *x, std::size_t xLen, const char *set, |
| 726 | std::size_t setLen, bool back) { |
| 727 | return Index<char>(x, xLen, set, setLen, back); |
| 728 | } |
| 729 | std::size_t RTDEF(Index2)(const char16_t *x, std::size_t xLen, |
| 730 | const char16_t *set, std::size_t setLen, bool back) { |
| 731 | return Index<char16_t>(x, xLen, set, setLen, back); |
| 732 | } |
| 733 | std::size_t RTDEF(Index4)(const char32_t *x, std::size_t xLen, |
| 734 | const char32_t *set, std::size_t setLen, bool back) { |
| 735 | return Index<char32_t>(x, xLen, set, setLen, back); |
| 736 | } |
| 737 | |
| 738 | void RTDEF(Index)(Descriptor &result, const Descriptor &string, |
| 739 | const Descriptor &substring, const Descriptor *back, int kind, |
| 740 | const char *sourceFile, int sourceLine) { |
| 741 | Terminator terminator{sourceFile, sourceLine}; |
| 742 | switch (string.raw().type) { |
| 743 | case CFI_type_char: |
| 744 | GeneralCharFuncKind<char, CharFunc::Index>( |
| 745 | result, string, substring, back, kind, terminator); |
| 746 | break; |
| 747 | case CFI_type_char16_t: |
| 748 | GeneralCharFuncKind<char16_t, CharFunc::Index>( |
| 749 | result, string, substring, back, kind, terminator); |
| 750 | break; |
| 751 | case CFI_type_char32_t: |
| 752 | GeneralCharFuncKind<char32_t, CharFunc::Index>( |
| 753 | result, string, substring, back, kind, terminator); |
| 754 | break; |
| 755 | default: |
| 756 | terminator.Crash( |
| 757 | "INDEX: bad string type code %d" , static_cast<int>(string.raw().type)); |
| 758 | } |
| 759 | } |
| 760 | |
| 761 | std::size_t RTDEF(LenTrim1)(const char *x, std::size_t chars) { |
| 762 | return LenTrim(x, chars); |
| 763 | } |
| 764 | std::size_t RTDEF(LenTrim2)(const char16_t *x, std::size_t chars) { |
| 765 | return LenTrim(x, chars); |
| 766 | } |
| 767 | std::size_t RTDEF(LenTrim4)(const char32_t *x, std::size_t chars) { |
| 768 | return LenTrim(x, chars); |
| 769 | } |
| 770 | |
| 771 | void RTDEF(LenTrim)(Descriptor &result, const Descriptor &string, int kind, |
| 772 | const char *sourceFile, int sourceLine) { |
| 773 | Terminator terminator{sourceFile, sourceLine}; |
| 774 | switch (string.raw().type) { |
| 775 | case CFI_type_char: |
| 776 | LenTrimKind<char>(result, string, kind, terminator); |
| 777 | break; |
| 778 | case CFI_type_char16_t: |
| 779 | LenTrimKind<char16_t>(result, string, kind, terminator); |
| 780 | break; |
| 781 | case CFI_type_char32_t: |
| 782 | LenTrimKind<char32_t>(result, string, kind, terminator); |
| 783 | break; |
| 784 | default: |
| 785 | terminator.Crash("LEN_TRIM: bad string type code %d" , |
| 786 | static_cast<int>(string.raw().type)); |
| 787 | } |
| 788 | } |
| 789 | |
| 790 | std::size_t RTDEF(Scan1)(const char *x, std::size_t xLen, const char *set, |
| 791 | std::size_t setLen, bool back) { |
| 792 | return ScanVerify<char, CharFunc::Scan>(x, xLen, set, setLen, back); |
| 793 | } |
| 794 | std::size_t RTDEF(Scan2)(const char16_t *x, std::size_t xLen, |
| 795 | const char16_t *set, std::size_t setLen, bool back) { |
| 796 | return ScanVerify<char16_t, CharFunc::Scan>(x, xLen, set, setLen, back); |
| 797 | } |
| 798 | std::size_t RTDEF(Scan4)(const char32_t *x, std::size_t xLen, |
| 799 | const char32_t *set, std::size_t setLen, bool back) { |
| 800 | return ScanVerify<char32_t, CharFunc::Scan>(x, xLen, set, setLen, back); |
| 801 | } |
| 802 | |
| 803 | void RTDEF(Scan)(Descriptor &result, const Descriptor &string, |
| 804 | const Descriptor &set, const Descriptor *back, int kind, |
| 805 | const char *sourceFile, int sourceLine) { |
| 806 | Terminator terminator{sourceFile, sourceLine}; |
| 807 | switch (string.raw().type) { |
| 808 | case CFI_type_char: |
| 809 | GeneralCharFuncKind<char, CharFunc::Scan>( |
| 810 | result, string, set, back, kind, terminator); |
| 811 | break; |
| 812 | case CFI_type_char16_t: |
| 813 | GeneralCharFuncKind<char16_t, CharFunc::Scan>( |
| 814 | result, string, set, back, kind, terminator); |
| 815 | break; |
| 816 | case CFI_type_char32_t: |
| 817 | GeneralCharFuncKind<char32_t, CharFunc::Scan>( |
| 818 | result, string, set, back, kind, terminator); |
| 819 | break; |
| 820 | default: |
| 821 | terminator.Crash( |
| 822 | "SCAN: bad string type code %d" , static_cast<int>(string.raw().type)); |
| 823 | } |
| 824 | } |
| 825 | |
| 826 | void RTDEF(Repeat)(Descriptor &result, const Descriptor &string, |
| 827 | std::int64_t ncopies, const char *sourceFile, int sourceLine) { |
| 828 | Terminator terminator{sourceFile, sourceLine}; |
| 829 | if (ncopies < 0) { |
| 830 | terminator.Crash( |
| 831 | "REPEAT has negative NCOPIES=%jd" , static_cast<std::intmax_t>(ncopies)); |
| 832 | } |
| 833 | std::size_t origBytes{string.ElementBytes()}; |
| 834 | result.Establish(string.type(), origBytes * ncopies, nullptr, 0, nullptr, |
| 835 | CFI_attribute_allocatable); |
| 836 | if (result.Allocate(kNoAsyncObject) != CFI_SUCCESS) { |
| 837 | terminator.Crash("REPEAT could not allocate storage for result" ); |
| 838 | } |
| 839 | const char *from{string.OffsetElement()}; |
| 840 | for (char *to{result.OffsetElement()}; ncopies-- > 0; to += origBytes) { |
| 841 | std::memcpy(to, from, origBytes); |
| 842 | } |
| 843 | } |
| 844 | |
| 845 | void RTDEF(Trim)(Descriptor &result, const Descriptor &string, |
| 846 | const char *sourceFile, int sourceLine) { |
| 847 | Terminator terminator{sourceFile, sourceLine}; |
| 848 | std::size_t resultBytes{0}; |
| 849 | switch (string.raw().type) { |
| 850 | case CFI_type_char: |
| 851 | resultBytes = |
| 852 | LenTrim(string.OffsetElement<const char>(), string.ElementBytes()); |
| 853 | break; |
| 854 | case CFI_type_char16_t: |
| 855 | resultBytes = LenTrim(string.OffsetElement<const char16_t>(), |
| 856 | string.ElementBytes() >> 1) |
| 857 | << 1; |
| 858 | break; |
| 859 | case CFI_type_char32_t: |
| 860 | resultBytes = LenTrim(string.OffsetElement<const char32_t>(), |
| 861 | string.ElementBytes() >> 2) |
| 862 | << 2; |
| 863 | break; |
| 864 | default: |
| 865 | terminator.Crash( |
| 866 | "TRIM: bad string type code %d" , static_cast<int>(string.raw().type)); |
| 867 | } |
| 868 | result.Establish(string.type(), resultBytes, nullptr, 0, nullptr, |
| 869 | CFI_attribute_allocatable); |
| 870 | RUNTIME_CHECK(terminator, result.Allocate(kNoAsyncObject) == CFI_SUCCESS); |
| 871 | std::memcpy(result.OffsetElement(), string.OffsetElement(), resultBytes); |
| 872 | } |
| 873 | |
| 874 | std::size_t RTDEF(Verify1)(const char *x, std::size_t xLen, const char *set, |
| 875 | std::size_t setLen, bool back) { |
| 876 | return ScanVerify<char, CharFunc::Verify>(x, xLen, set, setLen, back); |
| 877 | } |
| 878 | std::size_t RTDEF(Verify2)(const char16_t *x, std::size_t xLen, |
| 879 | const char16_t *set, std::size_t setLen, bool back) { |
| 880 | return ScanVerify<char16_t, CharFunc::Verify>(x, xLen, set, setLen, back); |
| 881 | } |
| 882 | std::size_t RTDEF(Verify4)(const char32_t *x, std::size_t xLen, |
| 883 | const char32_t *set, std::size_t setLen, bool back) { |
| 884 | return ScanVerify<char32_t, CharFunc::Verify>(x, xLen, set, setLen, back); |
| 885 | } |
| 886 | |
| 887 | void RTDEF(Verify)(Descriptor &result, const Descriptor &string, |
| 888 | const Descriptor &set, const Descriptor *back, int kind, |
| 889 | const char *sourceFile, int sourceLine) { |
| 890 | Terminator terminator{sourceFile, sourceLine}; |
| 891 | switch (string.raw().type) { |
| 892 | case CFI_type_char: |
| 893 | GeneralCharFuncKind<char, CharFunc::Verify>( |
| 894 | result, string, set, back, kind, terminator); |
| 895 | break; |
| 896 | case CFI_type_char16_t: |
| 897 | GeneralCharFuncKind<char16_t, CharFunc::Verify>( |
| 898 | result, string, set, back, kind, terminator); |
| 899 | break; |
| 900 | case CFI_type_char32_t: |
| 901 | GeneralCharFuncKind<char32_t, CharFunc::Verify>( |
| 902 | result, string, set, back, kind, terminator); |
| 903 | break; |
| 904 | default: |
| 905 | terminator.Crash( |
| 906 | "VERIFY: bad string type code %d" , static_cast<int>(string.raw().type)); |
| 907 | } |
| 908 | } |
| 909 | |
| 910 | void RTDEF(CharacterMax)(Descriptor &accumulator, const Descriptor &x, |
| 911 | const char *sourceFile, int sourceLine) { |
| 912 | MaxMin<false>(accumulator, x, sourceFile, sourceLine); |
| 913 | } |
| 914 | |
| 915 | void RTDEF(CharacterMin)(Descriptor &accumulator, const Descriptor &x, |
| 916 | const char *sourceFile, int sourceLine) { |
| 917 | MaxMin<true>(accumulator, x, sourceFile, sourceLine); |
| 918 | } |
| 919 | |
| 920 | RT_EXT_API_GROUP_END |
| 921 | } |
| 922 | } // namespace Fortran::runtime |
| 923 | |