Tuesday, June 1, 2010

Heap

This is the heap module, it's a simple Cheney style garbage collector. Again, no bit packing, and serialization of the heap just copies a tree like structure. The garbage collector is semi-exact, which has some nice properties with respect to static data, not sure I want that manner of inexactness on all places though. Guess I'll leave it at this for now.

I guess I should give some kind of rationale for this. Of course, you can go concurrent mark-and-sweep as shown by Erlang. But, my assumption is that at some point, for a sufficient number of cores -which may well be above sixteen- and more localized memory models, it just pays of to give each core its own memory and possibly implement light-weight threading by just swapping between different root nodes.

/**
 *  COPYRIGHT 2010, M.C.A. Devillers
 *
 *  THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY
 *  EXPRESSED OR IMPLIED.  ANY USE IS AT YOUR OWN RISK.
 *
 *  A simple library for a Cheney style heap. 
 *
 *  1. Every value is either a pointer or an integer of pointer size.
 *
 *  2. The heap holds nodes. Every node starts of with a two integer
 *     header. The first integer describes the size of the whole node, 
 *     the second integer is either 0, in which case all values in 
 *     that node are integers, or not, in which case all values in 
 *     that node are pointers to other nodes possibly in that heap.
 *
 *     Note: the Hi language runtime elaborates on the meaning of the
 *     second integer in the following manner: 
 *          0 - a constant, 1 - a record, otherwise - a thunk.
 *
 *  3. At the moment, the collector is semi-exact. Pointers which do
 *     not point into the heap are not chased. This makes it possible
 *     to use pointers to static data, and it makes it possible to
 *     use small integers as if they would be integers.
 *
 *     It implies serialization of static data is handled 
 *     different, it serializes everything and can only handle non-
 *     cyclic structures which is a severe drawback.
 *
 *  4. A heap may be GCed, in which case _a whole new heap is 
 *     allocated_ and the heap is copied to the new heap. This is to 
 *     facilitate multi-core processing of several coarse-grained
 *     threads without the need to keep unused to spaces around for 
 *     all heaps used. 
 *     Ordinary 'malloc' is used to allocate new heaps.
 *
 *  5. The heap size may be grown at will for special operations. 
 *     There are no facilities, as of yet, to shrink the heap size.
 *
 *  6. A heap may be serialized to a region in memory, in which case 
 *     only the first root node is copied. Unserialization inserts a 
 *     root node from a region in memory to a given heap.
 *
 *  A design which is neither very compact nor very fast, but minimal 
 *  and robust.
 */
#ifndef HEAP_H
#define HEAP_H

#include <stdio.h>
#include <stdint.h>
#include "../types/types.h"
 
#define ROOTS   1024

typedef struct {
    native_int      size;
    native_int*     low;
    native_int*     high;
    native_int*     free;
 
    native_int*     trigger_gc;
    native_int*     trigger_grow;

    native_int      root_free;
    native_int*     root[ROOTS];

    native_int*     memory;
} heap_t;

heap_t*     heap_create(native_int sz);
void*       heap_destroy(heap_t* hp);
native_int  heap_root_count(heap_t* hp);
native_int  heap_root_new(heap_t* hp);
void        heap_root_set(heap_t* hp, native_int n, native_int* r);
native_int* heap_root_get(heap_t* hp, native_int n);
native_int* heap_allocate(heap_t* hp, native_int sz);
heap_t*     heap_collect(heap_t* hp);
heap_t*     heap_try_collect(heap_t* hp);
heap_t*     heap_reserve(heap_t* hp, native_int sz);
native_int  heap_serialize(heap_t* hp, void* p, native_int sz);
native_int* heap_unserialize(heap_t* hp, void* p, native_int sz);
native_int  heap_size(heap_t* hp);
native_int  heap_used(heap_t* hp);
native_int  heap_free(heap_t* hp);
void        heap_info(FILE* f, heap_t* hp);
void        heap_debug(FILE* f, heap_t* hp);
#endif // HEAP_H
/**
 *  COPYRIGHT 2008, M.C.A. Devillers
 *
 *  THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY
 *  EXPRESSED OR IMPLIED.  ANY USE IS AT YOUR OWN RISK.
 */
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
#include "../assert/assert.h"
#include "heap.h"
#define BITS_SET(b0,b1)     ((b0) | (b1))
#define BITS_UNSET(b0,b1)   ((b0) & (~(b1)))
#define BITS_TEST(b0,b1)    (((b0) & (b1)) != 0)
#define BITS_FLIP(b0,b1)    ((b0) (b1))
#define BIT_SET(n,b)        BITS_SET(n, 1<<b)
#define BIT_UNSET(n,b)      BITS_UNSET(n, 1<<b)
#define BIT_TEST(n,b)       BITS_TEST(n, 1<<b)
#define BIT_FLIP(n,b)       BITS_FLIP(n, 1<<b)

#define BELOW(p,q)          (((void*) p) < ((void*) q))
#define BELOWEQ(p,q)        ((void*) p <= (void*) q)
#define IN_HEAP(p, l, h) \
    ( ((void*) p >= (void*) l) && ((void*) p < (void*) h) )

#define DIFFERENCE(p,q)     ((native_int)((void*)p - (void*) q))
#define INCREMENT(p,n)      ((native_int*)((void*)p + (int) n))
#define DECREMENT(p,n)      ((native_int*)((void*)p - (int) n))

