1 //===--- Pointer.cpp - Types for the constexpr VM ---------------*- 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 "Pointer.h" 10 #include "Boolean.h" 11 #include "Context.h" 12 #include "Floating.h" 13 #include "Function.h" 14 #include "Integral.h" 15 #include "InterpBlock.h" 16 #include "MemberPointer.h" 17 #include "PrimType.h" 18 #include "Record.h" 19 #include "clang/AST/ExprCXX.h" 20 #include "clang/AST/RecordLayout.h" 21 22 using namespace clang; 23 using namespace clang::interp; 24 25 Pointer::Pointer(Block *Pointee) 26 : Pointer(Pointee, Pointee->getDescriptor()->getMetadataSize(), 27 Pointee->getDescriptor()->getMetadataSize()) {} 28 29 Pointer::Pointer(Block *Pointee, uint64_t BaseAndOffset) 30 : Pointer(Pointee, BaseAndOffset, BaseAndOffset) {} 31 32 Pointer::Pointer(const Pointer &P) 33 : Offset(P.Offset), PointeeStorage(P.PointeeStorage), 34 StorageKind(P.StorageKind) { 35 36 if (isBlockPointer() && PointeeStorage.BS.Pointee) 37 PointeeStorage.BS.Pointee->addPointer(this); 38 } 39 40 Pointer::Pointer(Block *Pointee, unsigned Base, uint64_t Offset) 41 : Offset(Offset), StorageKind(Storage::Block) { 42 assert((Base == RootPtrMark || Base % alignof(void *) == 0) && "wrong base"); 43 44 PointeeStorage.BS = {Pointee, Base}; 45 46 if (Pointee) 47 Pointee->addPointer(this); 48 } 49 50 Pointer::Pointer(Pointer &&P) 51 : Offset(P.Offset), PointeeStorage(P.PointeeStorage), 52 StorageKind(P.StorageKind) { 53 54 if (StorageKind == Storage::Block && PointeeStorage.BS.Pointee) 55 PointeeStorage.BS.Pointee->replacePointer(&P, this); 56 } 57 58 Pointer::~Pointer() { 59 if (!isBlockPointer()) 60 return; 61 62 if (Block *Pointee = PointeeStorage.BS.Pointee) { 63 Pointee->removePointer(this); 64 PointeeStorage.BS.Pointee = nullptr; 65 Pointee->cleanup(); 66 } 67 } 68 69 void Pointer::operator=(const Pointer &P) { 70 // If the current storage type is Block, we need to remove 71 // this pointer from the block. 72 if (isBlockPointer()) { 73 if (P.isBlockPointer() && this->block() == P.block()) { 74 Offset = P.Offset; 75 PointeeStorage.BS.Base = P.PointeeStorage.BS.Base; 76 return; 77 } 78 79 if (Block *Pointee = PointeeStorage.BS.Pointee) { 80 Pointee->removePointer(this); 81 PointeeStorage.BS.Pointee = nullptr; 82 Pointee->cleanup(); 83 } 84 } 85 86 StorageKind = P.StorageKind; 87 Offset = P.Offset; 88 89 if (P.isBlockPointer()) { 90 PointeeStorage.BS = P.PointeeStorage.BS; 91 PointeeStorage.BS.Pointee = P.PointeeStorage.BS.Pointee; 92 93 if (PointeeStorage.BS.Pointee) 94 PointeeStorage.BS.Pointee->addPointer(this); 95 } else if (P.isIntegralPointer()) { 96 PointeeStorage.Int = P.PointeeStorage.Int; 97 } else if (P.isFunctionPointer()) { 98 PointeeStorage.Fn = P.PointeeStorage.Fn; 99 } else { 100 assert(false && "Unhandled storage kind"); 101 } 102 } 103 104 void Pointer::operator=(Pointer &&P) { 105 // If the current storage type is Block, we need to remove 106 // this pointer from the block. 107 if (isBlockPointer()) { 108 if (P.isBlockPointer() && this->block() == P.block()) { 109 Offset = P.Offset; 110 PointeeStorage.BS.Base = P.PointeeStorage.BS.Base; 111 return; 112 } 113 114 if (Block *Pointee = PointeeStorage.BS.Pointee) { 115 assert(P.block() != this->block()); 116 Pointee->removePointer(this); 117 PointeeStorage.BS.Pointee = nullptr; 118 Pointee->cleanup(); 119 } 120 } 121 122 StorageKind = P.StorageKind; 123 Offset = P.Offset; 124 125 if (P.isBlockPointer()) { 126 PointeeStorage.BS = P.PointeeStorage.BS; 127 PointeeStorage.BS.Pointee = P.PointeeStorage.BS.Pointee; 128 129 if (PointeeStorage.BS.Pointee) 130 PointeeStorage.BS.Pointee->addPointer(this); 131 } else if (P.isIntegralPointer()) { 132 PointeeStorage.Int = P.PointeeStorage.Int; 133 } else if (P.isFunctionPointer()) { 134 PointeeStorage.Fn = P.PointeeStorage.Fn; 135 } else { 136 assert(false && "Unhandled storage kind"); 137 } 138 } 139 140 APValue Pointer::toAPValue(const ASTContext &ASTCtx) const { 141 llvm::SmallVector<APValue::LValuePathEntry, 5> Path; 142 143 if (isZero()) 144 return APValue(static_cast<const Expr *>(nullptr), CharUnits::Zero(), Path, 145 /*IsOnePastEnd=*/false, /*IsNullPtr=*/true); 146 if (isIntegralPointer()) 147 return APValue(static_cast<const Expr *>(nullptr), 148 CharUnits::fromQuantity(asIntPointer().Value + this->Offset), 149 Path, 150 /*IsOnePastEnd=*/false, /*IsNullPtr=*/false); 151 if (isFunctionPointer()) 152 return asFunctionPointer().toAPValue(ASTCtx); 153 154 // Build the lvalue base from the block. 155 const Descriptor *Desc = getDeclDesc(); 156 APValue::LValueBase Base; 157 if (const auto *VD = Desc->asValueDecl()) 158 Base = VD; 159 else if (const auto *E = Desc->asExpr()) { 160 // Create a DynamicAlloc base of the right type. 161 if (const auto *NewExpr = dyn_cast<CXXNewExpr>(E)) { 162 QualType AllocatedType; 163 if (NewExpr->isArray()) { 164 assert(Desc->isArray()); 165 APInt ArraySize(64, static_cast<uint64_t>(Desc->getNumElems()), 166 /*IsSigned=*/false); 167 AllocatedType = 168 ASTCtx.getConstantArrayType(NewExpr->getAllocatedType(), ArraySize, 169 nullptr, ArraySizeModifier::Normal, 0); 170 } else { 171 AllocatedType = NewExpr->getAllocatedType(); 172 } 173 // FIXME: Suboptimal counting of dynamic allocations. Move this to Context 174 // or InterpState? 175 static int ReportedDynamicAllocs = 0; 176 DynamicAllocLValue DA(ReportedDynamicAllocs++); 177 Base = APValue::LValueBase::getDynamicAlloc(DA, AllocatedType); 178 } else { 179 Base = E; 180 } 181 } else 182 llvm_unreachable("Invalid allocation type"); 183 184 if (isUnknownSizeArray()) 185 return APValue(Base, CharUnits::Zero(), Path, 186 /*IsOnePastEnd=*/isOnePastEnd(), /*IsNullPtr=*/false); 187 188 CharUnits Offset = CharUnits::Zero(); 189 190 auto getFieldOffset = [&](const FieldDecl *FD) -> CharUnits { 191 // This shouldn't happen, but if it does, don't crash inside 192 // getASTRecordLayout. 193 if (FD->getParent()->isInvalidDecl()) 194 return CharUnits::Zero(); 195 const ASTRecordLayout &Layout = ASTCtx.getASTRecordLayout(FD->getParent()); 196 unsigned FieldIndex = FD->getFieldIndex(); 197 return ASTCtx.toCharUnitsFromBits(Layout.getFieldOffset(FieldIndex)); 198 }; 199 200 // Build the path into the object. 201 Pointer Ptr = *this; 202 while (Ptr.isField() || Ptr.isArrayElement()) { 203 if (Ptr.isArrayRoot()) { 204 Path.push_back(APValue::LValuePathEntry( 205 {Ptr.getFieldDesc()->asDecl(), /*IsVirtual=*/false})); 206 207 if (const auto *FD = dyn_cast<FieldDecl>(Ptr.getFieldDesc()->asDecl())) 208 Offset += getFieldOffset(FD); 209 210 Ptr = Ptr.getBase(); 211 } else if (Ptr.isArrayElement()) { 212 unsigned Index; 213 if (Ptr.isOnePastEnd()) 214 Index = Ptr.getArray().getNumElems(); 215 else 216 Index = Ptr.getIndex(); 217 218 Offset += (Index * ASTCtx.getTypeSizeInChars(Ptr.getType())); 219 Path.push_back(APValue::LValuePathEntry::ArrayIndex(Index)); 220 Ptr = Ptr.getArray(); 221 } else { 222 bool IsVirtual = false; 223 224 // Create a path entry for the field. 225 const Descriptor *Desc = Ptr.getFieldDesc(); 226 if (const auto *BaseOrMember = Desc->asDecl()) { 227 if (const auto *FD = dyn_cast<FieldDecl>(BaseOrMember)) { 228 Ptr = Ptr.getBase(); 229 Offset += getFieldOffset(FD); 230 } else if (const auto *RD = dyn_cast<CXXRecordDecl>(BaseOrMember)) { 231 IsVirtual = Ptr.isVirtualBaseClass(); 232 Ptr = Ptr.getBase(); 233 const Record *BaseRecord = Ptr.getRecord(); 234 235 const ASTRecordLayout &Layout = ASTCtx.getASTRecordLayout( 236 cast<CXXRecordDecl>(BaseRecord->getDecl())); 237 if (IsVirtual) 238 Offset += Layout.getVBaseClassOffset(RD); 239 else 240 Offset += Layout.getBaseClassOffset(RD); 241 242 } else { 243 Ptr = Ptr.getBase(); 244 } 245 Path.push_back(APValue::LValuePathEntry({BaseOrMember, IsVirtual})); 246 continue; 247 } 248 llvm_unreachable("Invalid field type"); 249 } 250 } 251 252 // FIXME(perf): We compute the lvalue path above, but we can't supply it 253 // for dummy pointers (that causes crashes later in CheckConstantExpression). 254 if (isDummy()) 255 Path.clear(); 256 257 // We assemble the LValuePath starting from the innermost pointer to the 258 // outermost one. SO in a.b.c, the first element in Path will refer to 259 // the field 'c', while later code expects it to refer to 'a'. 260 // Just invert the order of the elements. 261 std::reverse(Path.begin(), Path.end()); 262 263 return APValue(Base, Offset, Path, /*IsOnePastEnd=*/isOnePastEnd(), 264 /*IsNullPtr=*/false); 265 } 266 267 void Pointer::print(llvm::raw_ostream &OS) const { 268 OS << PointeeStorage.BS.Pointee << " ("; 269 if (isBlockPointer()) { 270 const Block *B = PointeeStorage.BS.Pointee; 271 OS << "Block) {"; 272 273 if (isRoot()) 274 OS << "rootptr(" << PointeeStorage.BS.Base << "), "; 275 else 276 OS << PointeeStorage.BS.Base << ", "; 277 278 if (isElementPastEnd()) 279 OS << "pastend, "; 280 else 281 OS << Offset << ", "; 282 283 if (B) 284 OS << B->getSize(); 285 else 286 OS << "nullptr"; 287 } else { 288 OS << "Int) {"; 289 OS << PointeeStorage.Int.Value << ", " << PointeeStorage.Int.Desc; 290 } 291 OS << "}"; 292 } 293 294 std::string Pointer::toDiagnosticString(const ASTContext &Ctx) const { 295 if (isZero()) 296 return "nullptr"; 297 298 if (isIntegralPointer()) 299 return (Twine("&(") + Twine(asIntPointer().Value + Offset) + ")").str(); 300 301 return toAPValue(Ctx).getAsString(Ctx, getType()); 302 } 303 304 bool Pointer::isInitialized() const { 305 if (!isBlockPointer()) 306 return true; 307 308 if (isRoot() && PointeeStorage.BS.Base == sizeof(GlobalInlineDescriptor)) { 309 const GlobalInlineDescriptor &GD = 310 *reinterpret_cast<const GlobalInlineDescriptor *>(block()->rawData()); 311 return GD.InitState == GlobalInitState::Initialized; 312 } 313 314 assert(PointeeStorage.BS.Pointee && 315 "Cannot check if null pointer was initialized"); 316 const Descriptor *Desc = getFieldDesc(); 317 assert(Desc); 318 if (Desc->isPrimitiveArray()) { 319 if (isStatic() && PointeeStorage.BS.Base == 0) 320 return true; 321 322 InitMapPtr &IM = getInitMap(); 323 324 if (!IM) 325 return false; 326 327 if (IM->first) 328 return true; 329 330 return IM->second->isElementInitialized(getIndex()); 331 } 332 333 if (asBlockPointer().Base == 0) 334 return true; 335 336 // Field has its bit in an inline descriptor. 337 return getInlineDesc()->IsInitialized; 338 } 339 340 void Pointer::initialize() const { 341 if (!isBlockPointer()) 342 return; 343 344 assert(PointeeStorage.BS.Pointee && "Cannot initialize null pointer"); 345 const Descriptor *Desc = getFieldDesc(); 346 347 if (isRoot() && PointeeStorage.BS.Base == sizeof(GlobalInlineDescriptor)) { 348 GlobalInlineDescriptor &GD = *reinterpret_cast<GlobalInlineDescriptor *>( 349 asBlockPointer().Pointee->rawData()); 350 GD.InitState = GlobalInitState::Initialized; 351 return; 352 } 353 354 assert(Desc); 355 if (Desc->isPrimitiveArray()) { 356 // Primitive global arrays don't have an initmap. 357 if (isStatic() && PointeeStorage.BS.Base == 0) 358 return; 359 360 // Nothing to do for these. 361 if (Desc->getNumElems() == 0) 362 return; 363 364 InitMapPtr &IM = getInitMap(); 365 if (!IM) 366 IM = 367 std::make_pair(false, std::make_shared<InitMap>(Desc->getNumElems())); 368 369 assert(IM); 370 371 // All initialized. 372 if (IM->first) 373 return; 374 375 if (IM->second->initializeElement(getIndex())) { 376 IM->first = true; 377 IM->second.reset(); 378 } 379 return; 380 } 381 382 // Field has its bit in an inline descriptor. 383 assert(PointeeStorage.BS.Base != 0 && 384 "Only composite fields can be initialised"); 385 getInlineDesc()->IsInitialized = true; 386 } 387 388 void Pointer::activate() const { 389 // Field has its bit in an inline descriptor. 390 assert(PointeeStorage.BS.Base != 0 && 391 "Only composite fields can be activated"); 392 393 if (isRoot() && PointeeStorage.BS.Base == sizeof(GlobalInlineDescriptor)) 394 return; 395 if (!getInlineDesc()->InUnion) 396 return; 397 398 getInlineDesc()->IsActive = true; 399 400 // Get the union, iterate over its fields and DEactivate all others. 401 Pointer UnionPtr = getBase(); 402 while (!UnionPtr.getFieldDesc()->isUnion()) 403 UnionPtr = UnionPtr.getBase(); 404 405 const Record *UnionRecord = UnionPtr.getRecord(); 406 for (const Record::Field &F : UnionRecord->fields()) { 407 Pointer FieldPtr = UnionPtr.atField(F.Offset); 408 if (FieldPtr == *this) { 409 } else { 410 FieldPtr.getInlineDesc()->IsActive = false; 411 // FIXME: Recurse. 412 } 413 } 414 415 Pointer B = getBase(); 416 while (!B.isRoot() && B.inUnion()) { 417 // FIXME: Need to de-activate other fields of parent records. 418 B.getInlineDesc()->IsActive = true; 419 assert(B.isActive()); 420 B = B.getBase(); 421 } 422 } 423 424 void Pointer::deactivate() const { 425 // TODO: this only appears in constructors, so nothing to deactivate. 426 } 427 428 bool Pointer::hasSameBase(const Pointer &A, const Pointer &B) { 429 // Two null pointers always have the same base. 430 if (A.isZero() && B.isZero()) 431 return true; 432 433 if (A.isIntegralPointer() && B.isIntegralPointer()) 434 return true; 435 if (A.isFunctionPointer() && B.isFunctionPointer()) 436 return true; 437 438 if (A.isIntegralPointer() || B.isIntegralPointer()) 439 return A.getSource() == B.getSource(); 440 441 if (A.StorageKind != B.StorageKind) 442 return false; 443 444 return A.asBlockPointer().Pointee == B.asBlockPointer().Pointee; 445 } 446 447 bool Pointer::pointToSameBlock(const Pointer &A, const Pointer &B) { 448 if (!A.isBlockPointer() || !B.isBlockPointer()) 449 return false; 450 return A.block() == B.block(); 451 } 452 453 bool Pointer::hasSameArray(const Pointer &A, const Pointer &B) { 454 return hasSameBase(A, B) && 455 A.PointeeStorage.BS.Base == B.PointeeStorage.BS.Base && 456 A.getFieldDesc()->IsArray; 457 } 458 459 std::optional<APValue> Pointer::toRValue(const Context &Ctx, 460 QualType ResultType) const { 461 const ASTContext &ASTCtx = Ctx.getASTContext(); 462 assert(!ResultType.isNull()); 463 // Method to recursively traverse composites. 464 std::function<bool(QualType, const Pointer &, APValue &)> Composite; 465 Composite = [&Composite, &Ctx, &ASTCtx](QualType Ty, const Pointer &Ptr, 466 APValue &R) { 467 if (const auto *AT = Ty->getAs<AtomicType>()) 468 Ty = AT->getValueType(); 469 470 // Invalid pointers. 471 if (Ptr.isDummy() || !Ptr.isLive() || !Ptr.isBlockPointer() || 472 Ptr.isPastEnd()) 473 return false; 474 475 // Primitive values. 476 if (std::optional<PrimType> T = Ctx.classify(Ty)) { 477 TYPE_SWITCH(*T, R = Ptr.deref<T>().toAPValue(ASTCtx)); 478 return true; 479 } 480 481 if (const auto *RT = Ty->getAs<RecordType>()) { 482 const auto *Record = Ptr.getRecord(); 483 assert(Record && "Missing record descriptor"); 484 485 bool Ok = true; 486 if (RT->getDecl()->isUnion()) { 487 const FieldDecl *ActiveField = nullptr; 488 APValue Value; 489 for (const auto &F : Record->fields()) { 490 const Pointer &FP = Ptr.atField(F.Offset); 491 QualType FieldTy = F.Decl->getType(); 492 if (FP.isActive()) { 493 if (std::optional<PrimType> T = Ctx.classify(FieldTy)) { 494 TYPE_SWITCH(*T, Value = FP.deref<T>().toAPValue(ASTCtx)); 495 } else { 496 Ok &= Composite(FieldTy, FP, Value); 497 } 498 ActiveField = FP.getFieldDesc()->asFieldDecl(); 499 break; 500 } 501 } 502 R = APValue(ActiveField, Value); 503 } else { 504 unsigned NF = Record->getNumFields(); 505 unsigned NB = Record->getNumBases(); 506 unsigned NV = Ptr.isBaseClass() ? 0 : Record->getNumVirtualBases(); 507 508 R = APValue(APValue::UninitStruct(), NB, NF); 509 510 for (unsigned I = 0; I < NF; ++I) { 511 const Record::Field *FD = Record->getField(I); 512 QualType FieldTy = FD->Decl->getType(); 513 const Pointer &FP = Ptr.atField(FD->Offset); 514 APValue &Value = R.getStructField(I); 515 516 if (std::optional<PrimType> T = Ctx.classify(FieldTy)) { 517 TYPE_SWITCH(*T, Value = FP.deref<T>().toAPValue(ASTCtx)); 518 } else { 519 Ok &= Composite(FieldTy, FP, Value); 520 } 521 } 522 523 for (unsigned I = 0; I < NB; ++I) { 524 const Record::Base *BD = Record->getBase(I); 525 QualType BaseTy = Ctx.getASTContext().getRecordType(BD->Decl); 526 const Pointer &BP = Ptr.atField(BD->Offset); 527 Ok &= Composite(BaseTy, BP, R.getStructBase(I)); 528 } 529 530 for (unsigned I = 0; I < NV; ++I) { 531 const Record::Base *VD = Record->getVirtualBase(I); 532 QualType VirtBaseTy = Ctx.getASTContext().getRecordType(VD->Decl); 533 const Pointer &VP = Ptr.atField(VD->Offset); 534 Ok &= Composite(VirtBaseTy, VP, R.getStructBase(NB + I)); 535 } 536 } 537 return Ok; 538 } 539 540 if (Ty->isIncompleteArrayType()) { 541 R = APValue(APValue::UninitArray(), 0, 0); 542 return true; 543 } 544 545 if (const auto *AT = Ty->getAsArrayTypeUnsafe()) { 546 const size_t NumElems = Ptr.getNumElems(); 547 QualType ElemTy = AT->getElementType(); 548 R = APValue(APValue::UninitArray{}, NumElems, NumElems); 549 550 bool Ok = true; 551 for (unsigned I = 0; I < NumElems; ++I) { 552 APValue &Slot = R.getArrayInitializedElt(I); 553 const Pointer &EP = Ptr.atIndex(I); 554 if (std::optional<PrimType> T = Ctx.classify(ElemTy)) { 555 TYPE_SWITCH(*T, Slot = EP.deref<T>().toAPValue(ASTCtx)); 556 } else { 557 Ok &= Composite(ElemTy, EP.narrow(), Slot); 558 } 559 } 560 return Ok; 561 } 562 563 // Complex types. 564 if (const auto *CT = Ty->getAs<ComplexType>()) { 565 QualType ElemTy = CT->getElementType(); 566 567 if (ElemTy->isIntegerType()) { 568 std::optional<PrimType> ElemT = Ctx.classify(ElemTy); 569 assert(ElemT); 570 INT_TYPE_SWITCH(*ElemT, { 571 auto V1 = Ptr.atIndex(0).deref<T>(); 572 auto V2 = Ptr.atIndex(1).deref<T>(); 573 R = APValue(V1.toAPSInt(), V2.toAPSInt()); 574 return true; 575 }); 576 } else if (ElemTy->isFloatingType()) { 577 R = APValue(Ptr.atIndex(0).deref<Floating>().getAPFloat(), 578 Ptr.atIndex(1).deref<Floating>().getAPFloat()); 579 return true; 580 } 581 return false; 582 } 583 584 // Vector types. 585 if (const auto *VT = Ty->getAs<VectorType>()) { 586 assert(Ptr.getFieldDesc()->isPrimitiveArray()); 587 QualType ElemTy = VT->getElementType(); 588 PrimType ElemT = *Ctx.classify(ElemTy); 589 590 SmallVector<APValue> Values; 591 Values.reserve(VT->getNumElements()); 592 for (unsigned I = 0; I != VT->getNumElements(); ++I) { 593 TYPE_SWITCH(ElemT, { 594 Values.push_back(Ptr.atIndex(I).deref<T>().toAPValue(ASTCtx)); 595 }); 596 } 597 598 assert(Values.size() == VT->getNumElements()); 599 R = APValue(Values.data(), Values.size()); 600 return true; 601 } 602 603 llvm_unreachable("invalid value to return"); 604 }; 605 606 // Invalid to read from. 607 if (isDummy() || !isLive() || isPastEnd()) 608 return std::nullopt; 609 610 // We can return these as rvalues, but we can't deref() them. 611 if (isZero() || isIntegralPointer()) 612 return toAPValue(ASTCtx); 613 614 // Just load primitive types. 615 if (std::optional<PrimType> T = Ctx.classify(ResultType)) { 616 TYPE_SWITCH(*T, return this->deref<T>().toAPValue(ASTCtx)); 617 } 618 619 // Return the composite type. 620 APValue Result; 621 if (!Composite(getType(), *this, Result)) 622 return std::nullopt; 623 return Result; 624 } 625 626 IntPointer IntPointer::atOffset(const ASTContext &ASTCtx, 627 unsigned Offset) const { 628 if (!this->Desc) 629 return *this; 630 const Record *R = this->Desc->ElemRecord; 631 if (!R) 632 return *this; 633 634 const Record::Field *F = nullptr; 635 for (auto &It : R->fields()) { 636 if (It.Offset == Offset) { 637 F = &It; 638 break; 639 } 640 } 641 if (!F) 642 return *this; 643 644 const FieldDecl *FD = F->Decl; 645 const ASTRecordLayout &Layout = ASTCtx.getASTRecordLayout(FD->getParent()); 646 unsigned FieldIndex = FD->getFieldIndex(); 647 uint64_t FieldOffset = 648 ASTCtx.toCharUnitsFromBits(Layout.getFieldOffset(FieldIndex)) 649 .getQuantity(); 650 return IntPointer{this->Desc, FieldOffset}; 651 } 652