clang 22.0.0git
Program.cpp
Go to the documentation of this file.
1//===--- Program.cpp - Bytecode 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 "Program.h"
10#include "Context.h"
11#include "Function.h"
12#include "Integral.h"
13#include "PrimType.h"
14#include "clang/AST/Decl.h"
15#include "clang/AST/DeclCXX.h"
17
18using namespace clang;
19using namespace clang::interp;
20
21unsigned Program::getOrCreateNativePointer(const void *Ptr) {
22 auto [It, Inserted] =
23 NativePointerIndices.try_emplace(Ptr, NativePointers.size());
24 if (Inserted)
25 NativePointers.push_back(Ptr);
26
27 return It->second;
28}
29
30const void *Program::getNativePointer(unsigned Idx) {
31 return NativePointers[Idx];
32}
33
35 const size_t CharWidth = S->getCharByteWidth();
36 const size_t BitWidth = CharWidth * Ctx.getCharBit();
37 unsigned StringLength = S->getLength();
38
39 PrimType CharType;
40 switch (CharWidth) {
41 case 1:
42 CharType = PT_Sint8;
43 break;
44 case 2:
45 CharType = PT_Uint16;
46 break;
47 case 4:
48 CharType = PT_Uint32;
49 break;
50 default:
51 llvm_unreachable("unsupported character width");
52 }
53
54 if (!Base)
55 Base = S;
56
57 // Create a descriptor for the string.
58 Descriptor *Desc =
59 allocateDescriptor(Base, CharType, Descriptor::GlobalMD, StringLength + 1,
60 /*isConst=*/true,
61 /*isTemporary=*/false,
62 /*isMutable=*/false);
63
64 // Allocate storage for the string.
65 // The byte length does not include the null terminator.
66 unsigned GlobalIndex = Globals.size();
67 unsigned Sz = Desc->getAllocSize();
68 auto *G = new (Allocator, Sz) Global(Ctx.getEvalID(), Desc, /*isStatic=*/true,
69 /*isExtern=*/false);
70 G->block()->invokeCtor();
71
72 new (G->block()->rawData())
74 Globals.push_back(G);
75
76 const Pointer Ptr(G->block());
77 if (CharWidth == 1) {
78 std::memcpy(&Ptr.elem<char>(0), S->getString().data(), StringLength);
79 } else {
80 // Construct the string in storage.
81 for (unsigned I = 0; I <= StringLength; ++I) {
82 const uint32_t CodePoint = I == StringLength ? 0 : S->getCodeUnit(I);
83 switch (CharType) {
84 case PT_Sint8: {
86 Ptr.elem<T>(I) = T::from(CodePoint, BitWidth);
87 break;
88 }
89 case PT_Uint16: {
91 Ptr.elem<T>(I) = T::from(CodePoint, BitWidth);
92 break;
93 }
94 case PT_Uint32: {
96 Ptr.elem<T>(I) = T::from(CodePoint, BitWidth);
97 break;
98 }
99 default:
100 llvm_unreachable("unsupported character type");
101 }
102 }
103 }
105
106 return GlobalIndex;
107}
108
109Pointer Program::getPtrGlobal(unsigned Idx) const {
110 assert(Idx < Globals.size());
111 return Pointer(Globals[Idx]->block());
112}
113
114std::optional<unsigned> Program::getGlobal(const ValueDecl *VD) {
115 if (auto It = GlobalIndices.find(VD); It != GlobalIndices.end())
116 return It->second;
117
118 // Find any previous declarations which were already evaluated.
119 std::optional<unsigned> Index;
120 for (const Decl *P = VD->getPreviousDecl(); P; P = P->getPreviousDecl()) {
121 if (auto It = GlobalIndices.find(P); It != GlobalIndices.end()) {
122 Index = It->second;
123 break;
124 }
125 }
126
127 // Map the decl to the existing index.
128 if (Index)
129 GlobalIndices[VD] = *Index;
130
131 return std::nullopt;
132}
133
134std::optional<unsigned> Program::getGlobal(const Expr *E) {
135 if (auto It = GlobalIndices.find(E); It != GlobalIndices.end())
136 return It->second;
137 return std::nullopt;
138}
139
140std::optional<unsigned> Program::getOrCreateGlobal(const ValueDecl *VD,
141 const Expr *Init) {
142 if (auto Idx = getGlobal(VD))
143 return Idx;
144
145 if (auto Idx = createGlobal(VD, Init)) {
146 GlobalIndices[VD] = *Idx;
147 return Idx;
148 }
149 return std::nullopt;
150}
151
153 assert(D);
154 // Dedup blocks since they are immutable and pointers cannot be compared.
155 if (auto It = DummyVariables.find(D.getOpaqueValue());
156 It != DummyVariables.end())
157 return It->second;
158
159 QualType QT;
160 bool IsWeak = false;
161 if (const auto *E = dyn_cast<const Expr *>(D)) {
162 QT = E->getType();
163 } else {
164 const auto *VD = cast<ValueDecl>(cast<const Decl *>(D));
165 IsWeak = VD->isWeak();
166 QT = VD->getType();
167 if (QT->isPointerOrReferenceType())
168 QT = QT->getPointeeType();
169 }
170 assert(!QT.isNull());
171
172 Descriptor *Desc;
173 if (OptPrimType T = Ctx.classify(QT))
174 Desc = createDescriptor(D, *T, /*SourceTy=*/nullptr, std::nullopt,
175 /*IsConst=*/QT.isConstQualified());
176 else
177 Desc = createDescriptor(D, QT.getTypePtr(), std::nullopt,
178 /*IsConst=*/QT.isConstQualified());
179 if (!Desc)
180 Desc = allocateDescriptor(D);
181
182 assert(Desc);
183
184 // Allocate a block for storage.
185 unsigned I = Globals.size();
186
187 auto *G = new (Allocator, Desc->getAllocSize())
188 Global(Ctx.getEvalID(), getCurrentDecl(), Desc, /*IsStatic=*/true,
189 /*IsExtern=*/false, IsWeak, /*IsDummy=*/true);
190 G->block()->invokeCtor();
191 assert(G->block()->isDummy());
192
193 Globals.push_back(G);
194 DummyVariables[D.getOpaqueValue()] = I;
195 return I;
196}
197
198std::optional<unsigned> Program::createGlobal(const ValueDecl *VD,
199 const Expr *Init) {
200 bool IsStatic, IsExtern;
201 bool IsWeak = VD->isWeak();
202 if (const auto *Var = dyn_cast<VarDecl>(VD)) {
204 IsExtern = Var->hasExternalStorage();
207 IsStatic = true;
208 IsExtern = false;
209 } else {
210 IsStatic = false;
211 IsExtern = true;
212 }
213
214 // Register all previous declarations as well. For extern blocks, just replace
215 // the index with the new variable.
216 std::optional<unsigned> Idx =
217 createGlobal(VD, VD->getType(), IsStatic, IsExtern, IsWeak, Init);
218 if (!Idx)
219 return std::nullopt;
220
221 Global *NewGlobal = Globals[*Idx];
222 for (const Decl *Redecl : VD->redecls()) {
223 unsigned &PIdx = GlobalIndices[Redecl];
224 if (Redecl != VD) {
225 if (Block *RedeclBlock = Globals[PIdx]->block();
226 RedeclBlock->isExtern()) {
227 Globals[PIdx] = NewGlobal;
228 // All pointers pointing to the previous extern decl now point to the
229 // new decl.
230 for (Pointer *Ptr = RedeclBlock->Pointers; Ptr; Ptr = Ptr->BS.Next) {
231 RedeclBlock->removePointer(Ptr);
232 Ptr->BS.Pointee = NewGlobal->block();
233 NewGlobal->block()->addPointer(Ptr);
234 }
235 }
236 }
237 PIdx = *Idx;
238 }
239
240 return *Idx;
241}
242
243std::optional<unsigned> Program::createGlobal(const Expr *E) {
244 if (auto Idx = getGlobal(E))
245 return Idx;
246 if (auto Idx = createGlobal(E, E->getType(), /*isStatic=*/true,
247 /*isExtern=*/false, /*IsWeak=*/false)) {
248 GlobalIndices[E] = *Idx;
249 return *Idx;
250 }
251 return std::nullopt;
252}
253
254std::optional<unsigned> Program::createGlobal(const DeclTy &D, QualType Ty,
255 bool IsStatic, bool IsExtern,
256 bool IsWeak, const Expr *Init) {
257 // Create a descriptor for the global.
258 Descriptor *Desc;
259 const bool IsConst = Ty.isConstQualified();
260 const bool IsTemporary = D.dyn_cast<const Expr *>();
261 const bool IsVolatile = Ty.isVolatileQualified();
262 if (OptPrimType T = Ctx.classify(Ty))
263 Desc = createDescriptor(D, *T, nullptr, Descriptor::GlobalMD, IsConst,
264 IsTemporary, /*IsMutable=*/false, IsVolatile);
265 else
266 Desc = createDescriptor(D, Ty.getTypePtr(), Descriptor::GlobalMD, IsConst,
267 IsTemporary, /*IsMutable=*/false, IsVolatile);
268
269 if (!Desc)
270 return std::nullopt;
271
272 // Allocate a block for storage.
273 unsigned I = Globals.size();
274
275 auto *G = new (Allocator, Desc->getAllocSize()) Global(
276 Ctx.getEvalID(), getCurrentDecl(), Desc, IsStatic, IsExtern, IsWeak);
277 G->block()->invokeCtor();
278
279 // Initialize GlobalInlineDescriptor fields.
280 auto *GD = new (G->block()->rawData()) GlobalInlineDescriptor();
281 if (!Init)
283 Globals.push_back(G);
284
285 return I;
286}
287
289 F = F->getCanonicalDecl();
290 assert(F);
291 auto It = Funcs.find(F);
292 return It == Funcs.end() ? nullptr : It->second.get();
293}
294
296 // Use the actual definition as a key.
297 RD = RD->getDefinition();
298 if (!RD)
299 return nullptr;
300
301 if (!RD->isCompleteDefinition())
302 return nullptr;
303
304 // Return an existing record if available. Otherwise, we insert nullptr now
305 // and replace that later, so recursive calls to this function with the same
306 // RecordDecl don't run into infinite recursion.
307 auto [It, Inserted] = Records.try_emplace(RD);
308 if (!Inserted)
309 return It->second;
310
311 // Number of bytes required by fields and base classes.
312 unsigned BaseSize = 0;
313 // Number of bytes required by virtual base.
314 unsigned VirtSize = 0;
315
316 // Helper to get a base descriptor.
317 auto GetBaseDesc = [this](const RecordDecl *BD,
318 const Record *BR) -> const Descriptor * {
319 if (!BR)
320 return nullptr;
321 return allocateDescriptor(BD, BR, std::nullopt, /*isConst=*/false,
322 /*isTemporary=*/false,
323 /*isMutable=*/false, /*IsVolatile=*/false);
324 };
325
326 // Reserve space for base classes.
327 Record::BaseList Bases;
328 Record::VirtualBaseList VirtBases;
329 if (const auto *CD = dyn_cast<CXXRecordDecl>(RD)) {
330 for (const CXXBaseSpecifier &Spec : CD->bases()) {
331 if (Spec.isVirtual())
332 continue;
333
334 // In error cases, the base might not be a RecordType.
335 const auto *BD = Spec.getType()->getAsCXXRecordDecl();
336 if (!BD)
337 return nullptr;
338 const Record *BR = getOrCreateRecord(BD);
339
340 const Descriptor *Desc = GetBaseDesc(BD, BR);
341 if (!Desc)
342 return nullptr;
343
344 BaseSize += align(sizeof(InlineDescriptor));
345 Bases.push_back({BD, BaseSize, Desc, BR});
346 BaseSize += align(BR->getSize());
347 }
348
349 for (const CXXBaseSpecifier &Spec : CD->vbases()) {
350 const auto *BD = Spec.getType()->castAsCXXRecordDecl();
351 const Record *BR = getOrCreateRecord(BD);
352
353 const Descriptor *Desc = GetBaseDesc(BD, BR);
354 if (!Desc)
355 return nullptr;
356
357 VirtSize += align(sizeof(InlineDescriptor));
358 VirtBases.push_back({BD, VirtSize, Desc, BR});
359 VirtSize += align(BR->getSize());
360 }
361 }
362
363 // Reserve space for fields.
364 Record::FieldList Fields;
365 for (const FieldDecl *FD : RD->fields()) {
366 FD = FD->getFirstDecl();
367 // Note that we DO create fields and descriptors
368 // for unnamed bitfields here, even though we later ignore
369 // them everywhere. That's so the FieldDecl's getFieldIndex() matches.
370
371 // Reserve space for the field's descriptor and the offset.
372 BaseSize += align(sizeof(InlineDescriptor));
373
374 // Classify the field and add its metadata.
375 QualType FT = FD->getType();
376 const bool IsConst = FT.isConstQualified();
377 const bool IsMutable = FD->isMutable();
378 const bool IsVolatile = FT.isVolatileQualified();
379 const Descriptor *Desc;
380 if (OptPrimType T = Ctx.classify(FT)) {
381 Desc = createDescriptor(FD, *T, nullptr, std::nullopt, IsConst,
382 /*isTemporary=*/false, IsMutable, IsVolatile);
383 } else {
384 Desc = createDescriptor(FD, FT.getTypePtr(), std::nullopt, IsConst,
385 /*isTemporary=*/false, IsMutable, IsVolatile);
386 }
387 if (!Desc)
388 return nullptr;
389 Fields.push_back({FD, BaseSize, Desc});
390 BaseSize += align(Desc->getAllocSize());
391 }
392
393 Record *R = new (Allocator) Record(RD, std::move(Bases), std::move(Fields),
394 std::move(VirtBases), VirtSize, BaseSize);
395 Records[RD] = R;
396 return R;
397}
398
401 bool IsConst, bool IsTemporary,
402 bool IsMutable, bool IsVolatile,
403 const Expr *Init) {
404
405 // Classes and structures.
406 if (const auto *RD = Ty->getAsRecordDecl()) {
407 if (const auto *Record = getOrCreateRecord(RD))
408 return allocateDescriptor(D, Record, MDSize, IsConst, IsTemporary,
409 IsMutable, IsVolatile);
410 return allocateDescriptor(D, MDSize);
411 }
412
413 // Arrays.
414 if (const auto *ArrayType = Ty->getAsArrayTypeUnsafe()) {
416 // Array of well-known bounds.
417 if (const auto *CAT = dyn_cast<ConstantArrayType>(ArrayType)) {
418 size_t NumElems = CAT->getZExtSize();
419 if (OptPrimType T = Ctx.classify(ElemTy)) {
420 // Arrays of primitives.
421 unsigned ElemSize = primSize(*T);
422 if (std::numeric_limits<unsigned>::max() / ElemSize <= NumElems) {
423 return {};
424 }
425 return allocateDescriptor(D, *T, MDSize, NumElems, IsConst, IsTemporary,
426 IsMutable);
427 }
428 // Arrays of composites. In this case, the array is a list of pointers,
429 // followed by the actual elements.
430 const Descriptor *ElemDesc = createDescriptor(
431 D, ElemTy.getTypePtr(), std::nullopt, IsConst, IsTemporary);
432 if (!ElemDesc)
433 return nullptr;
434 unsigned ElemSize = ElemDesc->getAllocSize() + sizeof(InlineDescriptor);
435 if (std::numeric_limits<unsigned>::max() / ElemSize <= NumElems)
436 return {};
437 return allocateDescriptor(D, Ty, ElemDesc, MDSize, NumElems, IsConst,
438 IsTemporary, IsMutable);
439 }
440
441 // Array of unknown bounds - cannot be accessed and pointer arithmetic
442 // is forbidden on pointers to such objects.
443 if (isa<IncompleteArrayType>(ArrayType) ||
444 isa<VariableArrayType>(ArrayType)) {
445 if (OptPrimType T = Ctx.classify(ElemTy)) {
446 return allocateDescriptor(D, *T, MDSize, IsConst, IsTemporary,
448 }
449 const Descriptor *Desc = createDescriptor(
450 D, ElemTy.getTypePtr(), std::nullopt, IsConst, IsTemporary);
451 if (!Desc)
452 return nullptr;
453 return allocateDescriptor(D, Desc, MDSize, IsTemporary,
455 }
456 }
457
458 // Atomic types.
459 if (const auto *AT = Ty->getAs<AtomicType>()) {
460 const Type *InnerTy = AT->getValueType().getTypePtr();
461 return createDescriptor(D, InnerTy, MDSize, IsConst, IsTemporary,
462 IsMutable);
463 }
464
465 // Complex types - represented as arrays of elements.
466 if (const auto *CT = Ty->getAs<ComplexType>()) {
467 OptPrimType ElemTy = Ctx.classify(CT->getElementType());
468 if (!ElemTy)
469 return nullptr;
470
471 return allocateDescriptor(D, *ElemTy, MDSize, 2, IsConst, IsTemporary,
472 IsMutable);
473 }
474
475 // Same with vector types.
476 if (const auto *VT = Ty->getAs<VectorType>()) {
477 OptPrimType ElemTy = Ctx.classify(VT->getElementType());
478 if (!ElemTy)
479 return nullptr;
480
481 return allocateDescriptor(D, *ElemTy, MDSize, VT->getNumElements(), IsConst,
482 IsTemporary, IsMutable);
483 }
484
485 return nullptr;
486}
StringRef P
const Decl * D
Expr * E
Defines the C++ Decl subclasses, other than those for templates (found in DeclTemplate....
Defines the C++ template declaration subclasses.
bool IsStatic
Definition: Format.cpp:3189
llvm::MachO::Records Records
Definition: MachO.h:40
Represents an array type, per C99 6.7.5.2 - Array Declarators.
Definition: TypeBase.h:3738
QualType getElementType() const
Definition: TypeBase.h:3750
Represents a base class of a C++ class.
Definition: DeclCXX.h:146
Complex values, per C99 6.2.5p11.
Definition: TypeBase.h:3293
Decl - This represents one declaration (or definition), e.g.
Definition: DeclBase.h:86
Decl * getPreviousDecl()
Retrieve the previous declaration that declares the same entity as this declaration,...
Definition: DeclBase.h:1061
redecl_range redecls() const
Returns an iterator range for all the redeclarations of the same decl.
Definition: DeclBase.h:1049
This represents one expression.
Definition: Expr.h:112
QualType getType() const
Definition: Expr.h:144
Represents a member of a struct/union/class.
Definition: Decl.h:3157
Represents a function declaration or definition.
Definition: Decl.h:1999
FunctionDecl * getCanonicalDecl() override
Retrieves the "canonical" declaration of the given declaration.
Definition: Decl.cpp:3688
A global _GUID constant.
Definition: DeclCXX.h:4392
A (possibly-)qualified type.
Definition: TypeBase.h:937
bool isVolatileQualified() const
Determine whether this type is volatile-qualified.
Definition: TypeBase.h:8427
bool isNull() const
Return true if this QualType doesn't point to a type yet.
Definition: TypeBase.h:1004
const Type * getTypePtr() const
Retrieves a pointer to the underlying (unqualified) type.
Definition: TypeBase.h:8343
bool isConstQualified() const
Determine whether this type is const-qualified.
Definition: TypeBase.h:8416
Represents a struct/union/class.
Definition: Decl.h:4309
field_range fields() const
Definition: Decl.h:4512
RecordDecl * getDefinition() const
Returns the RecordDecl that actually defines this struct/union/class.
Definition: Decl.h:4493
StringLiteral - This represents a string literal expression, e.g.
Definition: Expr.h:1801
bool isCompleteDefinition() const
Return true if this decl has its body fully specified.
Definition: Decl.h:3809
A template parameter object.
The base class of the type hierarchy.
Definition: TypeBase.h:1833
RecordDecl * getAsRecordDecl() const
Retrieves the RecordDecl this type refers to.
Definition: Type.h:41
QualType getPointeeType() const
If this is a pointer, ObjC object pointer, or block pointer, this returns the respective pointee.
Definition: Type.cpp:752
const ArrayType * getAsArrayTypeUnsafe() const
A variant of getAs<> for array types which silently discards qualifiers from the outermost type.
Definition: TypeBase.h:9212
bool isPointerOrReferenceType() const
Definition: TypeBase.h:8584
const T * getAs() const
Member-template getAs<specific type>'.
Definition: TypeBase.h:9159
An artificial decl, representing a global anonymous constant value which is uniquified by value withi...
Definition: DeclCXX.h:4449
Represent the declaration of a variable (in which case it is an lvalue) a function (in which case it ...
Definition: Decl.h:711
QualType getType() const
Definition: Decl.h:722
bool isWeak() const
Determine whether this symbol is weakly-imported, or declared with the weak or weak-ref attr.
Definition: Decl.cpp:5449
Represents a GCC generic vector type.
Definition: TypeBase.h:4191
A memory block, either on the stack or in the heap.
Definition: InterpBlock.h:44
unsigned getCharBit() const
Returns CHAR_BIT.
Definition: Context.cpp:389
static bool shouldBeGloballyIndexed(const ValueDecl *VD)
Returns whether we should create a global variable for the given ValueDecl.
Definition: Context.h:125
OptPrimType classify(QualType T) const
Classifies a type.
Definition: Context.cpp:310
unsigned getEvalID() const
Definition: Context.h:140
Bytecode function.
Definition: Function.h:86
A pointer to a memory block, live or dead.
Definition: Pointer.h:90
void initializeAllElements() const
Initialize all elements of a primitive array at once.
Definition: Pointer.cpp:530
T & elem(unsigned I) const
Dereferences the element at index I.
Definition: Pointer.h:675
BlockPointer BS
Definition: Pointer.h:819
std::optional< unsigned > getOrCreateGlobal(const ValueDecl *VD, const Expr *Init=nullptr)
Returns or creates a global an creates an index to it.
Definition: Program.cpp:140
Function * getFunction(const FunctionDecl *F)
Returns a function.
Definition: Program.cpp:288
Block * getGlobal(unsigned Idx)
Returns the value of a global.
Definition: Program.h:71
std::optional< unsigned > createGlobal(const ValueDecl *VD, const Expr *Init)
Creates a global and returns its index.
Definition: Program.cpp:198
const void * getNativePointer(unsigned Idx)
Returns the value of a marshalled native pointer.
Definition: Program.cpp:30
unsigned getOrCreateNativePointer(const void *Ptr)
Marshals a native pointer to an ID for embedding in bytecode.
Definition: Program.cpp:21
Pointer getPtrGlobal(unsigned Idx) const
Returns a pointer to a global.
Definition: Program.cpp:109
unsigned getOrCreateDummy(const DeclTy &D)
Returns or creates a dummy value for unknown declarations.
Definition: Program.cpp:152
Descriptor * createDescriptor(const DeclTy &D, PrimType T, const Type *SourceTy=nullptr, Descriptor::MetadataSize MDSize=std::nullopt, bool IsConst=false, bool IsTemporary=false, bool IsMutable=false, bool IsVolatile=false)
Creates a descriptor for a primitive type.
Definition: Program.h:119
unsigned createGlobalString(const StringLiteral *S, const Expr *Base=nullptr)
Emits a string literal among global data.
Definition: Program.cpp:34
UnsignedOrNone getCurrentDecl() const
Returns the current declaration ID.
Definition: Program.h:159
Record * getOrCreateRecord(const RecordDecl *RD)
Returns a record or creates one if it does not exist.
Definition: Program.cpp:295
Structure/Class descriptor.
Definition: Record.h:25
unsigned getSize() const
Returns the size of the record.
Definition: Record.h:61
constexpr size_t align(size_t Size)
Aligns a size to the pointer alignment.
Definition: PrimType.h:185
PrimType
Enumeration of the primitive types of the VM.
Definition: PrimType.h:34
size_t primSize(PrimType Type)
Returns the size of a primitive type in bytes.
Definition: PrimType.cpp:23
llvm::PointerUnion< const Decl *, const Expr * > DeclTy
Definition: Descriptor.h:29
The JSON file list parser is used to communicate input to InstallAPI.
bool isa(CodeGen::Address addr)
Definition: Address.h:330
const FunctionProtoType * T
Pointer * Next
Next link in the pointer chain.
Definition: Pointer.h:43
Token to denote structures of unknown size.
Definition: Descriptor.h:141
Describes a memory block created by an allocation site.
Definition: Descriptor.h:122
unsigned getAllocSize() const
Returns the allocated size, including metadata.
Definition: Descriptor.h:242
static constexpr MetadataSize GlobalMD
Definition: Descriptor.h:145
std::optional< unsigned > MetadataSize
Definition: Descriptor.h:143
Descriptor used for global variables.
Definition: Descriptor.h:51
Inline descriptor embedded in structures and arrays.
Definition: Descriptor.h:67
Mapping from primitive types to their representation.
Definition: PrimType.h:134