#include "internal.h"
#define _dispatch_data_retain(x) dispatch_retain(x)
#define _dispatch_data_release(x) dispatch_release(x)
#if DISPATCH_DATA_MOVABLE
#if DISPATCH_USE_RESOLVERS && !defined(DISPATCH_RESOLVED_VARIANT)
#error Resolved variant required for movable
#endif
static const dispatch_block_t _dispatch_data_destructor_unlock = ^{
DISPATCH_CRASH("unlock destructor called");
};
#define DISPATCH_DATA_DESTRUCTOR_UNLOCK (_dispatch_data_destructor_unlock)
#endif
const dispatch_block_t _dispatch_data_destructor_free = ^{
DISPATCH_CRASH("free destructor called");
};
const dispatch_block_t _dispatch_data_destructor_none = ^{
DISPATCH_CRASH("none destructor called");
};
const dispatch_block_t _dispatch_data_destructor_vm_deallocate = ^{
DISPATCH_CRASH("vmdeallocate destructor called");
};
struct dispatch_data_s _dispatch_data_empty = {
.do_vtable = DISPATCH_VTABLE(data),
.do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_next = DISPATCH_OBJECT_LISTLESS,
};
static dispatch_data_t
_dispatch_data_init(size_t n)
{
dispatch_data_t data = _dispatch_alloc(DISPATCH_VTABLE(data),
sizeof(struct dispatch_data_s) + n * sizeof(range_record));
data->num_records = n;
data->do_targetq = dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
data->do_next = DISPATCH_OBJECT_LISTLESS;
return data;
}
static void
_dispatch_data_destroy_buffer(const void* buffer, size_t size,
dispatch_queue_t queue, dispatch_block_t destructor)
{
if (destructor == DISPATCH_DATA_DESTRUCTOR_FREE) {
free((void*)buffer);
} else if (destructor == DISPATCH_DATA_DESTRUCTOR_NONE) {
} else if (destructor == DISPATCH_DATA_DESTRUCTOR_VM_DEALLOCATE) {
vm_deallocate(mach_task_self(), (vm_address_t)buffer, size);
} else {
if (!queue) {
queue = dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
}
dispatch_async_f(queue, destructor, _dispatch_call_block_and_release);
}
}
dispatch_data_t
dispatch_data_create(const void* buffer, size_t size, dispatch_queue_t queue,
dispatch_block_t destructor)
{
dispatch_data_t data;
if (!buffer || !size) {
if (destructor) {
_dispatch_data_destroy_buffer(buffer, size, queue,
_dispatch_Block_copy(destructor));
}
return dispatch_data_empty;
}
data = _dispatch_data_init(1);
data->leaf = true;
data->size = size;
data->records[0].from = 0;
data->records[0].length = size;
if (destructor == DISPATCH_DATA_DESTRUCTOR_DEFAULT) {
void *data_buf = malloc(size);
if (slowpath(!data_buf)) {
free(data);
return NULL;
}
buffer = memcpy(data_buf, buffer, size);
data->destructor = DISPATCH_DATA_DESTRUCTOR_FREE;
} else {
data->destructor = _dispatch_Block_copy(destructor);
#if DISPATCH_DATA_MOVABLE
data->locked = 1;
#endif
}
data->records[0].data_object = (void*)buffer;
if (queue) {
_dispatch_retain(queue);
data->do_targetq = queue;
}
return data;
}
void
_dispatch_data_dispose(dispatch_data_t dd)
{
dispatch_block_t destructor = dd->destructor;
if (destructor == NULL) {
size_t i;
for (i = 0; i < dd->num_records; ++i) {
_dispatch_data_release(dd->records[i].data_object);
}
#if DISPATCH_DATA_MOVABLE
} else if (destructor == DISPATCH_DATA_DESTRUCTOR_UNLOCK) {
dispatch_data_t data = (dispatch_data_t)dd->records[0].data_object;
(void)dispatch_atomic_dec2o(data, locked);
_dispatch_data_release(data);
#endif
} else {
_dispatch_data_destroy_buffer(dd->records[0].data_object,
dd->records[0].length, dd->do_targetq, destructor);
}
}
size_t
_dispatch_data_debug(dispatch_data_t dd, char* buf, size_t bufsiz)
{
size_t offset = 0;
if (dd->leaf) {
offset += snprintf(&buf[offset], bufsiz - offset,
"leaf: %d, size: %zd, data: %p", dd->leaf, dd->size,
dd->records[0].data_object);
} else {
offset += snprintf(&buf[offset], bufsiz - offset,
"leaf: %d, size: %zd, num_records: %zd", dd->leaf,
dd->size, dd->num_records);
size_t i;
for (i = 0; i < dd->num_records; ++i) {
range_record r = dd->records[i];
offset += snprintf(&buf[offset], bufsiz - offset,
"records[%zd] from: %zd, length %zd, data_object: %p", i,
r.from, r.length, r.data_object);
}
}
return offset;
}
size_t
dispatch_data_get_size(dispatch_data_t dd)
{
return dd->size;
}
dispatch_data_t
dispatch_data_create_concat(dispatch_data_t dd1, dispatch_data_t dd2)
{
dispatch_data_t data;
if (!dd1->size) {
_dispatch_data_retain(dd2);
return dd2;
}
if (!dd2->size) {
_dispatch_data_retain(dd1);
return dd1;
}
data = _dispatch_data_init(dd1->num_records + dd2->num_records);
data->size = dd1->size + dd2->size;
memcpy(data->records, dd1->records, dd1->num_records *
sizeof(range_record));
memcpy(data->records + dd1->num_records, dd2->records, dd2->num_records *
sizeof(range_record));
if (dd1->leaf) {
data->records[0].data_object = dd1;
}
if (dd2->leaf) {
data->records[dd1->num_records].data_object = dd2;
}
size_t i;
for (i = 0; i < data->num_records; ++i) {
_dispatch_data_retain(data->records[i].data_object);
}
return data;
}
dispatch_data_t
dispatch_data_create_subrange(dispatch_data_t dd, size_t offset,
size_t length)
{
dispatch_data_t data;
if (offset >= dd->size || !length) {
return dispatch_data_empty;
} else if ((offset + length) > dd->size) {
length = dd->size - offset;
} else if (length == dd->size) {
_dispatch_data_retain(dd);
return dd;
}
if (dd->leaf) {
data = _dispatch_data_init(1);
data->size = length;
data->records[0].from = offset;
data->records[0].length = length;
data->records[0].data_object = dd;
_dispatch_data_retain(dd);
return data;
}
data = dispatch_data_empty;
size_t i = 0, bytes_left = length;
while (i < dd->num_records && offset >= dd->records[i].length) {
offset -= dd->records[i++].length;
}
while (i < dd->num_records) {
size_t record_len = dd->records[i].length - offset;
if (record_len > bytes_left) {
record_len = bytes_left;
}
dispatch_data_t subrange = dispatch_data_create_subrange(
dd->records[i].data_object, dd->records[i].from + offset,
record_len);
dispatch_data_t concat = dispatch_data_create_concat(data, subrange);
_dispatch_data_release(data);
_dispatch_data_release(subrange);
data = concat;
bytes_left -= record_len;
if (!bytes_left) {
return data;
}
offset = 0;
i++;
}
DISPATCH_CRASH("dispatch_data_create_subrange out of bounds");
return NULL;
}
dispatch_data_t
dispatch_data_create_map(dispatch_data_t dd, const void **buffer_ptr,
size_t *size_ptr)
{
dispatch_data_t data = dd;
void *buffer = NULL;
size_t size = dd->size, offset = 0;
if (!size) {
data = dispatch_data_empty;
goto out;
}
if (!dd->leaf && dd->num_records == 1 &&
((dispatch_data_t)dd->records[0].data_object)->leaf) {
offset = dd->records[0].from;
dd = (dispatch_data_t)(dd->records[0].data_object);
}
if (dd->leaf) {
#if DISPATCH_DATA_MOVABLE
data = _dispatch_data_init(1);
(void)dispatch_atomic_inc2o(dd, locked);
data->size = size;
data->destructor = DISPATCH_DATA_DESTRUCTOR_UNLOCK;
data->records[0].data_object = dd;
data->records[0].from = offset;
data->records[0].length = size;
_dispatch_data_retain(dd);
#else
_dispatch_data_retain(data);
#endif
buffer = dd->records[0].data_object + offset;
goto out;
}
buffer = malloc(size);
if (!buffer) {
data = NULL;
size = 0;
goto out;
}
dispatch_data_apply(dd, ^(dispatch_data_t region DISPATCH_UNUSED,
size_t off, const void* buf, size_t len) {
memcpy(buffer + off, buf, len);
return (bool)true;
});
data = dispatch_data_create(buffer, size, NULL,
DISPATCH_DATA_DESTRUCTOR_FREE);
out:
if (buffer_ptr) {
*buffer_ptr = buffer;
}
if (size_ptr) {
*size_ptr = size;
}
return data;
}
static bool
_dispatch_data_apply(dispatch_data_t dd, size_t offset, size_t from,
size_t size, dispatch_data_applier_t applier)
{
bool result = true;
dispatch_data_t data = dd;
const void *buffer;
dispatch_assert(dd->size);
#if DISPATCH_DATA_MOVABLE
if (dd->leaf) {
data = _dispatch_data_init(1);
(void)dispatch_atomic_inc2o(dd, locked);
data->size = size;
data->destructor = DISPATCH_DATA_DESTRUCTOR_UNLOCK;
data->records[0].data_object = dd;
data->records[0].from = from;
data->records[0].length = size;
_dispatch_data_retain(dd);
buffer = dd->records[0].data_object + from;
result = applier(data, offset, buffer, size);
_dispatch_data_release(data);
return result;
}
#else
if (!dd->leaf && dd->num_records == 1 &&
((dispatch_data_t)dd->records[0].data_object)->leaf) {
from = dd->records[0].from;
dd = (dispatch_data_t)(dd->records[0].data_object);
}
if (dd->leaf) {
buffer = dd->records[0].data_object + from;
return applier(data, offset, buffer, size);
}
#endif
size_t i;
for (i = 0; i < dd->num_records && result; ++i) {
result = _dispatch_data_apply(dd->records[i].data_object,
offset, dd->records[i].from, dd->records[i].length,
applier);
offset += dd->records[i].length;
}
return result;
}
bool
dispatch_data_apply(dispatch_data_t dd, dispatch_data_applier_t applier)
{
if (!dd->size) {
return true;
}
return _dispatch_data_apply(dd, 0, 0, dd->size, applier);
}
dispatch_data_t
dispatch_data_copy_region(dispatch_data_t dd, size_t location,
size_t *offset_ptr)
{
if (location >= dd->size) {
*offset_ptr = 0;
return dispatch_data_empty;
}
dispatch_data_t data;
size_t size = dd->size, offset = 0, from = 0;
while (true) {
if (dd->leaf) {
_dispatch_data_retain(dd);
*offset_ptr = offset;
if (size == dd->size) {
return dd;
} else {
data = _dispatch_data_init(1);
data->size = size;
data->records[0].from = from;
data->records[0].length = size;
data->records[0].data_object = dd;
return data;
}
} else {
size_t i, pos;
for (i = 0; i < dd->num_records; ++i) {
pos = offset + dd->records[i].length;
if (location < pos) {
size = dd->records[i].length;
from = dd->records[i].from;
data = (dispatch_data_t)(dd->records[i].data_object);
if (dd->num_records == 1 && data->leaf) {
*offset_ptr = offset;
_dispatch_data_retain(dd);
return dd;
} else {
dd = data;
break;
}
} else {
offset = pos;
}
}
}
}
}