#define ADVANCE(p,n)        &(((native_int*)p)[(int)n])
#define RETREAT(p,n)        &(((native_int*)p)[0-(int)n])

#define HEAP_ALLIGNMENT     512
#define HEAP_MINIMAL_GROW   2048
#define HEAP_MINIMAL_FREE   1024
#define HEAP_MINIMAL_SIZE   4096

#define SMALL_INTEGER       64*1024
#define IS_SMALL_INTEGER(p) BELOW(p, (SMALL_INTEGER))


/** Allocate alligned memory.
 *
 *  /param m    the size to be allocared
 *  /param n    the alignement (some power of two, preferably cache-size)
 *  /return     a chunk of memory
 
 */
void* malloc_alligned(size_t m, int n) {
    TRACE;
    void* p = 0;
    if (posix_memalign(&p, n, m) == 0) {
        ASSERT(!IS_SMALL_INTEGER(p));
        return p;
    } else {
        return 0;
    }
} 

/** Create a heap.
 *
 *  The heap size is _not_ the number of bytes allocated, but the
 *  total number of bytes _including_ the header size.
 *
 *  Exits if a new heap cannot be allocated.
 *
 *  /param sz   the heap size
 *  /return     a heap
 */
 
heap_t* heap_create(native_int sz) {
    TRACE;
    ASSERT(sz >= HEAP_MINIMAL_SIZE);

    heap_t* r = (heap_t*) malloc_alligned(sz, HEAP_ALLIGNMENT);
    if (r == 0) {
        fprintf(stderr,
            "HEAP_PANIC[0]: failed to allocate a heap of size %u\n",
            (unsigned int) sz);
        exit(1);
    }

    r->size = sz;
    r->low  = (native_int*) &(r->memory);
    r->high = INCREMENT(r, sz);
    r->free = r->low;

    r->root_free = 0;
 
    DEBUG(native_int i;)
    DEBUG(for (i = 0; i < ROOTS; i++) r->root[i] = 0;)

    r->trigger_gc   = DECREMENT(r->high, HEAP_MINIMAL_FREE);
    r->trigger_grow = DECREMENT(r->high, HEAP_MINIMAL_GROW);

    DEBUG(native_int* p;)
    DEBUG(for (p = r->low; p < r->high; p++) *p = 0;)

    return r;
}

/** Destroy a heap.
 *
 *  /param hp   a heap
 */
void* heap_destroy(heap_t* hp) {
    TRACE;

    free(hp);
    return 0;
}

/** Give the number of roots in the heap.
 *
 *  /param hp   a heap
 *  /return     the number of roots
 */
 
native_int heap_root_count(heap_t* hp) {
    TRACE;

    return hp->root_free;
}

/** Allocate a new root slot in the heap.
 *
 *  The number of roots is a fixed number, see ROOTS.
 *
 *  Exits if there are no more roots available.
 *
 *  /param hp   a heap
 *  /return     a new root slot
 */
native_int heap_root_new(heap_t* r) {
    TRACE;
 
    native_int free = r->root_free;
    r->root_free++;
    if (r->root_free > ROOTS) {
        fprintf(stderr,
            "HEAP_PANIC[1]: out of roots %d\n",
            ROOTS);
        exit(1);
    }
 
    return free;
}

/** Set a root node to a pointer (in the heap).
 *
 *  /param hp   a heap
 *  /param n    a root slot
 *  /param r    a pointer to a structure
 */
void heap_root_set(heap_t* hp, native_int n, native_int* r) {
    TRACE;
    ASSERT(n < ROOTS);

    hp->root[n] = r;
}
 
/** Get a root node to a pointer (in the heap).
 *
 *  /param hp   a heap
 *  /param n    a root slot
 *  /return     a pointer to a structure
 */
 
native_int* heap_root_get(heap_t* hp, native_int n) {
    TRACE;
    ASSERT(n < ROOTS);

    return hp->root[n];
}

/** Allocate a node in the heap of given size.
 *
 *  Nodes may, but should not assumed, to be blanked. 
 *  Nodes may, but should not be assumed, to have an initialized 
 *  header field.
 *
 *  /param hp   a heap
 *  /param sz   the size requested
 *  /return     a pointer to a new node
 */
 
native_int* heap_allocate(heap_t* hp, native_int sz) {
    TRACE;

    native_int* p = hp->free;
    hp->free = ADVANCE(hp->free, sz);

    DEBUG(p[0] = sz;)
    DEBUG(native_int i;)
 
    DEBUG(for (i = 1; i < sz; i++) p[i] = 0;)

    return p; 
}
 
/** Copy nodes from one heap to another heap.
 *
 *  All nodes refered to by root nodes in the from heap are
 *  copied to the to heap.
 *
 *  Unsafe, the caller should make sure the to space holds
 *  enough room.
 *
 * /param from  the source heap
 * /param to    the destination heap
 * /retrun      the destination heap
 */
 
