LayoutOverrideSource.cpp revision 453dbcbe30093fbf947a0bec2fbd46e9694eafe9
1453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor//===--- LayoutOverrideSource.cpp --Override Record Layouts ---------------===// 2453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor// 3453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor// The LLVM Compiler Infrastructure 4453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor// 5453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor// This file is distributed under the University of Illinois Open Source 6453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor// License. See LICENSE.TXT for details. 7453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor// 8453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor//===----------------------------------------------------------------------===// 9453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor#include "clang/Frontend/LayoutOverrideSource.h" 10453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor#include "clang/AST/Decl.h" 11453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor#include "llvm/Support/raw_ostream.h" 12453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor#include <fstream> 13453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor#include <string> 14453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 15453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregorusing namespace clang; 16453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 17453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor/// \brief Parse a simple identifier. 18453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregorstd::string parseName(StringRef S) { 19453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor unsigned Offset = 0; 20453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor while (Offset < S.size() && 21453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor (isalpha(S[Offset]) || S[Offset] == '_' || 22453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor (Offset > 0 && isdigit(S[Offset])))) 23453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor ++Offset; 24453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 25453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor return S.substr(0, Offset).str(); 26453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor} 27453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 28453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas GregorLayoutOverrideSource::LayoutOverrideSource(llvm::StringRef Filename) { 29453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor std::ifstream Input(Filename.str().c_str()); 30453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor if (!Input.is_open()) 31453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor return; 32453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 33453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Parse the output of -fdump-record-layouts. 34453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor std::string CurrentType; 35453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor Layout CurrentLayout; 36453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor bool ExpectingType = false; 37453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 38453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor while (Input.good()) { 39453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor std::string Line; 40453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor getline(Input, Line); 41453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 42453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor StringRef LineStr(Line); 43453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 44453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Determine whether the following line will start a 45453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor if (LineStr.find("*** Dumping AST Record Layout") != StringRef::npos) { 46453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Flush the last type/layout, if there is one. 47453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor if (!CurrentType.empty()) 48453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor Layouts[CurrentType] = CurrentLayout; 49453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor CurrentLayout = Layout(); 50453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 51453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor ExpectingType = true; 52453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor continue; 53453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor } 54453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 55453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // If we're expecting a type, grab it. 56453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor if (ExpectingType) { 57453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor ExpectingType = false; 58453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 59453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor StringRef::size_type Pos; 60453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor if ((Pos = LineStr.find("struct ")) != StringRef::npos) 61453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor LineStr = LineStr.substr(Pos + strlen("struct ")); 62453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor else if ((Pos = LineStr.find("class ")) != StringRef::npos) 63453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor LineStr = LineStr.substr(Pos + strlen("class ")); 64453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor else if ((Pos = LineStr.find("union ")) != StringRef::npos) 65453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor LineStr = LineStr.substr(Pos + strlen("union ")); 66453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor else 67453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor continue; 68453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 69453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Find the name of the type. 70453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor CurrentType = parseName(LineStr); 71453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor CurrentLayout = Layout(); 72453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor continue; 73453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor } 74453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 75453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Check for the size of the type. 76453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor StringRef::size_type Pos = LineStr.find("Size:"); 77453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor if (Pos != StringRef::npos) { 78453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Skip past the "Size:" prefix. 79453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor LineStr = LineStr.substr(Pos + strlen("Size:")); 80453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 81453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor unsigned long long Size = 0; 82453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor (void)LineStr.getAsInteger(10, Size); 83453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor CurrentLayout.Size = Size; 84453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor continue; 85453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor } 86453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 87453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Check for the alignment of the type. 88453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor Pos = LineStr.find("Alignment:"); 89453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor if (Pos != StringRef::npos) { 90453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Skip past the "Alignment:" prefix. 91453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor LineStr = LineStr.substr(Pos + strlen("Alignment:")); 92453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 93453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor unsigned long long Alignment = 0; 94453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor (void)LineStr.getAsInteger(10, Alignment); 95453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor CurrentLayout.Align = Alignment; 96453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor continue; 97453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor } 98453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 99453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Check for the size/alignment of the type. 100453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor Pos = LineStr.find("sizeof="); 101453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor if (Pos != StringRef::npos) { 102453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor /* Skip past the sizeof= prefix. */ 103453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor LineStr = LineStr.substr(Pos + strlen("sizeof=")); 104453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 105453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Parse size. 106453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor unsigned long long Size = 0; 107453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor (void)LineStr.getAsInteger(10, Size); 108453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor CurrentLayout.Size = Size; 109453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 110453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor Pos = LineStr.find("align="); 111453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor if (Pos != StringRef::npos) { 112453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor /* Skip past the align= prefix. */ 113453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor LineStr = LineStr.substr(Pos + strlen("align=")); 114453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 115453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Parse alignment. 116453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor unsigned long long Alignment = 0; 117453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor (void)LineStr.getAsInteger(10, Alignment); 118453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor CurrentLayout.Align = Alignment; 119453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor } 120453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 121453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor continue; 122453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor } 123453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 124453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Check for the field offsets of the type. 125453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor Pos = LineStr.find("FieldOffsets: ["); 126453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor if (Pos == StringRef::npos) 127453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor continue; 128453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 129453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor LineStr = LineStr.substr(Pos + strlen("FieldOffsets: [")); 130453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor while (!LineStr.empty() && isdigit(LineStr[0])) { 131453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Parse this offset. 132453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor unsigned Idx = 1; 133453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor while (Idx < LineStr.size() && isdigit(LineStr[Idx])) 134453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor ++Idx; 135453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 136453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor unsigned long long Offset = 0; 137453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor (void)LineStr.substr(0, Idx).getAsInteger(10, Offset); 138453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 139453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor CurrentLayout.FieldOffsets.push_back(Offset); 140453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 141453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Skip over this offset, the following comma, and any spaces. 142453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor LineStr = LineStr.substr(Idx + 1); 143453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor while (!LineStr.empty() && isspace(LineStr[0])) 144453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor LineStr = LineStr.substr(1); 145453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor } 146453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor } 147453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 148453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Flush the last type/layout, if there is one. 149453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor if (!CurrentType.empty()) 150453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor Layouts[CurrentType] = CurrentLayout; 151453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor} 152453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 153453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregorbool 154453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas GregorLayoutOverrideSource::layoutRecordType(const RecordDecl *Record, 155453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor uint64_t &Size, uint64_t &Alignment, 156453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor llvm::DenseMap<const FieldDecl *, uint64_t> &FieldOffsets, 157453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor llvm::DenseMap<const CXXRecordDecl *, CharUnits> &BaseOffsets, 158453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor llvm::DenseMap<const CXXRecordDecl *, CharUnits> &VirtualBaseOffsets) 159453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor{ 160453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // We can't override unnamed declarations. 161453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor if (!Record->getIdentifier()) 162453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor return false; 163453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 164453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Check whether we have a layout for this record. 165453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor llvm::StringMap<Layout>::iterator Known = Layouts.find(Record->getName()); 166453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor if (Known == Layouts.end()) 167453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor return false; 168453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 169453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Provide field layouts. 170453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor unsigned NumFields = 0; 171453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor for (RecordDecl::field_iterator F = Record->field_begin(), 172453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor FEnd = Record->field_end(); 173453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor F != FEnd; ++F, ++NumFields) { 174453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor if (NumFields >= Known->second.FieldOffsets.size()) 175453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor continue; 176453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 177453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor FieldOffsets[*F] = Known->second.FieldOffsets[NumFields]; 178453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor } 179453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 180453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Wrong number of fields. 181453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor if (NumFields != Known->second.FieldOffsets.size()) 182453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor return false; 183453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 184453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor Size = Known->second.Size; 185453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor Alignment = Known->second.Align; 186453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor return true; 187453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor} 188453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 189453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregorvoid LayoutOverrideSource::dump() { 190453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor llvm::raw_ostream &OS = llvm::errs(); 191453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor for (llvm::StringMap<Layout>::iterator L = Layouts.begin(), 192453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor LEnd = Layouts.end(); 193453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor L != LEnd; ++L) { 194453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor OS << "Type: blah " << L->first() << '\n'; 195453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor OS << " Size:" << L->second.Size << '\n'; 196453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor OS << " Alignment:" << L->second.Align << '\n'; 197453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor OS << " FieldOffsets: ["; 198453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor for (unsigned I = 0, N = L->second.FieldOffsets.size(); I != N; ++I) { 199453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor if (I) 200453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor OS << ", "; 201453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor OS << L->second.FieldOffsets[I]; 202453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor } 203453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor OS << "]\n"; 204453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor } 205453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor} 206453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 207