| /* | |
| * 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. | |
| */ | |
| // Interned String type, 100% interned on creation. Comparisons are always just | |
| // a pointer comparison | |
| #ifndef wasm_support_istring_h | |
| #define wasm_support_istring_h | |
| #include <set> | |
| #include <string_view> | |
| #include <unordered_set> | |
| #include <assert.h> | |
| #include "threads.h" | |
| #include "utilities.h" | |
| namespace wasm { | |
| struct BYN_WARN_UNUSED IString { | |
| private: | |
| static const char* interned(std::string_view s); | |
| public: | |
| // Strings are stored in Pascal style: a size followed by the characters. We | |
| // keep the internal pointer pointing to the data, so that data() is a no-op; | |
| // computing the size, which is more rare, requires looking back and doing a | |
| // load. | |
| // | |
| // The size is limited to 4 bytes, so the maximum string we support is 4GB. | |
| // | |
| // The alternative approach of using a string_view here, i.e., keeping the | |
| // pointer and size in the IString, uses more more memory. That is, this | |
| // optimization saves a lot of space, because while it adds 4 bytes to each | |
| // interned string itself, we tend to have many views on each. | |
| // | |
| // We provide a View here, which is a simple interface. Users that need more | |
| // convert to a std::string_view with .view() or a cast. | |
| struct View { | |
| const char* internal = nullptr; | |
| const char* data() const { return internal; } | |
| size_t size() const { | |
| return internal ? *(const uint32_t*)(internal - 4) : 0; | |
| } | |
| char operator[](size_t x) const { return internal[x]; } | |
| std::string_view view() const { | |
| if (!internal) { | |
| // No size to read. | |
| return {}; | |
| } | |
| return {internal, size()}; | |
| } | |
| }; | |
| const View str; | |
| std::string_view view() const { return str.view(); } | |
| // Use an explicit constructor instead of `= default` because some older | |
| // compilers (e.g. Apple Clang in older Xcode versions) delete the default | |
| // constructor if there is a const member without an in-class initializer, | |
| // even if that member's type has a default constructor. | |
| // FIXME: Use `= default` once we bump the min clang/Xcode version that | |
| // we support. | |
| IString() : str({nullptr}) {} | |
| IString(View v) : str(v) {} | |
| IString(std::string_view s) : str{interned(s)} {} | |
| IString(const char* str) : str{interned(str)} {} | |
| IString(const std::string& str) : str{interned(str)} {} | |
| IString(const IString& other) = default; | |
| IString& operator=(const IString& other) { | |
| return *(new (this) IString(other)); | |
| } | |
| bool operator==(const IString& other) const { | |
| // Fast! No need to compare contents due to interning | |
| return str.internal == other.str.internal; | |
| } | |
| bool operator!=(const IString& other) const { return !(*this == other); } | |
| bool operator<(const IString& other) const { | |
| if (str.internal == other.str.internal) { | |
| return false; | |
| } | |
| return view() < other.view(); | |
| } | |
| bool operator<=(const IString& other) const { | |
| return *this == other || *this < other; | |
| } | |
| bool operator>(const IString& other) const { return !(*this <= other); } | |
| bool operator>=(const IString& other) const { return !(*this < other); } | |
| char operator[](int x) const { return str[x]; } | |
| explicit operator bool() const { return str.internal != nullptr; } | |
| // TODO: deprecate? | |
| bool is() const { return bool(*this); } | |
| bool isNull() const { return !bool(*this); } | |
| std::string toString() const { return {str.data(), str.size()}; } | |
| bool equals(std::string_view other) const { return str.view() == other; } | |
| bool startsWith(std::string_view prefix) const { | |
| return view().starts_with(prefix); | |
| } | |
| bool startsWith(IString other) const { return startsWith(other.view()); } | |
| // Disambiguate for string literals. | |
| template<int N> bool startsWith(const char (&str)[N]) const { | |
| return startsWith(std::string_view(str)); | |
| } | |
| bool endsWith(std::string_view suffix) const { | |
| return view().ends_with(suffix); | |
| } | |
| bool endsWith(IString other) const { return endsWith(other.view()); } | |
| // Disambiguate for string literals. | |
| template<int N> bool endsWith(const char (&str)[N]) const { | |
| return endsWith(std::string_view(str)); | |
| } | |
| IString substr(size_t pos, size_t len = std::string_view::npos) const { | |
| return IString(view().substr(pos, len)); | |
| } | |
| size_t size() const { return str.size(); } | |
| }; | |
| } // namespace wasm | |
| namespace std { | |
| template<> struct hash<wasm::IString> { | |
| size_t operator()(const wasm::IString& str) const { | |
| return std::hash<size_t>{}(uintptr_t(str.str.data())); | |
| } | |
| }; | |
| inline std::ostream& operator<<(std::ostream& os, const wasm::IString& str) { | |
| return os << str.view(); | |
| } | |
| } // namespace std | |
| #endif // wasm_support_istring_h |