heap_t* heap_copy(heap_t* from, heap_t* to) {
    TRACE;

    native_int* copy_low;
    native_int* copy_high;
 
    native_int  i,j;
    native_int* p0;
    native_int* p1;

    copy_low  = to->free;
    copy_high = to->free;

    // copy the roots to the to-space
    for (i = 0; i < from->root_free; i++) {
        p0 = from->root[i];
        if (IN_HEAP(p0, from->low, from->high)) {
            p1 = (native_int*) p0[0];
            if (IN_HEAP(p1, to->low, to->high)) {
                to->root[to->root_free + i] = p1;
            } else {
                for (j = 0; j < p0[0]; j++) {
                    copy_high[j] = p0[j];
                }
 
                p0[0] = (native_int) copy_high;
                to->root[to->root_free + i] = copy_high;
                copy_high = ADVANCE(copy_high, copy_high[0]);
            }
        }
    }
    to->root_free += from->root_free;

    // copy all references to the to-space
    while (copy_low != copy_high)  {
        if (copy_low[1] != 0) {
            for (i = 2; i < copy_low[0]; i++) {
                p0 = (native_int*) copy_low[i];
                if (IN_HEAP(p0, from->low, from->high)) {
                    p1 = (native_int*) p0[0];
                    if (IN_HEAP(p1, to->low, to->high)) {
                        copy_low[i] = (native_int) p1;
                    } else {
                        for (j = 0; j < p0[0]; j++) copy_high[j] = p0[j];
                        copy_low[i] = (native_int) copy_high;
                        p0[0] = (native_int) copy_high;
                        copy_high= ADVANCE(copy_high, copy_high[0]);
                    }
                }
            }
        }
        copy_low = ADVANCE(copy_low, copy_low[0]);
    }
    to->free = copy_low;
    return to;
}
 
/** Resize a given heap to a given size by a copy.
 *
 *  /param hp   a heap
 *  /param sz   the requested size
 *  /return     a copy of the heap of given size
 */
heap_t* heap_resize(heap_t* hp, native_int sz) {
    TRACE;
    heap_t* to;
    to = heap_create(sz);
    to = heap_copy(hp, to);
    heap_destroy(hp);
    return to;
}

/** Collect (copy) a heap.
 *
 *  /param hp   a heap
 *  /return     a garbage collected copy of the heap
 */
heap_t* heap_collect(heap_t* r) {
    TRACE;

    heap_t* s;
    s = r;
    s = heap_resize(s, s->size);
    if (BELOW(s->trigger_grow, s->free)) {
        s = heap_resize(s, s->size + s->size);
    }
    return s;
}

/** Collect (copy) a heap if it runs out of space.
 *
 *  /param hp   a heap
 *  /return     a collected heap, or the original
 */
heap_t* heap_try_collect(heap_t* r) {
    TRACE;
    heap_t* s;
    s = r;
    if (BELOW(s->trigger_gc, s->free)) {
        fprintf(stdout, "HEAP!\n"); // XXX
        s = heap_collect(s);
    }
    return s;
}

/** Reserve space in a heap.
 *
 *  /param hp   a heap
 *  /param sz   the requested size
 *  /return     a heap with at least sz free bytes
 */
heap_t* heap_reserve(heap_t* r, native_int sz) {
    TRACE;
 
    heap_t* s;
    s = r;
    if ((void*)heap_free(s) <= (void*)sz) {
        s = heap_collect(s);
    }
    while ((void*)heap_free(s) <= (void*)sz) {
        s = heap_resize(s, s->size + s->size);
    }

    return s;
}
 
/** Copy everything reachable (also outside the heap) to another
 *  heap.
 *
 *  Note: Doesn't handle cyclic structures.
 *
 *  /param from a source heap
 *  /param to   a destination heap
 *  /return     the destination heap
 */
heap_t* heap_copy_deep(heap_t* from, heap_t* to) {
    TRACE;

    native_int* copy_low;
    native_int* copy_high;
    native_int  i,j;
    native_int* p0;
    native_int* p1; 
    copy_low  = to->low;
    copy_high = to->low;

    ASSERT(from->root_free == 1);
    to->root_free = from->root_free;

    // copy the root to the to-space
    p0 = from->root[0];
    for (j = 0; j < p0[0]; j++) {
        copy_high[j] = p0[j];
    }
    to->root[0] = copy_high;
    copy_high = ADVANCE(copy_high, copy_high[0]);

    // copy all references to the to-space
    while (copy_low != copy_high)  {
        if (copy_low[1] != 0) {
            for (i = 2; i < copy_low[0]; i++) {
                p0 = (native_int*) copy_low[i];
                if (!IS_SMALL_INTEGER(p0)) {
                    for (j = 0; j < p0[0]; j++) copy_high[j] = p0[j];
                    copy_low[i] = (native_int) copy_high;
                    copy_high= ADVANCE(copy_high, copy_high[0]);
                }
            }
        }
        copy_low = ADVANCE(copy_low, copy_low[0]);
    }

    to->free = copy_low;

    return to;
}

/** Serialize a heap to memory.
 *
 *  /param hp   a heap
 *  /param p    a piece of memory
 *  /param sz   the size of the memory in bytes
 *  /return     the number of bytes used
 */
native_int heap_serialize(heap_t* t, void* p, native_int sz) {
    TRACE;

    // coerce p to a heap
    heap_t* r = (heap_t*) p;
    r->size = sz;
 
    r->low  = (native_int*) &(r->memory);
    r->high = INCREMENT(r, sz);
 
    r->free = r->low;
    r->root_free = 0;
    r->trigger_gc   = DECREMENT(r->high, HEAP_MINIMAL_FREE);
    r->trigger_grow = DECREMENT(r->high, HEAP_MINIMAL_GROW);
    // copy the first root to p
    native_int tmp;
    tmp = t->root_free;
    t->root_free = 1;
    heap_copy_deep(t, r);
    t->root_free = tmp;

    // return the size of the copied region
    return (native_int) (DIFFERENCE(r->free, r));
}

