// Copyright 2022 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. #if !V8_ENABLE_WEBASSEMBLY #error This header should only be included if WebAssembly is enabled. #endif // !V8_ENABLE_WEBASSEMBLY #ifndef V8_WASM_STRING_BUILDER_H_ #define V8_WASM_STRING_BUILDER_H_ #include <cstring> #include <string> #include <vector> #include "src/common/globals.h" namespace v8 { namespace internal { namespace wasm { // Similar to std::ostringstream, but about 4x faster. // This base class works best for small-ish strings (up to kChunkSize); for // producing large amounts of text, you probably want a subclass like // MultiLineStringBuilder. class StringBuilder { public: StringBuilder() : on_growth_(kReplacePreviousChunk) {} explicit StringBuilder(const StringBuilder&) = delete; StringBuilder& operator=(const StringBuilder&) = delete; ~StringBuilder() { for (char* chunk : chunks_) delete[] chunk; if (on_growth_ == kReplacePreviousChunk && start_ != stack_buffer_) { delete[] start_; } } // Reserves space for {n} characters and returns a pointer to its beginning. // Clients *must* write all {n} characters after calling this! // Don't call this directly, use operator<< overloads instead. char* allocate(size_t n) { if (remaining_bytes_ < n) Grow(n); char* result = cursor_; cursor_ += n; remaining_bytes_ -= n; return result; } // Convenience wrappers. void write(const byte* data, size_t n) { char* ptr = allocate(n); memcpy(ptr, data, n); } void write(const char* data, size_t n) { char* ptr = allocate(n); memcpy(ptr, data, n); } const char* start() const { return start_; } const char* cursor() const { return cursor_; } size_t length() const { return static_cast<size_t>(cursor_ - start_); } void rewind_to_start() { remaining_bytes_ += length(); cursor_ = start_; } protected: enum OnGrowth : bool { kKeepOldChunks, kReplacePreviousChunk }; // Useful for subclasses that divide the text into ranges, e.g. lines. explicit StringBuilder(OnGrowth on_growth) : on_growth_(on_growth) {} void start_here() { start_ = cursor_; } private: void Grow(size_t requested) { size_t used = length(); size_t required = used + requested; size_t chunk_size; if (on_growth_ == kKeepOldChunks) { // Usually grow by kChunkSize, unless super-long lines need even more. chunk_size = required < kChunkSize ? kChunkSize : required * 2; } else { // When we only have one chunk, always (at least) double its size // when it grows, to minimize both wasted memory and growth effort. chunk_size = required * 2; } char* new_chunk = new char[chunk_size]; memcpy(new_chunk, start_, used); if (on_growth_ == kKeepOldChunks) { chunks_.push_back(new_chunk); } else if (start_ != stack_buffer_) { delete[] start_; } start_ = new_chunk; cursor_ = new_chunk + used; remaining_bytes_ = chunk_size - used; } // Start small, to be cheap for the common case. static constexpr size_t kStackSize = 256; // If we have to grow, grow in big steps. static constexpr size_t kChunkSize = 1024 * 1024; char stack_buffer_[kStackSize]; std::vector<char*> chunks_; // A very simple Zone, essentially. char* start_ = stack_buffer_; char* cursor_ = stack_buffer_; size_t remaining_bytes_ = kStackSize; const OnGrowth on_growth_; }; inline StringBuilder& operator<<(StringBuilder& sb, const char* str) { size_t len = strlen(str); char* ptr = sb.allocate(len); memcpy(ptr, str, len); return sb; } inline StringBuilder& operator<<(StringBuilder& sb, char c) { *sb.allocate(1) = c; return sb; } inline StringBuilder& operator<<(StringBuilder& sb, const std::string& s) { sb.write(s.data(), s.length()); return sb; } inline StringBuilder& operator<<(StringBuilder& sb, uint32_t n) { if (n == 0) { *sb.allocate(1) = '0'; return sb; } static constexpr size_t kBufferSize = 10; // Just enough for a uint32. char buffer[kBufferSize]; char* end = buffer + kBufferSize; char* out = end; while (n != 0) { *(--out) = '0' + (n % 10); n /= 10; } sb.write(out, static_cast<size_t>(end - out)); return sb; } inline StringBuilder& operator<<(StringBuilder& sb, int value) { if (value >= 0) { sb << static_cast<uint32_t>(value); } else { sb << "-" << ((~static_cast<uint32_t>(value)) + 1); } return sb; } } // namespace wasm } // namespace internal } // namespace v8 #endif // V8_WASM_STRING_BUILDER_H_