shithub: femtolisp

ref: e7e5677d51c0c3bf605ecf35ca4e0ab8af3c90bf
dir: /femtolisp/cvalues.c/

View raw version
#define MAX_INL_SIZE 96
#ifdef BITS64
#define NWORDS(sz) (((sz)+7)>>3)
#else
#define NWORDS(sz) (((sz)+3)>>2)
#endif

static int struct_aligns[8] = {
    sizeof(struct { char a; int8_t i; }),
    sizeof(struct { char a; int16_t i; }),
    sizeof(struct { char a; char i[3]; }),
    sizeof(struct { char a; int32_t i; }),
    sizeof(struct { char a; char i[5]; }),
    sizeof(struct { char a; char i[6]; }),
    sizeof(struct { char a; char i[7]; }),
    sizeof(struct { char a; int64_t i; }) };
static int ALIGN2, ALIGN4, ALIGN8, ALIGNPTR;

typedef void (*cvinitfunc_t)(value_t, value_t, void*, void*);

value_t int8sym, uint8sym, int16sym, uint16sym, int32sym, uint32sym;
value_t int64sym, uint64sym;
value_t longsym, ulongsym, charsym, wcharsym;
value_t floatsym, doublesym;
value_t gftypesym, lispvaluesym, stringtypesym, wcstringtypesym;
value_t emptystringsym;

value_t structsym, arraysym, enumsym, cfunctionsym, voidsym, pointersym;
value_t unionsym;

value_t autoreleasesym, typeofsym, sizeofsym;

static void cvalue_init(value_t type, value_t v, void *dest);

void cvalue_print(ios_t *f, value_t v, int princ);
// exported guest functions
value_t cvalue_new(value_t *args, u_int32_t nargs);
value_t cvalue_sizeof(value_t *args, u_int32_t nargs);
value_t cvalue_typeof(value_t *args, u_int32_t nargs);

// compute the size of the metadata object for a cvalue
static size_t cv_nwords(cvalue_t *cv)
{
    if (cv->flags.prim) {
        if (cv->flags.inlined)
            return CPRIM_NWORDS_INL + NWORDS(cv->flags.inllen);
        return CPRIM_NWORDS;
    }
    if (cv->flags.inlined) {
        size_t s = CVALUE_NWORDS_INL +
            NWORDS(cv->flags.inllen + cv->flags.cstring);
        return (s < CVALUE_NWORDS) ? CVALUE_NWORDS : s;
    }
    return CVALUE_NWORDS;
}

void *cv_data(cvalue_t *cv)
{
    if (cv->flags.prim) {
        if (cv->flags.inlined) {
            return &((cprim_t*)cv)->data;
        }
        return ((cprim_t*)cv)->data;
    }
    else if (cv->flags.inlined) {
        return &cv->data;
    }
    return cv->data;
}

void *cvalue_data(value_t v)
{
    return cv_data((cvalue_t*)ptr(v));
}

static void autorelease(cvalue_t *cv)
{
    cv->flags.autorelease = 1;
    // TODO: add to finalizer list
}

value_t cvalue(value_t type, size_t sz)
{
    cvalue_t *pcv;

    if (issymbol(type)) {
        cprim_t *pcp;
        pcp = (cprim_t*)alloc_words(CPRIM_NWORDS_INL + NWORDS(sz));
        pcp->flagbits = INITIAL_FLAGS;
        pcp->flags.inllen = sz;
        pcp->flags.inlined = 1;
        pcp->flags.prim = 1;
        pcp->type = type;
        return tagptr(pcp, TAG_CVALUE);
    }
    PUSH(type);
    if (sz <= MAX_INL_SIZE) {
        size_t nw = CVALUE_NWORDS_INL + NWORDS(sz);
        pcv = (cvalue_t*)alloc_words((nw < CVALUE_NWORDS) ? CVALUE_NWORDS : nw);
        pcv->flagbits = INITIAL_FLAGS;
        pcv->flags.inllen = sz;
        pcv->flags.inlined = 1;
    }
    else {
        pcv = (cvalue_t*)alloc_words(CVALUE_NWORDS);
        pcv->flagbits = INITIAL_FLAGS;
        pcv->flags.inlined = 0;
        pcv->data = malloc(sz);
        pcv->len = sz;
        autorelease(pcv);
    }
    pcv->deps = NIL;
    pcv->type = POP();
    return tagptr(pcv, TAG_CVALUE);
}

value_t cvalue_from_data(value_t type, void *data, size_t sz)
{
    cvalue_t *pcv;
    value_t cv;
    cv = cvalue(type, sz);
    pcv = (cvalue_t*)ptr(cv);
    memcpy(cv_data(pcv), data, sz);
    return cv;
}

// this effectively dereferences a pointer
// just like *p in C, it only removes a level of indirection from the type,
// it doesn't copy any data.
// this method of creating a cvalue only allocates metadata.
// ptr is user-managed; we don't autorelease it unless the
// user explicitly calls (autorelease ) on the result of this function.
// 'parent' is an optional cvalue that this pointer is known to point
// into; NIL if none.
value_t cvalue_from_ref(value_t type, void *ptr, size_t sz, value_t parent)
{
    cvalue_t *pcv;
    value_t cv;

    PUSH(parent);
    PUSH(type);
    pcv = (cvalue_t*)alloc_words(CVALUE_NWORDS);
    pcv->flagbits = INITIAL_FLAGS;
    pcv->flags.inlined = 0;
    pcv->data = ptr;
    pcv->len = sz;
    pcv->deps = NIL;
    pcv->type = POP();
    parent = POP();
    if (parent != NIL) {
        // TODO: add dependency
    }
    cv = tagptr(pcv, TAG_CVALUE);
    return cv;
}

