eina: add generic infrastructure for a Eina_Safepointer

This is heavily inspired from Eo_Id infrastructure. Main change
are that the lower bit are always guaranteed to be zero and ignored
by all function. Also it may be a little bit less efficient in some
case, but we will tune it once we have real life usage of it.

Eo won't be migrated for 1.18 to it as Eo_Id is deeply integrated
and it is quite risky to touch it so close from a freeze. This can
wait.
This commit is contained in:
Cedric BAIL 2016-06-10 13:42:19 -07:00
parent 5964008946
commit 692b2c9fc9
8 changed files with 680 additions and 3 deletions

View File

@ -463,6 +463,8 @@ AC_CHECK_SIZEOF(int, 4)
AC_CHECK_SIZEOF(long, 4)
AC_CHECK_SIZEOF([uintptr_t])
EINA_SIZEOF_UINTPTR_T=$ac_cv_sizeof_uintptr_t
AC_SUBST([EINA_SIZEOF_UINTPTR_T])
AC_CHECK_TYPES([siginfo_t], [], [],
[[

View File

@ -97,7 +97,8 @@ lib/eina/eina_quaternion.h \
lib/eina/eina_vector.h \
lib/eina/eina_inline_vector.x \
lib/eina/eina_promise.h \
lib/eina/eina_bezier.h
lib/eina/eina_bezier.h \
lib/eina/eina_safepointer.h
lib_eina_libeina_la_SOURCES = \
lib/eina/eina_abi.c \
@ -168,7 +169,8 @@ lib/eina/eina_share_common.h \
lib/eina/eina_strbuf_common.h \
lib/eina/eina_quaternion.c \
lib/eina/eina_promise.c \
lib/eina/eina_bezier.c
lib/eina/eina_bezier.c \
lib/eina/eina_safepointer.c
if HAVE_WIN32
lib_eina_libeina_la_SOURCES += lib/eina/eina_file_win32.c

View File

@ -270,6 +270,7 @@ extern "C" {
#include <eina_quaternion.h>
#include <eina_promise.h>
#include <eina_bezier.h>
#include <eina_safepointer.h>
#undef EAPI
#define EAPI

View File

@ -67,6 +67,11 @@
#endif
#define EINA_SIZEOF_WCHAR_T @EINA_SIZEOF_WCHAR_T@
#ifdef EINA_SIZEOF_UINTPTR_T
# undef EINA_SIZEOF_UINTPTR_T
#endif
#define EINA_SIZEOF_UINTPTR_T @EINA_SIZEOF_UINTPTR_T@
#ifdef EINA_CONFIGURE_HAVE_DIRENT_H
# undef EINA_CONFIGURE_HAVE_DIRENT_H
#endif

View File

@ -0,0 +1,188 @@
/* EINA - EFL data type library
* Copyright (C) 2015-2016 Carsten Haitzler, Cedric Bail
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library;
* if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef EINA_SAFEPOINTER_INLINE_H_
#define EINA_SAFEPOINTER_INLINE_H_
#include <eina_trash.h>
#include <eina_log.h>
typedef struct _Eina_Memory_Table Eina_Memory_Table;
typedef struct _Eina_Memory_Entry Eina_Memory_Entry;
typedef uintptr_t Eina_Sp_Id;
#if EINA_SIZEOF_UINTPTR_T == 4
/* 32 bits */
# define EINA_BITS_MID_TABLE_ID 5
# define EINA_BITS_TABLE_ID 5
# define EINA_BITS_ENTRY_ID 12
# define EINA_BITS_GENERATION_COUNTER 8
# define EINA_BITS_FREE_COUNTER 2
# define EINA_DROPPED_TABLES 0
# define EINA_DROPPED_ENTRIES 3
typedef int16_t Eina_Table_Index;
typedef uint16_t Eina_Generation_Counter;
#else
/* 64 bits */
# define EINA_BITS_MID_TABLE_ID 11
# define EINA_BITS_TABLE_ID 11
# define EINA_BITS_ENTRY_ID 12
# define EINA_BITS_GENERATION_COUNTER 28
# define EINA_BITS_FREE_COUNTER 2
# define EINA_DROPPED_TABLES 2
# define EINA_DROPPED_ENTRIES 2
typedef int16_t Eina_Table_Index;
typedef uint32_t Eina_Generation_Counter;
#endif
/* Shifts macros to manipulate the SP id */
#define EINA_SHIFT_GENERATION (EINA_BITS_FREE_COUNTER)
#define EINA_SHIFT_ENTRY_ID (EINA_SHIFT_GENERATION + \
EINA_BITS_GENERATION_COUNTER)
#define EINA_SHIFT_TABLE_ID (EINA_SHIFT_ENTRY_ID + \
EINA_BITS_ENTRY_ID)
#define EINA_SHIFT_MID_TABLE_ID (EINA_SHIFT_TABLE_ID + \
EINA_BITS_TABLE_ID)
/* Maximum ranges - a few tables and entries are dropped to minimize the amount
* of wasted bytes, see _eina_safepointer_calloc */
#define EINA_MAX_MID_TABLE_ID (1 << EINA_BITS_MID_TABLE_ID)
#define EINA_MAX_TABLE_ID ((1 << EINA_BITS_TABLE_ID) - EINA_DROPPED_TABLES )
#define EINA_MAX_ENTRY_ID ((1 << EINA_BITS_ENTRY_ID) - EINA_DROPPED_ENTRIES)
#define EINA_MAX_GENERATIONS (1 << EINA_BITS_GENERATION_COUNTER)
/* Masks */
#define EINA_MASK_MID_TABLE_ID (EINA_MAX_MID_TABLE_ID - 1)
#define EINA_MASK_TABLE_ID ((1 << EINA_BITS_TABLE_ID) - 1)
#define EINA_MASK_ENTRY_ID ((1 << EINA_BITS_ENTRY_ID) - 1)
#define EINA_MASK_GENERATIONS (EINA_MAX_GENERATIONS - 1)
/* Macro to extract from an Eo id the indexes of the tables */
#define EINA_SP_DECOMPOSE_ID(ID, MID_TABLE, TABLE, ENTRY, GENERATION) \
MID_TABLE = (ID >> EINA_SHIFT_MID_TABLE_ID) & EINA_MASK_MID_TABLE_ID; \
TABLE = (ID >> EINA_SHIFT_TABLE_ID) & EINA_MASK_TABLE_ID; \
ENTRY = (ID >> EINA_SHIFT_ENTRY_ID) & EINA_MASK_ENTRY_ID; \
GENERATION = (ID >> EINA_SHIFT_GENERATION) & EINA_MASK_GENERATIONS;
struct _Eina_Memory_Entry
{
/* Pointer to the object or
Eina_Trash entry if not active */
void *ptr;
unsigned int active : 1;
/* Valid generation for this entry */
unsigned int generation : EINA_BITS_GENERATION_COUNTER;
};
struct _Eina_Memory_Table
{
/* Pointer to the first recycled entry */
Eina_Trash *trash;
/* Packed mid table and table indexes */
Eina_Sp_Id partial_id;
/* Indicates where start the "never used" entries */
Eina_Table_Index start;
/* Entries of the table holding real pointers and generations */
Eina_Memory_Entry entries[EINA_MAX_ENTRY_ID];
};
EAPI extern Eina_Memory_Table **_eina_sp_ids_tables[EINA_MAX_MID_TABLE_ID];
EAPI extern int _eina_sp_log_dom;
#ifdef _EINA_SP_ERR
#undef _EINA_SP_ERR
#endif
#define _EINA_SP_ERR(...) EINA_LOG_DOM_ERR(_eina_sp_log_dom, __VA_ARGS__)
static inline Eina_Memory_Entry *
_eina_safepointer_entry_get(const Eina_Safepointer *safe,
Eina_Memory_Table **rtable)
{
Eina_Table_Index mid_table_id, table_id, entry_id;
Eina_Generation_Counter generation;
Eina_Sp_Id id = (Eina_Sp_Id) safe;
EINA_SP_DECOMPOSE_ID(id, mid_table_id, table_id, entry_id, generation);
if (_eina_sp_ids_tables[mid_table_id] &&
_eina_sp_ids_tables[mid_table_id][table_id] &&
entry_id < EINA_MAX_ENTRY_ID)
{
Eina_Memory_Table *table;
Eina_Memory_Entry *entry;
table = _eina_sp_ids_tables[mid_table_id][table_id];
entry = &(table->entries[entry_id]);
if (entry->active &&
entry->generation == generation)
{
if (rtable) *rtable = table;
return entry;
}
}
_EINA_SP_ERR("Pointer %p is not a pointer to a valid object.", (void *) safe);
return NULL;
}
static inline void *
eina_safepointer_get(const Eina_Safepointer *safe)
{
Eina_Memory_Entry *entry;
if (!safe) return NULL;
entry = _eina_safepointer_entry_get(safe, NULL);
if (!entry) return NULL;
return entry->ptr;
}
#undef _EINA_SP_ERR
#ifndef _EINA_INTERNAL_SAFEPOINTER
#undef EINA_BITS_MID_TABLE_ID
#undef EINA_BITS_TABLE_ID
#undef EINA_BITS_ENTRY_ID
#undef EINA_BITS_GENERATION_COUNTER
#undef EINA_DROPPED_TABLES
#undef EINA_DROPPED_ENTRIES
#undef EINA_SHIFT_MID_TABLE_ID
#undef EINA_SHIFT_TABLE_ID
#undef EINA_SHIFT_ENTRY_ID
#undef EINA_MAX_MID_TABLE_ID
#undef EINA_MAX_TABLE_ID
#undef EINA_MAX_ENTRY_ID
#undef EINA_MAX_GENERATIONS
#undef EINA_MASK_MID_TABLE_ID
#undef EINA_MASK_TABLE_ID
#undef EINA_MASK_ENTRY_ID
#undef EINA_MASK_GENERATIONS
#undef EINA_SP_DECOMPOSE_ID
#endif
#endif

View File

@ -155,6 +155,7 @@ EAPI Eina_Inlist *_eina_tracking = NULL;
S(thread_queue);
S(rbtree);
S(promise);
S(safepointer);
/* no model for now
S(model);
*/
@ -202,7 +203,8 @@ static const struct eina_desc_setup _eina_desc_setup[] = {
S(cpu),
S(thread_queue),
S(rbtree),
S(promise)
S(promise),
S(safepointer)
/* no model for now
S(model)
*/

View File

@ -0,0 +1,362 @@
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <inttypes.h>
#include "eina_config.h"
#include "eina_private.h"
#define _EINA_INTERNAL_SAFEPOINTER
#include "eina_safepointer.h"
#include "eina_mempool.h"
#include "eina_trash.h"
#include "eina_log.h"
#include "eina_lock.h"
typedef struct _Eina_Memory_Header Eina_Memory_Header;
#ifdef ERR
#undef ERR
#endif
#define ERR(...) EINA_LOG_DOM_ERR(_eina_sp_log_dom, __VA_ARGS__)
#ifdef DBG
#undef DBG
#endif
#define DBG(...) EINA_LOG_DOM_DBG(_eina_sp_log_dom, __VA_ARGS__)
/* Macro used to compose an Eo id */
#define SP_COMPOSE_PARTIAL_ID(MID_TABLE, TABLE) \
( \
((Eina_Sp_Id)(MID_TABLE & EINA_MASK_MID_TABLE_ID) << EINA_SHIFT_MID_TABLE_ID) | \
((Eina_Sp_Id)(TABLE & EINA_MASK_TABLE_ID) << EINA_SHIFT_TABLE_ID) \
)
#define SP_COMPOSE_FINAL_ID(PARTIAL_ID, ENTRY, GENERATION) \
(PARTIAL_ID | \
((ENTRY & EINA_MASK_ENTRY_ID) << EINA_SHIFT_ENTRY_ID) | \
((GENERATION & EINA_MASK_GENERATIONS) << EINA_SHIFT_GENERATION))
struct _Eina_Memory_Header
{
EINA_MAGIC;
size_t size;
};
EAPI Eina_Memory_Table **_eina_sp_ids_tables[EINA_MAX_MID_TABLE_ID] = { NULL };
EAPI int _eina_sp_log_dom = -1;
/* Spare empty table */
static Eina_Memory_Table *empty_table = NULL;
// We are using a Spinlock even with the amount of syscall we do as it shouldn't
// take that long anyway.
static Eina_Spinlock sl;
#define MEM_PAGE_SIZE 4096
#define SAFEPOINTER_MAGIC 0x7DEADC03
static void *
_eina_safepointer_calloc(int number, size_t size)
{
#ifdef HAVE_MMAP
Eina_Memory_Header *header;
size_t newsize;
size = size * number + sizeof (Eina_Memory_Header);
newsize = ((size / MEM_PAGE_SIZE) +
(size % MEM_PAGE_SIZE ? 1 : 0))
* MEM_PAGE_SIZE;
header = mmap(NULL, newsize, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (header == MAP_FAILED)
{
ERR("mmap of Eina_Safepointer table region failed.");
return NULL;
}
header->size = newsize;
EINA_MAGIC_SET(header, SAFEPOINTER_MAGIC);
return (void*)(header + 1);
#else
return calloc(number, size);
#endif
}
static void
_eina_safepointer_free(void *pointer)
{
#ifdef HAVE_MMAP
Eina_Memory_Header *header;
if (!pointer) return ;
header = (Eina_Memory_Header*)(pointer) - 1;
if (!EINA_MAGIC_CHECK(header, SAFEPOINTER_MAGIC))
EINA_MAGIC_FAIL(header, SAFEPOINTER_MAGIC);
EINA_MAGIC_SET(header, 0);
munmap(header, header->size);
#else
free((void*) ((uintptr_t) pointer & ~0x3));
#endif
}
#ifdef EINA_DEBUG_MALLOC
static void
_eina_safepointer_protect(void *pointer, Eina_Bool may_not_write)
{
#ifdef HAVE_MMAP
Eina_Memory_Header *header;
if (!pointer) return ;
header = (Eina_Memory_Header*)(pointer) - 1;
if (!EINA_MAGIC_CHECK(header, SAFEPOINTER_MAGIC))
EINA_MAGIC_FAIL(header, SAFEPOINTER_MAGIC);
mprotect(header, header->size, PROT_READ | ( may_not_write ? 0 : PROT_WRITE));
#else
(void) pointer;
#endif
}
#define PROTECT(Ptr) _eina_safepointer_protect(Ptr, EINA_TRUE)
#define UNPROTECT(Ptr) _eina_safepointer_protect(Ptr, EINA_FALSE)
#else
#define PROTECT(Ptr)
#define UNPROTECT(Ptr)
#endif
static Eina_Memory_Table *
_eina_safepointer_table_new(Eina_Table_Index mid_table_id,
Eina_Table_Index table_id)
{
Eina_Memory_Table *table;
if (empty_table)
{
/* Recycle the available empty table */
table = empty_table;
empty_table = NULL;
UNPROTECT(table);
}
else
{
table = _eina_safepointer_calloc(1, sizeof (Eina_Memory_Table));
if (!table)
{
ERR("Failed to allocate leaf table at [%i][%i]", mid_table_id, table_id);
return NULL;
}
}
table->partial_id = SP_COMPOSE_PARTIAL_ID(mid_table_id,
table_id);
PROTECT(table);
UNPROTECT(_eina_sp_ids_tables[mid_table_id]);
_eina_sp_ids_tables[mid_table_id][table_id] = table;
PROTECT(_eina_sp_ids_tables[mid_table_id]);
return table;
}
static Eina_Memory_Table *
_eina_safepointer_table_find(void)
{
Eina_Table_Index mid_table_id;
for (mid_table_id = 0; mid_table_id < EINA_MAX_MID_TABLE_ID; mid_table_id++)
{
Eina_Table_Index table_id;
if (!_eina_sp_ids_tables[mid_table_id])
{
_eina_sp_ids_tables[mid_table_id] = _eina_safepointer_calloc(EINA_MAX_TABLE_ID, sizeof (Eina_Memory_Table*));
}
if (!_eina_sp_ids_tables[mid_table_id])
{
ERR("Failed to allocate mid table at [%i]", mid_table_id);
return NULL;
}
for (table_id = 0; table_id < EINA_MAX_TABLE_ID; table_id++)
{
Eina_Memory_Table *table;
table = _eina_sp_ids_tables[mid_table_id][table_id];
if (!table)
table = _eina_safepointer_table_new(mid_table_id, table_id);
if (!table) return NULL;
if (table->trash ||
table->start < EINA_MAX_ENTRY_ID)
return table;
}
}
return NULL;
}
static Eina_Memory_Entry *
_eina_safepointer_entry_find(Eina_Memory_Table *table)
{
Eina_Memory_Entry *entry = NULL;
if (table->trash)
{
entry = eina_trash_pop(&table->trash);
}
else if (table->start < EINA_MAX_ENTRY_ID)
{
entry = &(table->entries[table->start]);
table->start++;
}
else
{
ERR("Impossible to find an entry in %" PRIxPTR ".", table->partial_id);
}
return entry;
}
EAPI const Eina_Safepointer *
eina_safepointer_register(const void *target)
{
Eina_Memory_Table *table;
Eina_Memory_Entry *entry = NULL;
Eina_Sp_Id id = 0;
// We silently handle NULL
if (!target) return NULL;
eina_spinlock_take(&sl);
table = _eina_safepointer_table_find();
if (!table) goto no_table;
UNPROTECT(table);
entry = _eina_safepointer_entry_find(table);
if (!entry) goto on_error;
entry->ptr = (void*) target;
entry->active = 1;
entry->generation++;
if (entry->generation == EINA_MAX_GENERATIONS)
entry->generation = 1;
id = SP_COMPOSE_FINAL_ID(table->partial_id,
(entry - table->entries),
entry->generation);
on_error:
PROTECT(table);
no_table:
eina_spinlock_release(&sl);
return (void*) id;
}
EAPI void
eina_safepointer_unregister(const Eina_Safepointer *safe)
{
Eina_Memory_Table *table;
Eina_Memory_Entry *entry;
Eina_Table_Index entry_id;
// We silently handle NULL
if (!safe) return ;
entry = _eina_safepointer_entry_get(safe, &table);
if (!entry) return ;
eina_spinlock_take(&sl);
// In case of a race condition during a double free attempt
// The entry could have been unactivated since we did found it
// So check again.
if (!entry->active) goto on_error;
UNPROTECT(table);
entry->active = 0;
eina_trash_push(&table->trash, entry);
PROTECT(table);
entry_id = entry - table->entries;
if (entry_id == EINA_MAX_ENTRY_ID - 1)
{
Eina_Table_Index i;
for (i = entry_id; i >= 0; i--)
{
if (table->entries[i].active)
break ;
}
// No more active entry
// Could be speed up by tracking the
// number of allocated entries, but
// with all the syscall around, not sure
// it is worth it.
if (i == -1)
{
Eina_Table_Index mid_table_id, table_id;
mid_table_id = (table->partial_id >> EINA_SHIFT_MID_TABLE_ID) & EINA_MASK_MID_TABLE_ID;
table_id = (table->partial_id >> EINA_SHIFT_TABLE_ID) & EINA_MASK_TABLE_ID;
UNPROTECT(_eina_sp_ids_tables[mid_table_id]);
_eina_sp_ids_tables[mid_table_id][table_id] = NULL;
PROTECT(_eina_sp_ids_tables[mid_table_id]);
if (!empty_table)
empty_table = table;
else
_eina_safepointer_free(table);
}
}
on_error:
eina_spinlock_release(&sl);
}
Eina_Bool
eina_safepointer_init(void)
{
eina_magic_string_set(SAFEPOINTER_MAGIC, "Safepointer");
_eina_sp_log_dom = eina_log_domain_register("eina_safepointer",
EINA_LOG_COLOR_DEFAULT);
if (_eina_sp_log_dom < 0)
{
EINA_LOG_ERR("Could not register log domain: eina_safepointer.");
return EINA_FALSE;
}
eina_spinlock_new(&sl);
DBG("entry[Size, Align] = { %zu, %u }",
sizeof (Eina_Memory_Entry), eina_mempool_alignof(sizeof (Eina_Memory_Entry)));
DBG("table[Size, Align] = { %zu, %u }\n",
sizeof (Eina_Memory_Table), eina_mempool_alignof(sizeof (Eina_Memory_Table)));
return EINA_TRUE;
}
Eina_Bool
eina_safepointer_shutdown(void)
{
eina_spinlock_free(&sl);
return EINA_TRUE;
}

View File

@ -0,0 +1,115 @@
/* EINA - EFL data type library
* Copyright (C) 2015-2016 Carsten Haitzler, Cedric Bail
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library;
* if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef EINA_SAFEPOINTER_H__
#define EINA_SAFEPOINTER_H__
/**
* @addtogroup Eina_Safepointer_Group Safe Pointer
*
* @brief These functions provide a wrapper that protect access to pointers
*
* Eina_Safepointer is an pointer to index converter that allow an increased
* level of safety by forbidding direct access to the pointer. The protection
* work by using a set of indirection table that are mmapped and mprotected
* against write access. This the pointer they store and that map to a specific
* index is always correct. Also once a pointer is unregistered the index
* won't be served back for 2^8 on 32 bits system and 2^28 on 64 bits system
* for that specific slot. Finally we do guarantee that the lower 2 bits of the
* returned index are actually never used and completly ignored by our API.
* So you can safely store whatever information you want in it, we will ignore
* it and threat as if it wasn't there.
*
* @note The use of Eina_Safepointer is thread safe.
*/
/**
* @addtogroup Eina_Data_Types_Group Data Types
*
* @{
*/
/**
* @addtogroup Eina_Containers_Group Containers
*
* @{
*/
/**
* @defgroup Eina_Safepointer_Group Safe Pointer
*
* @{
*/
/**
* @typedef Eina_Safepointer
* Type of the protected index.
*/
typedef struct _Eina_Safepointer Eina_Safepointer;
/**
* @brief Register a pointer and get an Eina_Safepointer that map to it.
*
* @param target The pointer to register.
* @return A valid pointer that is an index to the mapped pointer.
*
* @note It will return @c NULL on error or if @p target is @c NULL.
*
* @note The lower 2 bits of the returned pointer will always be 0.
*
* @note The returned pointer can be used like a pointer, but can not
* be touched except with Eina_Safepointer functions.
*/
EAPI const Eina_Safepointer *eina_safepointer_register(const void *target);
/**
* @brief Unregister an Eina_Safepointer and the pointer that map to it.
*
* @param safe The index to unregister from the mapping.
*
* @note This function will ignore the lower 2 bits of the given pointer.
*/
EAPI void eina_safepointer_unregister(const Eina_Safepointer *safe);
/**
* @brief Get the associated pointer from an Eina_Safepointer mapping.
*
* @param safe The Eina_Safepointer index to lookup at.
* @return The pointer registered with that index or @c NULL in any other case.
*
* @note It is always safe to ask for a pointer for any value of the mapping.
* If the pointer is invalid or @c NULL, we will return @c NULL and not crash.
*/
static inline void *eina_safepointer_get(const Eina_Safepointer *safe);
/**
* @}
*/
/**
* @}
*/
/**
* @}
*/
# include "eina_inline_safepointer.x"
#endif