/** Unserialize memory to a heap.
 *
 *  /param hp   a heap
 *  /param p    a piece of memory
 *  /param sz   the size of the memory in bytes
 *  /return     the root of the structure copied
 */
native_int* heap_unserialize(heap_t* t, void* p, native_int sz) {
    TRACE;

    native_int i;
    heap_t* r = (heap_t*) p;

    // get the pointer offset...
    native_int offset = DIFFERENCE((&(r->memory)), r->root[0]);

    // alligning code
    r->low  = INCREMENT(r->low, offset);
    r->high = INCREMENT(r->high, offset);
    r->free = INCREMENT(r->free, offset);
    
    for (i = 0; i < ROOTS; i++) {
        r->root[i] = INCREMENT(r->root[i], offset);
    }
    native_int* low  = r->low;
    native_int* high = r->free;
    while (low != high)  {
        if (low[1] != 0) {
            for (i = 2; i < low[0]; i++) {
                if (!IS_SMALL_INTEGER(low[i])) {
                    low[i] = (native_int) 
                        INCREMENT((native_int*)low[i], offset);
                }
            }
        }
        low = ADVANCE(low, low[0]);
    }
    // copying code
    heap_copy(r, t);
    t->root_free--;
    return t->root[t->root_free];
}

/** The size, total number of integers/pointers, of a heap.
 *
 *  /param hp   a heap
 *  /return     the size of the heap
 */
native_int heap_size(heap_t* hp) {
    return (native_int)
        (((void*) hp->high - (void*) hp->low)/sizeof(native_int));
}

/** The free space, total number of unallocated integers/pointers, of a heap.
 *
 *  /param hp   a heap
 *  /return     the size of the free space of the heap
 */
native_int heap_free(heap_t* hp) {
    return (native_int)
        (((void*) hp->high - (void*) hp->free)/sizeof(native_int));
}

/** The used space, total number of allocated integers/pointers, of a heap.
 *
 *  /param hp   a heap
 *  /return     the size of the used space of the heap
 */
native_int heap_used(heap_t* hp) {
    return (native_int)
        (((void*) hp->free - (void*) hp->low)/sizeof(native_int));
}

/** Print some information to a file pointer.
 *
 *  /param f    a file pointer
 *  /param hp   a heap
 */
void heap_info(FILE* f, heap_t* hp) {
    fprintf(f, "**********************************************************************\n");
    fprintf(f, "GARBAGE COLLECTION INFORMATION\n");
    fprintf(f, "0-heap size           : %ld\n",   hp->size);
    fprintf(f, "0-heap low            : %p\n",    hp->low);
    fprintf(f, "0-heap high           : %p\n",    hp->high);
    fprintf(f, "0-heap free           : %p\n",    hp->free);
    fprintf(f, "0-heap roots          : %d\n",    hp->root_free);
    fprintf(f, "0-heap gc trigger     : %p\n",    hp->trigger_gc);
    fprintf(f, "0-heap grow trigger   : %p\n\n",  hp->trigger_grow);
    fprintf(f, "1-heap size           : %ld\n",   heap_size(hp));
    fprintf(f, "1-heap used           : %ld\n",   heap_used(hp));
    fprintf(f, "1-heap free           : %ld\n\n", heap_free(hp));
    fprintf(f, "2-heap minimal free   : %d\n",    HEAP_MINIMAL_FREE);
    fprintf(f, "2-heap grow           : %d\n\n",  HEAP_MINIMAL_GROW);
    fprintf(f, "**********************************************************************\n");
}

// just for debugging purposes, loops on cyclic structures
void heap_print_node(FILE* f, native_int* n) {
    unsigned int i;
    fprintf(f, "------------------------------------\n");
    fprintf(f, "node[%p].size : %d\n", (void*) n, (unsigned int) n[0]);
    fprintf(f, "node[%p].tag  : %d\n", (void*) n, (unsigned int) n[1]);
    if (n[1] == 0) {
        for (i = 2; i < n[0]; i++) {
            fprintf(f, "node[%p].[%d]  : %d\n", (void*) n, i, (unsigned int) n[i]);
        }
    } else {
        for (i = 2; i < n[0]; i++) {
            fprintf(f, "node[%p].[%d]  : %p\n", (void*) n, i, (void*) n[i]);
        }
        for (i = 2; i < n[0]; i++) {
            heap_print_node(f, (native_int*) n[i]);
        }
    }
}

// just for debugging purposes
void heap_debug(FILE* f, heap_t* hp) {
    native_int i;
    heap_info(f, hp);
    for (i = 0; i < hp->root_free; i++) {
        fprintf(f, "ROOT %d:\n", i);
        heap_print_node(f, hp->root[i]);
        fprintf(f, "**********************************************************************\n");
    }
}

Types

The is a scandalously wasteful manner of representing primitive values like constants, but it works for the moment. Every constant is preceded with a header, describing the size, a record tag, a type tag, and a series of bits describing the value. On Intel 64 architectures, it wastes memory like hell.

