Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ Template:
Next
---------------------

- ABI BREAKING: [Inline `cbor_incref` and `cbor_decref` fast paths](https://github.com/PJK/libcbor/pull/434)
- `cbor_incref` and `cbor_decref` are now `static inline` in `cbor/common.h`; the deallocation branch of `cbor_decref` moved into a new exported helper `_cbor_decref_free`. Source-compatible — same signatures and semantics — but the two symbols are no longer exported from the shared library, so binaries relying on their extern resolution (including `dlsym`) will fail to link against the new `.so`. Rebuilding against the updated headers is sufficient.

0.14.0 (2026-04-07)
---------------------

Expand Down
122 changes: 56 additions & 66 deletions src/cbor/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -81,80 +81,70 @@ bool cbor_is_float(const cbor_item_t* item) {
return cbor_isa_float_ctrl(item) && !cbor_float_ctrl_is_ctrl(item);
}

cbor_item_t* cbor_incref(cbor_item_t* item) {
item->refcount++;
return item;
}

void cbor_decref(cbor_item_t** item_ref) {
cbor_item_t* item = *item_ref;
CBOR_ASSERT(item->refcount > 0);
if (--item->refcount == 0) {
switch (item->type) {
case CBOR_TYPE_UINT:
/* Fallthrough */
case CBOR_TYPE_NEGINT:
/* Combined allocation, freeing the item suffices */
{ break; }
case CBOR_TYPE_BYTESTRING: {
if (cbor_bytestring_is_definite(item)) {
_cbor_free(item->data);
} else {
/* We need to decref all chunks */
cbor_item_t** handle = cbor_bytestring_chunks_handle(item);
for (size_t i = 0; i < cbor_bytestring_chunk_count(item); i++)
cbor_decref(&handle[i]);
_cbor_free(((struct cbor_indefinite_string_data*)item->data)->chunks);
_cbor_free(item->data);
}
break;
}
case CBOR_TYPE_STRING: {
if (cbor_string_is_definite(item)) {
_cbor_free(item->data);
} else {
/* We need to decref all chunks */
cbor_item_t** handle = cbor_string_chunks_handle(item);
for (size_t i = 0; i < cbor_string_chunk_count(item); i++)
cbor_decref(&handle[i]);
_cbor_free(((struct cbor_indefinite_string_data*)item->data)->chunks);
_cbor_free(item->data);
}
break;
}
case CBOR_TYPE_ARRAY: {
/* Get all items and decref them */
cbor_item_t** handle = cbor_array_handle(item);
size_t size = cbor_array_size(item);
for (size_t i = 0; i < size; i++)
if (handle[i] != NULL) cbor_decref(&handle[i]);
void _cbor_decref_free(cbor_item_t* item) {
switch (item->type) {
case CBOR_TYPE_UINT:
/* Fallthrough */
case CBOR_TYPE_NEGINT:
/* Combined allocation, freeing the item suffices */
{ break; }
case CBOR_TYPE_BYTESTRING: {
if (cbor_bytestring_is_definite(item)) {
_cbor_free(item->data);
break;
}
case CBOR_TYPE_MAP: {
struct cbor_pair* handle = cbor_map_handle(item);
for (size_t i = 0; i < item->metadata.map_metadata.end_ptr;
i++, handle++) {
cbor_decref(&handle->key);
if (handle->value != NULL) cbor_decref(&handle->value);
}
} else {
/* We need to decref all chunks */
cbor_item_t** handle = cbor_bytestring_chunks_handle(item);
for (size_t i = 0; i < cbor_bytestring_chunk_count(item); i++)
cbor_decref(&handle[i]);
_cbor_free(((struct cbor_indefinite_string_data*)item->data)->chunks);
_cbor_free(item->data);
break;
}
case CBOR_TYPE_TAG: {
if (item->metadata.tag_metadata.tagged_item != NULL)
cbor_decref(&item->metadata.tag_metadata.tagged_item);
break;
}
case CBOR_TYPE_STRING: {
if (cbor_string_is_definite(item)) {
_cbor_free(item->data);
} else {
/* We need to decref all chunks */
cbor_item_t** handle = cbor_string_chunks_handle(item);
for (size_t i = 0; i < cbor_string_chunk_count(item); i++)
cbor_decref(&handle[i]);
_cbor_free(((struct cbor_indefinite_string_data*)item->data)->chunks);
_cbor_free(item->data);
break;
}
case CBOR_TYPE_FLOAT_CTRL: {
/* Floats have combined allocation */
break;
break;
}
case CBOR_TYPE_ARRAY: {
/* Get all items and decref them */
cbor_item_t** handle = cbor_array_handle(item);
size_t size = cbor_array_size(item);
for (size_t i = 0; i < size; i++)
if (handle[i] != NULL) cbor_decref(&handle[i]);
_cbor_free(item->data);
break;
}
case CBOR_TYPE_MAP: {
struct cbor_pair* handle = cbor_map_handle(item);
for (size_t i = 0; i < item->metadata.map_metadata.end_ptr;
i++, handle++) {
cbor_decref(&handle->key);
if (handle->value != NULL) cbor_decref(&handle->value);
}
_cbor_free(item->data);
break;
}
case CBOR_TYPE_TAG: {
if (item->metadata.tag_metadata.tagged_item != NULL)
cbor_decref(&item->metadata.tag_metadata.tagged_item);
_cbor_free(item->data);
break;
}
case CBOR_TYPE_FLOAT_CTRL: {
/* Floats have combined allocation */
break;
}
_cbor_free(item);
*item_ref = NULL;
}
_cbor_free(item);
}

void cbor_intermediate_decref(cbor_item_t* item) { cbor_decref(&item); }
Expand Down
31 changes: 29 additions & 2 deletions src/cbor/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -311,20 +311,47 @@ CBOR_EXPORT bool cbor_is_undef(const cbor_item_t* item);
*
* This function can be used to extend reference counting to client code.
*
* Defined `static inline` so the single-instruction increment is emitted
* directly at the call site — meaningful for hot loops that build
* #cbor_item_t trees, where every constructed item touches the refcount.
*
* @param item Reference to an item
* @return The input \p item
*/
CBOR_EXPORT cbor_item_t* cbor_incref(cbor_item_t* item);
static inline cbor_item_t* cbor_incref(cbor_item_t* item) {
item->refcount++;
return item;
}

/** Slow path of #cbor_decref: performs the actual deallocation once the
* reference count reaches zero. Do not call directly; use #cbor_decref.
*
* Split from #cbor_decref so the fast path (decrement + zero check) can
* inline while the recursive free logic — which dominates code size —
* stays out of line.
*/
CBOR_EXPORT void _cbor_decref_free(cbor_item_t* item);

/** Decreases the item's reference count by one, deallocating the item if
* needed
*
* In case the item is deallocated, the reference count of all items this
* item references will also be #cbor_decref 'ed recursively.
*
* The fast path (decrement, check for zero) is `static inline`; only the
* uncommon deallocation branch calls into #_cbor_decref_free. This keeps
* every non-final decref a couple of instructions at the call site.
*
* @param item Reference to an item. Will be set to `NULL` if deallocated
*/
CBOR_EXPORT void cbor_decref(cbor_item_t** item);
static inline void cbor_decref(cbor_item_t** item_ref) {
cbor_item_t* item = *item_ref;
CBOR_ASSERT(item->refcount > 0);
if (--item->refcount == 0) {
_cbor_decref_free(item);
*item_ref = NULL;
}
}

/** Decreases the item's reference count by one, deallocating the item if
* needed
Expand Down