diff --git a/Makefile b/Makefile index f2a9371..cf89ba5 100644 --- a/Makefile +++ b/Makefile @@ -21,21 +21,29 @@ OPT_FLAGS?= $(addprefix -march=,$(TARGET_CPU)) -fgraphite -fopenmp -floop-parall -floop-interchange -ftree-loop-distribution -floop-strip-mine -floop-block \ -fno-stack-check -CXX_OPT_FLAGS?= $(OPT_FLAGS) -felide-constructors +CXX_OPT_FLAGS?= $(OPT_FLAGS) CFLAGS += $(COMMON_FLAGS) --std=gnu11 -CXXFLAGS += $(COMMON_FLAGS) --std=gnu++20 #-fno-exceptions +CXXFLAGS += $(COMMON_FLAGS) --std=gnu++20 -felide-constructors LDFLAGS += STRIP=strip -RELEASE_CFLAGS?= -O3 -flto $(OPT_FLAGS) -RELEASE_CXXFLAGS?= -O3 -flto $(CXX_OPT_FLAGS) -RELEASE_LDFLAGS?= -O3 -flto +ifneq ($(TARGET_SPEC_FLAGS),no) + RELEASE_CFLAGS?= -O3 -flto $(OPT_FLAGS) + RELEASE_CXXFLAGS?= -O3 -flto $(CXX_OPT_FLAGS) + RELEASE_LDFLAGS?= -O3 -flto -DEBUG_CFLAGS?= -O0 -g -DDEBUG -DEBUG_CXXFLAGS?=-O0 -g -DDEBUG -DEBUG_LDFLAGS?= + DEBUG_CFLAGS?= -O0 -g + DEBUG_CXXFLAGS?=-O0 -g + DEBUG_LDFLAGS?= +endif + +DEBUG_CFLAGS+=-DDEBUG +DEBUG_CXXFLAGS+=-DDEBUG + +RELEASE_CFLAGS+=-DRELEASE +RELEASE_CXXFLAGS+=-DRELEASE # Objects @@ -69,6 +77,11 @@ all: | clean .PHONY: install .PHONY: uninstall +.PHONY: test +test: + @rm -f $(PROJECT)-cpp-test + @$(MAKE) $(PROJECT)-cpp-test + # Targets dirs: @@ -130,3 +143,7 @@ uninstall: -rm $(DESTDIR)$(PREFIX)/lib/lib$(PROJECT).{a,so} cd $(INCLUDE) && find . -type f | xargs -I {} rm "$(DESTDIR)$(PREFIX)/include/{}" -rmdir $(DESTDIR)$(PREFIX)/include/$(PROJECT) + +$(PROJECT)-cpp-test: lib$(PROJECT).a + g++ -O3 --std=gnu++20 -Iinclude/ -g -Wall -Wextra src/test/*.cpp -o $@ -l:$< + valgrind ./$@ diff --git a/README.md b/README.md index f68b7f7..52e6b72 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,16 @@ Automatic copy-on-write semantic memory slices for use in C (and C++) # Usage See `include/cow.h` for documentation on each function. -Each function, macro, and type definition in the header will be prefixed with `cow_` or `COW_`. + +## C API +Each function, macro, and type definition in the header will be prefixed with `cow_` or `COW_`. Internal non-prototpyed items use the namespace `_cow_` or `_COW_`. + +### C++ wrapper API +The C++ interface defines the type `Cow`, a reference-counted wrapper over `cow_t` instances that supports cloning through its subtype, `Cow::Fake`, and automatically ensures the originally created `cow_t` is not destroyed until all its clones are, as well as the namespace `_cow_util` which contains memory accessor helpers `Span` and `Slice` (aka `Span::Slice`). + +There are also the following: + * `cow/area.hpp` (namespace `_cow_util`) - The `Area` type is a copy-constructable wrapper around *both* `Cow` and `Cow::Fake`, allowing for implicit cloning. + * `cow/slice.hpp` (namespace `_cow_util`) - Contains the definitions for `Span` and `Slice`. Included automatically by `cow.hpp` (*see above*). ## Building Run `make` to build to build the `release` (optimised) target of the library. @@ -16,8 +25,27 @@ It will create two files: `libcow-debug.a` and `libcow-debug.so`. Each target compiles both a static and dynamic library. You may need to run `make clean` before switching build targets. To build both targets, run `make all`. +To disable default target-specific (e.g. optimisation) flags, set `TARGET_SPEC_FLAGS=no` when running `make`. + +Run `sudo make install` to install the libraries (static and dynamic) and header files (C and C++). +Run `sudo make uninstall` to remove the libraries and header files. + +By default, the install target is `/usr/local/`. Set the `PREFIX` variable when running `make install` / `make uninstall` to specify a different path. + +### Full build and installation +```shell +$ make && sudo make install +``` + +Will build with the default optimisation configuration and install the following files/directories: + * /usr/local/lib/libcow.a + * /usr/local/lib/libcow.so + * /usr/local/include/cow.h + * /usr/local/include/cow.hpp + * /usr/local/include/cow/ + ### Notes -* The `release` target specifies `-march=native` by default. This may be undesirable, if so, run `make MARCH="" release` instead. +* The `release` target specifies `-march=native` by default. This may be undesirable, if so, set `TARGET_CPU=""` when running `make`. * Many optimisation flags for the `release` configuration are specific to GCC (with graphite enabled by default), if builds on other compilers (or non-graphite enabled GCC builds) complain, either set the `OPT_FLAGS` env var or remove the problem flags from the Makefile. * `release` builds are stripped by default. run `make STRIP=: release` to prevent stripping. * The targets are all built with `-fno-strict-aliasing`, but functions in the header file are still annotated with `restrict` needed. This is just to inform users that the function will assume the pointer is not aliased. (When included in C++, where `restrict` is not a keyword, we temporarily define it to be `__restrict__`, which is the GCC equivalent for C++). diff --git a/include/cow.h b/include/cow.h index af9fc4f..a6180f4 100644 --- a/include/cow.h +++ b/include/cow.h @@ -9,7 +9,7 @@ extern "C" { #include // Copy-on-write mapped memory. -typedef struct cow cow_t; +typedef struct cow_mapped_slice cow_t; /// Create a new copy-on-write area of `size` bytes. /// Writes to this instance pointer (`cow_ptr()`) are written to the allocated memory. @@ -25,6 +25,19 @@ int cow_is_fake(const cow_t* cow); /// Get the size of this cow area. size_t cow_size(const cow_t* cow); +/// Get the size of this cow area by assuming layout. This should work assuming "cow_t.h"'s build assertions didn't fail and avoids an extra call. +/// XXX: Deprecated function unpon seeing preferable codegen for using `cow_size()` over this. Use `cow_size()` instead. +static inline +#ifdef _COW_NO_ASSUME_ABI +#define _cow_size_unsafe(v) cow_size(v) +#else +// XXX: This macro is *VERY* ABI sensitive. This shouldn't be used if the ABI has changed since the build of libcow's `cow_t.h` passed its static assertions in *both* the C and C++ implementations. +// The C++ API uses this by default for its `Cow::size()` function. +#define _cow_size_unsafe(v) (*(((size_t*)(v))+1)) + __attribute__((deprecated("size() is safer and offers better codegen."))) +#endif + size_t cow_size_unsafe(const cow_t* v) { return _cow_size_unsafe(v); } + /// Get the `void*` pointer to the start of the area. #define cow_ptr(v) (*((void**)(v))) /// Get the `T*` pointer to the start of the area. diff --git a/include/cow.hpp b/include/cow.hpp new file mode 100644 index 0000000..75313b9 --- /dev/null +++ b/include/cow.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include "cow.h" + +#include + +#include "cow/slice.hpp" + +struct Cow : public _cow_util::Span { + struct Fake; + Cow() = delete; + + explicit Cow(size_t size); + Cow(Cow&& m); + virtual ~Cow(); + + virtual Fake clone() const; + + inline void* area() override { return cow_ptr(get_raw()); } + inline const void* area() const override { return cow_ptr_of(const void, get_raw()); } + + /// Get the size of the mapped area. + /// + /// Note: This calls into `_inner`'s internals. To skip the call on non-LTO builds, use `size_unsafe()`. + size_t size() const override; + /// Get the size by assuming the ABI layout of cow_t to be correct. Potentially faster but ABI sensitive. + /// This shouldn't be a problem if all build static assertions passed. + /// + /// Note: This behaviour can be diabled by building with `-DCOW_NO_ASSUME_ABI`. In this case, this function calls out to the C API to determine the size. + /// There is also likely no benefit using this over `size()` in LTO enabled builds. + /// + /// XXX: Deprecated function for now. It seems `size()` offers better codegen on LTO and non-LTO enabled builds. + [[deprecated("size() is safer and offers better codegen.")]] inline size_t size_unsafe() const { return _cow_size_unsafe(get_raw()); } + + static Cow from_raw(cow_t* owned); + + virtual cow_t* raw() const; + + private: + struct _inner; + Cow(cow_t* raw); + + protected: + Cow(const Cow& c); + const std::shared_ptr<_inner> super; + virtual cow_t* get_raw() const; + +}; + +struct Cow::Fake : public Cow { + Fake() = delete; + Fake(const Cow& real); + Fake(const Fake& copy); + Fake(Fake&& move); + ~Fake(); + + Fake clone() const override; + + static Fake from_real(const Cow& real); + + inline cow_t* raw() const override { return fake; } + + protected: + cow_t* get_raw() const override; + private: + + cow_t* const fake; +}; diff --git a/include/cow/area.hpp b/include/cow/area.hpp new file mode 100644 index 0000000..9bc26e4 --- /dev/null +++ b/include/cow/area.hpp @@ -0,0 +1,37 @@ +// `Area` is a copyable type that opaquely represents either `Cow` or `Cow::Fake`. +#pragma once + +#include +#include + +#include + +namespace _cow_util { + struct Area { + Area() = delete; + + explicit Area(size_t sz); + Area(const Area& area); + Area(Area&& area); + + Area(Cow&& move); + explicit Area(const Cow& copy); + + inline const Cow* operator->() const { return _area.get(); } + inline Cow* operator->() { return _area.get(); } + + inline const Cow* operator*() const { return _area.get(); } + inline Cow* operator*() { return _area.get(); } + + inline operator const Cow&() const { return *_area.get(); } + inline operator Cow&() { return *_area.get(); } + + inline bool is_clone() const { return dynamic_cast(_area.get()) != nullptr; } + + inline cow_t* raw() const { return _area->raw(); } + + ~Area(); + private: + const std::unique_ptr _area; + }; +} diff --git a/include/cow/slice.hpp b/include/cow/slice.hpp new file mode 100644 index 0000000..76a3ab1 --- /dev/null +++ b/include/cow/slice.hpp @@ -0,0 +1,118 @@ +#pragma once + +namespace _cow_util { + /// A type that spans a sized region of memory + template + struct Span { + virtual const void* area() const =0; + virtual void* area() = 0; + virtual size_t size() const = 0; + + inline T* ptr() { return (T*)area(); } + inline const T* ptr() const { return (const T*)area(); } + + inline size_t size_bytes() const { return size() * sizeof(T); } + + inline unsigned char* as_bytes() { return (unsigned char*)area(); } + inline const unsigned char* as_bytes() const { return (const unsigned char*)area(); } + + inline T& operator[](size_t index) { + if(index >= size()) throw "index out of range"; + return ptr()[index]; + } + inline const T& operator[](size_t index) const { + if(index >= size()) throw "index out of range"; + return ptr()[index]; + } + + inline T* operator&() { return &(*this)[0]; } + inline const T* operator&() const {return &(*this)[0]; } + + inline T* operator->() { return &(*this)[0]; } + inline const T* operator->() const {return &(*this)[0]; } + + inline T& operator*() { return (*this)[0]; } + inline const T& operator*() const { return (*this)[0]; } + + inline operator const T*() const { return &(*this)[0]; } + inline operator T*() { return &(*this)[0]; } + + template + inline U* area_as() requires(sizeof(T) % sizeof(U) == 0) { return (U*)area(); } + template + inline const U* area_as() const requires(sizeof(T) % sizeof(U) == 0) { return (U*)area(); } + + template + size_t size_as() const requires(sizeof(T) % sizeof(U) == 0) { return size_bytes() / sizeof(U); } + + struct Slice; + + inline operator Slice() { return Slice(ptr(), size()); } + inline operator const Slice() const { return Slice(const_cast(ptr()), size()); } + + inline bool bounds_ok(size_t start) const + { + return start < size(); + } + inline bool bounds_ok(size_t start, size_t len) const + { + return (start + len) <= size() && bounds_ok(start); + } + + inline ssize_t wrap_len(ssize_t len) const + { + if(size() ==0 ) return 0; + return len < 0 ? wrap_len(size() + len) : ((size_t)len) % size(); + } + + /// Slice (absolute). Specify start and end. + inline const Slice slice_abs(size_t start, size_t end) const { auto len = end -start; if(bounds_ok(start,len)) return Slice(const_cast(ptr()+start), len); else throw "Out of bounds slice"; } + inline Slice slice_abs(size_t start, size_t end) { auto len = end -start; if(bounds_ok(start,len)) return Slice(ptr()+start, len); else throw "Out of bounds slice"; } + + /// Slice (relative). Specify start and length. + inline const Slice slice(size_t start, size_t len) const { if(bounds_ok(start,len)) return Slice(const_cast(ptr()+start), len); else throw "Out of bounds slice"; } + inline Slice slice(size_t start, size_t len) { if(bounds_ok(start,len)) return Slice(ptr()+start, len); else throw "Out of bounds slice"; } + + + /// Slice from 0. Specify length. + inline Slice slice(size_t len) { return slice(0, len); } + inline const Slice slice(size_t len) const { return slice(0, len); }//slice(start, size()-start); } + + /// Slice total. + inline Slice slice() { return slice(0, size()); } + inline const Slice slice() const { return slice(0, size()); } + + /// Slice wrapping. Specify start and end that may wrap over and/or under the span's size. + inline Slice slice_wrap(ssize_t start, ssize_t end) { return slice_abs((size_t)wrap_len(start), (size_t)wrap_len(end)); } + inline const Slice slice_wrap(ssize_t start, ssize_t end) const { return slice_abs((size_t)wrap_len(start), (size_t)wrap_len(end)); } + inline Slice slice_wrap(ssize_t len) { return slice_abs((size_t)wrap_len(len)); } + inline const Slice slice_wrap(ssize_t len) const { return slice_abs((size_t)wrap_len(len)); } + + template + inline Span::Slice reinterpret() { return typename Span::Slice((U*)area(), size_bytes() / sizeof(U)); } + template + inline Span::Slice reinterpret() const { return typename Span::Slice((const U*)area(), size_bytes() / sizeof(U)); } + }; + + /// A slice of memory with a backing pointer and size. + template + struct Span::Slice : public Span { + inline Slice(T* ptr, size_t sz) : _area((void*)ptr), _size(sz){} + inline Slice(const Span& slice) : _area(const_cast(slice.area())), _size(slice.size()){} + inline Slice(const Slice& copy) = default; + inline Slice(Slice&& copy) : _area(copy._area), _size(copy._size){ *const_cast(©._size) = 0; } + Slice() = delete; + + inline const void* area() const override { return _area; } + inline void* area() override { return _area; } + inline size_t size() const override { return _size; } + + private: + void* const _area; + const size_t _size; + }; + + + template + using Slice = Span::Slice; +} diff --git a/src/area.cpp b/src/area.cpp new file mode 100644 index 0000000..85cd7a8 --- /dev/null +++ b/src/area.cpp @@ -0,0 +1,16 @@ +#include + +namespace _cow_util { + Area::Area(size_t sz) : _area(std::make_unique(sz)){} + Area::Area(const Area& copy) : + _area(std::make_unique(*copy._area.get())){} + Area::Area(Area&& move) : + _area(std::move(*const_cast*>(&move._area))){} + Area::~Area(){} + + Area::Area(Cow&& r) : + _area(std::make_unique(std::move(r))){} + Area::Area(const Cow& r) : + _area(std::make_unique(r.clone())){} + +} diff --git a/src/cow.c b/src/cow.c index de79f11..cd5e95a 100644 --- a/src/cow.c +++ b/src/cow.c @@ -31,14 +31,8 @@ #define LASSERT(expr, msg) ASSERT(LIKELY(expr), "(unexpected) " msg) #define UASSERT(expr, msg) ASSERT(UNLIKELY(expr), "(expected) " msg) -struct cow { - void* origin; // ptr to mapped memory. This *MUST* be the first field and have an offset of 0. - - int fd; // Will be ORd with ~INT_MAX if it's a clone. Will be >0 if it's the original. - size_t size; -}; // cow_t, *cow - -_Static_assert(offsetof(cow_t, origin) == 0, "`cow_t.origin` must have an offset of 0."); +// struct cow { ... } +#include "cow_t.h" static __attribute__((noreturn)) __attribute__((noinline)) __attribute__((cold)) void die(const char* error) { @@ -92,7 +86,7 @@ size_t cow_size(const cow_t* cow) return cow->size; } -cow_t* cow_create(size_t size) +inline internal cow_t _cow_create_unboxed(size_t size) { cow_t ret; ret.size = size; @@ -101,15 +95,24 @@ cow_t* cow_create(size_t size) if(ret.origin == MAP_FAILED) die("cow_create:mmap"); TRACE("mapped new origin cow page of %lu size at %p (memfd %d)", size, ret.origin, ret.fd); - return box_value(ret); + return ret; +} +cow_t* cow_create(size_t size) +{ + return box_value(_cow_create_unboxed(size)); } -void cow_free(cow_t* restrict cow) +inline internal void _cow_free_unboxed(const cow_t* cow) { TRACE("unmapping %s cow of %lu size from %p (fd %d, real fd %d)", cow_is_fake(cow) ? "fake" : "and closing fd of origin", cow->size, cow->origin, cow->fd, cow_real_fd(cow)); munmap(cow->origin, cow->size); if(!cow_is_fake(cow)) close(cow->fd); +} + +void cow_free(cow_t* restrict cow) +{ + _cow_free_unboxed(cow); free(cow); } diff --git a/src/cow.cpp b/src/cow.cpp new file mode 100644 index 0000000..3a05c08 --- /dev/null +++ b/src/cow.cpp @@ -0,0 +1,59 @@ +#include + +#include + +#include "cow_t.h" + +struct Cow::_inner { + cow_t cow; + + inline const cow_t* ptr() const { return &cow; } + inline cow_t* ptr() { return &cow; } + + ~_inner(); + _inner(size_t sz); + _inner(cow_t* ptr); + + _inner(const _inner& copy) = delete; + _inner(_inner&& move) = delete; + _inner() = delete; +}; +Cow::_inner::~_inner() { + _cow_free_unboxed(ptr()); +} +Cow::_inner::_inner(size_t sz) : cow(_cow_create_unboxed(sz)){} +Cow::_inner::_inner(cow_t* ptr) : cow(*ptr) +{ + free(ptr); +} + +Cow::Cow(size_t size) : super(std::make_shared<_inner>(size)){} +Cow::Cow(cow_t* raw) : super(std::make_shared<_inner>(raw)){} + +Cow::Cow(Cow&& m) : super(std::move(*const_cast*>(&m.super))){} +Cow::Cow(const Cow& c) : super(c.super){} +Cow::~Cow(){} + +Cow Cow::from_raw(cow_t* owned) { if(cow_is_fake(owned)) throw "Trying to create real from fake raw"; else return Cow(owned); } + +Cow::Fake Cow::clone() const { return Fake::from_real(*this); } +cow_t* Cow::get_raw() const { return super->ptr(); } + +size_t Cow::size() const { return super->cow.size; } + +cow_t* Cow::raw() const { return &super->cow; } + +Cow::Fake::Fake(const Cow& copy) : Cow(copy), fake(cow_clone(copy.super->ptr())){} +Cow::Fake::Fake(const Fake& copy) : Cow(copy), fake(cow_clone(copy.fake)){}//Fake(*static_cast(©)){} +Cow::Fake::Fake(Fake&& move) : Cow(std::move(move)), fake(move.fake) +{ + *const_cast(&move.fake) = nullptr; +} +Cow::Fake::~Fake() { if(fake) cow_free(fake); } + +Cow::Fake Cow::Fake::Fake::from_real(const Cow& real) { return Fake(real); } + +Cow::Fake Cow::Fake::clone() const { return Fake(*static_cast(this)); } +cow_t* Cow::Fake::get_raw() const { return fake; } + + diff --git a/src/cow_t.h b/src/cow_t.h new file mode 100644 index 0000000..3719982 --- /dev/null +++ b/src/cow_t.h @@ -0,0 +1,47 @@ +// Internal header for defining the `cow_t` concrete type to be used with the C and C++ APIs. +#ifndef _COW_T_H +#define _COW_T_H + +#define internal __attribute__((visibility("internal"))) + +#ifdef __cplusplus +#define restrict __restrict__ +extern "C" { +#endif + +#include + +#include + +struct cow_mapped_slice { + void* origin; // ptr to mapped memory. This *MUST* be the first field and have an offset of 0. + + size_t size; // Should be at this offset. + int fd; // Will be ORd with ~INT_MAX if it's a clone. Will be >0 if it's the original. +}; // cow_t, *cow + +#ifdef __cplusplus +static_assert +#else +_Static_assert +#endif + (offsetof(cow_t, origin) == 0, "`cow_t.origin` must have an offset of 0."); + +#ifndef _COW_NO_ASSUME_ABI +#ifdef __cplusplus +static_assert +#else +_Static_assert +#endif + (offsetof(cow_t, size) == sizeof(void*), "`cow_t.size` should have an offset equal to `sizeof(void*)` or cow_size_unsafe() becomes UB."); +#endif + +cow_t _cow_create_unboxed(size_t size) internal; +void _cow_free_unboxed(const cow_t* cow) internal; + +#ifdef __cplusplus +} +#undef restruct +#endif + +#endif /* _COW_T_H */ diff --git a/src/test/main.cpp b/src/test/main.cpp new file mode 100644 index 0000000..84256dc --- /dev/null +++ b/src/test/main.cpp @@ -0,0 +1,220 @@ +#include + +#include +#include +#include +#include + +#include + +#include + +using namespace _cow_util; + +/// UTF8 multibyte 4. +struct utf8_t { + const static constexpr size_t MULTIBYTE =4; + typedef std::array Unicode; + + constexpr inline utf8_t() { data[0] = 0; } + + template + constexpr inline utf8_t(const char (&buffer)[N]) + { + static_assert(N<=MULTIBYTE, "Expected multibyte 4"); + data = { + buffer[0], + buffer[1], + buffer[2], + buffer[3], + 0, + }; + } + + constexpr inline utf8_t(char ascii) { + data[0] = ascii; + data[1] = 0; + } + + constexpr inline utf8_t(const char* c) { + for(size_t i=0;ibelow = nullptr; + if(right) right->left = nullptr; + if(below) below->above = nullptr; + if(left) left->right = nullptr; + } + private: + size_t id; + + //TODO + //Cow raw_tiles; + //Cow taw_graphics; + + // Links will be NULL if there is no loaded segment in their position. + // The objects themselves live in Map's segment registry (seg_reg). + Segment* above; + Segment* right; + Segment* below; + Segment* left; + }; + + struct Map { + // Remove a registered Segment. Resetting its neighbours' links if needed. + // The reference will be invalid after calling this function. + void unregister_segment(Segment& segment) + { + if(segment.id seg_reg; + }; +} + + + +template +void print_slice(Slice memory) +{ + printf("slice: { %p, %lu (%lu bytes) }\n", memory.area(), memory.size(), memory.size_bytes()); + +} + +void write_fake(Cow& clone, const char* string) +{ + strncpy(clone.area_as(), string, clone.size_as()-1); +} + +void read_fake(const Cow& clone) +{ + printf("read_fake: %s\n", clone.area_as()); +} + +void moving_cow(Cow moved) +{ + auto moved_clone = moved.reinterpret(); + strncpy(&moved_clone, "Ummmm....", moved_clone.size()); + + read_fake(moved); +} + +int main() +{ + Cow _area(4000); + + Area area = std::move(_area); + write_fake(area, "Hello???"); + Area area2 = area; + write_fake(area2, "Hi"); + Area area3 = std::move(area2); + Area area4 = std::move(area); + read_fake(area3); + read_fake(area4); + + printf("Is clone: a1: %d, a2: %d\n", area4.is_clone(), area3.is_clone()); + + utf8_t ch = "あ"; + utf8_t ch2('a'); + utf8_t ch3 = ch.c_str(); + utf8_t ch4 = ch3.data; + utf8_t ch5 = ch4; + (void)ch5; + printf("Test: %s, %s, %s\n", (const char*)ch, ch2.c_str(), ch3.c_str()); + + Cow real(4096); + memset(real, 0, real.size_bytes()); + + printf("Created real: "); + print_slice(real); + print_slice(real.slice_wrap(-20, -10)); + + write_fake(real, "Hello world"); + read_fake(real); + + Cow::Fake clone = real; + printf("Fake size: %lu\n", clone.size()); + printf("Fake ptr: %p\n", clone.area()); + + read_fake(clone); + write_fake(clone, "hello fake!"); + read_fake(clone); + read_fake(real); + + printf("First byte of: real = %x, fake = %x\n", real[0], clone[0]); + moving_cow(std::move(real)); //moving real is fine + // <-- real is now dropped. But `clone` still holds Rc to _inner (cow_t). + printf("First byte of: fake = %x\n", clone[0]); + read_fake(clone); //clone still functions because of refcount on origin. + + return 0; +}