/** 
 *  COPYRIGHT 2008, M.C.A. Devillers
 *
 *  THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY
 *  EXPRESSED OR IMPLIED.  ANY USE IS AT YOUR OWN RISK.
 *
 *  Internally used types and conversions from them to native C types.
 *
 *  A number of these routines are added at the moment pure for 
 *  debugging purposes. It is expected the compiler compiles static 
 *  values to C static values.
 *
 *  A Hi value is either a constant, a record, or a thunk. They are
 *  tagged with record and type tags.
 *
 *  A type tag is a size bounded series of bits.
 *  
 *  t[0]    size of the tag (number of native_ints)
 *  t[1]    0
 *  t[2..]  bits which make up the tag
 *
 *  A constant is a size bounded, record and type tagged, series of
 *  bits.
 *
 *  c[0]    size of the constant (number of native_ints)
 *  c[1]    0
 *  c[2]    constant record field pointer
 *  c[3]    constant type field pointer
 *  c[4..]  bits which make up the constant (with unused extra bits)
 *
 *  A record is a size bounded, record and type tagged, series of
 *  pointers.
 *
 *  r[0]    size of the record (number of native_ints)
 *  r[1]    1
 *  r[2]    record record field pointer
 *  r[3]    record type field pointer
 *  r[4..]  pointers which make up the record
 *
 *  This encoding is pretty portable but uses a lot of bits. 
 *  For example, any simple integer value is encoded with 5 * 64 is 
 *  320 bytes on an Intel 64 bit system, not counting tags.
 *
 *  Bit packing, and using a seperate info record for rtti is for 
 *  later.
 *
 *  Assuming that most tags are shared, some of that burden is 
 *  aleviated. 
 */

#ifndef TYPES_H
#define TYPES_H

#include <stdlib.h>
#include <stdint.h>

 typedef uintptr_t native_int;

#define HI_SIZE(t) \
    ((sizeof(t) + sizeof(native_int) - 1)/(sizeof(native_int)))

typedef void                hi_void;

#if SCHAR_MAX == 127
typedef unsigned char       hi_uint8;
typedef signed char         hi_sint8;
#else
 #error "int8 size not supported"
#endif
#if SHRT_MAX == 32767
typedef unsigned short      hi_uint16;
typedef signed short        hi_sint16;
#elif INT_MAX == 32767
typedef unsigned int        hi_uint16;
typedef signed int          hi_sint16;
#else
 #error "int16 size not supported"
#endif
#if INT_MAX == 2147483647
typedef unsigned int        hi_uint32;
typedef signed int          hi_sint32;
#elif SHRT_MAX == 2147483647
typedef unsigned short      hi_uint32;
typedef signed short        hi_sint32;
#elif LONG_MAX == 2147483647
typedef unsigned long       hi_uint32;
typedef signed long         hi_sint32;
#else
 #error "int32 size not supported"
#endif
#if INT_MAX == 9223372036854775807
typedef unsigned int        hi_uint64;
typedef signed int          hi_sint64;
#elif LONG_MAX == 9223372036854775807
typedef unsigned long       hi_uint64;
typedef signed long         hi_sint64;
#else
 #error "int64 size not supported"
#endif
typedef float      hi_float;
typedef double     hi_double;
typedef void*      hi_pointer;


static native_int* system_bool_tag;
static native_int* system_char_tag;
static native_int* system_int_tag;
static native_int* system_float_tag;
static native_int* system_pointer_tag;
static native_int* system_false_tag;
static native_int* system_true_tag;


 native_int* convert_from_tag(char* v);
char*       convert_to_tag(native_int* v);


native_int* convert_from_uint8(hi_uint8 v);
native_int* convert_from_sint8(hi_sint8 v);
native_int* convert_from_uint16(hi_uint16 v);
native_int* convert_from_sint16(hi_sint16 v);
native_int* convert_from_uint32(hi_uint32 v);
native_int* convert_from_sint32(hi_sint32 v);
native_int* convert_from_uint64(hi_uint64 v);
native_int* convert_from_sint64(hi_sint64 v);
native_int* convert_from_float(hi_float v);
native_int* convert_from_double(hi_double v);
native_int* convert_from_pointer(hi_pointer v);
native_int* convert_from_string(char* v);

hi_uint8    convert_to_uint8(native_int* v);
hi_sint8    convert_to_sint8(native_int* v);
hi_uint16   convert_to_uint16(native_int* v);
hi_sint16   convert_to_sint16(native_int* v);
hi_uint32   convert_to_uint32(native_int* v);
hi_sint32   convert_to_sint32(native_int* v);
hi_uint64   convert_to_uint64(native_int* v);
hi_sint64   convert_to_sint64(native_int* v);
hi_float    convert_to_float(native_int* v);
hi_double   convert_to_double(native_int* v);
hi_pointer  convert_to_pointer(native_int* v);

char*       convert_to_string(native_int* v);
#endif // TYPES_H




