Skip to content

Commit e322329

Browse files
committed
Add Vec::from_raw_parts() and into_raw_parts().
1 parent 02ec2fc commit e322329

File tree

2 files changed

+129
-52
lines changed

2 files changed

+129
-52
lines changed

subspace/containers/vec.h

Lines changed: 80 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,47 @@ class Vec {
6464

6565
public:
6666
// sus::construct::Default trait.
67-
inline constexpr Vec() noexcept : Vec(kDefault) {}
67+
inline constexpr Vec() noexcept : Vec(nullptr, 0_usize, 0_usize) {}
6868

69-
static inline Vec with_capacity(usize cap) noexcept {
70-
return Vec(kWithCap, cap);
69+
/// Creates a Vec<T> with at least the specified capacity.
70+
///
71+
/// The vector will be able to hold at least `capacity` elements without
72+
/// reallocating. This method is allowed to allocate for more elements than
73+
/// capacity. If capacity is 0, the vector will not allocate.
74+
///
75+
/// It is important to note that although the returned vector has the minimum
76+
/// capacity specified, the vector will have a zero length.
77+
///
78+
/// # Panics
79+
/// Panics if the capacity exceeds `isize::MAX` bytes.
80+
static inline Vec with_capacity(usize capacity) noexcept {
81+
check(::sus::mem::size_of<T>() * capacity <= usize(size_t{PTRDIFF_MAX}));
82+
auto v = Vec(nullptr, 0_usize, 0_usize);
83+
// TODO: Consider rounding up to nearest 2^N for some N? A min capacity?
84+
v.grow_to_exact(capacity);
85+
return v;
86+
}
87+
88+
/// Creates a Vec<T> directly from a pointer, a capacity, and a length.
89+
///
90+
/// # Safety
91+
///
92+
/// This is highly unsafe, due to the number of invariants that aren’t
93+
/// checked:
94+
///
95+
/// * `T` needs to have an alignment no more than what `ptr` was allocated
96+
/// with.
97+
/// * The size of `T` times the `capacity` (ie. the allocated size in bytes)
98+
/// needs to be the same size the pointer was allocated with.
99+
/// * `length` needs to be less than or equal to `capacity`.
100+
/// * The first `length` values must be properly initialized values of type
101+
/// `T`.
102+
/// * The allocated size in bytes must be no larger than `isize::MAX`.
103+
/// * If `ptr` is null, then `length` and `capacity` must be `0_usize`, and
104+
/// vice versa.
105+
static Vec from_raw_parts(::sus::marker::UnsafeFnMarker, T* ptr, usize length,
106+
usize capacity) noexcept {
107+
return Vec(ptr, length, capacity);
71108
}
72109

73110
/// Constructs a vector by taking all the elements from the iterator.
@@ -88,17 +125,17 @@ class Vec {
88125
}
89126

90127
Vec(Vec&& o) noexcept
91-
: storage_(::sus::mem::replace_ptr(mref(o.storage_), moved_from_value())),
92-
len_(::sus::mem::replace(mref(o.len_), 0_usize)),
93-
capacity_(::sus::mem::replace(mref(o.capacity_), 0_usize)) {
128+
: storage_(::sus::mem::replace_ptr(mref(o.storage_), nullptr)),
129+
len_(::sus::mem::replace(mref(o.len_), kMovedFromLen)),
130+
capacity_(::sus::mem::replace(mref(o.capacity_), kMovedFromCapacity)) {
94131
check(!is_moved_from());
95132
}
96133
Vec& operator=(Vec&& o) noexcept {
97134
check(!o.is_moved_from());
98135
if (is_alloced()) free_storage();
99-
storage_ = ::sus::mem::replace_ptr(mref(o.storage_), moved_from_value());
100-
len_ = ::sus::mem::replace(mref(o.len_), 0_usize);
101-
capacity_ = ::sus::mem::replace(mref(o.capacity_), 0_usize);
136+
storage_ = ::sus::mem::replace_ptr(mref(o.storage_), nullptr);
137+
len_ = ::sus::mem::replace(mref(o.len_), kMovedFromLen);
138+
capacity_ = ::sus::mem::replace(mref(o.capacity_), kMovedFromCapacity);
102139
return *this;
103140
}
104141

@@ -145,6 +182,25 @@ class Vec {
145182
}
146183
}
147184

185+
/// Decomposes a `Vec<T>` into its raw components.
186+
///
187+
/// Returns the raw pointer to the underlying data, the length of the vector
188+
/// (in elements), and the allocated capacity of the data (in elements). These
189+
/// are the same arguments in the same order as the arguments to
190+
/// `from_raw_parts()`.
191+
///
192+
/// After calling this function, the caller is responsible for the memory
193+
/// previously managed by the `Vec`. The only way to do this is to convert the
194+
/// raw pointer, length, and capacity back into a `Vec` with the
195+
/// `from_raw_parts()` function, allowing the destructor to perform the
196+
/// cleanup.
197+
::sus::Tuple<T*, usize, usize> into_raw_parts() && noexcept {
198+
check(!is_moved_from());
199+
return sus::tuple(::sus::mem::replace_ptr(mref(storage_), nullptr),
200+
::sus::mem::replace(mref(len_), kMovedFromLen),
201+
::sus::mem::replace(mref(capacity_), kMovedFromCapacity));
202+
}
203+
148204
/// Clears the vector, removing all values.
149205
///
150206
/// Note that this method has no effect on the allocated capacity of the
@@ -169,6 +225,7 @@ class Vec {
169225
void reserve(usize additional) noexcept {
170226
check(!is_moved_from());
171227
if (len_ + additional <= capacity_) return; // Nothing to do.
228+
// TODO: Consider rounding up to nearest 2^N for some N?
172229
grow_to_exact(apply_growth_function(additional));
173230
}
174231

@@ -206,16 +263,16 @@ class Vec {
206263
const auto bytes = ::sus::mem::size_of<T>() * cap;
207264
check(bytes <= usize(size_t{PTRDIFF_MAX}));
208265
if (!is_alloced()) {
209-
storage_ = static_cast<char*>(malloc(bytes.primitive_value));
266+
storage_ = static_cast<T*>(malloc(bytes.primitive_value));
210267
} else {
211268
if constexpr (::sus::mem::relocate_by_memcpy<T>) {
212-
storage_ = static_cast<char*>(realloc(storage_, bytes.primitive_value));
269+
storage_ = static_cast<T*>(realloc(storage_, bytes.primitive_value));
213270
} else {
214271
auto* const new_storage =
215-
static_cast<char*>(malloc(bytes.primitive_value));
272+
static_cast<T*>(malloc(bytes.primitive_value));
216273
auto* old_t = reinterpret_cast<T*>(storage_);
217274
auto* new_t = reinterpret_cast<T*>(new_storage);
218-
const size_t len = len_.primitive_value;
275+
const size_t len = size_t{len_};
219276
for (auto i = size_t{0}; i < len; ++i) {
220277
new (new_t) T(::sus::move(*old_t));
221278
old_t->~T();
@@ -280,7 +337,7 @@ class Vec {
280337
{
281338
check(!is_moved_from());
282339
reserve(1_usize);
283-
new (as_mut_ptr() + len_.primitive_value) T(::sus::move(t));
340+
new (as_mut_ptr() + size_t{len_}) T(::sus::move(t));
284341
len_ += 1_usize;
285342
}
286343

@@ -305,7 +362,7 @@ class Vec {
305362
{
306363
check(!is_moved_from());
307364
reserve(1_usize);
308-
new (as_mut_ptr() + len_.primitive_value) T(::sus::forward<Us>(args)...);
365+
new (as_mut_ptr() + size_t{len_}) T(::sus::forward<Us>(args)...);
309366
len_ += 1_usize;
310367
}
311368

@@ -461,19 +518,8 @@ class Vec {
461518
}
462519

463520
private:
464-
enum Default { kDefault };
465-
inline constexpr Vec(Default)
466-
: storage_(nullptr), len_(0_usize), capacity_(0_usize) {}
467-
468-
enum WithCap { kWithCap };
469-
Vec(WithCap, usize cap)
470-
: storage_(cap > 0_usize ? static_cast<char*>(malloc(
471-
size_t{::sus::mem::size_of<T>() * cap}))
472-
: nullptr),
473-
len_(0_usize),
474-
capacity_(cap) {
475-
check(::sus::mem::size_of<T>() * cap <= usize(size_t{PTRDIFF_MAX}));
476-
}
521+
Vec(T* ptr, usize len, usize cap)
522+
: storage_(ptr), len_(len), capacity_(cap) {}
477523

478524
constexpr usize apply_growth_function(usize additional) const noexcept {
479525
usize goal = additional + len_;
@@ -489,7 +535,7 @@ class Vec {
489535

490536
inline void destroy_storage_objects() {
491537
if constexpr (!std::is_trivially_destructible_v<T>) {
492-
const size_t len = len_.primitive_value;
538+
const size_t len = size_t{len_};
493539
for (auto i = size_t{0}; i < len; ++i)
494540
reinterpret_cast<T*>(storage_)[i].~T();
495541
}
@@ -507,14 +553,13 @@ class Vec {
507553

508554
// Checks if Vec has been moved from.
509555
constexpr inline bool is_moved_from() const noexcept {
510-
return storage_ == moved_from_value();
511-
}
512-
// The value used in storage_ to indicate moved-from.
513-
constexpr static char* moved_from_value() noexcept {
514-
return static_cast<char*>(nullptr) + alignof(T);
556+
return len_ > capacity_;
515557
}
516558

517-
alignas(T*) char* storage_;
559+
static constexpr usize kMovedFromLen = 1_usize;
560+
static constexpr usize kMovedFromCapacity = 0_usize;
561+
562+
T* storage_;
518563
usize len_;
519564
usize capacity_;
520565

subspace/containers/vec_unittest.cc

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,17 @@ TEST(Vec, WithCapacity) {
4444
}
4545
{
4646
auto v1 = Vec<i32>::with_capacity(1_usize);
47-
EXPECT_EQ(v1.capacity(), 1_usize);
47+
EXPECT_GE(v1.capacity(), 1_usize);
4848
EXPECT_EQ(v1.len(), 0_usize);
4949
}
5050
{
5151
auto v2 = Vec<i32>::with_capacity(2_usize);
52-
EXPECT_EQ(v2.capacity(), 2_usize);
52+
EXPECT_GE(v2.capacity(), 2_usize);
5353
EXPECT_EQ(v2.len(), 0_usize);
5454
}
5555
{
56-
auto v3 = Vec<i32>::with_capacity(3_usize);
57-
EXPECT_EQ(v3.capacity(), 3_usize);
56+
auto v3 = Vec<i32>::with_capacity(1025_usize);
57+
EXPECT_GE(v3.capacity(), 1025_usize);
5858
EXPECT_EQ(v3.len(), 0_usize);
5959
}
6060
}
@@ -317,7 +317,8 @@ TEST(Vec, IntoIter) {
317317
}
318318

319319
TEST(Vec, Growth) {
320-
auto v = Vec<i32>::with_capacity(2_usize);
320+
auto v = Vec<i32>();
321+
v.reserve_exact(2_usize);
321322
EXPECT_EQ(v.capacity(), 2_usize);
322323
while (v.capacity() == 2_usize) v.push(1_i32);
323324
// we grew capacity when we pushed the first item past existing capacity.
@@ -350,7 +351,8 @@ struct TrivialLies {
350351
TEST(Vec, GrowthTriviallyRelocatable) {
351352
static auto moves = 0_usize;
352353
static auto destructs = 0_usize;
353-
auto v = Vec<TrivialLies<true>>::with_capacity(1_usize);
354+
auto v = Vec<TrivialLies<true>>();
355+
v.reserve_exact(1_usize);
354356
v.push(TrivialLies<true>(moves, destructs));
355357

356358
moves = destructs = 0_usize;
@@ -363,7 +365,8 @@ TEST(Vec, GrowthTriviallyRelocatable) {
363365
TEST(Vec, GrowthNonTriviallyRelocatable) {
364366
static auto moves = 0_usize;
365367
static auto destructs = 0_usize;
366-
auto v = Vec<TrivialLies<false>>::with_capacity(1_usize);
368+
auto v = Vec<TrivialLies<false>>();
369+
v.reserve_exact(1_usize);
367370
v.push(TrivialLies<false>(moves, destructs));
368371
v[0u].i = 42_i32;
369372

@@ -379,7 +382,8 @@ TEST(Vec, GrowthNonTriviallyRelocatable) {
379382

380383
TEST(Vec, Reserve) {
381384
{
382-
auto v = Vec<i32>::with_capacity(2_usize);
385+
auto v = Vec<i32>();
386+
v.reserve_exact(2_usize);
383387
EXPECT_EQ(v.capacity(), 2_usize);
384388
v.reserve(1_usize); // We already have room, so do nothing.
385389
v.reserve(1_usize); // We already have room, so do nothing.
@@ -395,7 +399,8 @@ TEST(Vec, Reserve) {
395399
}
396400
{
397401
// Reserve considers the length of the vector.
398-
auto v = Vec<i32>::with_capacity(2_usize);
402+
auto v = Vec<i32>();
403+
v.reserve_exact(2_usize);
399404
v.push(1_i32);
400405
v.reserve(1_usize); // We already have room, so do nothing.
401406
EXPECT_EQ(v.capacity(), 2_usize);
@@ -408,7 +413,8 @@ TEST(Vec, Reserve) {
408413

409414
TEST(Vec, ReserveExact) {
410415
{
411-
auto v = Vec<i32>::with_capacity(2_usize);
416+
auto v = Vec<i32>();
417+
v.reserve_exact(2_usize);
412418
EXPECT_EQ(v.capacity(), 2_usize);
413419
v.reserve_exact(1_usize); // We already have room, so do nothing.
414420
v.reserve_exact(1_usize); // We already have room, so do nothing.
@@ -424,7 +430,9 @@ TEST(Vec, ReserveExact) {
424430
}
425431
{
426432
// Reserve considers the length of the vector.
427-
auto v = Vec<i32>::with_capacity(2_usize);
433+
auto v = Vec<i32>();
434+
v.reserve_exact(2_usize);
435+
EXPECT_EQ(v.capacity(), 2_usize);
428436
v.push(1_i32);
429437
v.reserve_exact(1_usize); // We already have room, so do nothing.
430438
EXPECT_EQ(v.capacity(), 2_usize);
@@ -437,7 +445,8 @@ TEST(Vec, ReserveExact) {
437445

438446
TEST(Vec, GrowToExact) {
439447
{
440-
auto v = Vec<i32>::with_capacity(2_usize);
448+
auto v = Vec<i32>();
449+
v.reserve_exact(2_usize);
441450
EXPECT_EQ(v.capacity(), 2_usize);
442451
v.grow_to_exact(1_usize); // We already have room, so do nothing.
443452
v.grow_to_exact(1_usize); // We already have room, so do nothing.
@@ -453,7 +462,8 @@ TEST(Vec, GrowToExact) {
453462
}
454463
{
455464
// GrowTo does not consider the length of the vector.
456-
auto v = Vec<i32>::with_capacity(2_usize);
465+
auto v = Vec<i32>();
466+
v.reserve_exact(2_usize);
457467
v.push(1_i32);
458468
v.grow_to_exact(1_usize); // We already have room, so do nothing.
459469
EXPECT_EQ(v.capacity(), 2_usize);
@@ -501,7 +511,7 @@ TEST(Vec, Destroy) {
501511
static auto moves = 0_usize;
502512
static auto destructs = 0_usize;
503513
auto o = sus::Option<Vec<TrivialLies<false>>>::none();
504-
o.insert(Vec<TrivialLies<false>>::with_capacity(1_usize));
514+
o.insert(Vec<TrivialLies<false>>());
505515
o->push(TrivialLies<false>(moves, destructs));
506516
o->push(TrivialLies<false>(moves, destructs));
507517

@@ -513,7 +523,8 @@ TEST(Vec, Destroy) {
513523
TEST(Vec, Clear) {
514524
static auto moves = 0_usize;
515525
static auto destructs = 0_usize;
516-
auto v = Vec<TrivialLies<false>>::with_capacity(1_usize);
526+
auto v = Vec<TrivialLies<false>>();
527+
v.reserve_exact(2_usize);
517528
v.push(TrivialLies<false>(moves, destructs));
518529
v.push(TrivialLies<false>(moves, destructs));
519530

@@ -545,11 +556,13 @@ TEST(Vec, Move) {
545556

546557
static auto moves = 0_usize;
547558
static auto destructs = 0_usize;
548-
auto v = Vec<TrivialLies<false>>::with_capacity(1_usize);
559+
auto v = Vec<TrivialLies<false>>();
560+
v.reserve_exact(1_usize);
549561
v.push(TrivialLies<false>(moves, destructs));
550562
v.push(TrivialLies<false>(moves, destructs));
551563

552-
auto v2 = Vec<TrivialLies<false>>::with_capacity(1_usize);
564+
auto v2 = Vec<TrivialLies<false>>();
565+
v2.reserve_exact(1_usize);
553566
v2.push(TrivialLies<false>(moves, destructs));
554567
v2.push(TrivialLies<false>(moves, destructs));
555568

@@ -636,6 +649,25 @@ TEST(Vec, Clone) {
636649
}
637650
}
638651

652+
TEST(Vec, RawParts) {
653+
auto v = Vec<i32>();
654+
v.reserve_exact(12_usize);
655+
v.push(1);
656+
v.push(2);
657+
v.push(3);
658+
const i32* v_ptr = v.as_ptr();
659+
auto raw = sus::move(v).into_raw_parts();
660+
static_assert(std::same_as<decltype(raw), sus::Tuple<i32*, usize, usize>>);
661+
auto [ptr, len, cap] = sus::move(raw);
662+
EXPECT_EQ(ptr, v_ptr);
663+
EXPECT_EQ(len, 3_usize);
664+
EXPECT_EQ(cap, 12_usize);
665+
auto v2 = sus::Vec<i32>::from_raw_parts(unsafe_fn, ptr, len, cap);
666+
EXPECT_EQ(v2.capacity(), 12_usize);
667+
EXPECT_EQ(v2.len(), 3_usize);
668+
EXPECT_EQ(v2.as_ptr(), v_ptr);
669+
}
670+
639671
TEST(Vec, CloneInto) {
640672
static auto count = 0_usize;
641673
struct S {

0 commit comments

Comments
 (0)