| /* |
| * Copyright 2022 WebAssembly Community Group participants |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <limits> |
| #include <mutex> |
| |
| #include "istring.h" |
| #include "mixed_arena.h" |
| |
| namespace wasm { |
| |
| const char* IString::interned(std::string_view s) { |
| // A set of interned Views, i.e., that contains our pascal-style strings. We |
| // need to query this using a std::string_view, as that is what we receive as |
| // input (turning it into pascal-style storage would add overhead). To do so, |
| // use overloading in the hash and equality functions (which works thanks to |
| // `is_transparent`). |
| struct InternedHash { |
| using is_transparent = void; |
| size_t operator()(View v) const { |
| return std::hash<std::string_view>{}(v.view()); |
| } |
| size_t operator()(std::string_view sv) const { |
| return std::hash<std::string_view>{}(sv); |
| } |
| }; |
| struct InternedEqual { |
| using is_transparent = void; |
| bool operator()(View a, View b) const { return a.view() == b.view(); } |
| bool operator()(std::string_view a, View b) const { return a == b.view(); } |
| bool operator()(View a, std::string_view b) const { return a.view() == b; } |
| bool operator()(std::string_view a, std::string_view b) const { |
| return a == b; |
| } |
| }; |
| using StringSet = std::unordered_set<View, InternedHash, InternedEqual>; |
| |
| // The authoritative global set of interned string views. |
| static StringSet globalStrings; |
| |
| // The global backing store for interned strings that do not otherwise have |
| // stable addresses. |
| static MixedArena arena; |
| |
| // Guards access to `globalStrings`. (note: `arena` is thread-safe anyhow) |
| static std::mutex mutex; |
| |
| // A thread-local cache of strings to reduce contention. |
| thread_local static StringSet localStrings; |
| |
| if (auto it = localStrings.find(s); it != localStrings.end()) { |
| // We already had a local copy of this string. |
| return it->internal; |
| } |
| |
| // No copy yet in the local cache. Check the global cache. |
| std::unique_lock<std::mutex> lock(mutex); |
| if (auto it = globalStrings.find(s); it != globalStrings.end()) { |
| // We already had a global copy of this string. Cache it locally. |
| localStrings.insert(*it); |
| return it->internal; |
| } |
| |
| // We have a new string. Create a copy of the data at a stable address with a |
| // header we can use. Make sure it is null terminated so legacy uses that get |
| // a C string still work. |
| size_t size = s.size(); |
| // The string's size must fit in 32 bits. |
| assert(size <= std::numeric_limits<uint32_t>::max()); |
| char* buffer = |
| (char*)arena.allocSpace(sizeof(uint32_t) + size + 1, alignof(uint32_t)); |
| *(uint32_t*)(buffer) = size; |
| char* data = buffer + sizeof(uint32_t); |
| std::copy(s.begin(), s.end(), data); |
| data[size] = '\0'; |
| |
| // Intern our new string. |
| View v{data}; |
| globalStrings.insert(v); |
| localStrings.insert(v); |
| return data; |
| } |
| |
| } // namespace wasm |