/**
 *  COPYRIGHT 2008, M.C.A. Devillers
 *
 *  THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY
 *  EXPRESSED OR IMPLIED.  ANY USE IS AT YOUR OWN RISK.
 *
 */
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "ffi.h"
#include "types.h"
static native_int system_bool[20] = {
    2 + (11 / sizeof(native_int)),
    0,
    (native_int) "system.bool"
};
static native_int* system_bool_tag = system_bool;
static native_int system_char[20] = {
    2 + (11 / sizeof(native_int)),
    0,
    (native_int) "system.char"
};
static native_int* system_char_tag = system_char;
static native_int system_int[20] = {
    2 + (10 / sizeof(native_int)),
    0,
    (native_int) "system.int"
};
static native_int* system_int_tag = system_int;
static native_int system_float[20] = {
    2 + (12 / sizeof(native_int)),
    20,
    0,
    (native_int) "system.float"
};
static native_int* system_float_tag = system_float;
static native_int system_string[20] = {
    2 + (12 / sizeof(native_int)),
    20,
    0,
    (native_int) "system.float"
};
static native_int* system_string_tag = system_string;
//////////////////////////////////////////////////////////////////////
native_int* convert_from_tag(char* v) {
    int v_len = strnlen(v, 2048);
    //v[v_len] = (char) 0;
    int sz = 2 + (v_len + sizeof(native_int))/sizeof(native_int);
    native_int* n = (native_int*) malloc(sz * sizeof(native_int));
    n[0] = sz;
    n[1] = 0;
    n[2] = 0;
    char* p = (char*) &(n[2]);
    strcpy(p, v);
    return n;
}
char*       convert_to_tag(native_int* v) {
    char* p = (char*) (&(v[2]));
    return p;
}
//////////////////////////////////////////////////////////////////////
native_int* convert_from_uint8(hi_uint8 v) {
    int sz = 4 + HI_SIZE(hi_uint8);
    native_int* n = (native_int*) malloc(sz * sizeof(native_int));
    n[0] = sz;
    n[1] = 0;
    n[2] = (native_int) system_int;
    n[3] = (native_int) system_int;
    n[4] = (native_int) v;
    return n;
}
native_int* convert_from_sint8(hi_sint8 v) {
    int sz = 4 + HI_SIZE(hi_sint8);
    native_int* n = (native_int*) malloc(sz * sizeof(native_int));
    n[0] = sz;
    n[1] = 0;
    n[2] = (native_int) system_int;
    n[3] = (native_int) system_int;
    n[4] = (native_int) v;
    return n;
}
native_int* convert_from_uint16(hi_uint16 v) {
    int sz = 4 + HI_SIZE(hi_uint16);
    native_int* n = (native_int*) malloc(sz * sizeof(native_int));
    n[0] = sz;
    n[1] = 0;
    n[2] = (native_int) system_int;
    n[3] = (native_int) system_int;
    n[4] = (native_int) v;
    return n;
}

native_int* convert_from_sint16(hi_sint16 v) {
    int sz = 4 + HI_SIZE(hi_sint16);
    native_int* n = (native_int*) malloc(sz * sizeof(native_int));
    n[0] = sz;
    n[1] = 0;
    n[2] = (native_int) system_int;
    n[3] = (native_int) system_int;
    n[4] = (native_int) v;
    return n;
}

native_int* convert_from_uint32(hi_uint32 v) {
    int sz = 4 + HI_SIZE(hi_uint32);
    native_int* n = (native_int*) malloc(sz * sizeof(native_int));
    n[0] = sz;
    n[1] = 0;
    n[2] = (native_int) system_int;
    n[3] = (native_int) system_int;
    n[4] = (native_int) v;
    return n;
}

native_int* convert_from_sint32(hi_sint32 v) {
    int sz = 4 + HI_SIZE(hi_sint32);
    native_int* n = (native_int*) malloc(sz * sizeof(native_int));
    n[0] = sz;
    n[1] = 0;
    n[2] = (native_int) system_int;
    n[3] = (native_int) system_int;
    n[4] = (native_int) v;
    return n;
}

native_int* convert_from_uint64(hi_uint64 v) {
    int sz = 4 + HI_SIZE(hi_uint64);
    native_int* n = (native_int*) malloc(sz * sizeof(native_int));
    n[0] = sz;
    n[1] = 0;
    n[2] = (native_int) system_int;
    n[3] = (native_int) system_int;
    hi_uint64* p = (hi_uint64*) &(n[4]);
    *p = v;
    return n;
}

native_int* convert_from_sint64(hi_sint64 v) {
    int sz = 4 + HI_SIZE(hi_sint64);
    native_int* n = (native_int*) malloc(sz * sizeof(native_int));
    n[0] = sz;
    n[1] = 0;
    n[2] = (native_int) system_int;
    n[3] = (native_int) system_int;
    hi_sint64* p = (hi_uint64*) &(n[4]);
    *p = v;
    return n;
}

native_int* convert_from_float(hi_float v) {
    int sz = 4 + HI_SIZE(hi_float);
    native_int* n = (native_int*) malloc(sz * sizeof(native_int));
    n[0] = sz;
    n[1] = 0;
    n[2] = (native_int) system_int;
    n[3] = (native_int) system_int;
    hi_float* p = (hi_float*) &(n[4]);
    *p = v;
    return n;
}

native_int* convert_from_double(hi_double v) {
    int sz = 4 + HI_SIZE(hi_double);
    native_int* n = (native_int*) malloc(sz * sizeof(native_int));
    n[0] = sz;
    n[1] = 0;
    n[2] = (native_int) system_int;
    n[3] = (native_int) system_int;
    hi_double* p = (hi_double*) &(n[4]);
    *p = v;
    return n;
}

native_int* convert_from_pointer(hi_pointer v) {
    int sz = 4 + HI_SIZE(hi_pointer);
    native_int* n = (native_int*) malloc(sz * sizeof(native_int));
    n[0] = sz;
    n[1] = 0;
    n[2] = (native_int) system_int;
    n[3] = (native_int) system_int;
    hi_pointer* p = (hi_pointer*) &(n[4]);
    *p = v;
    return n;
}

