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