1cfca06d7SDimitry Andric //===-- ConstString.cpp ---------------------------------------------------===//
2f034231aSEd Maste //
35f29bb8aSDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
45f29bb8aSDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
55f29bb8aSDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6f034231aSEd Maste //
7f034231aSEd Maste //===----------------------------------------------------------------------===//
8f3fbd1c0SDimitry Andric
974a628f7SDimitry Andric #include "lldb/Utility/ConstString.h"
10f3fbd1c0SDimitry Andric
1174a628f7SDimitry Andric #include "lldb/Utility/Stream.h"
12f3fbd1c0SDimitry Andric
1314f1b3e8SDimitry Andric #include "llvm/ADT/StringMap.h"
1494994d37SDimitry Andric #include "llvm/ADT/iterator.h"
1594994d37SDimitry Andric #include "llvm/Support/Allocator.h"
1694994d37SDimitry Andric #include "llvm/Support/DJB.h"
1794994d37SDimitry Andric #include "llvm/Support/FormatProviders.h"
18e81d9d49SDimitry Andric #include "llvm/Support/RWMutex.h"
1974a628f7SDimitry Andric #include "llvm/Support/Threading.h"
20f034231aSEd Maste
2174a628f7SDimitry Andric #include <array>
2294994d37SDimitry Andric #include <utility>
2374a628f7SDimitry Andric
24344a3780SDimitry Andric #include <cinttypes>
25344a3780SDimitry Andric #include <cstdint>
26344a3780SDimitry Andric #include <cstring>
27205afe67SEd Maste
28f034231aSEd Maste using namespace lldb_private;
29f034231aSEd Maste
3014f1b3e8SDimitry Andric class Pool {
31f034231aSEd Maste public:
32cfca06d7SDimitry Andric /// The default BumpPtrAllocatorImpl slab size.
33cfca06d7SDimitry Andric static const size_t AllocatorSlabSize = 4096;
34cfca06d7SDimitry Andric static const size_t SizeThreshold = AllocatorSlabSize;
35cfca06d7SDimitry Andric /// Every Pool has its own allocator which receives an equal share of
36cfca06d7SDimitry Andric /// the ConstString allocations. This means that when allocating many
37cfca06d7SDimitry Andric /// ConstStrings, every allocator sees only its small share of allocations and
38cfca06d7SDimitry Andric /// assumes LLDB only allocated a small amount of memory so far. In reality
39cfca06d7SDimitry Andric /// LLDB allocated a total memory that is N times as large as what the
40cfca06d7SDimitry Andric /// allocator sees (where N is the number of string pools). This causes that
41cfca06d7SDimitry Andric /// the BumpPtrAllocator continues a long time to allocate memory in small
42cfca06d7SDimitry Andric /// chunks which only makes sense when allocating a small amount of memory
43cfca06d7SDimitry Andric /// (which is true from the perspective of a single allocator). On some
44cfca06d7SDimitry Andric /// systems doing all these small memory allocations causes LLDB to spend
45cfca06d7SDimitry Andric /// a lot of time in malloc, so we need to force all these allocators to
46cfca06d7SDimitry Andric /// behave like one allocator in terms of scaling their memory allocations
47cfca06d7SDimitry Andric /// with increased demand. To do this we set the growth delay for each single
48cfca06d7SDimitry Andric /// allocator to a rate so that our pool of allocators scales their memory
49cfca06d7SDimitry Andric /// allocations similar to a single BumpPtrAllocatorImpl.
50cfca06d7SDimitry Andric ///
51cfca06d7SDimitry Andric /// Currently we have 256 string pools and the normal growth delay of the
52cfca06d7SDimitry Andric /// BumpPtrAllocatorImpl is 128 (i.e., the memory allocation size increases
53cfca06d7SDimitry Andric /// every 128 full chunks), so by changing the delay to 1 we get a
54cfca06d7SDimitry Andric /// total growth delay in our allocator collection of 256/1 = 256. This is
55cfca06d7SDimitry Andric /// still only half as fast as a normal allocator but we can't go any faster
56cfca06d7SDimitry Andric /// without decreasing the number of string pools.
57cfca06d7SDimitry Andric static const size_t AllocatorGrowthDelay = 1;
58cfca06d7SDimitry Andric typedef llvm::BumpPtrAllocatorImpl<llvm::MallocAllocator, AllocatorSlabSize,
59cfca06d7SDimitry Andric SizeThreshold, AllocatorGrowthDelay>
60cfca06d7SDimitry Andric Allocator;
61f034231aSEd Maste typedef const char *StringPoolValueType;
62cfca06d7SDimitry Andric typedef llvm::StringMap<StringPoolValueType, Allocator> StringPool;
63f034231aSEd Maste typedef llvm::StringMapEntry<StringPoolValueType> StringPoolEntryType;
64f034231aSEd Maste
65f034231aSEd Maste static StringPoolEntryType &
GetStringMapEntryFromKeyData(const char * keyData)6614f1b3e8SDimitry Andric GetStringMapEntryFromKeyData(const char *keyData) {
67773dd0e6SDimitry Andric return StringPoolEntryType::GetStringMapEntryFromKeyData(keyData);
68f034231aSEd Maste }
69f034231aSEd Maste
GetConstCStringLength(const char * ccstr)70773dd0e6SDimitry Andric static size_t GetConstCStringLength(const char *ccstr) {
7114f1b3e8SDimitry Andric if (ccstr != nullptr) {
72f73363f1SDimitry Andric // Since the entry is read only, and we derive the entry entirely from
73f73363f1SDimitry Andric // the pointer, we don't need the lock.
74f034231aSEd Maste const StringPoolEntryType &entry = GetStringMapEntryFromKeyData(ccstr);
75f034231aSEd Maste return entry.getKey().size();
76f034231aSEd Maste }
77f034231aSEd Maste return 0;
78f034231aSEd Maste }
79f034231aSEd Maste
GetMangledCounterpart(const char * ccstr)80ac9a064cSDimitry Andric StringPoolValueType GetMangledCounterpart(const char *ccstr) {
8114f1b3e8SDimitry Andric if (ccstr != nullptr) {
82ac9a064cSDimitry Andric const PoolEntry &pool = selectPool(llvm::StringRef(ccstr));
83ac9a064cSDimitry Andric llvm::sys::SmartScopedReader<false> rlock(pool.m_mutex);
84f034231aSEd Maste return GetStringMapEntryFromKeyData(ccstr).getValue();
85e81d9d49SDimitry Andric }
86f3fbd1c0SDimitry Andric return nullptr;
87f034231aSEd Maste }
88f034231aSEd Maste
GetConstCString(const char * cstr)8914f1b3e8SDimitry Andric const char *GetConstCString(const char *cstr) {
90f3fbd1c0SDimitry Andric if (cstr != nullptr)
91f034231aSEd Maste return GetConstCStringWithLength(cstr, strlen(cstr));
92e81d9d49SDimitry Andric return nullptr;
93f034231aSEd Maste }
94f034231aSEd Maste
GetConstCStringWithLength(const char * cstr,size_t cstr_len)9514f1b3e8SDimitry Andric const char *GetConstCStringWithLength(const char *cstr, size_t cstr_len) {
96f3fbd1c0SDimitry Andric if (cstr != nullptr)
97e81d9d49SDimitry Andric return GetConstCStringWithStringRef(llvm::StringRef(cstr, cstr_len));
98e81d9d49SDimitry Andric return nullptr;
99f034231aSEd Maste }
100f034231aSEd Maste
GetConstCStringWithStringRef(llvm::StringRef string_ref)1017fa27ce4SDimitry Andric const char *GetConstCStringWithStringRef(llvm::StringRef string_ref) {
10214f1b3e8SDimitry Andric if (string_ref.data()) {
103ac9a064cSDimitry Andric const uint32_t string_hash = StringPool::hash(string_ref);
104ac9a064cSDimitry Andric PoolEntry &pool = selectPool(string_hash);
105e81d9d49SDimitry Andric
106e81d9d49SDimitry Andric {
107ac9a064cSDimitry Andric llvm::sys::SmartScopedReader<false> rlock(pool.m_mutex);
108ac9a064cSDimitry Andric auto it = pool.m_string_map.find(string_ref, string_hash);
109ac9a064cSDimitry Andric if (it != pool.m_string_map.end())
110e81d9d49SDimitry Andric return it->getKeyData();
111e81d9d49SDimitry Andric }
112e81d9d49SDimitry Andric
113ac9a064cSDimitry Andric llvm::sys::SmartScopedWriter<false> wlock(pool.m_mutex);
11414f1b3e8SDimitry Andric StringPoolEntryType &entry =
115ac9a064cSDimitry Andric *pool.m_string_map
116ac9a064cSDimitry Andric .insert(std::make_pair(string_ref, nullptr), string_hash)
11714f1b3e8SDimitry Andric .first;
118f034231aSEd Maste return entry.getKeyData();
119f034231aSEd Maste }
120e81d9d49SDimitry Andric return nullptr;
121f034231aSEd Maste }
122f034231aSEd Maste
123f034231aSEd Maste const char *
GetConstCStringAndSetMangledCounterPart(llvm::StringRef demangled,const char * mangled_ccstr)12494994d37SDimitry Andric GetConstCStringAndSetMangledCounterPart(llvm::StringRef demangled,
12514f1b3e8SDimitry Andric const char *mangled_ccstr) {
126e81d9d49SDimitry Andric const char *demangled_ccstr = nullptr;
127e81d9d49SDimitry Andric
128e81d9d49SDimitry Andric {
129ac9a064cSDimitry Andric const uint32_t demangled_hash = StringPool::hash(demangled);
130ac9a064cSDimitry Andric PoolEntry &pool = selectPool(demangled_hash);
131ac9a064cSDimitry Andric llvm::sys::SmartScopedWriter<false> wlock(pool.m_mutex);
132e81d9d49SDimitry Andric
13394994d37SDimitry Andric // Make or update string pool entry with the mangled counterpart
134ac9a064cSDimitry Andric StringPool &map = pool.m_string_map;
135ac9a064cSDimitry Andric StringPoolEntryType &entry =
136ac9a064cSDimitry Andric *map.try_emplace_with_hash(demangled, demangled_hash).first;
13794994d37SDimitry Andric
13894994d37SDimitry Andric entry.second = mangled_ccstr;
139f034231aSEd Maste
140f034231aSEd Maste // Extract the const version of the demangled_cstr
141e81d9d49SDimitry Andric demangled_ccstr = entry.getKeyData();
142e81d9d49SDimitry Andric }
143e81d9d49SDimitry Andric
144e81d9d49SDimitry Andric {
145f034231aSEd Maste // Now assign the demangled const string as the counterpart of the
146f034231aSEd Maste // mangled const string...
147ac9a064cSDimitry Andric PoolEntry &pool = selectPool(llvm::StringRef(mangled_ccstr));
148ac9a064cSDimitry Andric llvm::sys::SmartScopedWriter<false> wlock(pool.m_mutex);
149f034231aSEd Maste GetStringMapEntryFromKeyData(mangled_ccstr).setValue(demangled_ccstr);
150e81d9d49SDimitry Andric }
151e81d9d49SDimitry Andric
152f034231aSEd Maste // Return the constant demangled C string
153f034231aSEd Maste return demangled_ccstr;
154f034231aSEd Maste }
155f034231aSEd Maste
GetConstTrimmedCStringWithLength(const char * cstr,size_t cstr_len)15614f1b3e8SDimitry Andric const char *GetConstTrimmedCStringWithLength(const char *cstr,
15714f1b3e8SDimitry Andric size_t cstr_len) {
15814f1b3e8SDimitry Andric if (cstr != nullptr) {
1595f29bb8aSDimitry Andric const size_t trimmed_len = strnlen(cstr, cstr_len);
160f034231aSEd Maste return GetConstCStringWithLength(cstr, trimmed_len);
161f034231aSEd Maste }
162e81d9d49SDimitry Andric return nullptr;
163f034231aSEd Maste }
164f034231aSEd Maste
GetMemoryStats() const1656f8fc217SDimitry Andric ConstString::MemoryStats GetMemoryStats() const {
1666f8fc217SDimitry Andric ConstString::MemoryStats stats;
16714f1b3e8SDimitry Andric for (const auto &pool : m_string_pools) {
168e81d9d49SDimitry Andric llvm::sys::SmartScopedReader<false> rlock(pool.m_mutex);
1696f8fc217SDimitry Andric const Allocator &alloc = pool.m_string_map.getAllocator();
1706f8fc217SDimitry Andric stats.bytes_total += alloc.getTotalMemory();
1716f8fc217SDimitry Andric stats.bytes_used += alloc.getBytesAllocated();
172f034231aSEd Maste }
1736f8fc217SDimitry Andric return stats;
174f034231aSEd Maste }
175f034231aSEd Maste
176f034231aSEd Maste protected:
17714f1b3e8SDimitry Andric struct PoolEntry {
178e81d9d49SDimitry Andric mutable llvm::sys::SmartRWMutex<false> m_mutex;
179f034231aSEd Maste StringPool m_string_map;
180f034231aSEd Maste };
181f034231aSEd Maste
182e81d9d49SDimitry Andric std::array<PoolEntry, 256> m_string_pools;
183ac9a064cSDimitry Andric
selectPool(const llvm::StringRef & s)184ac9a064cSDimitry Andric PoolEntry &selectPool(const llvm::StringRef &s) {
185ac9a064cSDimitry Andric return selectPool(StringPool::hash(s));
186ac9a064cSDimitry Andric }
187ac9a064cSDimitry Andric
selectPool(uint32_t h)188ac9a064cSDimitry Andric PoolEntry &selectPool(uint32_t h) {
189ac9a064cSDimitry Andric return m_string_pools[((h >> 24) ^ (h >> 16) ^ (h >> 8) ^ h) & 0xff];
190ac9a064cSDimitry Andric }
191e81d9d49SDimitry Andric };
192e81d9d49SDimitry Andric
193f73363f1SDimitry Andric // Frameworks and dylibs aren't supposed to have global C++ initializers so we
194f73363f1SDimitry Andric // hide the string pool in a static function so that it will get initialized on
195f73363f1SDimitry Andric // the first call to this static function.
196f034231aSEd Maste //
197f73363f1SDimitry Andric // Note, for now we make the string pool a pointer to the pool, because we
198f73363f1SDimitry Andric // can't guarantee that some objects won't get destroyed after the global
199f73363f1SDimitry Andric // destructor chain is run, and trying to make sure no destructors touch
200f73363f1SDimitry Andric // ConstStrings is difficult. So we leak the pool instead.
StringPool()20114f1b3e8SDimitry Andric static Pool &StringPool() {
20274a628f7SDimitry Andric static llvm::once_flag g_pool_initialization_flag;
203e81d9d49SDimitry Andric static Pool *g_string_pool = nullptr;
204f034231aSEd Maste
20574a628f7SDimitry Andric llvm::call_once(g_pool_initialization_flag,
20614f1b3e8SDimitry Andric []() { g_string_pool = new Pool(); });
207f034231aSEd Maste
208f034231aSEd Maste return *g_string_pool;
209f034231aSEd Maste }
210f034231aSEd Maste
ConstString(const char * cstr)21114f1b3e8SDimitry Andric ConstString::ConstString(const char *cstr)
21214f1b3e8SDimitry Andric : m_string(StringPool().GetConstCString(cstr)) {}
213f034231aSEd Maste
ConstString(const char * cstr,size_t cstr_len)21414f1b3e8SDimitry Andric ConstString::ConstString(const char *cstr, size_t cstr_len)
21514f1b3e8SDimitry Andric : m_string(StringPool().GetConstCStringWithLength(cstr, cstr_len)) {}
216f034231aSEd Maste
ConstString(llvm::StringRef s)2177fa27ce4SDimitry Andric ConstString::ConstString(llvm::StringRef s)
2185f29bb8aSDimitry Andric : m_string(StringPool().GetConstCStringWithStringRef(s)) {}
219f034231aSEd Maste
operator <(ConstString rhs) const2205f29bb8aSDimitry Andric bool ConstString::operator<(ConstString rhs) const {
221f034231aSEd Maste if (m_string == rhs.m_string)
222f034231aSEd Maste return false;
223f034231aSEd Maste
224773dd0e6SDimitry Andric llvm::StringRef lhs_string_ref(GetStringRef());
225773dd0e6SDimitry Andric llvm::StringRef rhs_string_ref(rhs.GetStringRef());
226f034231aSEd Maste
227f034231aSEd Maste // If both have valid C strings, then return the comparison
228f034231aSEd Maste if (lhs_string_ref.data() && rhs_string_ref.data())
229f034231aSEd Maste return lhs_string_ref < rhs_string_ref;
230f034231aSEd Maste
231e81d9d49SDimitry Andric // Else one of them was nullptr, so if LHS is nullptr then it is less than
232e81d9d49SDimitry Andric return lhs_string_ref.data() == nullptr;
233f034231aSEd Maste }
234f034231aSEd Maste
operator <<(Stream & s,ConstString str)2355f29bb8aSDimitry Andric Stream &lldb_private::operator<<(Stream &s, ConstString str) {
236f034231aSEd Maste const char *cstr = str.GetCString();
237f3fbd1c0SDimitry Andric if (cstr != nullptr)
238f034231aSEd Maste s << cstr;
239f034231aSEd Maste
240f034231aSEd Maste return s;
241f034231aSEd Maste }
242f034231aSEd Maste
GetLength() const24314f1b3e8SDimitry Andric size_t ConstString::GetLength() const {
244773dd0e6SDimitry Andric return Pool::GetConstCStringLength(m_string);
245f034231aSEd Maste }
246f034231aSEd Maste
Equals(ConstString lhs,ConstString rhs,const bool case_sensitive)2475f29bb8aSDimitry Andric bool ConstString::Equals(ConstString lhs, ConstString rhs,
24814f1b3e8SDimitry Andric const bool case_sensitive) {
249f3fbd1c0SDimitry Andric if (lhs.m_string == rhs.m_string)
250f3fbd1c0SDimitry Andric return true;
251f3fbd1c0SDimitry Andric
25214f1b3e8SDimitry Andric // Since the pointers weren't equal, and identical ConstStrings always have
253f73363f1SDimitry Andric // identical pointers, the result must be false for case sensitive equality
254f73363f1SDimitry Andric // test.
255f3fbd1c0SDimitry Andric if (case_sensitive)
256f3fbd1c0SDimitry Andric return false;
257f3fbd1c0SDimitry Andric
258f3fbd1c0SDimitry Andric // perform case insensitive equality test
259773dd0e6SDimitry Andric llvm::StringRef lhs_string_ref(lhs.GetStringRef());
260773dd0e6SDimitry Andric llvm::StringRef rhs_string_ref(rhs.GetStringRef());
261344a3780SDimitry Andric return lhs_string_ref.equals_insensitive(rhs_string_ref);
262f3fbd1c0SDimitry Andric }
263f3fbd1c0SDimitry Andric
Compare(ConstString lhs,ConstString rhs,const bool case_sensitive)2645f29bb8aSDimitry Andric int ConstString::Compare(ConstString lhs, ConstString rhs,
26514f1b3e8SDimitry Andric const bool case_sensitive) {
266f034231aSEd Maste // If the iterators are the same, this is the same string
267f21a844fSEd Maste const char *lhs_cstr = lhs.m_string;
268f21a844fSEd Maste const char *rhs_cstr = rhs.m_string;
269f034231aSEd Maste if (lhs_cstr == rhs_cstr)
270f034231aSEd Maste return 0;
27114f1b3e8SDimitry Andric if (lhs_cstr && rhs_cstr) {
272773dd0e6SDimitry Andric llvm::StringRef lhs_string_ref(lhs.GetStringRef());
273773dd0e6SDimitry Andric llvm::StringRef rhs_string_ref(rhs.GetStringRef());
274f3fbd1c0SDimitry Andric
27514f1b3e8SDimitry Andric if (case_sensitive) {
276f034231aSEd Maste return lhs_string_ref.compare(rhs_string_ref);
27714f1b3e8SDimitry Andric } else {
278344a3780SDimitry Andric return lhs_string_ref.compare_insensitive(rhs_string_ref);
279f3fbd1c0SDimitry Andric }
280f3fbd1c0SDimitry Andric }
281f034231aSEd Maste
282f034231aSEd Maste if (lhs_cstr)
283f3fbd1c0SDimitry Andric return +1; // LHS isn't nullptr but RHS is
284f034231aSEd Maste else
285f3fbd1c0SDimitry Andric return -1; // LHS is nullptr but RHS isn't
286f034231aSEd Maste }
287f034231aSEd Maste
Dump(Stream * s,const char * fail_value) const28814f1b3e8SDimitry Andric void ConstString::Dump(Stream *s, const char *fail_value) const {
28914f1b3e8SDimitry Andric if (s != nullptr) {
290f034231aSEd Maste const char *cstr = AsCString(fail_value);
291f3fbd1c0SDimitry Andric if (cstr != nullptr)
292f034231aSEd Maste s->PutCString(cstr);
293f034231aSEd Maste }
294f034231aSEd Maste }
295f034231aSEd Maste
DumpDebug(Stream * s) const29614f1b3e8SDimitry Andric void ConstString::DumpDebug(Stream *s) const {
297f034231aSEd Maste const char *cstr = GetCString();
298f034231aSEd Maste size_t cstr_len = GetLength();
299f3fbd1c0SDimitry Andric // Only print the parens if we have a non-nullptr string
300f034231aSEd Maste const char *parens = cstr ? "\"" : "";
3010cac4ca3SEd Maste s->Printf("%*p: ConstString, string = %s%s%s, length = %" PRIu64,
3020cac4ca3SEd Maste static_cast<int>(sizeof(void *) * 2),
3030cac4ca3SEd Maste static_cast<const void *>(this), parens, cstr, parens,
3040cac4ca3SEd Maste static_cast<uint64_t>(cstr_len));
305f034231aSEd Maste }
306f034231aSEd Maste
SetCString(const char * cstr)30714f1b3e8SDimitry Andric void ConstString::SetCString(const char *cstr) {
308f034231aSEd Maste m_string = StringPool().GetConstCString(cstr);
309f034231aSEd Maste }
310f034231aSEd Maste
SetString(llvm::StringRef s)3117fa27ce4SDimitry Andric void ConstString::SetString(llvm::StringRef s) {
3127fa27ce4SDimitry Andric m_string = StringPool().GetConstCStringWithStringRef(s);
313f034231aSEd Maste }
314f034231aSEd Maste
SetStringWithMangledCounterpart(llvm::StringRef demangled,ConstString mangled)31594994d37SDimitry Andric void ConstString::SetStringWithMangledCounterpart(llvm::StringRef demangled,
3165f29bb8aSDimitry Andric ConstString mangled) {
31714f1b3e8SDimitry Andric m_string = StringPool().GetConstCStringAndSetMangledCounterPart(
31814f1b3e8SDimitry Andric demangled, mangled.m_string);
319f034231aSEd Maste }
320f034231aSEd Maste
GetMangledCounterpart(ConstString & counterpart) const32114f1b3e8SDimitry Andric bool ConstString::GetMangledCounterpart(ConstString &counterpart) const {
322f034231aSEd Maste counterpart.m_string = StringPool().GetMangledCounterpart(m_string);
323f21a844fSEd Maste return (bool)counterpart;
324f034231aSEd Maste }
325f034231aSEd Maste
SetCStringWithLength(const char * cstr,size_t cstr_len)32614f1b3e8SDimitry Andric void ConstString::SetCStringWithLength(const char *cstr, size_t cstr_len) {
327f034231aSEd Maste m_string = StringPool().GetConstCStringWithLength(cstr, cstr_len);
328f034231aSEd Maste }
329f034231aSEd Maste
SetTrimmedCStringWithLength(const char * cstr,size_t cstr_len)33014f1b3e8SDimitry Andric void ConstString::SetTrimmedCStringWithLength(const char *cstr,
33114f1b3e8SDimitry Andric size_t cstr_len) {
332f034231aSEd Maste m_string = StringPool().GetConstTrimmedCStringWithLength(cstr, cstr_len);
333f034231aSEd Maste }
334f034231aSEd Maste
GetMemoryStats()3356f8fc217SDimitry Andric ConstString::MemoryStats ConstString::GetMemoryStats() {
3366f8fc217SDimitry Andric return StringPool().GetMemoryStats();
337f034231aSEd Maste }
33874a628f7SDimitry Andric
format(const ConstString & CS,llvm::raw_ostream & OS,llvm::StringRef Options)33974a628f7SDimitry Andric void llvm::format_provider<ConstString>::format(const ConstString &CS,
34074a628f7SDimitry Andric llvm::raw_ostream &OS,
34174a628f7SDimitry Andric llvm::StringRef Options) {
342cfca06d7SDimitry Andric format_provider<StringRef>::format(CS.GetStringRef(), OS, Options);
343cfca06d7SDimitry Andric }
344