native_int* convert_from_string(char* v) {
    int v_len = strnlen(v, 2048);
    //v[v_len] = (char) 0;
    int sz = 4 + (v_len + sizeof(native_int))/sizeof(native_int);
    native_int* n = (native_int*) malloc(sz * sizeof(native_int));
    n[0] = sz;
    n[1] = 0;
    n[2] = (native_int) system_string;
    n[3] = (native_int) system_string;
    char* p = (char*) &(n[4]);
    strcpy(p, v);
    return n;
}

//////////////////////////////////////////////////////////////////////

hi_uint8    convert_to_uint8(native_int* v) {
    hi_uint8* p = (hi_uint8*) (&(v[4]));
    return *p;
}

hi_sint8    convert_to_sint8(native_int* v) {
    hi_sint8* p = (hi_sint8*) (&(v[4]));
    return *p;
}

hi_uint16   convert_to_uint16(native_int* v) {
    hi_uint16* p = (hi_uint16*) (&(v[4]));
    return *p;
}

hi_sint16   convert_to_sint16(native_int* v) {
    hi_sint16* p = (hi_sint16*) (&(v[4]));
    return *p;
}

hi_uint32   convert_to_uint32(native_int* v) {
    hi_uint32* p = (hi_uint32*) (&(v[4]));
    return *p;
}

hi_sint32   convert_to_sint32(native_int* v) {
    hi_sint32* p = (hi_sint32*) (&(v[4]));
    return *p;
}

hi_uint64   convert_to_uint64(native_int* v) {
    hi_uint64* p = (hi_uint64*) (&(v[4]));
    return *p;
}

hi_sint64   convert_to_sint64(native_int* v) {
    hi_sint64* p = (hi_sint64*) (&(v[4]));
    return *p;
}

hi_float    convert_to_float(native_int* v) {
    hi_float* p = (hi_float*) (&(v[4]));
    return *p;
}

hi_double   convert_to_double(native_int* v) {
    hi_double* p = (hi_double*) (&(v[4]));
    return *p;
}

hi_pointer  convert_to_pointer(native_int* v) {
    hi_pointer* p = (hi_pointer*) (&(v[4]));
    return *p;
}

char*       convert_to_string(native_int* v) {
    char* p = (char*) (&(v[4]));
    return p;
}
//////////////////////////////////////////////////////////////////////


Note, this is fresh code. Spotted some bugs in the tags.

Assertions

I splitted the runtime into multiple parts. This is the most boring part, just used for debugging.

/**
 *  COPYRIGHT 2010, M.C.A. Devillers
 *
 *  THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY
 *  EXPRESSED OR IMPLIED.  ANY USE IS AT YOUR OWN RISK.
 *
 *  Two macros which handle debugging.
 */
#ifndef ASSERT_H
#define ASSERT_H
#include <stdlib.h>
#include <stdio.h>
#ifndef TRACE
#define TRACE \
    {fprintf(stdout, \
        "file %s: line %d: trace\n", \
            __FILE__, \
            __LINE__); \
    fflush(stdout);}
