1 //===-- runtime/transformational.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 // Implements the transformational intrinsic functions of Fortran 2018 that 10 // rearrange or duplicate data without (much) regard to type. These are 11 // CSHIFT, EOSHIFT, PACK, RESHAPE, SPREAD, TRANSPOSE, and UNPACK. 12 // 13 // Many of these are defined in the 2018 standard with text that makes sense 14 // only if argument arrays have lower bounds of one. Rather than interpret 15 // these cases as implying a hidden constraint, these implementations 16 // work with arbitrary lower bounds. This may be technically an extension 17 // of the standard but it more likely to conform with its intent. 18 19 #include "transformational.h" 20 #include "copy.h" 21 #include "terminator.h" 22 #include "tools.h" 23 #include <algorithm> 24 25 namespace Fortran::runtime { 26 27 // Utility for CSHIFT & EOSHIFT rank > 1 cases that determines the shift count 28 // for each of the vector sections of the result. 29 class ShiftControl { 30 public: 31 ShiftControl(const Descriptor &s, Terminator &t, int dim) 32 : shift_{s}, terminator_{t}, shiftRank_{s.rank()}, dim_{dim} {} 33 void Init(const Descriptor &source) { 34 int rank{source.rank()}; 35 RUNTIME_CHECK(terminator_, shiftRank_ == 0 || shiftRank_ == rank - 1); 36 auto catAndKind{shift_.type().GetCategoryAndKind()}; 37 RUNTIME_CHECK( 38 terminator_, catAndKind && catAndKind->first == TypeCategory::Integer); 39 shiftElemLen_ = catAndKind->second; 40 if (shiftRank_ > 0) { 41 int k{0}; 42 for (int j{0}; j < rank; ++j) { 43 if (j + 1 != dim_) { 44 const Dimension &shiftDim{shift_.GetDimension(k)}; 45 lb_[k++] = shiftDim.LowerBound(); 46 RUNTIME_CHECK(terminator_, 47 shiftDim.Extent() == source.GetDimension(j).Extent()); 48 } 49 } 50 } else { 51 shiftCount_ = 52 GetInt64(shift_.OffsetElement<char>(), shiftElemLen_, terminator_); 53 } 54 } 55 SubscriptValue GetShift(const SubscriptValue resultAt[]) const { 56 if (shiftRank_ > 0) { 57 SubscriptValue shiftAt[maxRank]; 58 int k{0}; 59 for (int j{0}; j < shiftRank_ + 1; ++j) { 60 if (j + 1 != dim_) { 61 shiftAt[k] = lb_[k] + resultAt[j] - 1; 62 ++k; 63 } 64 } 65 return GetInt64( 66 shift_.Element<char>(shiftAt), shiftElemLen_, terminator_); 67 } else { 68 return shiftCount_; // invariant count extracted in Init() 69 } 70 } 71 72 private: 73 const Descriptor &shift_; 74 Terminator &terminator_; 75 int shiftRank_; 76 int dim_; 77 SubscriptValue lb_[maxRank]; 78 std::size_t shiftElemLen_; 79 SubscriptValue shiftCount_{}; 80 }; 81 82 // Fill an EOSHIFT result with default boundary values 83 static void DefaultInitialize( 84 const Descriptor &result, Terminator &terminator) { 85 auto catAndKind{result.type().GetCategoryAndKind()}; 86 RUNTIME_CHECK( 87 terminator, catAndKind && catAndKind->first != TypeCategory::Derived); 88 std::size_t elementLen{result.ElementBytes()}; 89 std::size_t bytes{result.Elements() * elementLen}; 90 if (catAndKind->first == TypeCategory::Character) { 91 switch (int kind{catAndKind->second}) { 92 case 1: 93 std::fill_n(result.OffsetElement<char>(), bytes, ' '); 94 break; 95 case 2: 96 std::fill_n(result.OffsetElement<char16_t>(), bytes / 2, 97 static_cast<char16_t>(' ')); 98 break; 99 case 4: 100 std::fill_n(result.OffsetElement<char32_t>(), bytes / 4, 101 static_cast<char32_t>(' ')); 102 break; 103 default: 104 terminator.Crash("EOSHIFT: bad CHARACTER kind %d", kind); 105 } 106 } else { 107 std::memset(result.raw().base_addr, 0, bytes); 108 } 109 } 110 111 static inline std::size_t AllocateResult(Descriptor &result, 112 const Descriptor &source, int rank, const SubscriptValue extent[], 113 Terminator &terminator, const char *function) { 114 std::size_t elementLen{source.ElementBytes()}; 115 const DescriptorAddendum *sourceAddendum{source.Addendum()}; 116 result.Establish(source.type(), elementLen, nullptr, rank, extent, 117 CFI_attribute_allocatable, sourceAddendum != nullptr); 118 if (sourceAddendum) { 119 *result.Addendum() = *sourceAddendum; 120 } 121 for (int j{0}; j < rank; ++j) { 122 result.GetDimension(j).SetBounds(1, extent[j]); 123 } 124 if (int stat{result.Allocate()}) { 125 terminator.Crash( 126 "%s: Could not allocate memory for result (stat=%d)", function, stat); 127 } 128 return elementLen; 129 } 130 131 extern "C" { 132 133 // CSHIFT where rank of ARRAY argument > 1 134 void RTNAME(Cshift)(Descriptor &result, const Descriptor &source, 135 const Descriptor &shift, int dim, const char *sourceFile, int line) { 136 Terminator terminator{sourceFile, line}; 137 int rank{source.rank()}; 138 RUNTIME_CHECK(terminator, rank > 1); 139 RUNTIME_CHECK(terminator, dim >= 1 && dim <= rank); 140 ShiftControl shiftControl{shift, terminator, dim}; 141 shiftControl.Init(source); 142 SubscriptValue extent[maxRank]; 143 source.GetShape(extent); 144 AllocateResult(result, source, rank, extent, terminator, "CSHIFT"); 145 SubscriptValue resultAt[maxRank]; 146 for (int j{0}; j < rank; ++j) { 147 resultAt[j] = 1; 148 } 149 SubscriptValue sourceLB[maxRank]; 150 source.GetLowerBounds(sourceLB); 151 SubscriptValue dimExtent{extent[dim - 1]}; 152 SubscriptValue dimLB{sourceLB[dim - 1]}; 153 SubscriptValue &resDim{resultAt[dim - 1]}; 154 for (std::size_t n{result.Elements()}; n > 0; n -= dimExtent) { 155 SubscriptValue shiftCount{shiftControl.GetShift(resultAt)}; 156 SubscriptValue sourceAt[maxRank]; 157 for (int j{0}; j < rank; ++j) { 158 sourceAt[j] = sourceLB[j] + resultAt[j] - 1; 159 } 160 SubscriptValue &sourceDim{sourceAt[dim - 1]}; 161 sourceDim = dimLB + shiftCount % dimExtent; 162 if (shiftCount < 0) { 163 sourceDim += dimExtent; 164 } 165 for (resDim = 1; resDim <= dimExtent; ++resDim) { 166 CopyElement(result, resultAt, source, sourceAt, terminator); 167 if (++sourceDim == dimLB + dimExtent) { 168 sourceDim = dimLB; 169 } 170 } 171 result.IncrementSubscripts(resultAt); 172 } 173 } 174 175 // CSHIFT where rank of ARRAY argument == 1 176 void RTNAME(CshiftVector)(Descriptor &result, const Descriptor &source, 177 std::int64_t shift, const char *sourceFile, int line) { 178 Terminator terminator{sourceFile, line}; 179 RUNTIME_CHECK(terminator, source.rank() == 1); 180 const Dimension &sourceDim{source.GetDimension(0)}; 181 SubscriptValue extent{sourceDim.Extent()}; 182 AllocateResult(result, source, 1, &extent, terminator, "CSHIFT"); 183 SubscriptValue lb{sourceDim.LowerBound()}; 184 for (SubscriptValue j{0}; j < extent; ++j) { 185 SubscriptValue resultAt{1 + j}; 186 SubscriptValue sourceAt{lb + (j + shift) % extent}; 187 if (sourceAt < 0) { 188 sourceAt += extent; 189 } 190 CopyElement(result, &resultAt, source, &sourceAt, terminator); 191 } 192 } 193 194 // EOSHIFT of rank > 1 195 void RTNAME(Eoshift)(Descriptor &result, const Descriptor &source, 196 const Descriptor &shift, const Descriptor *boundary, int dim, 197 const char *sourceFile, int line) { 198 Terminator terminator{sourceFile, line}; 199 SubscriptValue extent[maxRank]; 200 int rank{source.GetShape(extent)}; 201 RUNTIME_CHECK(terminator, rank > 1); 202 RUNTIME_CHECK(terminator, dim >= 1 && dim <= rank); 203 std::size_t elementLen{ 204 AllocateResult(result, source, rank, extent, terminator, "EOSHIFT")}; 205 int boundaryRank{-1}; 206 if (boundary) { 207 boundaryRank = boundary->rank(); 208 RUNTIME_CHECK(terminator, boundaryRank == 0 || boundaryRank == rank - 1); 209 RUNTIME_CHECK(terminator, 210 boundary->type() == source.type() && 211 boundary->ElementBytes() == elementLen); 212 if (boundaryRank > 0) { 213 int k{0}; 214 for (int j{0}; j < rank; ++j) { 215 if (j != dim - 1) { 216 RUNTIME_CHECK( 217 terminator, boundary->GetDimension(k).Extent() == extent[j]); 218 ++k; 219 } 220 } 221 } 222 } 223 ShiftControl shiftControl{shift, terminator, dim}; 224 shiftControl.Init(source); 225 SubscriptValue resultAt[maxRank]; 226 for (int j{0}; j < rank; ++j) { 227 resultAt[j] = 1; 228 } 229 if (!boundary) { 230 DefaultInitialize(result, terminator); 231 } 232 SubscriptValue sourceLB[maxRank]; 233 source.GetLowerBounds(sourceLB); 234 SubscriptValue boundaryAt[maxRank]; 235 if (boundaryRank > 0) { 236 boundary->GetLowerBounds(boundaryAt); 237 } 238 SubscriptValue dimExtent{extent[dim - 1]}; 239 SubscriptValue dimLB{sourceLB[dim - 1]}; 240 SubscriptValue &resDim{resultAt[dim - 1]}; 241 for (std::size_t n{result.Elements()}; n > 0; n -= dimExtent) { 242 SubscriptValue shiftCount{shiftControl.GetShift(resultAt)}; 243 SubscriptValue sourceAt[maxRank]; 244 for (int j{0}; j < rank; ++j) { 245 sourceAt[j] = sourceLB[j] + resultAt[j] - 1; 246 } 247 SubscriptValue &sourceDim{sourceAt[dim - 1]}; 248 sourceDim = dimLB + shiftCount; 249 for (resDim = 1; resDim <= dimExtent; ++resDim) { 250 if (sourceDim >= dimLB && sourceDim < dimLB + dimExtent) { 251 CopyElement(result, resultAt, source, sourceAt, terminator); 252 } else if (boundary) { 253 CopyElement(result, resultAt, *boundary, boundaryAt, terminator); 254 } 255 ++sourceDim; 256 } 257 result.IncrementSubscripts(resultAt); 258 if (boundaryRank > 0) { 259 boundary->IncrementSubscripts(boundaryAt); 260 } 261 } 262 } 263 264 // EOSHIFT of vector 265 void RTNAME(EoshiftVector)(Descriptor &result, const Descriptor &source, 266 std::int64_t shift, const Descriptor *boundary, const char *sourceFile, 267 int line) { 268 Terminator terminator{sourceFile, line}; 269 RUNTIME_CHECK(terminator, source.rank() == 1); 270 SubscriptValue extent{source.GetDimension(0).Extent()}; 271 std::size_t elementLen{ 272 AllocateResult(result, source, 1, &extent, terminator, "EOSHIFT")}; 273 if (boundary) { 274 RUNTIME_CHECK(terminator, boundary->rank() == 0); 275 RUNTIME_CHECK(terminator, 276 boundary->type() == source.type() && 277 boundary->ElementBytes() == elementLen); 278 } 279 if (!boundary) { 280 DefaultInitialize(result, terminator); 281 } 282 SubscriptValue lb{source.GetDimension(0).LowerBound()}; 283 for (SubscriptValue j{1}; j <= extent; ++j) { 284 SubscriptValue sourceAt{lb + j - 1 + shift}; 285 if (sourceAt >= lb && sourceAt < lb + extent) { 286 CopyElement(result, &j, source, &sourceAt, terminator); 287 } 288 } 289 } 290 291 // PACK 292 void RTNAME(Pack)(Descriptor &result, const Descriptor &source, 293 const Descriptor &mask, const Descriptor *vector, const char *sourceFile, 294 int line) { 295 Terminator terminator{sourceFile, line}; 296 CheckConformability(source, mask, terminator, "PACK", "ARRAY=", "MASK="); 297 auto maskType{mask.type().GetCategoryAndKind()}; 298 RUNTIME_CHECK( 299 terminator, maskType && maskType->first == TypeCategory::Logical); 300 SubscriptValue trues{0}; 301 if (mask.rank() == 0) { 302 if (IsLogicalElementTrue(mask, nullptr)) { 303 trues = source.Elements(); 304 } 305 } else { 306 SubscriptValue maskAt[maxRank]; 307 mask.GetLowerBounds(maskAt); 308 for (std::size_t n{mask.Elements()}; n > 0; --n) { 309 if (IsLogicalElementTrue(mask, maskAt)) { 310 ++trues; 311 } 312 mask.IncrementSubscripts(maskAt); 313 } 314 } 315 SubscriptValue extent{trues}; 316 if (vector) { 317 RUNTIME_CHECK(terminator, vector->rank() == 1); 318 RUNTIME_CHECK(terminator, 319 source.type() == vector->type() && 320 source.ElementBytes() == vector->ElementBytes()); 321 extent = vector->GetDimension(0).Extent(); 322 RUNTIME_CHECK(terminator, extent >= trues); 323 } 324 AllocateResult(result, source, 1, &extent, terminator, "PACK"); 325 SubscriptValue sourceAt[maxRank], resultAt{1}; 326 source.GetLowerBounds(sourceAt); 327 if (mask.rank() == 0) { 328 if (IsLogicalElementTrue(mask, nullptr)) { 329 for (SubscriptValue n{trues}; n > 0; --n) { 330 CopyElement(result, &resultAt, source, sourceAt, terminator); 331 ++resultAt; 332 source.IncrementSubscripts(sourceAt); 333 } 334 } 335 } else { 336 SubscriptValue maskAt[maxRank]; 337 mask.GetLowerBounds(maskAt); 338 for (std::size_t n{source.Elements()}; n > 0; --n) { 339 if (IsLogicalElementTrue(mask, maskAt)) { 340 CopyElement(result, &resultAt, source, sourceAt, terminator); 341 ++resultAt; 342 } 343 source.IncrementSubscripts(sourceAt); 344 mask.IncrementSubscripts(maskAt); 345 } 346 } 347 if (vector) { 348 SubscriptValue vectorAt{ 349 vector->GetDimension(0).LowerBound() + resultAt - 1}; 350 for (; resultAt <= extent; ++resultAt, ++vectorAt) { 351 CopyElement(result, &resultAt, *vector, &vectorAt, terminator); 352 } 353 } 354 } 355 356 // RESHAPE 357 // F2018 16.9.163 358 void RTNAME(Reshape)(Descriptor &result, const Descriptor &source, 359 const Descriptor &shape, const Descriptor *pad, const Descriptor *order, 360 const char *sourceFile, int line) { 361 // Compute and check the rank of the result. 362 Terminator terminator{sourceFile, line}; 363 RUNTIME_CHECK(terminator, shape.rank() == 1); 364 RUNTIME_CHECK(terminator, shape.type().IsInteger()); 365 SubscriptValue resultRank{shape.GetDimension(0).Extent()}; 366 RUNTIME_CHECK(terminator, 367 resultRank >= 0 && resultRank <= static_cast<SubscriptValue>(maxRank)); 368 369 // Extract and check the shape of the result; compute its element count. 370 SubscriptValue resultExtent[maxRank]; 371 std::size_t shapeElementBytes{shape.ElementBytes()}; 372 std::size_t resultElements{1}; 373 SubscriptValue shapeSubscript{shape.GetDimension(0).LowerBound()}; 374 for (SubscriptValue j{0}; j < resultRank; ++j, ++shapeSubscript) { 375 resultExtent[j] = GetInt64( 376 shape.Element<char>(&shapeSubscript), shapeElementBytes, terminator); 377 RUNTIME_CHECK(terminator, resultExtent[j] >= 0); 378 resultElements *= resultExtent[j]; 379 } 380 381 // Check that there are sufficient elements in the SOURCE=, or that 382 // the optional PAD= argument is present and nonempty. 383 std::size_t elementBytes{source.ElementBytes()}; 384 std::size_t sourceElements{source.Elements()}; 385 std::size_t padElements{pad ? pad->Elements() : 0}; 386 if (resultElements < sourceElements) { 387 RUNTIME_CHECK(terminator, padElements > 0); 388 RUNTIME_CHECK(terminator, pad->ElementBytes() == elementBytes); 389 } 390 391 // Extract and check the optional ORDER= argument, which must be a 392 // permutation of [1..resultRank]. 393 int dimOrder[maxRank]; 394 if (order) { 395 RUNTIME_CHECK(terminator, order->rank() == 1); 396 RUNTIME_CHECK(terminator, order->type().IsInteger()); 397 RUNTIME_CHECK(terminator, order->GetDimension(0).Extent() == resultRank); 398 std::uint64_t values{0}; 399 SubscriptValue orderSubscript{order->GetDimension(0).LowerBound()}; 400 std::size_t orderElementBytes{order->ElementBytes()}; 401 for (SubscriptValue j{0}; j < resultRank; ++j, ++orderSubscript) { 402 auto k{GetInt64(order->Element<char>(&orderSubscript), orderElementBytes, 403 terminator)}; 404 RUNTIME_CHECK( 405 terminator, k >= 1 && k <= resultRank && !((values >> k) & 1)); 406 values |= std::uint64_t{1} << k; 407 dimOrder[k - 1] = j; 408 } 409 } else { 410 for (int j{0}; j < resultRank; ++j) { 411 dimOrder[j] = j; 412 } 413 } 414 415 // Allocate result descriptor 416 AllocateResult( 417 result, source, resultRank, resultExtent, terminator, "RESHAPE"); 418 419 // Populate the result's elements. 420 SubscriptValue resultSubscript[maxRank]; 421 result.GetLowerBounds(resultSubscript); 422 SubscriptValue sourceSubscript[maxRank]; 423 source.GetLowerBounds(sourceSubscript); 424 std::size_t resultElement{0}; 425 std::size_t elementsFromSource{std::min(resultElements, sourceElements)}; 426 for (; resultElement < elementsFromSource; ++resultElement) { 427 CopyElement(result, resultSubscript, source, sourceSubscript, terminator); 428 source.IncrementSubscripts(sourceSubscript); 429 result.IncrementSubscripts(resultSubscript, dimOrder); 430 } 431 if (resultElement < resultElements) { 432 // Remaining elements come from the optional PAD= argument. 433 SubscriptValue padSubscript[maxRank]; 434 pad->GetLowerBounds(padSubscript); 435 for (; resultElement < resultElements; ++resultElement) { 436 CopyElement(result, resultSubscript, *pad, padSubscript, terminator); 437 pad->IncrementSubscripts(padSubscript); 438 result.IncrementSubscripts(resultSubscript, dimOrder); 439 } 440 } 441 } 442 443 // SPREAD 444 void RTNAME(Spread)(Descriptor &result, const Descriptor &source, int dim, 445 std::int64_t ncopies, const char *sourceFile, int line) { 446 Terminator terminator{sourceFile, line}; 447 int rank{source.rank() + 1}; 448 RUNTIME_CHECK(terminator, rank <= maxRank); 449 ncopies = std::max<std::int64_t>(ncopies, 0); 450 SubscriptValue extent[maxRank]; 451 int k{0}; 452 for (int j{0}; j < rank; ++j) { 453 extent[j] = j == dim - 1 ? ncopies : source.GetDimension(k++).Extent(); 454 } 455 AllocateResult(result, source, rank, extent, terminator, "SPREAD"); 456 SubscriptValue resultAt[maxRank]; 457 for (int j{0}; j < rank; ++j) { 458 resultAt[j] = 1; 459 } 460 SubscriptValue &resultDim{resultAt[dim - 1]}; 461 SubscriptValue sourceAt[maxRank]; 462 source.GetLowerBounds(sourceAt); 463 for (std::size_t n{result.Elements()}; n > 0; n -= ncopies) { 464 for (resultDim = 1; resultDim <= ncopies; ++resultDim) { 465 CopyElement(result, resultAt, source, sourceAt, terminator); 466 } 467 result.IncrementSubscripts(resultAt); 468 source.IncrementSubscripts(sourceAt); 469 } 470 } 471 472 // TRANSPOSE 473 void RTNAME(Transpose)(Descriptor &result, const Descriptor &matrix, 474 const char *sourceFile, int line) { 475 Terminator terminator{sourceFile, line}; 476 RUNTIME_CHECK(terminator, matrix.rank() == 2); 477 SubscriptValue extent[2]{ 478 matrix.GetDimension(1).Extent(), matrix.GetDimension(0).Extent()}; 479 AllocateResult(result, matrix, 2, extent, terminator, "TRANSPOSE"); 480 SubscriptValue resultAt[2]{1, 1}; 481 SubscriptValue matrixLB[2]; 482 matrix.GetLowerBounds(matrixLB); 483 for (std::size_t n{result.Elements()}; n-- > 0; 484 result.IncrementSubscripts(resultAt)) { 485 SubscriptValue matrixAt[2]{ 486 matrixLB[0] + resultAt[1] - 1, matrixLB[1] + resultAt[0] - 1}; 487 CopyElement(result, resultAt, matrix, matrixAt, terminator); 488 } 489 } 490 491 // UNPACK 492 void RTNAME(Unpack)(Descriptor &result, const Descriptor &vector, 493 const Descriptor &mask, const Descriptor &field, const char *sourceFile, 494 int line) { 495 Terminator terminator{sourceFile, line}; 496 RUNTIME_CHECK(terminator, vector.rank() == 1); 497 int rank{mask.rank()}; 498 RUNTIME_CHECK(terminator, rank > 0); 499 SubscriptValue extent[maxRank]; 500 mask.GetShape(extent); 501 CheckConformability(mask, field, terminator, "UNPACK", "MASK=", "FIELD="); 502 std::size_t elementLen{ 503 AllocateResult(result, field, rank, extent, terminator, "UNPACK")}; 504 RUNTIME_CHECK(terminator, 505 vector.type() == field.type() && vector.ElementBytes() == elementLen); 506 SubscriptValue resultAt[maxRank], maskAt[maxRank], fieldAt[maxRank], 507 vectorAt{vector.GetDimension(0).LowerBound()}; 508 for (int j{0}; j < rank; ++j) { 509 resultAt[j] = 1; 510 } 511 mask.GetLowerBounds(maskAt); 512 field.GetLowerBounds(fieldAt); 513 SubscriptValue vectorLeft{vector.GetDimension(0).Extent()}; 514 for (std::size_t n{result.Elements()}; n-- > 0;) { 515 if (IsLogicalElementTrue(mask, maskAt)) { 516 if (vectorLeft-- == 0) { 517 terminator.Crash("UNPACK: VECTOR= argument has fewer elements than " 518 "MASK= has .TRUE. entries"); 519 } 520 CopyElement(result, resultAt, vector, &vectorAt, terminator); 521 ++vectorAt; 522 } else { 523 CopyElement(result, resultAt, field, fieldAt, terminator); 524 } 525 result.IncrementSubscripts(resultAt); 526 mask.IncrementSubscripts(maskAt); 527 field.IncrementSubscripts(fieldAt); 528 } 529 } 530 531 } // extern "C" 532 } // namespace Fortran::runtime 533