diff --git a/include/macros.h b/include/macros.h index fde7315..9e34e64 100644 --- a/include/macros.h +++ b/include/macros.h @@ -55,7 +55,13 @@ #define _cconv__hot _hot #define _cconv__entry __attribute__((constructor)) #define _cconv__exit __attribute__((destructor)) + +// Allocates #define _cconv__alloc __attribute__((malloc)) +// Infallible alloc +#define _cconv__ialloc __attribute__((malloc)) __attribute__((returns_nonnull)) +#define _cconv__nonnull __attribute__((returns_nonnull)) + #define _callcv(name) _cconv__ ## name #define _callconv(name) _callcv(name) @@ -130,7 +136,7 @@ _mixin void _drain_val(void* x, ...) { IGNORE(x); } // This compiles to no-op on #define box(t) aligned_alloc(_Alignof(t), sizeof(t)) #define box_value(v) ({ let _box = box(var(v)); *_box = (v); _box; }) -#define unbox_value(v) ({ let _v = (v); let _res = *_v; free(_v); _res; } +#define unbox_value(v) ({ let _v = (v); let _res = *_v; free(_v); _res; }) #define stackalloc(t) __builtin_alloca_with_align(sizeof(t), _Alignof(t)) // Function macros @@ -257,4 +263,9 @@ static_assert_eq(bswap(bswap(128lu)), 128, "bswap128 (lu) failed (3)"); #define VERSION_REV(ver) _AS_u32(_AS_u32(ver) & 0xffu) _Static_assert( (VERSION_COMP(VERSION(1,2,3,4), VER_COMP_MIN) >> VER_COMP_MIN) == 2u, "invalid native version spec"); +// Misc. + +#define PTR_ASSIGN(ptr, val) ( (ptr) ? (*(ptr) = (val), (ptr)) : (ptr) ) +#define PTR_ASSIGNx(ptr, val) ({ let _ptr = (ptr); let _val = (val); PTR_ASSIGN(_ptr, _val); }) + #endif /* _MACROS_H */ diff --git a/include/str.h b/include/str.h index ecd647c..867bdd8 100644 --- a/include/str.h +++ b/include/str.h @@ -6,7 +6,7 @@ #include #define STRF_OWNED AS(1ul << 5, int) -#define STRF_DERRIVED AS(1ul << 6, int) +#define STRF_DERIVED AS(1ul << 6, int) enum str_ownership { STR_NULL = 0, @@ -15,10 +15,10 @@ enum str_ownership { STR_OWNED_MALLOC, STR_OWNED_STACK, - STR_DERRIVED = STRF_DERRIVED, - STR_DERRIVED_STATIC, - STR_DERRIVED_MALLOC, - STR_DERRIVED_STACK, + STR_DERIVED = STRF_DERIVED, + STR_DERIVED_STATIC, + STR_DERIVED_MALLOC, + STR_DERIVED_STACK, }; typedef struct string str_t; diff --git a/src/str.c b/src/str.c index a85a6a4..89381ba 100644 --- a/src/str.c +++ b/src/str.c @@ -11,17 +11,18 @@ struct string { enum str_ownership owned; union _str_meta_inner { // `STRF_OWNED` - struct { + struct str_meta_owned { _Atomic usize refs; // Number of derrivations from this void (*m_free)(void*); // How to free (when `_MALLOC`). + _Atomic bool orphan; // If a `derive` references this parent somehow, but this string has been `str_free()`d. Tell the derived string that sets the `refs` to 0 to free it. } owned; - // `STRF_DERRIVED` - struct { - // String this is derrived from, can be either owned or derrived. + // `STRF_DERIVED` + struct str_meta_derived { + // String this is derived from, can be either owned or derived. struct string* _shared parent; // `str`'s offset from `parent->str`. usize offset; - } derrived; + } derived; } _inner; } meta; // The start of this string slice. Regardless of slicing @@ -30,32 +31,88 @@ struct string { //TODO: We can use offsetof() to reverse the result of `str_new()` from char* (str field) to str_t*. + + inline static bool _str_meta_is_owned(enum str_ownership owned) { return AS(owned, u32) & AS(STRF_OWNED, u32); } +// Returns true for owned, false for derived +inline static bool _str_meta_parts_from(enum str_ownership o, union _str_meta_inner *pIN inner, struct str_meta_owned* *pOUT own, struct str_meta_derived* *pOUT derive) +{ + if(_str_meta_is_owned(o)) + return (PTR_ASSIGN(own, &inner->owned), true); + else return (PTR_ASSIGN(derive, &inner->derived), false); +} + inline static bool _str_is_owned(const str_t*pIN str) { return _str_meta_is_owned(str->meta.owned); } -static union _str_meta_inner _str_default_meta(enum str_ownership oship) +inline static bool _str_is_owned_parts(str_t*pIN str, struct str_meta_owned* *pOUT own, struct str_meta_derived* *pOUT derive) { - bool owned = _str_meta_is_owned(oship); + return _str_meta_parts_from(str->meta.owned, &str->meta._inner, own, derive); +} + +static union _str_meta_inner _str_default_meta(enum str_ownership oship, bool *pOUT owned) +{ + union _str_meta_inner output; + + struct str_meta_owned* o_own = NULL; + struct str_meta_derived* o_der = NULL; + + // PTR_ASSIGNx always evaluates both args, so this is fine + PTR_ASSIGNx(owned, + _str_meta_parts_from(oship, &output, + &o_own, + &o_der) + ); switch(oship) { - + case STR_OWNED_STACK: + case STR_OWNED_STATIC: + debug_assert(o_own); + *o_own = (struct str_meta_owned){ + .refs = 0, + .m_free = NULL, + .orphan = false, + }; + break; + case STR_OWNED_MALLOC: + debug_assert(o_own); + *o_own = (struct str_meta_owned){ + .refs = 0, + .m_free = &free, + .orphan = false, + }; + break; + + case STR_DERIVED_STATIC: + case STR_DERIVED_STACK: + case STR_DERIVED_MALLOC: + debug_assert(o_der); + *o_der = (struct str_meta_derived){ + .parent = NULL, + .offset = 0, + }; + break; + default: + ERROR("Cannot assign metadata from ownership type 0x%x, returning 0'd metadata", AS(oship, u32)); + memset(&output, 0, sizeof(output)); } - TODO("default str metadata"); + return output; } +/// Allocates a bare new str_t. The metadata will be incomplete for `derived` string slices, and `str` will be `NULL`. For `owned` strings, `str` will be `calloc()`ed to `cap+1`. static str_t _str_alloc_bare(usize cap, enum str_ownership owned) { - bool is_owned = _str_meta_is_owned(owned); + bool is_owned; + let meta_inner = _str_default_meta(owned, &is_owned); struct str_meta meta = { .owned = owned, - ._inner = _str_default_meta(owned), + ._inner = meta_inner, }; str_t str = { .len = 0, @@ -65,3 +122,33 @@ static str_t _str_alloc_bare(usize cap, enum str_ownership owned) str.str = is_owned ? calloc(cap+1, 1) : NULL; return str; } + +static void _str_force_free(struct str_meta_owned*pIN o_own, void* ptr) +{ + (o_own->m_free ?: &free)(ptr); +} + +void str_free(str_t* restrict strp) +{ + str_t str = unbox_value(strp); + + struct str_meta_owned* o_own = NULL; + struct str_meta_derived* o_der = NULL; + + if(_str_is_owned_parts(&str, &o_own, &o_der)) + { + debug_assert(o_own); + if(o_own->refs > 0) o_own->orphan = true; + else if(str.meta.owned == STR_OWNED_MALLOC && str.str) + _str_force_free(o_own, str.str); + } + else { + debug_assert(o_der); + TODO(" \ + //TODO: Find top-level parent (traverse list of potentially derived `o_der->parent`s. \ + //TODO: While doing so, compute the full offset that this slice has from the top owned parent. \ + //TODO: Then, decrement its `refs` \ + //TODO: Then, if it is marked as `orphan`, and we have decremented its `refs` to 0, free its `ptr`. \ + "); + } +}