value_t cvalue_string(size_t sz)
{
    value_t cv;
    char *data;
    cvalue_t *pcv;

    if (sz == 0)
        return symbol_value(emptystringsym);
    // secretly allocate space for 1 more byte, hide a NUL there so
    // any string will always be NUL terminated.
    cv = cvalue(symbol_value(stringtypesym), sz+1);
    pcv = (cvalue_t*)ptr(cv);
    data = cv_data(pcv);
    data[sz] = '\0';
    if (pcv->flags.inlined)
        pcv->flags.inllen = sz;
    else
        pcv->len = sz;
    pcv->flags.cstring = 1;
    return cv;
}

value_t cvalue_static_cstring(char *str)
{
    value_t v = cvalue_from_ref(symbol_value(stringtypesym), str, strlen(str),
                                NIL);
    ((cvalue_t*)ptr(v))->flags.cstring = 1;
    return v;
}

value_t string_from_cstr(char *str)
{
    size_t n = strlen(str);
    value_t v = cvalue_string(n);
    memcpy(cvalue_data(v), str, n);
    return v;
}

int isstring(value_t v)
{
    return (iscvalue(v) && ((cvalue_t*)ptr(v))->flags.cstring);
}

// convert to malloc representation (fixed address)
/*
static void cv_pin(cvalue_t *cv)
{
    if (!cv->flags.inlined)
        return;
    size_t sz = cv->flags.inllen;
    void *data = malloc(sz);
    cv->flags.inlined = 0;
    // TODO: handle flags.cstring
    if (cv->flags.prim) {
        memcpy(data, (void*)(&((cprim_t*)cv)->data), sz);
        ((cprim_t*)cv)->data = data;
    }
    else {
        memcpy(data, (void*)(&cv->data), sz);
        cv->data = data;
    }
    autorelease(cv);
}
*/