#endif
#ifndef ASSERT
#define ASSERT(b) \
    {if (!(b)) { \
        fprintf(stderr, \
            "file %s: line %d: assertion failed (%s)\n", \
                __FILE__, \
                __LINE__, \
                #b); \
        exit(1); \
        } \
    };
#endif
#ifndef DEBUG
#define DEBUG(b)    b
#endif
#endif // ASSERT_H

Monday, May 31, 2010

Uploading Stuff

I am programming again. I decided I might as well start uploading source code from my compiler, or at least the runtime, to this space. So, to everyone who is reading this, which probably adds up to me, watch it the coming weeks!

Lonely? Why not migrate to nowhere land?

Saturday, May 29, 2010

Long Time, No See

Now, this was a long break. I had some unfruitful discussion on LtU on purity, which led nowhere. I guess that was to be expected. A few of my colleagues, those who were interested in language theory, sometimes jokingly referred to LtU as a counter-example of professional discussion skills, and I can't say I really give a lot about the `academic opinion by common religion,' so I gave up on that.

Anyway. The language I have now has a runtime with a heap with one root which is GCed, the accompanying language is -what I would call for lack of a better definition- a `referentially transparent but impure' language. I.e., all data-structures are referentially transparent, but the side-effects to the OS may occur anywhere. Actually, I like it as it is, and I like programming in this manner.

I gave it some thought though. I don't like purity that much, and I would prefer variables which live at the module level such that you can program with normal stateful module hierarchies, even if I would prefer purity by default.

Since I am starting from scratch again, I am rewriting the runtime to be able to handle multiple roots. This goes a bit at the expense of a `nice' model where, for instance, serializing the heap with one root is good enough to give a good approximation where the full state of the running application can be recovered. So, I would need to rethink that, but, ah well, can't have it all.

Again?

OCaml Threads on Multi-core Processors

Project : OCaml Threads on Multi-core Processors
Authors :  
  Programmers   : Adrien Jonquet (Master student - UPMC Paris 6)   
              and Mathias Bourgoin (Master sudent  - UPMC Paris 6)
  Supervisors   : Benjamin Canou (Phd student - LIP6 - UPMC Paris 6), 
                  Philippe Wang  (Phd student - LIP6 - UPMC Paris 6)
              and Emmanuel Chailloux (Professor - LIP6 - UPMC Paris 6)

Abstract : 
      OCaml currently allows concurrent programming, with a serious
      restriction : only one thread can be executed at the same time.
      We propose to provide threads for OCaml which can be run in
      parallel, then concurrent programs can take advantage of the now
      widespread multicore CPUs.  To achieve this goal, we propose to
      write a new threads API based on an alternative compatible
      runtime, including a new garbage collector which allowing
      multithreading on multicore.

Threads in OCaml :

Historically, OCaml's runtime, including the garbage collector, has
been designed for Caml Light, its predecessor.  Caml Light's runtime
was efficient (which highly participated in its success), including a
garbage collector taking advantage of the absence of threads.

OCaml's garbage collector combines various techniques described in
detail in [1]. It works on two generations, the old and the young. It
mainly uses a Stop&Copy algorithm on the young generation (minor
garbage collection) and an incremental Mark&Sweep on the old
generation (major garbage collection).

The main idea of Stop&Copy is to use a secondary memory in order to
copy and compact the memory regions to be saved.  The heap is divided
into two parts: the useful part (called from-space), and the part
being re-written (called to-space).  It depends only on the size of
the objects to be kept, but implies to double the memory used by the
program.

The idea of Mark&Sweep is to mark memory blocks actually used in the
first phase, and to delete unmarked blocks in a second �sweeping�
phase.  It does not compact the memory after freeing it and depends on
the total size of the heap (not only the object to be kept), but can
be done incrementally.  A final ''Compact'' phase can be added, moving
the non freed block at the beginning of the heap.

A young object that survives a minor garbage collection is relocated
to the old generation. The Stop&Copy uses the old generation as the
to-space.  When it is finished, the entire from-space is completely
freed.

This GC algorithm cannot be executed with other threads running in
parallel. The Stop&Copy for young generation, and the Compact phase
for old generation, become invalid if another thread is accessing data
in the heap since they can move pointers.  These phases also modify
the internal structures of the runtime (like the base heap address) so
even if other threads are suspended before a GC call, they have to be
stopped before an access to these structures.

Moreover, allocation is done by decreasing the lower bound of the heap
(simply a pointer decrement without checking if another thread is
already performing an allocation).  As well, some primitives are coded
in a very optimised way, by directly modifying internal structures.
These practices are made possible by the assumption that only one
thread is running at a time.  Also, the compiler inlines some
allocations, which means that not only the runtime but also some code
emitted by the compiler makes assumptions on the absence of
parallelism.

Proposition : 

We want to add POSIX-like threads, which can be executed in parallel,
to OCaml.  Since the current runtime has not be designed with this
model in mind, we need to provide a compatible alternative runtime, in
particular a new garbage collector and a new allocator.

The main design choices for a GC, as described in [2] for Java, are
the following :
    1. serial versus parallel, 
    2. concurrent versus stop-the-world
and 3. compacting versus non-compacting versus copying.

We plan to design a simple garbage collection with mainly a unique
serial, stop-the-world, copying algorithm adapted for POSIX
threads. The current allocation as explained in the previous section
is really efficient, and this is very important for languages as OCaml
which allocate a lot of small objects. A first step will be to use a
similar mechanism encapsulated in a critical section. However, locking
a mutex for each allocation will probably cost too much so we will
then have to find a way to lock the heap less often (by using local
small heaps for example).

Programming a garbage collector is not a really long or difficult
task, but the debugging and tuning phases are on the contrary very
long because they require to find a good test base (which we intend to
publish along with the runtime), and difficult because the algorithm
may terminate without complaining but corrupting the memory, making
bugs appear later and anywhere in the code.  We also plan to provide
an interface to ease the plugging of another alternative garbage
collector into the runtime, and to write its documentation.

Finally, we intend to test the new runtime extensively, and to provide
benchmarks of concurrent algorithm on multicore machines, as well as
on sequential ones to determine the overhead compared to the current
garbage collector.

Conclusion :

Our project will be considered successful when we have an alternative
OCaml runtime allowing shared memory parallel threads and taking good
avantage of the now widespread multicore architectures.  This means
that a good-for-parallelism program running on an architecture with 2
or more cores should return the result quicker with the new runtime
than the same program with the current runtime. This work can be
evaluated to a 6 man-months project that we can split as : 5 months
for the programmers and 1 month for the supervisors. It can be started
at the end of May to finish before mid-August. The delivery will be
under open software licence as CeCILL-B
(http://www.cecill.info/index.en.html) and will contain the sources of
a small patch for the OCaml compiler, a new threads API, a compatible
runtime and a relevant set of tests.

References : 

[1] Emmanuel Chailloux, Pascal Manoury, and Bruno Pagano.
    Developing applications with Objective Caml 
    URL = http://caml.inria.fr/pub/docs/oreilly-book/.
    french version : O'Reilly, Apr 2000.

[2] Sun Microsystems.
    Memory management In The Hotspot Virtual Machine.
    Technical White Paper, Apr 2006.

[3] Bruno Pagano, Olivier Andrieu, Benjamin Canou, Emmanuel Chailloux, 
    Jean-Louis Cola�o, Thomas Moniot, and Philippe Wang.
    Certified Development Tools Implementation in OCaml.
    In Pratical Aspects of Declarative Laguages (PADL),
    pages 2--17, LNCS, Springer, San Francisco, Jan 2008.

[4] Esterel technologies.
    MLCov, a code coverage tool for OCaml with MC/DC, 
    URL = http://www.esterel-technologies.com/technology/free-software
    Dec, 2007.