qmckl/org/qmckl_context.org

13 KiB

Context

Context handling

The context variable is a handle for the state of the library, and is stored in a data structure which can't be seen outside of the library. To simplify compatibility with other languages, the pointer to the internal data structure is converted into a 64-bit signed integer, defined in the qmckl_context type. A value of QMCKL_NULL_CONTEXT for the context is equivalent to a NULL pointer.

typedef int64_t qmckl_context ;
#define QMCKL_NULL_CONTEXT (qmckl_context) 0

An immutable context would have required to implement a garbage collector. To keep the library simple, we have chosen to implement the context as a mutable data structure, so it has to be handled with care.

By convention, in this file context is a qmckl_context variable and ctx is a qmckl_context_struct* pointer.

Data structure

The context keeps a ``date'' that allows to check which data needs to be recomputed. The date is incremented when the electron coordinates are updated.

When a new element is added to the context, the functions qmckl_context_create, qmckl_context_destroy and qmckl_context_copy should be updated inorder to make deep copies.

A tag is used internally to check if the memory domain pointed by a pointer is a valid context. This allows to check that even if the pointer associated with a context is non-null, we can still verify that it points to the expected data structure.

#define VALID_TAG   0xBEEFFACE
#define INVALID_TAG 0xDEADBEEF

The qmckl_context_check function checks if the domain pointed by the pointer is a valid context. It returns the input qmckl_context if the context is valid, QMCKL_NULL_CONTEXT otherwise.

qmckl_context qmckl_context_check(const qmckl_context context) ;
qmckl_context qmckl_context_check(const qmckl_context context) {

if (context == QMCKL_NULL_CONTEXT)
 return QMCKL_NULL_CONTEXT;

const qmckl_context_struct* const ctx = (const qmckl_context_struct* const) context;

/* Try to access memory */
if (ctx->tag != VALID_TAG) {
   return QMCKL_NULL_CONTEXT;
}

return context;
}

Creation

To create a new context, qmckl_context_create() should be used.

  • Upon success, it returns a pointer to a new context with the qmckl_context type
  • It returns QMCKL_NULL_CONTEXT upon failure to allocate the internal data structure
qmckl_context qmckl_context_create() {

qmckl_context_struct* const ctx =
 (qmckl_context_struct* const) malloc (sizeof(qmckl_context_struct));

if (ctx == NULL) {
 return QMCKL_NULL_CONTEXT;
}

/* Set all pointers and values to NULL */
{
 memset(ctx, 0, sizeof(qmckl_context_struct));
}

/* Initialize lock */
{
 pthread_mutexattr_t attr;
 int rc;

 rc = pthread_mutexattr_init(&attr);
 assert (rc == 0);

 (void) pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);

 rc = pthread_mutex_init ( &(ctx->mutex), &attr);
 assert (rc == 0);

 (void) pthread_mutexattr_destroy(&attr);
}

/* Initialize data */
ctx->tag = VALID_TAG;

const qmckl_context context = (const qmckl_context) ctx;
assert ( qmckl_context_check(context) != QMCKL_NULL_CONTEXT );

ctx->numprec.precision = QMCKL_DEFAULT_PRECISION;
ctx->numprec.range = QMCKL_DEFAULT_RANGE;

ctx->ao_basis.uninitialized = (1 << 10) - 1;
ctx->electron.uninitialized = (1 << 2) - 1;

/* Allocate qmckl_memory_struct */
{
 const size_t size = 128L;
 qmckl_memory_info_struct * new_array = calloc(size, sizeof(qmckl_memory_info_struct));
 if (new_array == NULL) {
   free(ctx);
   return QMCKL_NULL_CONTEXT;
 }
 memset( &(new_array[0]), 0, size * sizeof(qmckl_memory_info_struct) );

 ctx->memory.element = new_array;
 ctx->memory.array_size = size;
 ctx->memory.n_allocated = (size_t) 0;
}

return (qmckl_context) ctx;
}

Locking

