diff --git a/CMakeLists.txt b/CMakeLists.txt index 7430f41af..86c1f22b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -243,6 +243,7 @@ if(BUILD_TESTS) test/runner.c test/runner-unix.c test/test-harness.c + test/test-tb-object.c test/test-tb-ref.c test/test-tb-stk.c test/test-tb-stack.c) diff --git a/src/toolbox/CMakeLists.txt b/src/toolbox/CMakeLists.txt index e2b7fc222..c2360b585 100644 --- a/src/toolbox/CMakeLists.txt +++ b/src/toolbox/CMakeLists.txt @@ -35,14 +35,36 @@ set(TOOL_OBJS set(LSTORE_PROJECT_OBJS ${TOOL_OBJS} ${NETWORK_OBJS}) set(LSTORE_PROJECT_INCLUDES - tbx/apr_wrapper.h tbx/constructor_wrapper.h - tbx/fmttypes.h tbx/interval_skiplist.h tbx/list.h - tbx/network.h tbx/pigeon_hole.h tbx/stack.h tbx/net_sock.h - tbx/pigeon_coop.h tbx/skiplist.h toolbox_config.h tbx/random.h - tbx/string_token.h tbx/type_malloc.h tbx/transfer_buffer.h tbx/packer.h - tbx/append_printf.h tbx/chksum.h tbx/varint.h tbx/atomic_counter.h tbx/dns_cache.h - tbx/iniparse.h tbx/log.h tbx/assert_result.h tbx/visibility.h - tbx/tbx_decl.h) + tbx/append_printf.h + tbx/apr_wrapper.h + tbx/assert_result.h + tbx/atomic_counter.h + tbx/chksum.h + tbx/constructor_wrapper.h + tbx/dns_cache.h + tbx/fmttypes.h + tbx/iniparse.h + tbx/interval_skiplist.h + tbx/list.h + tbx/log.h + tbx/net_sock.h + tbx/network.h + tbx/object.h + tbx/packer.h + tbx/pigeon_coop.h + tbx/pigeon_hole.h + tbx/random.h + tbx/skiplist.h + tbx/stack.h + tbx/string_token.h + tbx/tbx_decl.h + tbx/transfer_buffer.h + tbx/type_malloc.h + tbx/varint.h + tbx/visibility.h + toolbox_config.h +) + set(LSTORE_PROJECT_INCLUDES_NAMESPACE tbx) set(LSTORE_PROJECT_EXECUTABLES) set(LSTORE_LIBS ${APR_LIBRARY} diff --git a/src/toolbox/tbx/object.h b/src/toolbox/tbx/object.h new file mode 100644 index 000000000..66fe5e37e --- /dev/null +++ b/src/toolbox/tbx/object.h @@ -0,0 +1,82 @@ +/* + Copyright 2016 Vanderbilt University + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#pragma once +#ifndef ACCRE_OBJECT_H_INCLUDED +#define ACCRE_OBJECT_H_INCLUDED + +#include + +// Types +typedef struct tbx_vtable_t tbx_vtable_t; +typedef struct tbx_obj_t tbx_obj_t; + +// Exported types. OK to be exported +/*! + * A single instance of this exists per type. It MUST be the first element of + * the type's vtable to allow type punning to work. + */ +struct tbx_vtable_t { + /*! Function to be called when refcount reaches zero */ + tbx_ref_release_fn_t free_fn; + /*! Human printable name of this type */ + char *name; +}; + +/*! + * Each instance contains this object. It MUST be the first element of the + * object to allow type punning for opaque types + */ +struct tbx_obj_t { + const tbx_vtable_t *vtable; + tbx_ref_t refcount; +}; + +// Inline functions +/*! + * @brief Properly initializes reference count + * @param obj Object to initialize + * @param vtable The desired vtable for the object to use + * This function is necessary because any cross-CPU caches need to be atomically + * updated to be notified. Otherwise cache coherency can act up + */ +static inline void tbx_obj_init(tbx_obj_t *obj, const tbx_vtable_t *vtable) { + tbx_ref_init(&obj->refcount); + obj->vtable = vtable; +} + +/*! + * @brief Grabs a new reference incrementing the reference count + * @param ref Refcount to increment + * @returns Pointer to refcount + */ +static inline tbx_obj_t *tbx_obj_get(tbx_obj_t *obj) { + tbx_ref_get(&obj->refcount); + return obj; +} + +/*! + * @brief Decrements a reference count, cleaning up the object if necessary + * @param ref Counter to decremnt + * @param cleanup Function to call to destroy + * @returns True if object was removed, false otherwise + */ +static inline bool tbx_obj_put(tbx_obj_t *obj) { + return tbx_ref_put(&obj->refcount, obj->vtable->free_fn); +} + + +#endif diff --git a/src/toolbox/tbx/ref.h b/src/toolbox/tbx/ref.h index 7e459a2c9..fdc6e50dc 100644 --- a/src/toolbox/tbx/ref.h +++ b/src/toolbox/tbx/ref.h @@ -76,4 +76,8 @@ static inline bool tbx_ref_put(tbx_ref_t *ref, tbx_ref_release_fn_t cleanup) { } } +// Precompiler Macros +#define container_of(ptr, type, member) \ + ((type *) ((char *) (ptr) - offsetof(type, member))) + #endif diff --git a/test/task.h b/test/task.h index 3e2eb9937..fea21d810 100644 --- a/test/task.h +++ b/test/task.h @@ -69,8 +69,10 @@ #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#if 0 #define container_of(ptr, type, member) \ ((type *) ((char *) (ptr) - offsetof(type, member))) +#endif typedef enum { TCP = 0, diff --git a/test/test-list.h b/test/test-list.h index f1d581433..d7ba194f1 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -1,11 +1,15 @@ TEST_DECLARE(always_win) - +TEST_DECLARE(tb_object) +TEST_DECLARE(tb_object_api) +TEST_DECLARE(tb_ref) TEST_DECLARE(tb_stack) TEST_DECLARE(tb_stk_escape_text) -TEST_DECLARE(tb_ref) + TASK_LIST_START TEST_ENTRY(always_win) + TEST_ENTRY(tb_object) + TEST_ENTRY(tb_object_api) + TEST_ENTRY(tb_ref) TEST_ENTRY(tb_stack) TEST_ENTRY(tb_stk_escape_text) - TEST_ENTRY(tb_ref) TASK_LIST_END diff --git a/test/test-tb-object.c b/test/test-tb-object.c new file mode 100644 index 000000000..127de706c --- /dev/null +++ b/test/test-tb-object.c @@ -0,0 +1,95 @@ +#include "task.h" +#include +#include + +typedef struct { + tbx_vtable_t base; + void (*do_counter)(tbx_ref_t *ref); +} test_vtable_t; + +typedef struct { + tbx_obj_t desc; + int counter; +} test_obj_t; + +static int counter = 0; +static void inc_counter(tbx_ref_t *ref) { + counter++; +} + +static void dec_counter(tbx_ref_t *ref) { + counter--; +} + +static void inc_obj_counter(tbx_ref_t *desc) { + test_obj_t *obj = (test_obj_t *) container_of(desc, tbx_obj_t, refcount); + (obj->counter)++; +} + +static void dec_obj_counter(tbx_ref_t *desc) { + test_obj_t *obj = (test_obj_t *) container_of(desc, tbx_obj_t, refcount); + (obj->counter)--; +} + +const test_vtable_t vtableA = { .base.name = "TestA", + .base.free_fn = inc_obj_counter, + .do_counter = inc_counter }; + +const test_vtable_t vtableB = { .base.name = "TestB", + .base.free_fn = dec_obj_counter, + .do_counter = dec_counter }; + +TEST_IMPL(tb_object) { + test_obj_t objA; + objA.counter = 0; + objA.desc.vtable = &vtableA.base; + tbx_ref_init(&objA.desc.refcount); + + test_obj_t objB; + objB.counter = 0; + objB.desc.vtable = &vtableB.base; + tbx_ref_init(&objB.desc.refcount); + + // Test access to vtable base (type punning) + ASSERT(strcmp("TestA", ((tbx_vtable_t *)objA.desc.vtable)->name) == 0); + ASSERT(strcmp("TestB", ((tbx_vtable_t *)objB.desc.vtable)->name) == 0); + + // Test proper vtable access + ((test_vtable_t *) objB.desc.vtable)->do_counter(NULL); + ASSERT(counter == -1); + ((test_vtable_t *) objA.desc.vtable)->do_counter(NULL); + ASSERT(counter == 0); + ((test_vtable_t *) objB.desc.vtable)->do_counter(NULL); + ASSERT(counter == -1); + ((test_vtable_t *) objB.desc.vtable)->do_counter(NULL); + ASSERT(counter == -2); + ((test_vtable_t *) objB.desc.vtable)->do_counter(NULL); + ASSERT(counter == -3); + + // Test refcounting and free pointer deallocation + tbx_ref_put(&objA.desc.refcount, ((tbx_vtable_t *) objA.desc.vtable)->free_fn); + tbx_ref_put(&objB.desc.refcount, ((tbx_vtable_t *) objB.desc.vtable)->free_fn); + ASSERT(objA.counter == 1); + ASSERT(objB.counter == -1); + + return 0; +} + +TEST_IMPL(tb_object_api) { + test_obj_t objA; + objA.counter = 0; + tbx_obj_init(&objA.desc, (tbx_vtable_t *) &vtableA); + ASSERT(objA.counter == 0); + + tbx_obj_t *ret = tbx_obj_get(&objA.desc); + ASSERT(objA.counter == 0); + ASSERT(ret = &objA.desc); + + tbx_obj_put(&objA.desc); + ASSERT(objA.counter == 0); + + tbx_obj_put(&objA.desc); + ASSERT(objA.counter == 1); + + return 0; +}