-
Notifications
You must be signed in to change notification settings - Fork 303
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
196 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
// Copyright (c) 2023 Cloudflare, Inc. | ||
// Licensed under the Apache 2.0 license found in the LICENSE file or at: | ||
// https://opensource.org/licenses/Apache-2.0 | ||
|
||
#include "workerd/util/string-buffer.h" | ||
#include <kj/test.h> | ||
|
||
namespace workerd { | ||
namespace { | ||
|
||
KJ_TEST("append StringPtr") { | ||
StringBuffer<100> buffer(100); | ||
buffer.append(kj::StringPtr("abcdef")); | ||
KJ_EXPECT("abcdef"_kj == buffer.toString()); | ||
} | ||
|
||
KJ_TEST("append String") { | ||
StringBuffer<100> buffer(100); | ||
auto str = kj::heapString("abc"_kj); | ||
buffer.append(str); | ||
KJ_EXPECT("abc"_kj == buffer.toString()); | ||
} | ||
|
||
KJ_TEST("append char array") { | ||
StringBuffer<100> buffer(100); | ||
auto str = kj::heapString("abc"); | ||
buffer.append(str); | ||
KJ_EXPECT("abc"_kj == buffer.toString()); | ||
} | ||
|
||
KJ_TEST("overflow") { | ||
StringBuffer<10> buffer(11); | ||
|
||
for (auto i = 0; i < 100; i++) { | ||
// 3 character will test all sorts of boundary conditions | ||
// with 11-bytes heap chunks. | ||
buffer.append("abc"); | ||
} | ||
KJ_EXPECT(buffer.toString().size() == 300); | ||
KJ_EXPECT( | ||
"abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc" | ||
"abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc" | ||
"abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc" | ||
"abcabcabcabcabcabcabc"_kj == buffer.toString()); | ||
} | ||
|
||
} // namespace | ||
} // namespace workerd |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
// Copyright (c) 2023 Cloudflare, Inc. | ||
// Licensed under the Apache 2.0 license found in the LICENSE file or at: | ||
// https://opensource.org/licenses/Apache-2.0 | ||
#pragma once | ||
|
||
#include <algorithm> | ||
#include <cstring> | ||
#include <list> | ||
#include <kj/string.h> | ||
|
||
namespace workerd { | ||
|
||
template<size_t StackSize> | ||
class StringBuffer { | ||
// String buffer optimized for appending a lot of strings together. | ||
// Allocates StackSize chunk on the stack and uses that until full. | ||
// Keeps allocating new chunks of at least HeapChunkSize as needed. | ||
// Doesn't perform any heap allocations if string stays within | ||
// StackSize bytes (without \0) | ||
|
||
public: | ||
KJ_DISALLOW_COPY_AND_MOVE(StringBuffer); | ||
|
||
explicit StringBuffer(size_t heapChunkSize): heapChunkSize(heapChunkSize), tail(&arr[0]), cap(StackSize) {} | ||
|
||
void append() {} | ||
|
||
template <typename First, typename... Rest> | ||
void append(First&& first, Rest&&...rest) { | ||
appendImpl(kj::fwd<First>(first)); | ||
append(kj::fwd<Rest>(rest)...); | ||
} | ||
|
||
kj::String toString() { | ||
auto result = kj::heapString(len); | ||
copyTo(result.begin()); | ||
return result; | ||
} | ||
|
||
private: | ||
const size_t heapChunkSize; | ||
// minimum heap chunk size | ||
|
||
char arr[StackSize]; | ||
// chunk on the stack | ||
|
||
std::list<kj::Array<char>> chunks; | ||
// on the heap chunks | ||
|
||
char *tail; | ||
// points after the last used bytes in current chunk | ||
|
||
size_t cap; | ||
// number of bytes available in current chunk | ||
|
||
size_t len = 0; | ||
// total length of the data appended so far | ||
|
||
void appendImpl(const char* ptr, size_t size) { | ||
size_t toCopy = std::min(size, cap); | ||
memcpy(tail, ptr, toCopy); | ||
tail += toCopy; | ||
cap -= toCopy; | ||
|
||
if (toCopy != size) { | ||
// prepare new chunk | ||
size_t remaining = size - toCopy; | ||
size_t chunkSize = std::max(remaining, heapChunkSize); // don't chunk large strings | ||
auto chunk = kj::heapArray<char>(chunkSize); | ||
|
||
// copy the rest of the string to the new chunk | ||
memcpy(chunk.begin(), ptr + toCopy, remaining); | ||
tail = chunk.begin() + remaining; | ||
cap = chunk.size() - remaining; | ||
|
||
chunks.push_back(kj::mv(chunk)); | ||
} | ||
|
||
len += size; | ||
} | ||
|
||
void appendImpl(const kj::StringPtr& str) { | ||
appendImpl(str.begin(), str.size()); | ||
} | ||
|
||
template<size_t size> | ||
void appendImpl(const char (&arr)[size]) { | ||
appendImpl(arr, size - 1 /* assume 0-terminated strings */); | ||
} | ||
|
||
inline void appendImpl(const kj::ArrayPtr<const char>& arr) { | ||
appendImpl(arr.begin(), arr.size()); | ||
} | ||
|
||
inline void appendImpl(const kj::String& str) { | ||
appendImpl(str.asPtr()); | ||
} | ||
|
||
void copyTo(char* dest) { | ||
// copy stack portion first | ||
size_t onStack = std::min(len, StackSize); | ||
memcpy(dest, arr, onStack); | ||
dest += onStack; | ||
|
||
// copy from heap chunks | ||
if (onStack < len) { | ||
size_t remaining = len - onStack; | ||
for (auto& chunk: chunks) { | ||
size_t inChunk = std::min(remaining, chunk.size()); // last chunk won't be full | ||
memcpy(dest, chunk.begin(), inChunk); | ||
dest += inChunk; | ||
remaining -= inChunk; | ||
} | ||
|
||
KJ_IREQUIRE(remaining == 0); | ||
} | ||
} | ||
}; | ||
|
||
} // namespace workerd |