1 /**
2 * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "runtime/string_table.h"
17
18 #include "runtime/include/relayout_profiler.h"
19 #include "runtime/include/runtime.h"
20 #include "runtime/mem/object_helpers.h"
21
22 namespace ark {
23
GetOrInternString(const uint8_t * mutf8Data,uint32_t utf16Length,const LanguageContext & ctx)24 coretypes::String *StringTable::GetOrInternString(const uint8_t *mutf8Data, uint32_t utf16Length,
25 const LanguageContext &ctx)
26 {
27 bool canBeCompressed = coretypes::String::CanBeCompressedMUtf8(mutf8Data);
28 auto *str = internalTable_.GetString(mutf8Data, utf16Length, canBeCompressed, ctx);
29 if (str == nullptr) {
30 str = table_.GetOrInternString(mutf8Data, utf16Length, canBeCompressed, ctx);
31 }
32 return str;
33 }
34
GetOrInternString(const uint16_t * utf16Data,uint32_t utf16Length,const LanguageContext & ctx)35 coretypes::String *StringTable::GetOrInternString(const uint16_t *utf16Data, uint32_t utf16Length,
36 const LanguageContext &ctx)
37 {
38 auto *str = internalTable_.GetString(utf16Data, utf16Length, ctx);
39 if (str == nullptr) {
40 str = table_.GetOrInternString(utf16Data, utf16Length, ctx);
41 }
42 return str;
43 }
44
GetOrInternString(coretypes::String * string,const LanguageContext & ctx)45 coretypes::String *StringTable::GetOrInternString(coretypes::String *string, const LanguageContext &ctx)
46 {
47 auto *str = internalTable_.GetString(string, ctx);
48 if (str == nullptr) {
49 str = table_.GetOrInternString(string, ctx);
50 }
51 return str;
52 }
53
GetOrInternInternalString(const panda_file::File & pf,panda_file::File::EntityId id,const LanguageContext & ctx)54 coretypes::String *StringTable::GetOrInternInternalString(const panda_file::File &pf, panda_file::File::EntityId id,
55 const LanguageContext &ctx)
56 {
57 auto data = pf.GetStringData(id);
58
59 ADD_PROFILE_STRING_ITEM(pf.GetFilename(), utf::Mutf8AsCString(data.data));
60
61 coretypes::String *str = table_.GetString(data.data, data.utf16Length, data.isAscii, ctx);
62 if (str != nullptr) {
63 table_.PreBarrierOnGet(str);
64 return str;
65 }
66 return internalTable_.GetOrInternString(pf, id, ctx);
67 }
68
Sweep(const GCObjectVisitor & gcObjectVisitor)69 void StringTable::Sweep(const GCObjectVisitor &gcObjectVisitor)
70 {
71 table_.Sweep(gcObjectVisitor);
72 }
73
UpdateMoved()74 bool StringTable::UpdateMoved()
75 {
76 return table_.UpdateMoved();
77 }
78
Size()79 size_t StringTable::Size()
80 {
81 return internalTable_.Size() + table_.Size();
82 }
83
VisitStrings(const StringVisitor & visitor)84 void StringTable::Table::VisitStrings(const StringVisitor &visitor)
85 {
86 os::memory::ReadLockHolder holder(tableLock_);
87 for (auto entry : table_) {
88 visitor(entry.second);
89 }
90 }
91
GetString(const uint8_t * utf8Data,uint32_t utf16Length,bool canBeCompressed,const LanguageContext & ctx)92 coretypes::String *StringTable::Table::GetString(const uint8_t *utf8Data, uint32_t utf16Length, bool canBeCompressed,
93 [[maybe_unused]] const LanguageContext &ctx)
94 {
95 uint32_t hashCode = coretypes::String::ComputeHashcodeMutf8(utf8Data, utf16Length, canBeCompressed);
96 os::memory::ReadLockHolder holder(tableLock_);
97 for (auto it = table_.find(hashCode); it != table_.end(); it++) {
98 auto foundString = it->second;
99 if (coretypes::String::StringsAreEqualMUtf8(foundString, utf8Data, utf16Length, canBeCompressed)) {
100 return foundString;
101 }
102 }
103 return nullptr;
104 }
105
GetString(const uint16_t * utf16Data,uint32_t utf16Length,const LanguageContext & ctx)106 coretypes::String *StringTable::Table::GetString(const uint16_t *utf16Data, uint32_t utf16Length,
107 [[maybe_unused]] const LanguageContext &ctx)
108 {
109 uint32_t hashCode = coretypes::String::ComputeHashcodeUtf16(const_cast<uint16_t *>(utf16Data), utf16Length);
110 os::memory::ReadLockHolder holder(tableLock_);
111 for (auto it = table_.find(hashCode); it != table_.end(); it++) {
112 auto foundString = it->second;
113 if (coretypes::String::StringsAreEqualUtf16(foundString, utf16Data, utf16Length)) {
114 return foundString;
115 }
116 }
117 return nullptr;
118 }
119
GetString(coretypes::String * string,const LanguageContext & ctx)120 coretypes::String *StringTable::Table::GetString(coretypes::String *string, [[maybe_unused]] const LanguageContext &ctx)
121 {
122 ASSERT(string != nullptr);
123 os::memory::ReadLockHolder holder(tableLock_);
124 auto hash = string->GetHashcode();
125 for (auto it = table_.find(hash); it != table_.end(); it++) {
126 auto foundString = it->second;
127 if (coretypes::String::StringsAreEqual(foundString, string)) {
128 return foundString;
129 }
130 }
131 return nullptr;
132 }
133
ForceInternString(coretypes::String * string,const LanguageContext & ctx)134 void StringTable::Table::ForceInternString(coretypes::String *string, [[maybe_unused]] const LanguageContext &ctx)
135 {
136 os::memory::WriteLockHolder holder(tableLock_);
137 table_.insert(std::pair<uint32_t, coretypes::String *>(string->GetHashcode(), string));
138 }
139
InternString(coretypes::String * string,const LanguageContext & ctx)140 coretypes::String *StringTable::Table::InternString(coretypes::String *string,
141 [[maybe_unused]] const LanguageContext &ctx)
142 {
143 ASSERT(string != nullptr);
144 uint32_t hashCode = string->GetHashcode();
145 os::memory::WriteLockHolder holder(tableLock_);
146 // Check string is not present before actually creating and inserting
147 for (auto it = table_.find(hashCode); it != table_.end(); it++) {
148 auto foundString = it->second;
149 if (coretypes::String::StringsAreEqual(foundString, string)) {
150 return foundString;
151 }
152 }
153 table_.insert(std::pair<uint32_t, coretypes::String *>(hashCode, string));
154 return string;
155 }
156
PreBarrierOnGet(coretypes::String * str)157 void StringTable::Table::PreBarrierOnGet(coretypes::String *str)
158 {
159 // Need pre barrier if string exists in string table, because this string can be got from the
160 // string table (like phoenix) and write to a field during concurrent phase and GC does not see it on Remark
161 ASSERT_MANAGED_CODE();
162 auto *preWrb = Thread::GetCurrent()->GetPreWrbEntrypoint();
163 if (preWrb != nullptr) {
164 reinterpret_cast<mem::ObjRefProcessFunc>(preWrb)(str);
165 }
166 }
167
GetOrInternString(const uint8_t * mutf8Data,uint32_t utf16Length,bool canBeCompressed,const LanguageContext & ctx)168 coretypes::String *StringTable::Table::GetOrInternString(const uint8_t *mutf8Data, uint32_t utf16Length,
169 bool canBeCompressed, const LanguageContext &ctx)
170 {
171 coretypes::String *result = GetString(mutf8Data, utf16Length, canBeCompressed, ctx);
172 if (result != nullptr) {
173 PreBarrierOnGet(result);
174 return result;
175 }
176
177 // Even if this string is not inserted, it should get removed during GC
178 result =
179 coretypes::String::CreateFromMUtf8(mutf8Data, utf16Length, canBeCompressed, ctx, Thread::GetCurrent()->GetVM());
180 if (UNLIKELY(result == nullptr)) {
181 return nullptr;
182 }
183 result = InternString(result, ctx);
184
185 return result;
186 }
187
GetOrInternString(const uint16_t * utf16Data,uint32_t utf16Length,const LanguageContext & ctx)188 coretypes::String *StringTable::Table::GetOrInternString(const uint16_t *utf16Data, uint32_t utf16Length,
189 const LanguageContext &ctx)
190 {
191 coretypes::String *result = GetString(utf16Data, utf16Length, ctx);
192 if (result != nullptr) {
193 PreBarrierOnGet(result);
194 return result;
195 }
196
197 // Even if this string is not inserted, it should get removed during GC
198 result = coretypes::String::CreateFromUtf16(utf16Data, utf16Length, ctx, Thread::GetCurrent()->GetVM());
199 if (UNLIKELY(result == nullptr)) {
200 return nullptr;
201 }
202
203 result = InternString(result, ctx);
204
205 return result;
206 }
207
GetOrInternString(coretypes::String * string,const LanguageContext & ctx)208 coretypes::String *StringTable::Table::GetOrInternString(coretypes::String *string, const LanguageContext &ctx)
209 {
210 coretypes::String *result = GetString(string, ctx);
211 if (result != nullptr) {
212 PreBarrierOnGet(result);
213 return result;
214 }
215 result = InternString(string, ctx);
216 return result;
217 }
218
UpdateMoved()219 bool StringTable::Table::UpdateMoved()
220 {
221 os::memory::WriteLockHolder holder(tableLock_);
222 LOG(DEBUG, GC) << "=== StringTable Update moved. BEGIN ===";
223 LOG(DEBUG, GC) << "Iterate over: " << table_.size() << " elements in string table";
224 bool updated = false;
225 for (auto it = table_.begin(), end = table_.end(); it != end;) {
226 auto *object = it->second;
227 if (object->IsForwarded()) {
228 ObjectHeader *fwdString = ark::mem::GetForwardAddress(object);
229 it->second = static_cast<coretypes::String *>(fwdString);
230 LOG(DEBUG, GC) << "StringTable: forward " << std::hex << object << " -> " << fwdString;
231 updated = true;
232 }
233 ++it;
234 }
235 LOG(DEBUG, GC) << "=== StringTable Update moved. END ===";
236 return updated;
237 }
238
239 // NOTE(alovkov): make parallel
Sweep(const GCObjectVisitor & gcObjectVisitor)240 void StringTable::Table::Sweep(const GCObjectVisitor &gcObjectVisitor)
241 {
242 os::memory::WriteLockHolder holder(tableLock_);
243 LOG(DEBUG, GC) << "=== StringTable Sweep. BEGIN ===";
244 LOG(DEBUG, GC) << "StringTable iterate over: " << table_.size() << " elements in string table";
245 for (auto it = table_.begin(), end = table_.end(); it != end;) {
246 auto *object = it->second;
247 if (object->IsForwarded()) {
248 ASSERT(gcObjectVisitor(object) != ObjectStatus::DEAD_OBJECT);
249 ObjectHeader *fwdString = ark::mem::GetForwardAddress(object);
250 it->second = static_cast<coretypes::String *>(fwdString);
251 ++it;
252 LOG(DEBUG, GC) << "StringTable: forward " << std::hex << object << " -> " << fwdString;
253 } else if (gcObjectVisitor(object) == ObjectStatus::DEAD_OBJECT) {
254 LOG(DEBUG, GC) << "StringTable: delete string " << std::hex << object
255 << ", val = " << ConvertToString(object);
256 table_.erase(it++);
257 } else {
258 ++it;
259 }
260 }
261 LOG(DEBUG, GC) << "StringTable size after sweep = " << table_.size();
262 LOG(DEBUG, GC) << "=== StringTable Sweep. END ===";
263 }
264
Size()265 size_t StringTable::Table::Size()
266 {
267 os::memory::ReadLockHolder holder(tableLock_);
268 return table_.size();
269 }
270
GetOrInternString(const uint8_t * mutf8Data,uint32_t utf16Length,bool canBeCompressed,const LanguageContext & ctx)271 coretypes::String *StringTable::InternalTable::GetOrInternString(const uint8_t *mutf8Data, uint32_t utf16Length,
272 bool canBeCompressed, const LanguageContext &ctx)
273 {
274 coretypes::String *result = GetString(mutf8Data, utf16Length, canBeCompressed, ctx);
275 if (result != nullptr) {
276 return result;
277 }
278
279 result = coretypes::String::CreateFromMUtf8(mutf8Data, utf16Length, canBeCompressed, ctx,
280 Thread::GetCurrent()->GetVM(), false);
281 if (UNLIKELY(result == nullptr)) {
282 return nullptr;
283 }
284 result = InternStringNonMovable(result, ctx);
285 return result;
286 }
287
GetOrInternString(const uint16_t * utf16Data,uint32_t utf16Length,const LanguageContext & ctx)288 coretypes::String *StringTable::InternalTable::GetOrInternString(const uint16_t *utf16Data, uint32_t utf16Length,
289 const LanguageContext &ctx)
290 {
291 coretypes::String *result = GetString(utf16Data, utf16Length, ctx);
292 if (result != nullptr) {
293 return result;
294 }
295
296 result = coretypes::String::CreateFromUtf16(utf16Data, utf16Length, ctx, Thread::GetCurrent()->GetVM(), false);
297 if (UNLIKELY(result == nullptr)) {
298 return nullptr;
299 }
300 result = InternStringNonMovable(result, ctx);
301 return result;
302 }
303
GetOrInternString(coretypes::String * string,const LanguageContext & ctx)304 coretypes::String *StringTable::InternalTable::GetOrInternString(coretypes::String *string, const LanguageContext &ctx)
305 {
306 coretypes::String *result = GetString(string, ctx);
307 if (result != nullptr) {
308 return result;
309 }
310 result = InternString(string, ctx);
311 return result;
312 }
313
GetOrInternString(const panda_file::File & pf,panda_file::File::EntityId id,const LanguageContext & ctx)314 coretypes::String *StringTable::InternalTable::GetOrInternString(const panda_file::File &pf,
315 panda_file::File::EntityId id,
316 const LanguageContext &ctx)
317 {
318 auto data = pf.GetStringData(id);
319 coretypes::String *result = GetString(data.data, data.utf16Length, data.isAscii, ctx);
320 if (result != nullptr) {
321 return result;
322 }
323 result = coretypes::String::CreateFromMUtf8(data.data, data.utf16Length, data.isAscii, ctx,
324 Thread::GetCurrent()->GetVM(), false);
325 if (UNLIKELY(result == nullptr)) {
326 return nullptr;
327 }
328
329 result = InternStringNonMovable(result, ctx);
330
331 // Update cache.
332 os::memory::WriteLockHolder lock(mapsLock_);
333 auto it = maps_.find(&pf);
334 if (it != maps_.end()) {
335 (it->second)[id] = result;
336 } else {
337 PandaUnorderedMap<panda_file::File::EntityId, coretypes::String *, EntityIdEqual> map;
338 map[id] = result;
339 maps_[&pf] = std::move(map);
340 }
341 return result;
342 }
343
GetStringFast(const panda_file::File & pf,panda_file::File::EntityId id)344 coretypes::String *StringTable::InternalTable::GetStringFast(const panda_file::File &pf, panda_file::File::EntityId id)
345 {
346 os::memory::ReadLockHolder lock(mapsLock_);
347 auto it = maps_.find(&pf);
348 if (it != maps_.end()) {
349 auto idIt = it->second.find(id);
350 if (idIt != it->second.end()) {
351 return idIt->second;
352 }
353 }
354 return nullptr;
355 }
356
VisitRoots(const StringVisitor & visitor,mem::VisitGCRootFlags flags)357 void StringTable::InternalTable::VisitRoots(const StringVisitor &visitor, mem::VisitGCRootFlags flags)
358 {
359 ASSERT(BitCount(flags & (mem::VisitGCRootFlags::ACCESS_ROOT_ALL | mem::VisitGCRootFlags::ACCESS_ROOT_ONLY_NEW)) ==
360 1);
361
362 ASSERT(BitCount(flags & (mem::VisitGCRootFlags::START_RECORDING_NEW_ROOT |
363 mem::VisitGCRootFlags::END_RECORDING_NEW_ROOT)) <= 1);
364 // need to set flags before we iterate, because concurrent allocation should be in proper table
365 if ((flags & mem::VisitGCRootFlags::START_RECORDING_NEW_ROOT) != 0) {
366 os::memory::WriteLockHolder holder(tableLock_);
367 recordNewString_ = true;
368 } else if ((flags & mem::VisitGCRootFlags::END_RECORDING_NEW_ROOT) != 0) {
369 os::memory::WriteLockHolder holder(tableLock_);
370 recordNewString_ = false;
371 }
372
373 if ((flags & mem::VisitGCRootFlags::ACCESS_ROOT_ALL) != 0) {
374 os::memory::ReadLockHolder lock(tableLock_);
375 for (const auto &v : table_) {
376 visitor(v.second);
377 }
378 } else if ((flags & mem::VisitGCRootFlags::ACCESS_ROOT_ONLY_NEW) != 0) {
379 os::memory::ReadLockHolder lock(tableLock_);
380 for (const auto str : newStringTable_) {
381 visitor(str);
382 }
383 } else {
384 LOG(FATAL, RUNTIME) << "Unknown VisitGCRootFlags: " << static_cast<uint32_t>(flags);
385 }
386 if ((flags & mem::VisitGCRootFlags::END_RECORDING_NEW_ROOT) != 0) {
387 os::memory::WriteLockHolder holder(tableLock_);
388 newStringTable_.clear();
389 }
390 }
391
InternStringNonMovable(coretypes::String * string,const LanguageContext & ctx)392 coretypes::String *StringTable::InternalTable::InternStringNonMovable(coretypes::String *string,
393 const LanguageContext &ctx)
394 {
395 auto *result = InternString(string, ctx);
396 os::memory::WriteLockHolder holder(tableLock_);
397 if (recordNewString_) {
398 newStringTable_.push_back(result);
399 }
400 return result;
401 }
402
403 } // namespace ark
404