For thread safety, the context may be locked/unlocked. The lock is initialized with the PTHREAD_MUTEX_RECURSIVE attribute, and the number of times the thread has locked it is saved in the lock_count attribute.

void qmckl_lock(qmckl_context context) {
if (context == QMCKL_NULL_CONTEXT)
 return ;
qmckl_context_struct* const ctx = (qmckl_context_struct* const) context;
errno = 0;
int rc = pthread_mutex_lock( &(ctx->mutex) );
if (rc != 0) {
 fprintf(stderr, "DEBUG qmckl_lock:%s\n", strerror(rc) );
 fflush(stderr);
}
assert (rc == 0);
ctx->lock_count += 1;
/*
printf("  lock : %d\n", ctx->lock_count);
,*/
}

void qmckl_unlock(const qmckl_context context) {
qmckl_context_struct* const ctx = (qmckl_context_struct* const) context;
int rc = pthread_mutex_unlock( &(ctx->mutex) );
if (rc != 0) {
 fprintf(stderr, "DEBUG qmckl_unlock:%s\n", strerror(rc) );
 fflush(stderr);
}
assert (rc == 0);
ctx->lock_count -= 1;
/*
printf("unlock : %d\n", ctx->lock_count);
,*/
}

TODO Copy

qmckl_context_copy makes a deep copy of a context. It returns QMCKL_NULL_CONTEXT upon failure.

qmckl_context qmckl_context_copy(const qmckl_context context) {

const qmckl_context checked_context = qmckl_context_check(context);

if (checked_context == QMCKL_NULL_CONTEXT) {
 return QMCKL_NULL_CONTEXT;
}

/*
qmckl_lock(context);
{

 const qmckl_context_struct* const old_ctx =
   (qmckl_context_struct* const) checked_context;

 qmckl_context_struct* const new_ctx =
   (qmckl_context_struct* const) malloc (context, sizeof(qmckl_context_struct));

 if (new_ctx == NULL) {
   qmckl_unlock(context);
   return QMCKL_NULL_CONTEXT;
 }

  * Copy the old context on the new one *
  * TODO Deep copies should be done here *
 memcpy(new_ctx, old_ctx, sizeof(qmckl_context_struct));

 qmckl_unlock( (qmckl_context) new_ctx );

 return (qmckl_context) new_ctx;
}
qmckl_unlock(context);
*/
 return QMCKL_NULL_CONTEXT;
}

Destroy

The context is destroyed with qmckl_context_destroy, leaving the ancestors untouched. It frees the context, and returns the previous context.

qmckl_exit_code qmckl_context_destroy(const qmckl_context context) {

const qmckl_context checked_context = qmckl_context_check(context);
if (checked_context == QMCKL_NULL_CONTEXT) return QMCKL_INVALID_CONTEXT;

qmckl_context_struct* const ctx = (qmckl_context_struct* const) context;
assert (ctx != NULL);  /* Shouldn't be possible because the context is valid */

qmckl_lock(context);
{
/* Memory: Remove all allocated data */
for (size_t pos = (size_t) 0 ; pos < ctx->memory.array_size ; ++pos) {
  if (ctx->memory.element[pos].pointer != NULL) {
    free(ctx->memory.element[pos].pointer);
    memset( &(ctx->memory.element[pos]), 0, sizeof(qmckl_memory_info_struct) );
    ctx->memory.n_allocated -= 1;
  }
}
assert (ctx->memory.n_allocated == (size_t) 0);
free(ctx->memory.element);
ctx->memory.element = NULL;
ctx->memory.array_size = (size_t) 0;
}
qmckl_unlock(context);

ctx->tag = INVALID_TAG;

const int rc_destroy = pthread_mutex_destroy( &(ctx->mutex) );
if (rc_destroy != 0) {
/* DEBUG */
 fprintf(stderr, "qmckl_context_destroy: %s (count = %d)\n", strerror(rc_destroy), ctx->lock_count);
 abort();
}

free(ctx);

return QMCKL_SUCCESS;
}