// Copyright 2017 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef V8_TORQUE_UTILS_H_ #define V8_TORQUE_UTILS_H_ #include #include #include #include #include #include #include "src/base/functional.h" #include "src/base/optional.h" #include "src/torque/contextual.h" #include "src/torque/source-positions.h" namespace v8 { namespace internal { namespace torque { std::string StringLiteralUnquote(const std::string& s); std::string StringLiteralQuote(const std::string& s); // Decodes "file://" URIs into file paths which can then be used // with the standard stream API. V8_EXPORT_PRIVATE base::Optional FileUriDecode( const std::string& s); struct TorqueMessage { enum class Kind { kError, kLint }; std::string message; base::Optional position; Kind kind; }; DECLARE_CONTEXTUAL_VARIABLE(TorqueMessages, std::vector); template std::string ToString(Args&&... args) { std::stringstream stream; USE((stream << std::forward(args))...); return stream.str(); } class V8_EXPORT_PRIVATE MessageBuilder { public: MessageBuilder() = delete; MessageBuilder(const std::string& message, TorqueMessage::Kind kind); MessageBuilder& Position(SourcePosition position) { message_.position = position; return *this; } [[noreturn]] void Throw() const; ~MessageBuilder() { // This will also get called in case the error is thrown. Report(); } private: void Report() const; TorqueMessage message_; std::vector extra_messages_; }; // Used for throwing exceptions. Retrieve TorqueMessage from the contextual // for specific error information. struct TorqueAbortCompilation {}; template static MessageBuilder Message(TorqueMessage::Kind kind, Args&&... args) { return MessageBuilder(ToString(std::forward(args)...), kind); } template MessageBuilder Error(Args&&... args) { return Message(TorqueMessage::Kind::kError, std::forward(args)...); } template MessageBuilder Lint(Args&&... args) { return Message(TorqueMessage::Kind::kLint, std::forward(args)...); } bool IsLowerCamelCase(const std::string& s); bool IsUpperCamelCase(const std::string& s); bool IsSnakeCase(const std::string& s); bool IsValidNamespaceConstName(const std::string& s); bool IsValidTypeName(const std::string& s); template [[noreturn]] void ReportError(Args&&... args) { Error(std::forward(args)...).Throw(); } std::string CapifyStringWithUnderscores(const std::string& camellified_string); std::string CamelifyString(const std::string& underscore_string); std::string SnakeifyString(const std::string& camel_string); std::string DashifyString(const std::string& underscore_string); std::string UnderlinifyPath(std::string path); bool StartsWithSingleUnderscore(const std::string& str); void ReplaceFileContentsIfDifferent(const std::string& file_path, const std::string& contents); std::string CurrentPositionAsString(); template class Deduplicator { public: const T* Add(T x) { return &*(storage_.insert(std::move(x)).first); } private: std::unordered_set> storage_; }; template T& DereferenceIfPointer(T* x) { return *x; } template T&& DereferenceIfPointer(T&& x) { return std::forward(x); } template struct ListPrintAdaptor { const T& list; const std::string& separator; L transformer; friend std::ostream& operator<<(std::ostream& os, const ListPrintAdaptor& l) { bool first = true; for (auto& e : l.list) { if (first) { first = false; } else { os << l.separator; } os << DereferenceIfPointer(l.transformer(e)); } return os; } }; template auto PrintList(const T& list, const std::string& separator = ", ") { using ElementType = decltype(*list.begin()); auto id = [](ElementType el) { return el; }; return ListPrintAdaptor{list, separator, id}; } template auto PrintList(const T& list, const std::string& separator, L&& transformer) { return ListPrintAdaptor{list, separator, std::forward(transformer)}; } template void PrintCommaSeparatedList(std::ostream& os, const T& list, C&& transform) { os << PrintList(list, ", ", std::forward(transform)); } template void PrintCommaSeparatedList(std::ostream& os, const T& list) { os << PrintList(list, ", "); } struct BottomOffset { size_t offset; BottomOffset& operator=(std::size_t offset) { this->offset = offset; return *this; } BottomOffset& operator++() { ++offset; return *this; } BottomOffset operator+(size_t x) const { return BottomOffset{offset + x}; } BottomOffset operator-(size_t x) const { DCHECK_LE(x, offset); return BottomOffset{offset - x}; } bool operator<(const BottomOffset& other) const { return offset < other.offset; } bool operator<=(const BottomOffset& other) const { return offset <= other.offset; } bool operator==(const BottomOffset& other) const { return offset == other.offset; } bool operator!=(const BottomOffset& other) const { return offset != other.offset; } }; inline std::ostream& operator<<(std::ostream& out, BottomOffset from_bottom) { return out << "BottomOffset{" << from_bottom.offset << "}"; } // An iterator-style range of stack slots. class StackRange { public: StackRange(BottomOffset begin, BottomOffset end) : begin_(begin), end_(end) { DCHECK_LE(begin_, end_); } bool operator==(const StackRange& other) const { return begin_ == other.begin_ && end_ == other.end_; } void Extend(StackRange adjacent) { DCHECK_EQ(end_, adjacent.begin_); end_ = adjacent.end_; } size_t Size() const { return end_.offset - begin_.offset; } BottomOffset begin() const { return begin_; } BottomOffset end() const { return end_; } private: BottomOffset begin_; BottomOffset end_; }; inline std::ostream& operator<<(std::ostream& out, StackRange range) { return out << "StackRange{" << range.begin() << ", " << range.end() << "}"; } template class Stack { public: using value_type = T; Stack() = default; Stack(std::initializer_list initializer) : Stack(std::vector(initializer)) {} explicit Stack(std::vector v) : elements_(std::move(v)) {} size_t Size() const { return elements_.size(); } const T& Peek(BottomOffset from_bottom) const { return elements_.at(from_bottom.offset); } void Poke(BottomOffset from_bottom, T x) { elements_.at(from_bottom.offset) = std::move(x); } void Push(T x) { elements_.push_back(std::move(x)); } StackRange TopRange(size_t slot_count) const { DCHECK_GE(Size(), slot_count); return StackRange{AboveTop() - slot_count, AboveTop()}; } StackRange PushMany(const std::vector& v) { for (const T& x : v) { Push(x); } return TopRange(v.size()); } const T& Top() const { return Peek(AboveTop() - 1); } T Pop() { T result = std::move(elements_.back()); elements_.pop_back(); return result; } std::vector PopMany(size_t count) { DCHECK_GE(elements_.size(), count); std::vector result; result.reserve(count); for (auto it = elements_.end() - count; it != elements_.end(); ++it) { result.push_back(std::move(*it)); } elements_.resize(elements_.size() - count); return result; } // The invalid offset above the top element. This is useful for StackRange. BottomOffset AboveTop() const { return BottomOffset{Size()}; } // Delete the slots in {range}, moving higher slots to fill the gap. void DeleteRange(StackRange range) { DCHECK_LE(range.end(), AboveTop()); if (range.Size() == 0) return; for (BottomOffset i = range.end(); i < AboveTop(); ++i) { elements_[i.offset - range.Size()] = std::move(elements_[i.offset]); } elements_.resize(elements_.size() - range.Size()); } bool operator==(const Stack& other) const { return elements_ == other.elements_; } bool operator!=(const Stack& other) const { return elements_ != other.elements_; } T* begin() { return elements_.data(); } T* end() { return begin() + elements_.size(); } const T* begin() const { return elements_.data(); } const T* end() const { return begin() + elements_.size(); } private: std::vector elements_; }; template T* CheckNotNull(T* x) { CHECK_NOT_NULL(x); return x; } template inline std::ostream& operator<<(std::ostream& os, const Stack& t) { os << "Stack{"; PrintCommaSeparatedList(os, t); os << "}"; return os; } static const char* const kBaseNamespaceName = "base"; static const char* const kTestNamespaceName = "test"; // Erase elements of a container that has a constant-time erase function, like // std::set or std::list. Calling this on std::vector would have quadratic // complexity. template void EraseIf(Container* container, F f) { for (auto it = container->begin(); it != container->end();) { if (f(*it)) { it = container->erase(it); } else { ++it; } } } class NullStreambuf : public std::streambuf { public: int overflow(int c) override { setp(buffer_, buffer_ + sizeof(buffer_)); return (c == traits_type::eof()) ? '\0' : c; } private: char buffer_[64]; }; class NullOStream : public std::ostream { public: NullOStream() : std::ostream(&buffer_) {} private: NullStreambuf buffer_; }; inline bool StringStartsWith(const std::string& s, const std::string& prefix) { if (s.size() < prefix.size()) return false; return s.substr(0, prefix.size()) == prefix; } inline bool StringEndsWith(const std::string& s, const std::string& suffix) { if (s.size() < suffix.size()) return false; return s.substr(s.size() - suffix.size()) == suffix; } class V8_NODISCARD IfDefScope { public: IfDefScope(std::ostream& os, std::string d); ~IfDefScope(); IfDefScope(const IfDefScope&) = delete; IfDefScope& operator=(const IfDefScope&) = delete; private: std::ostream& os_; std::string d_; }; class V8_NODISCARD NamespaceScope { public: NamespaceScope(std::ostream& os, std::initializer_list namespaces); ~NamespaceScope(); NamespaceScope(const NamespaceScope&) = delete; NamespaceScope& operator=(const NamespaceScope&) = delete; private: std::ostream& os_; std::vector d_; }; class V8_NODISCARD IncludeGuardScope { public: IncludeGuardScope(std::ostream& os, std::string file_name); ~IncludeGuardScope(); IncludeGuardScope(const IncludeGuardScope&) = delete; IncludeGuardScope& operator=(const IncludeGuardScope&) = delete; private: std::ostream& os_; std::string d_; }; class V8_NODISCARD IncludeObjectMacrosScope { public: explicit IncludeObjectMacrosScope(std::ostream& os); ~IncludeObjectMacrosScope(); IncludeObjectMacrosScope(const IncludeObjectMacrosScope&) = delete; IncludeObjectMacrosScope& operator=(const IncludeObjectMacrosScope&) = delete; private: std::ostream& os_; }; // A value of ResidueClass is a congruence class of integers modulo a power // of 2. // In contrast to common modulo arithmetic, we also allow addition and // multiplication of congruence classes with different modulus. In this case, we // do an abstract-interpretation style approximation to produce an as small as // possible congruence class. ResidueClass is used to represent partial // knowledge about offsets and sizes to validate alignment constraints. // ResidueClass(x,m) = {y \in Z | x == y mod 2^m} = {x+k2^m | k \in Z} where Z // is the set of all integers. // Notation: 2^x is 2 to the power of x. class ResidueClass { public: ResidueClass(size_t value, size_t modulus_log_2 = kMaxModulusLog2) // NOLINT(runtime/explicit) : value_(value), modulus_log_2_(std::min(modulus_log_2, kMaxModulusLog2)) { if (modulus_log_2_ < kMaxModulusLog2) { value_ %= size_t{1} << modulus_log_2_; } } // 0 modulo 1, in other words, the class of all integers. static ResidueClass Unknown() { return ResidueClass{0, 0}; } // If the modulus corresponds to the size of size_t, it represents a concrete // value. base::Optional SingleValue() const { if (modulus_log_2_ == kMaxModulusLog2) return value_; return base::nullopt; } friend ResidueClass operator+(const ResidueClass& a, const ResidueClass& b) { return ResidueClass{a.value_ + b.value_, std::min(a.modulus_log_2_, b.modulus_log_2_)}; } // Reasoning for the choice of the new modulus: // {x+k2^a | k \in Z} * {y+l2^b | l \in Z} // = {xy + xl2^b + yk2^a + kl2^(a+b)| k,l \in Z}, // which is a subset of {xy + k2^c | k \in Z} // if 2^c is a common divisor of x2^b, y2^a and hence also of 2^(a+b) since // x<2^a and y<2^b. // So we use the gcd of x2^b and y2^a as the new modulus. friend ResidueClass operator*(const ResidueClass& a, const ResidueClass& b) { return ResidueClass{a.value_ * b.value_, std::min(a.modulus_log_2_ + b.AlignmentLog2(), b.modulus_log_2_ + a.AlignmentLog2())}; } friend std::ostream& operator<<(std::ostream& os, const ResidueClass& a); ResidueClass& operator+=(const ResidueClass& other) { *this = *this + other; return *this; } ResidueClass& operator*=(const ResidueClass& other) { *this = *this * other; return *this; } // 2^AlignmentLog2() is the larget power of 2 that divides all elements of the // congruence class. size_t AlignmentLog2() const; size_t Alignment() const { DCHECK_LT(AlignmentLog2(), kMaxModulusLog2); return size_t{1} << AlignmentLog2(); } private: // The value is the representative of the congruence class. It's always // smaller than 2^modulus_log_2_. size_t value_; // Base 2 logarithm of the modulus. size_t modulus_log_2_; // size_t values are modulo 2^kMaxModulusLog2, so we don't consider larger // modulus. static const size_t kMaxModulusLog2 = 8 * sizeof(size_t); }; template class Worklist { public: bool IsEmpty() const { DCHECK_EQ(queue_.size(), contained_.size()); return queue_.empty(); } bool Enqueue(T value) { if (contained_.find(value) != contained_.end()) return false; queue_.push(value); contained_.insert(value); DCHECK_EQ(queue_.size(), contained_.size()); return true; } T Dequeue() { DCHECK(!IsEmpty()); T value = queue_.front(); queue_.pop(); contained_.erase(value); DCHECK_EQ(queue_.size(), contained_.size()); return value; } private: std::queue queue_; std::unordered_set contained_; }; template std::vector TransformVector(const std::vector& v, F f) { std::vector result; std::transform(v.begin(), v.end(), std::back_inserter(result), f); return result; } template std::vector TransformVector(const std::vector& v) { return TransformVector(v, [](const U& x) -> T { return x; }); } } // namespace torque } // namespace internal } // namespace v8 #endif // V8_TORQUE_UTILS_H_