#define num_ctor(typenam, cnvt, tag, fromstr)                           \
static void cvalue_##typenam##_init(value_t type, value_t arg,          \
                                    void *dest, void *data)             \
{                                                                       \
    typenam##_t n=0;                                                    \
    (void)data; (void)type;                                             \
    if (isfixnum(arg)) {                                                \
        n = numval(arg);                                                \
    }                                                                   \
    else if (iscvalue(arg)) {                                           \
        cvalue_t *cv = (cvalue_t*)ptr(arg);                             \
        void *p = cv_data(cv);                                          \
        if (valid_numtype(cv_numtype(cv)))                              \
            n = (typenam##_t)conv_to_##cnvt(p, cv_numtype(cv));         \
        else                                                            \
            goto cnvt_error;                                            \
    }                                                                   \
    else {                                                              \
        goto cnvt_error;                                                \
    }                                                                   \
    *((typenam##_t*)dest) = n;                                          \
    return;                                                             \
 cnvt_error:                                                            \
    type_error(#typenam, "number", arg);                                \
}                                                                       \
value_t cvalue_##typenam(value_t *args, u_int32_t nargs)                \
{                                                                       \
    if (nargs==0) { PUSH(fixnum(0)); args = &Stack[SP-1]; }             \
    value_t cv = cvalue(typenam##sym, sizeof(typenam##_t));             \
    ((cprim_t*)ptr(cv))->flags.numtype = tag;                           \
    cvalue_##typenam##_init(typenam##sym,                               \
                            args[0], &((cprim_t*)ptr(cv))->data, 0);    \
    return cv;                                                          \
}                                                                       \
value_t mk_##typenam(typenam##_t n)                                     \
{                                                                       \
    value_t cv = cvalue(typenam##sym, sizeof(typenam##_t));             \
    ((cprim_t*)ptr(cv))->flags.numtype = tag;                           \
    *(typenam##_t*)&((cprim_t*)ptr(cv))->data = n;                      \
    return cv;                                                          \
}

num_ctor(int8, int32, T_INT8, strtoi64)
num_ctor(uint8, uint32, T_UINT8, strtoui64)
num_ctor(int16, int32, T_INT16, strtoi64)
num_ctor(uint16, uint32, T_UINT16, strtoui64)
num_ctor(int32, int32, T_INT32, strtoi64)
num_ctor(uint32, uint32, T_UINT32, strtoui64)
num_ctor(int64, int64, T_INT64, strtoi64)
num_ctor(uint64, uint64, T_UINT64, strtoui64)
num_ctor(char, uint32, T_UINT8, strtoui64)
num_ctor(wchar, int32, T_INT32, strtoi64)
#ifdef BITS64
num_ctor(long, int64, T_INT64, strtoi64)
num_ctor(ulong, uint64, T_UINT64, strtoui64)
#else
num_ctor(long, int32, T_INT32, strtoi64)
num_ctor(ulong, uint32, T_UINT32, strtoui64)
#endif
num_ctor(float, double, T_FLOAT, strtodouble)
num_ctor(double, double, T_DOUBLE, strtodouble)

value_t size_wrap(size_t sz)
{
    if (fits_fixnum(sz))
        return fixnum(sz);
    assert(sizeof(void*) == sizeof(size_t));
    return mk_ulong(sz);
}

size_t toulong(value_t n, char *fname)
{
    if (isfixnum(n))
        return numval(n);
    if (iscvalue(n)) {
        cvalue_t *cv = (cvalue_t*)ptr(n);
        if (valid_numtype(cv_numtype(cv))) {
            return conv_to_ulong(cv_data(cv), cv_numtype(cv));
        }
    }
    type_error(fname, "number", n);
    return 0;
}

value_t char_from_code(uint32_t code)
{
    value_t ccode = fixnum(code);
    if (code > 0x7f)
        return cvalue_wchar(&ccode, 1);
    return cvalue_char(&ccode, 1);
}

static void cvalue_enum_init(value_t type, value_t arg, void *dest, void *data)
{
    int n=0;
    value_t syms;

    (void)data;
    syms = car(cdr(type));
    if (!iscons(syms))
        type_error("enum", "cons", syms);
    if (issymbol(arg)) {
        while (iscons(syms)) {
            if (car_(syms) == arg) {
                *(int*)dest = n;
                return;
            }
            n++;
            syms = cdr_(syms);
        }
        lerror(ArgError, "enum: invalid enum value");
    }
    if (isfixnum(arg)) {
        n = (int)numval(arg);
    }
    else if (iscvalue(arg)) {
        cvalue_t *cv = (cvalue_t*)ptr(arg);
        if (!valid_numtype(cv_numtype(cv)))
            type_error("enum", "number", arg);
        n = conv_to_int32(cv_data(cv), cv_numtype(cv));
    }
    if ((unsigned)n >= llength(syms))
        lerror(ArgError, "enum: value out of range");
    *(int*)dest = n;
}

value_t cvalue_enum(value_t *args, u_int32_t nargs)
{
    argcount("enum", nargs, 2);
    value_t cv = cvalue(list2(enumsym, args[0]), 4);
    ((cvalue_t*)ptr(cv))->flags.numtype = T_INT32;
    cvalue_enum_init(cv_type((cvalue_t*)ptr(cv)),
                     args[1], cv_data((cvalue_t*)ptr(cv)), NULL);
    return cv;
}

static void array_init_fromargs(char *dest, value_t *vals, size_t cnt,
                                value_t eltype, size_t elsize)
{
    size_t i;
    for(i=0; i < cnt; i++) {
        cvalue_init(eltype, vals[i], dest);
        dest += elsize;
    }
}

static int isarray(value_t v)
{
    if (!iscvalue(v)) return 0;
    value_t type = cv_type((cvalue_t*)ptr(v));
    return (iscons(type) && car_(type)==arraysym);
}

static size_t predict_arraylen(value_t arg)
{
    if (isvector(arg))
        return vector_size(arg);
    else if (iscons(arg))
        return llength(arg);
    else if (arg == NIL)
        return 0;
    if (isarray(arg))
        return cvalue_arraylen(arg);
    return 1;
}

static void cvalue_array_init(value_t type, value_t arg, void *dest, void *data)
{
    size_t elsize, i, cnt, sz;
    int junk;
    value_t eltype = car(cdr(type));

    if (data != 0)
        elsize = (size_t)data;  // already computed by constructor
    else
        elsize = ctype_sizeof(eltype, &junk);

    cnt = predict_arraylen(arg);

    if (iscons(cdr_(cdr_(type)))) {
        size_t tc = toulong(car_(cdr_(cdr_(type))), "array");
        if (tc != cnt)
            lerror(ArgError, "array: size mismatch");
    }

    sz = elsize * cnt;

    if (isvector(arg)) {
        array_init_fromargs((char*)dest, &vector_elt(arg,0), cnt,
                            eltype, elsize);
        return;
    }
    else if (iscons(arg) || arg==NIL) {
        i = 0;
        while (iscons(arg)) {
            if (SP >= N_STACK)
                break;
            PUSH(car_(arg));
            i++;
            arg = cdr_(arg);
        }
        if (i != cnt)
            lerror(ArgError, "array: size mismatch");
        array_init_fromargs((char*)dest, &Stack[SP-i], i, eltype, elsize);
        POPN(i);
        return;
    }
    else if (iscvalue(arg)) {
        cvalue_t *cv = (cvalue_t*)ptr(arg);
        if (isarray(arg)) {
            value_t aet = car(cdr(cv_type(cv)));
            if (aet == eltype) {
                if (cv_len(cv) == sz)
                    memcpy(dest, cv_data(cv), sz);
                else
                    lerror(ArgError, "array: size mismatch");
                return;
            }
            else {
                // TODO: initialize array from different type elements
                lerror(ArgError, "array: element type mismatch");
            }
        }
    }
    if (cnt == 1)
        cvalue_init(eltype, arg, dest);
    else
        type_error("array", "sequence", arg);
}

static value_t alloc_array(value_t type, size_t sz)
{
    value_t cv;
    if (car_(cdr_(type)) == charsym) {
        PUSH(type);
        cv = cvalue_string(sz);
        ((cvalue_t*)ptr(cv))->type = POP();
    }
    else {
        cv = cvalue(type, sz);
    }
    return cv;
}

value_t cvalue_array(value_t *args, u_int32_t nargs)
{
    size_t elsize, cnt, sz;
    int junk;

    if (nargs < 1)
        argcount("array", nargs, 1);

    cnt = nargs - 1;
    elsize = ctype_sizeof(args[0], &junk);
    sz = elsize * cnt;

    value_t cv = alloc_array(listn(3, arraysym, args[0], size_wrap(cnt)), sz);
    array_init_fromargs(cv_data((cvalue_t*)ptr(cv)), &args[1], cnt,
                        args[0], elsize);
    return cv;
}

// NOTE: v must be an array
size_t cvalue_arraylen(value_t v)
{
    cvalue_t *cv = (cvalue_t*)ptr(v);
    value_t type = cv_type(cv);

    if (iscons(cdr_(cdr_(type)))) {
        return toulong(car_(cdr_(cdr_(type))), "length");
    }
    // incomplete array type
    int junk;
    value_t eltype = car_(cdr_(type));
    size_t elsize = ctype_sizeof(eltype, &junk);
    return elsize ? cv_len(cv)/elsize : 0;
}

value_t cvalue_relocate(value_t v)
{
    size_t nw;
    cvalue_t *cv = (cvalue_t*)ptr(v);
    cvalue_t *nv;
    value_t ncv;

    if (!cv->flags.islispfunction) {
        nw = cv_nwords(cv);
        nv = (cvalue_t*)alloc_words(nw);
        memcpy(nv, cv, nw*sizeof(value_t));
        ncv = tagptr(nv, TAG_CVALUE);
        forward(v, ncv);
    }
    else {
        // guestfunctions are permanent objects, unmanaged
        nv = cv;
        ncv = v;
    }
    nv->type = relocate(nv->type);
    return ncv;
}

size_t cvalue_struct_offs(value_t type, value_t field, int computeTotal,
                          int *palign)
{
    value_t fld = car(cdr_(type));
    size_t fsz, ssz = 0;
    int al;
    *palign = 0;

    while (iscons(fld)) {
        fsz = ctype_sizeof(car(cdr(car_(fld))), &al);

        ssz = ALIGN(ssz, al);
        if (al > *palign)
            *palign = al;

        if (!computeTotal && field==car_(car_(fld))) {
            // found target field
            return ssz;
        }

        ssz += fsz;
        fld = cdr_(fld);
    }
    return ALIGN(ssz, *palign);
}

static size_t cvalue_union_size(value_t type, int *palign)
{
    value_t fld = car(cdr_(type));
    size_t fsz, usz = 0;
    int al;
    *palign = 0;

    while (iscons(fld)) {
        fsz = ctype_sizeof(car(cdr(car_(fld))), &al);
        if (al > *palign) *palign = al;
        if (fsz > usz) usz = fsz;
        fld = cdr_(fld);
    }
    return ALIGN(usz, *palign);
}

// *palign is an output argument giving the alignment required by type
size_t ctype_sizeof(value_t type, int *palign)
{
    if (type == int8sym || type == uint8sym || type == charsym) {
        *palign = 1;
        return 1;
    }
    if (type == int16sym || type == uint16sym) {
        *palign = ALIGN2;
        return 2;
    }
    if (type == int32sym || type == uint32sym || type == wcharsym ||
        type == floatsym) {
        *palign = ALIGN4;
        return 4;
    }
    if (type == int64sym || type == uint64sym || type == doublesym) {
        *palign = ALIGN8;
        return 8;
    }
    if (type == longsym || type == ulongsym) {
#ifdef BITS64
        *palign = ALIGN8;
        return 8;
#else
        *palign = ALIGN4;
        return 4;
#endif
    }
    if (iscons(type)) {
        value_t hed = car_(type);
        if (hed == pointersym || hed == cfunctionsym || hed == lispvaluesym) {
            *palign = ALIGNPTR;
            return sizeof(void*);
        }
        if (hed == arraysym) {
            value_t t = car(cdr_(type));
            if (!iscons(cdr_(cdr_(type))))
                lerror(ArgError, "sizeof: incomplete type");
            value_t n = car_(cdr_(cdr_(type)));
            size_t sz = toulong(n, "sizeof");
            return sz * ctype_sizeof(t, palign);
        }
        else if (hed == structsym) {
            return cvalue_struct_offs(type, NIL, 1, palign);
        }
        else if (hed == unionsym) {
            return cvalue_union_size(type, palign);
        }
        else if (hed == enumsym) {
            *palign = ALIGN4;
            return 4;
        }
    }
    lerror(ArgError, "sizeof: invalid c type");
    return 0;
}

value_t cvalue_sizeof(value_t *args, u_int32_t nargs)
{
    cvalue_t *cv;
    argcount("sizeof", nargs, 1);
    if (iscvalue(args[0])) {
        cv = (cvalue_t*)ptr(args[0]);
        return size_wrap(cv_len(cv));
    }
    int a;
    return size_wrap(ctype_sizeof(args[0], &a));
}

value_t cvalue_typeof(value_t *args, u_int32_t nargs)
{
    argcount("typeof", nargs, 1);
    switch(tag(args[0])) {
    case TAG_CONS: return conssym;
    case TAG_NUM1:
    case TAG_NUM:  return fixnumsym;
    case TAG_SYM:  return symbolsym;
    case TAG_VECTOR: return vectorsym;
    case TAG_BUILTIN: return builtinsym;
    }
    return cv_type((cvalue_t*)ptr(args[0]));
}

value_t cvalue_copy(value_t v)
{
    assert(iscvalue(v));
    PUSH(v);
    cvalue_t *cv = (cvalue_t*)ptr(v);
    size_t nw = cv_nwords(cv);
    value_t *pnv = alloc_words(nw);
    v = POP(); cv = (cvalue_t*)ptr(v);
    memcpy(pnv, cv, nw * sizeof(value_t));
    if (!cv->flags.inlined) {
        size_t len = cv_len(cv);
        if (cv->flags.cstring) len++;
        void *data = malloc(len);
        memcpy(data, cv_data(cv), len);
        if (cv->flags.prim)
            ((cprim_t*)pnv)->data = data;
        else
            ((cvalue_t*)pnv)->data = data;
        autorelease((cvalue_t*)pnv);
    }

    return tagptr(pnv, TAG_CVALUE);
}

static void cvalue_init(value_t type, value_t v, void *dest)
{
    cvinitfunc_t f;

    if (issymbol(type)) {
        f = ((symbol_t*)ptr(type))->dlcache;
    }
    else if (iscons(type)) {
        value_t head = car_(type);
        f = ((symbol_t*)ptr(head))->dlcache;
    }
    else {
        f = NULL;
    }
    if (f == NULL)
        lerror(ArgError, "c-value: invalid c type");

    f(type, v, dest, NULL);
}

static numerictype_t sym_to_numtype(value_t type)
{
    if (type == int8sym)
        return T_INT8;
    else if (type == uint8sym || type == charsym)
        return T_UINT8;
    else if (type == int16sym)
        return T_INT16;
    else if (type == uint16sym)
        return T_UINT16;
#ifdef BITS64
    else if (type == int32sym || type == wcharsym)
#else
    else if (type == int32sym || type == wcharsym || type == longsym)
#endif
        return T_INT32;
#ifdef BITS64
    else if (type == uint32sym)
#else
    else if (type == uint32sym || type == ulongsym)
#endif
        return T_UINT32;
#ifdef BITS64
    else if (type == int64sym || type == longsym)
#else
    else if (type == int64sym)
#endif
        return T_INT64;
#ifdef BITS64
    else if (type == uint64sym || type == ulongsym)
#else
    else if (type == uint64sym)
#endif
        return T_UINT64;
    assert(false);
    return N_NUMTYPES;
}

// (new type . args)
// this provides (1) a way to allocate values with a shared type for
// efficiency, (2) a uniform interface for allocating cvalues of any
// type, including user-defined.
value_t cvalue_new(value_t *args, u_int32_t nargs)
{
    if (nargs < 1 || nargs > 2)
        argcount("c-value", nargs, 2);
    value_t type = args[0];
    value_t cv;
    if (iscons(type) && car_(type) == arraysym) {
        // special case to handle incomplete array types bla[]
        value_t eltype = car(cdr_(type));
        int junk;
        size_t elsz = ctype_sizeof(eltype, &junk);
        size_t cnt;
        if (iscons(cdr_(cdr_(type))))
            cnt = toulong(car_(cdr_(cdr_(type))), "array");
        else if (nargs == 2)
            cnt = predict_arraylen(args[1]);
        else
            cnt = 0;
        cv = alloc_array(type, elsz * cnt);
        if (nargs == 2)
            cvalue_array_init(type, args[1], cv_data((cvalue_t*)ptr(cv)),
                              (void*)elsz);
    }
    else {
        int junk;
        cv = cvalue(type, ctype_sizeof(type, &junk));
        if (issymbol(type)) {
            ((cvalue_t*)ptr(cv))->flags.numtype = sym_to_numtype(type);
        }
        if (nargs == 2)
            cvalue_init(type, args[1], cv_data((cvalue_t*)ptr(cv)));
    }
    return cv;
}

// NOTE: this only compares lexicographically; it ignores numeric formats
value_t cvalue_compare(value_t a, value_t b)
{
    cvalue_t *ca = (cvalue_t*)ptr(a);
    cvalue_t *cb = (cvalue_t*)ptr(b);
    char *adata = cv_data(ca);
    char *bdata = cv_data(cb);
    size_t asz = cv_len(ca);
    size_t bsz = cv_len(cb);
    size_t minsz = asz < bsz ? asz : bsz;
    int diff = memcmp(adata, bdata, minsz);
    if (diff == 0) {
        if (asz > bsz)
            return fixnum(1);
        else if (asz < bsz)
            return fixnum(-1);
    }
    return fixnum(diff);
}

static void check_addr_args(char *fname, size_t typesize, value_t *args,
                            void **data, ulong_t *index)
{
    size_t sz;
    if (!iscvalue(args[0]))
        type_error(fname, "cvalue", args[0]);
    *data = cv_data((cvalue_t*)ptr(args[0]));
    sz = cv_len((cvalue_t*)ptr(args[0]));
    cvalue_t *cv = (cvalue_t*)ptr(args[1]);
    if (isfixnum(args[1]))
        *index = numval(args[1]);
    else if (!iscvalue(args[1]) || !valid_numtype(cv_numtype(cv)))
        type_error(fname, "number", args[1]);
    else
        *index = conv_to_ulong(cv_data(cv), cv_numtype(cv));
    if (*index > sz - typesize)
        bounds_error(fname, args[0], args[1]);
}

value_t cvalue_get_int8(value_t *args, u_int32_t nargs)
{
    void *data; ulong_t index;
    argcount("get-int8", nargs, 2);
    check_addr_args("get-int8", sizeof(int8_t), args, &data, &index);
    return fixnum(((int8_t*)data)[index]);
}

value_t cvalue_set_int8(value_t *args, u_int32_t nargs)
{
    void *data; ulong_t index; int32_t val=0;
    argcount("set-int8", nargs, 3);
    check_addr_args("set-int8", sizeof(int8_t), args, &data, &index);
    cvalue_t *cv = (cvalue_t*)ptr(args[2]);
    if (isfixnum(args[2]))
        val = numval(args[2]);
    else if (!iscvalue(args[2]) || !valid_numtype(cv_numtype(cv)))
        type_error("set-int8", "number", args[2]);
    else
        val = conv_to_int32(cv_data(cv), cv_numtype(cv));
    ((int8_t*)data)[index] = val;
    return args[2];
}

value_t guestfunc(guestfunc_t f)
{
    value_t gf = cvalue(symbol_value(gftypesym), sizeof(void*));
    ((cvalue_t*)ptr(gf))->data = f;
    ((cvalue_t*)ptr(gf))->flags.islispfunction = 1;
    size_t nw = cv_nwords((cvalue_t*)ptr(gf));
    // directly-callable values are assumed not to move for
    // evaluator performance, so put guestfunction metadata on the
    // unmanaged heap
    cvalue_t *buf = malloc_aligned(nw * sizeof(value_t), 8);
    memcpy(buf, ptr(gf), nw*sizeof(value_t));
    return tagptr(buf, TAG_BUILTIN);
}

#define cv_intern(tok) tok##sym = symbol(#tok)
#define ctor_cv_intern(tok) cv_intern(tok); set(tok##sym, guestfunc(cvalue_##tok))
#define symbol_dlcache(s) (((symbol_t*)ptr(s))->dlcache)
#define cache_initfunc(tok) symbol_dlcache(tok##sym) = &cvalue_##tok##_init

void cvalues_init()
{
    int i;

    // compute struct field alignment required for primitives of sizes 1-8
    for(i=0; i < 8; i++)
        struct_aligns[i] -= (i+1);
    ALIGN2 = struct_aligns[1];
    ALIGN4 = struct_aligns[3];
    ALIGN8 = struct_aligns[7];
    ALIGNPTR = struct_aligns[sizeof(void*)-1];

    cv_intern(uint32);
    cv_intern(pointer);
    cfunctionsym = symbol("c-function");
    cv_intern(lispvalue);
    gftypesym = symbol("*guest-function-type*");
    setc(gftypesym, listn(3, cfunctionsym, lispvaluesym,
                          list2(list2(pointersym, lispvaluesym), uint32sym)));
    set(uint32sym, guestfunc(cvalue_uint32));

    ctor_cv_intern(int8);
    ctor_cv_intern(uint8);
    ctor_cv_intern(int16);
    ctor_cv_intern(uint16);
    ctor_cv_intern(int32);
    ctor_cv_intern(int64);
    ctor_cv_intern(uint64);
    ctor_cv_intern(char);
    ctor_cv_intern(wchar);
    ctor_cv_intern(long);
    ctor_cv_intern(ulong);
    ctor_cv_intern(float);
    ctor_cv_intern(double);

    ctor_cv_intern(array);
    ctor_cv_intern(enum);
    cv_intern(struct);
    cv_intern(union);
    cv_intern(void);
    set(symbol("c-value"), guestfunc(cvalue_new));
    set(symbol("get-int8"), guestfunc(cvalue_get_int8));
    set(symbol("set-int8"), guestfunc(cvalue_set_int8));

    cv_intern(autorelease);
    ctor_cv_intern(typeof);
    ctor_cv_intern(sizeof);

    // set up references to the init functions for each primitive type.
    // this is used for fast access in constructors for compound types
    // like arrays that need to initialize (but not allocate) elements.
    cache_initfunc(int8);
    cache_initfunc(uint8);
    cache_initfunc(int16);
    cache_initfunc(uint16);
    cache_initfunc(int32);
    cache_initfunc(uint32);
    cache_initfunc(int64);
    cache_initfunc(uint64);
    cache_initfunc(char);
    cache_initfunc(wchar);
    cache_initfunc(long);
    cache_initfunc(ulong);
    cache_initfunc(float);
    cache_initfunc(double);

    cache_initfunc(array);
    cache_initfunc(enum);

    stringtypesym = symbol("*string-type*");
    setc(stringtypesym, list2(arraysym, charsym));

    wcstringtypesym = symbol("*wcstring-type*");
    setc(wcstringtypesym, list2(arraysym, wcharsym));

    emptystringsym = symbol("*empty-string*");
    setc(emptystringsym, cvalue_static_cstring(""));
}

#define RETURN_NUM_AS(var, type) return(mk_##type((type##_t)var))

value_t return_from_uint64(uint64_t Uaccum)
{
    if (fits_fixnum(Uaccum)) {
        return fixnum((fixnum_t)Uaccum);
    }
    if (Uaccum > (uint64_t)S64_MAX) {
        RETURN_NUM_AS(Uaccum, uint64);
    }
    else if (Uaccum > (uint64_t)UINT_MAX) {
        RETURN_NUM_AS(Uaccum, int64);
    }
    else if (Uaccum > (uint64_t)INT_MAX) {
        RETURN_NUM_AS(Uaccum, uint32);
    }
    RETURN_NUM_AS(Uaccum, int32);
}

value_t return_from_int64(int64_t Saccum)
{
    if (fits_fixnum(Saccum)) {
        return fixnum((fixnum_t)Saccum);
    }
    if (Saccum > (int64_t)UINT_MAX || Saccum < (int64_t)INT_MIN) {
        RETURN_NUM_AS(Saccum, int64);
    }
    else if (Saccum > (int64_t)INT_MAX) {
        RETURN_NUM_AS(Saccum, uint32);
    }
    RETURN_NUM_AS(Saccum, int32);
}

value_t fl_add_any(value_t *args, u_int32_t nargs, fixnum_t carryIn)
{
    uint64_t Uaccum=0;
    int64_t Saccum = carryIn;
    double Faccum=0;
    uint32_t i;

    for(i=0; i < nargs; i++) {
        if (isfixnum(args[i])) {
            Saccum += numval(args[i]);
            continue;
        }
        else if (iscvalue(args[i])) {
            cvalue_t *cv = (cvalue_t*)ptr(args[i]);
            void *a = cv_data(cv);
            int64_t i64;
            switch(cv_numtype(cv)) {
            case T_INT8:   Saccum += *(int8_t*)a; break;
            case T_UINT8:  Saccum += *(uint8_t*)a; break;
            case T_INT16:  Saccum += *(int16_t*)a; break;
            case T_UINT16: Saccum += *(uint16_t*)a; break;
            case T_INT32:  Saccum += *(int32_t*)a; break;
            case T_UINT32: Saccum += *(uint32_t*)a; break;
            case T_INT64:
                i64 = *(int64_t*)a;
                if (i64 > 0)
                    Uaccum += (uint64_t)i64;
                else
                    Saccum += i64;
                break;
            case T_UINT64: Uaccum += *(uint64_t*)a; break;
            case T_FLOAT:  Faccum += *(float*)a; break;
            case T_DOUBLE: Faccum += *(double*)a; break;
            default:
                goto add_type_error;
            }
            continue;
        }
    add_type_error:
        type_error("+", "number", args[i]);
    }
    if (Faccum != 0) {
        Faccum += Uaccum;
        Faccum += Saccum;
        return mk_double(Faccum);
    }
    else if (Saccum < 0) {
        uint64_t negpart = (uint64_t)(-Saccum);
        if (negpart > Uaccum) {
            Saccum += (int64_t)Uaccum;
            // return value in Saccum
            if (Saccum >= INT_MIN) {
                if (fits_fixnum(Saccum)) {
                    return fixnum((fixnum_t)Saccum);
                }
                RETURN_NUM_AS(Saccum, int32);
            }
            RETURN_NUM_AS(Saccum, int64);
        }
        Uaccum -= negpart;
    }
    else {
        Uaccum += (uint64_t)Saccum;
    }
    // return value in Uaccum
    return return_from_uint64(Uaccum);
}

value_t fl_neg(value_t n)
{
    if (isfixnum(n)) {
        return fixnum(-numval(n));
    }
    else if (iscvalue(n)) {
        cvalue_t *cv = (cvalue_t*)ptr(n);
        void *a = cv_data(cv);
        uint32_t ui32;
        int32_t i32;
        int64_t i64;
        switch(cv_numtype(cv)) {
        case T_INT8:   return fixnum(-(int32_t)*(int8_t*)a);
        case T_UINT8:  return fixnum(-(int32_t)*(uint8_t*)a);
        case T_INT16:  return fixnum(-(int32_t)*(int16_t*)a);
        case T_UINT16: return fixnum(-(int32_t)*(uint16_t*)a);
        case T_INT32:
            i32 = *(int32_t*)a;
            if (i32 == (int32_t)BIT31)
                return mk_uint32((uint32_t)BIT31);
            return mk_int32(-i32);
        case T_UINT32:
            ui32 = *(uint32_t*)a;
            if (ui32 <= ((uint32_t)INT_MAX)+1) return mk_int32(-(int32_t)ui32);
            return mk_int64(-(int64_t)ui32);
        case T_INT64:
            i64 = *(int64_t*)a;
            if (i64 == (int64_t)BIT63)
                return mk_uint64((uint64_t)BIT63);
            return mk_int64(-i64);
        case T_UINT64: return mk_int64(-(int64_t)*(uint64_t*)a);
        case T_FLOAT:  return mk_float(-*(float*)a);
        case T_DOUBLE: return mk_double(-*(double*)a);
            break;
        }
    }
    type_error("-", "number", n);
}

value_t fl_mul_any(value_t *args, u_int32_t nargs, int64_t Saccum)
{
    uint64_t Uaccum=1;
    double Faccum=1;
    uint32_t i;

    for(i=0; i < nargs; i++) {
        if (isfixnum(args[i])) {
            Saccum *= numval(args[i]);
            continue;
        }
        else if (iscvalue(args[i])) {
            cvalue_t *cv = (cvalue_t*)ptr(args[i]);
            void *a = cv_data(cv);
            int64_t i64;
            switch(cv_numtype(cv)) {
            case T_INT8:   Saccum *= *(int8_t*)a; break;
            case T_UINT8:  Saccum *= *(uint8_t*)a; break;
            case T_INT16:  Saccum *= *(int16_t*)a; break;
            case T_UINT16: Saccum *= *(uint16_t*)a; break;
            case T_INT32:  Saccum *= *(int32_t*)a; break;
            case T_UINT32: Saccum *= *(uint32_t*)a; break;
            case T_INT64:
                i64 = *(int64_t*)a;
                if (i64 > 0)
                    Uaccum *= (uint64_t)i64;
                else
                    Saccum *= i64;
                break;
            case T_UINT64: Uaccum *= *(uint64_t*)a; break;
            case T_FLOAT:  Faccum *= *(float*)a; break;
            case T_DOUBLE: Faccum *= *(double*)a; break;
            default:
                goto mul_type_error;
            }
            continue;
        }
    mul_type_error:
        type_error("*", "number", args[i]);
    }
    if (Faccum != 1) {
        Faccum *= Uaccum;
        Faccum *= Saccum;
        return mk_double(Faccum);
    }
    else if (Saccum < 0) {
        Saccum *= (int64_t)Uaccum;
        if (Saccum >= INT_MIN) {
            if (fits_fixnum(Saccum)) {
                return fixnum((fixnum_t)Saccum);
            }
            RETURN_NUM_AS(Saccum, int32);
        }
        RETURN_NUM_AS(Saccum, int64);
    }
    else {
        Uaccum *= (uint64_t)Saccum;
    }
    return return_from_uint64(Uaccum);
}

value_t fl_div2(value_t a, value_t b)
{
    double da, db;
    int_t ai, bi;
    int ta, tb;
    void *aptr=NULL, *bptr=NULL;
    cvalue_t *cv;

    if (isfixnum(a)) {
        ai = numval(a);
        aptr = &ai;
        ta = T_FIXNUM;
    }
    else if (iscvalue(a)) {
        cv = (cvalue_t*)ptr(a);
        ta = cv_numtype(cv);
        if (ta <= T_DOUBLE)
            aptr = cv_data(cv);
    }
    if (aptr == NULL)
        type_error("/", "number", a);
    if (isfixnum(b)) {
        bi = numval(b);
        bptr = &bi;
        tb = T_FIXNUM;
    }
    else if (iscvalue(b)) {
        cv = (cvalue_t*)ptr(b);
        tb = cv_numtype(cv);
        if (tb <= T_DOUBLE)
            bptr = cv_data(cv);
    }
    if (bptr == NULL)
        type_error("/", "number", b);

    if (ta == T_FLOAT) {
        db = conv_to_double(bptr, tb);
        da = (double)*(float*)aptr / db;
        return mk_double(da);
    }
    if (ta == T_DOUBLE) {
        db = conv_to_double(bptr, tb);
        da = *(double*)aptr / db;
        return mk_double(da);
    }
    if (tb == T_FLOAT) {
        da = conv_to_double(aptr, ta);
        da /= (double)*(float*)bptr;
        return mk_double(da);
    }
    if (tb == T_DOUBLE) {
        da = conv_to_double(aptr, ta);
        da /= *(double*)bptr;
        return mk_double(da);
    }

    int64_t a64, b64;

    if (ta == T_UINT64) {
        if (tb == T_UINT64) {
            if (*(uint64_t*)bptr == 0) goto div_error;
            return return_from_uint64(*(uint64_t*)aptr / *(uint64_t*)bptr);
        }
        b64 = conv_to_int64(bptr, tb);
        if (b64 < 0) {
            return return_from_int64(-(int64_t)(*(uint64_t*)aptr /
                                                (uint64_t)(-b64)));
        }
        if (b64 == 0)
            goto div_error;
        return return_from_uint64(*(uint64_t*)aptr / (uint64_t)b64);
    }
    if (tb == T_UINT64) {
        if (*(uint64_t*)bptr == 0) goto div_error;
        a64 = conv_to_int64(aptr, ta);
        if (a64 < 0) {
            return return_from_int64(-((int64_t)((uint64_t)(-a64) /
                                                 *(uint64_t*)bptr)));
        }
        return return_from_uint64((uint64_t)a64 / *(uint64_t*)bptr);
    }

    b64 = conv_to_int64(bptr, tb);
    if (b64 == 0) goto div_error;

    return return_from_int64(conv_to_int64(aptr, ta) / b64);
 div_error:
    lerror(DivideError, "/: division by zero");
}

static void *int_data_ptr(value_t a, int *pnumtype, char *fname)
{
    cvalue_t *cv;
    if (iscvalue(a)) {
        cv = (cvalue_t*)ptr(a);
        *pnumtype = cv_numtype(cv);
        if (*pnumtype < T_FLOAT)
            return cv_data(cv);
    }
    type_error(fname, "integer", a);
    return NULL;
}

value_t fl_bitwise_not(value_t a)
{
    cvalue_t *cv;
    int ta;
    void *aptr;

    if (iscvalue(a)) {
        cv = (cvalue_t*)ptr(a);
        ta = cv_numtype(cv);
        aptr = cv_data(cv);
        switch (ta) {
        case T_INT8:   return mk_int8(~*(int8_t *)aptr);
        case T_UINT8:  return mk_uint8(~*(uint8_t *)aptr);
        case T_INT16:  return mk_int16(~*(int16_t *)aptr);
        case T_UINT16: return mk_uint16(~*(uint16_t*)aptr);
        case T_INT32:  return mk_int32(~*(int32_t *)aptr);
        case T_UINT32: return mk_uint32(~*(uint32_t*)aptr);
        case T_INT64:  return mk_int64(~*(int64_t *)aptr);
        case T_UINT64: return mk_uint64(~*(uint64_t*)aptr);
        }
    }
    type_error("~", "integer", a);
    return NIL;
}

#define BITSHIFT_OP(name, op)                                       \
value_t fl_##name(value_t a, int n)                                 \
{                                                                   \
    cvalue_t *cv;                                                   \
    int ta;                                                         \
    void *aptr;                                                     \
    if (iscvalue(a)) {                                              \
        cv = (cvalue_t*)ptr(a);                                     \
        ta = cv_numtype(cv);                                        \
        aptr = cv_data(cv);                                         \
        switch (ta) {                                               \
        case T_INT8:   return mk_int8((*(int8_t *)aptr) op n);      \
        case T_UINT8:  return mk_uint8((*(uint8_t *)aptr) op n);    \
        case T_INT16:  return mk_int16((*(int16_t *)aptr) op n);    \
        case T_UINT16: return mk_uint16((*(uint16_t*)aptr) op n);   \
        case T_INT32:  return mk_int32((*(int32_t *)aptr) op n);    \
        case T_UINT32: return mk_uint32((*(uint32_t*)aptr) op n);   \
        case T_INT64:  return mk_int64((*(int64_t *)aptr) op n);    \
        case T_UINT64: return mk_uint64((*(uint64_t*)aptr) op n);   \
        }                                                           \
    }                                                               \
    type_error(#op, "integer", a);                                  \
    return NIL;                                                     \
}
BITSHIFT_OP(shl,<<)
BITSHIFT_OP(shr,>>)

value_t fl_bitwise_op(value_t a, value_t b, int opcode, char *fname)
{
    int_t ai, bi;
    int ta, tb, itmp;
    void *aptr=NULL, *bptr=NULL, *ptmp;
    int64_t b64;

    if (isfixnum(a)) {
        ta = T_FIXNUM;
        ai = numval(a);
        aptr = &ai;
        bptr = int_data_ptr(b, &tb, fname);
    }
    else {
        aptr = int_data_ptr(a, &ta, fname);
        if (isfixnum(b)) {
            tb = T_FIXNUM;
            bi = numval(b);
            bptr = &bi;
        }
        else {
            bptr = int_data_ptr(b, &tb, fname);
        }
    }
    if (ta < tb) {
        itmp = ta; ta = tb; tb = itmp;
        ptmp = aptr; aptr = bptr; bptr = ptmp;
    }
    // now a's type is larger than or same as b's
    b64 = conv_to_int64(bptr, tb);
    switch (opcode) {
    case 0:
    switch (ta) {
    case T_INT8:   return mk_int8(  *(int8_t *)aptr  & (int8_t  )b64);
    case T_UINT8:  return mk_uint8( *(uint8_t *)aptr & (uint8_t )b64);
    case T_INT16:  return mk_int16( *(int16_t*)aptr  & (int16_t )b64);
    case T_UINT16: return mk_uint16(*(uint16_t*)aptr & (uint16_t)b64);
    case T_INT32:  return mk_int32( *(int32_t*)aptr  & (int32_t )b64);
    case T_UINT32: return mk_uint32(*(uint32_t*)aptr & (uint32_t)b64);
    case T_INT64:  return mk_int64( *(int64_t*)aptr  & (int64_t )b64);
    case T_UINT64: return mk_uint64(*(uint64_t*)aptr & (uint64_t)b64);
    }
    break;
    case 1:
    switch (ta) {
    case T_INT8:   return mk_int8(  *(int8_t *)aptr  | (int8_t  )b64);
    case T_UINT8:  return mk_uint8( *(uint8_t *)aptr | (uint8_t )b64);
    case T_INT16:  return mk_int16( *(int16_t*)aptr  | (int16_t )b64);
    case T_UINT16: return mk_uint16(*(uint16_t*)aptr | (uint16_t)b64);
    case T_INT32:  return mk_int32( *(int32_t*)aptr  | (int32_t )b64);
    case T_UINT32: return mk_uint32(*(uint32_t*)aptr | (uint32_t)b64);
    case T_INT64:  return mk_int64( *(int64_t*)aptr  | (int64_t )b64);
    case T_UINT64: return mk_uint64(*(uint64_t*)aptr | (uint64_t)b64);
    }
    break;
    case 2:
    switch (ta) {
    case T_INT8:   return mk_int8(  *(int8_t *)aptr  ^ (int8_t  )b64);
    case T_UINT8:  return mk_uint8( *(uint8_t *)aptr ^ (uint8_t )b64);
    case T_INT16:  return mk_int16( *(int16_t*)aptr  ^ (int16_t )b64);
    case T_UINT16: return mk_uint16(*(uint16_t*)aptr ^ (uint16_t)b64);
    case T_INT32:  return mk_int32( *(int32_t*)aptr  ^ (int32_t )b64);
    case T_UINT32: return mk_uint32(*(uint32_t*)aptr ^ (uint32_t)b64);
    case T_INT64:  return mk_int64( *(int64_t*)aptr  ^ (int64_t )b64);
    case T_UINT64: return mk_uint64(*(uint64_t*)aptr ^ (uint64_t)b64);
    }
    }
    assert(0);
    return NIL;
}