#include "moar.h"

/* Representation function convenience accessors. Could potentially be made into
 * macros in the future, but hopefully the compiler is smart enough to inline
 * them anyway. */

void MVM_repr_init(MVMThreadContext *tc, MVMObject *obj) {
    if (REPR(obj)->initialize)
        REPR(obj)->initialize(tc, STABLE(obj), obj, OBJECT_BODY(obj));
}

MVMObject * MVM_repr_alloc(MVMThreadContext *tc, MVMObject *type) {
    return REPR(type)->allocate(tc, STABLE(type));
}

MVMObject * MVM_repr_alloc_init(MVMThreadContext *tc, MVMObject *type) {
    MVMObject *obj = REPR(type)->allocate(tc, STABLE(type));

    if (REPR(obj)->initialize) {
        MVMROOT(tc, obj) {
            REPR(obj)->initialize(tc, STABLE(obj), obj, OBJECT_BODY(obj));
        }
    }

    return obj;
}

MVMObject * MVM_repr_clone(MVMThreadContext *tc, MVMObject *obj) {
    MVMObject *res;
    if (IS_CONCRETE(obj)) {
        MVM_gc_root_temp_push(tc, (MVMCollectable **)&obj);
        res = REPR(obj)->allocate(tc, STABLE(obj));
        MVM_gc_root_temp_push(tc, (MVMCollectable **)&res);
        REPR(obj)->copy_to(tc, STABLE(obj), OBJECT_BODY(obj), res, OBJECT_BODY(res));
        MVM_gc_root_temp_pop_n(tc, 2);
    } else {
        res = obj;
    }
    return res;
}

void MVM_repr_compose(MVMThreadContext *tc, MVMObject *type, MVMObject *obj) {
    REPR(type)->compose(tc, STABLE(type), obj);
}

MVM_PUBLIC void MVM_repr_pos_set_elems(MVMThreadContext *tc, MVMObject *obj, MVMint64 elems) {
    REPR(obj)->pos_funcs.set_elems(tc, STABLE(obj), obj,
        OBJECT_BODY(obj), elems);
}

void MVM_repr_populate_indices_array(MVMThreadContext *tc, MVMObject *arr, MVMint64 *elems) {
    MVMint64 i;
    *elems = MVM_repr_elems(tc, arr);
    if (*elems > tc->num_multi_dim_indices)
        tc->multi_dim_indices = MVM_realloc(tc->multi_dim_indices,
            *elems * sizeof(MVMint64));
    for (i = 0; i < *elems; i++)
        tc->multi_dim_indices[i] = MVM_repr_at_pos_i(tc, arr, i);
}

void MVM_repr_set_dimensions(MVMThreadContext *tc, MVMObject *obj, MVMObject *dims) {
    if (IS_CONCRETE(obj)) {
        MVMint64 num_dims;
        MVM_repr_populate_indices_array(tc, dims, &num_dims);
        REPR(obj)->pos_funcs.set_dimensions(tc, STABLE(obj), obj,
            OBJECT_BODY(obj), num_dims, tc->multi_dim_indices);
    }
    else {
        MVM_exception_throw_adhoc(tc, "Cannot set dimensions on a type object. Did you forget a '.new'?");
    }
}

MVM_PUBLIC MVMObject * MVM_repr_pos_slice(MVMThreadContext *tc, MVMObject *src, MVMint64 start, MVMint64 end) {
    MVMObject *dest = NULL;
    MVMROOT(tc, src) {
        dest = MVM_repr_alloc_init(tc, src);
        REPR(src)->pos_funcs.slice(tc, STABLE(src), src, OBJECT_BODY(src), dest, start, end);
    }
    return dest;
}

MVM_PUBLIC void MVM_repr_pos_splice(MVMThreadContext *tc, MVMObject *obj, MVMObject *replacement, MVMint64 offset, MVMint64 count) {
    REPR(obj)->pos_funcs.splice(tc, STABLE(obj), obj,
        OBJECT_BODY(obj), replacement,
        offset, count);
}

MVM_PUBLIC MVMint64 MVM_repr_exists_pos(MVMThreadContext *tc, MVMObject *obj, MVMint64 index) {
    MVMint64 elems = REPR(obj)->elems(tc, STABLE(obj), obj, OBJECT_BODY(obj));
    if (index < 0)
        index += elems;
    return index >= 0 && index < elems && !MVM_is_null(tc, MVM_repr_at_pos_o(tc, obj, index));
}

MVMint64 MVM_repr_at_pos_i(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx) {
    MVMRegister value;
    if (REPR(obj)->ID == MVM_REPR_ID_VMArray) {
        if (((MVMArrayREPRData *)STABLE(obj)->REPR_data)->slot_type == MVM_ARRAY_I64) {
            MVM_VMArray_at_pos_i(tc, STABLE(obj), obj, OBJECT_BODY(obj), idx, &value);
        }
        else {
            MVM_VMArray_at_pos(tc, STABLE(obj), obj, OBJECT_BODY(obj), idx, &value, MVM_reg_int64);
        }
    }
    else {
        REPR(obj)->pos_funcs.at_pos(tc, STABLE(obj), obj, OBJECT_BODY(obj),
            idx, &value, MVM_reg_int64);
    }
    return value.i64;
}

MVMuint64 MVM_repr_at_pos_u(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx) {
    MVMRegister value;
    if (REPR(obj)->ID == MVM_REPR_ID_VMArray) {
        if (((MVMArrayREPRData *)STABLE(obj)->REPR_data)->slot_type == MVM_ARRAY_U64) {
            MVM_VMArray_at_pos_u(tc, STABLE(obj), obj, OBJECT_BODY(obj), idx, &value);
        }
        else {
            MVM_VMArray_at_pos(tc, STABLE(obj), obj, OBJECT_BODY(obj), idx, &value, MVM_reg_uint64);
        }
    }
    else {
        REPR(obj)->pos_funcs.at_pos(tc, STABLE(obj), obj, OBJECT_BODY(obj),
            idx, &value, MVM_reg_uint64);
    }
    return value.u64;
}

MVMnum64 MVM_repr_at_pos_n(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx) {
    MVMRegister value;
    if (REPR(obj)->ID == MVM_REPR_ID_VMArray) {
        if (((MVMArrayREPRData *)STABLE(obj)->REPR_data)->slot_type == MVM_ARRAY_N64) {
            MVM_VMArray_at_pos_n(tc, STABLE(obj), obj, OBJECT_BODY(obj), idx, &value);
        }
        else {
            MVM_VMArray_at_pos(tc, STABLE(obj), obj, OBJECT_BODY(obj), idx, &value, MVM_reg_num64);
        }
    }
    else {
        REPR(obj)->pos_funcs.at_pos(tc, STABLE(obj), obj, OBJECT_BODY(obj),
            idx, &value, MVM_reg_num64);
    }
    return value.n64;
}

MVMString * MVM_repr_at_pos_s(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx) {
    MVMRegister value;
    if (REPR(obj)->ID == MVM_REPR_ID_VMArray) {
        MVM_VMArray_at_pos_s(tc, STABLE(obj), obj, OBJECT_BODY(obj),
            idx, &value);
    }
    else {
        REPR(obj)->pos_funcs.at_pos(tc, STABLE(obj), obj, OBJECT_BODY(obj),
            idx, &value, MVM_reg_str);
    }
    return value.s;
}

MVMObject * MVM_repr_at_pos_o(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx) {
    if (MVM_LIKELY(IS_CONCRETE(obj))) {
        MVMRegister value;
        if (REPR(obj)->ID == MVM_REPR_ID_VMArray) {
            MVM_VMArray_at_pos_o(tc, STABLE(obj), obj, OBJECT_BODY(obj),
                idx, &value);
        }
        else if (REPR(obj)->ID == MVM_REPR_ID_P6opaque) {
            MVM_P6opaque_at_pos(tc, STABLE(obj), obj, OBJECT_BODY(obj),
                idx, &value, MVM_reg_obj);
        }
        else {
            REPR(obj)->pos_funcs.at_pos(tc, STABLE(obj), obj, OBJECT_BODY(obj),
                idx, &value, MVM_reg_obj);
        }
        return value.o;
    }
    return tc->instance->VMNull;
}

static void at_pos_multidim(MVMThreadContext *tc, MVMObject *obj, MVMObject *indices, MVMRegister *value, MVMuint16 kind) {
    MVMint64 num_indices;
    MVM_repr_populate_indices_array(tc, indices, &num_indices);
    REPR(obj)->pos_funcs.at_pos_multidim(tc, STABLE(obj), obj,
        OBJECT_BODY(obj), num_indices, tc->multi_dim_indices, value, kind);
}

MVMint64 MVM_repr_at_pos_multidim_i(MVMThreadContext *tc, MVMObject *obj, MVMObject *indices) {
    MVMRegister r;
    at_pos_multidim(tc, obj, indices, &r, MVM_reg_int64);
    return r.i64;
}

MVMuint64 MVM_repr_at_pos_multidim_u(MVMThreadContext *tc, MVMObject *obj, MVMObject *indices) {
    MVMRegister r;
    at_pos_multidim(tc, obj, indices, &r, MVM_reg_uint64);
    return r.u64;
}

MVMnum64 MVM_repr_at_pos_multidim_n(MVMThreadContext *tc, MVMObject *obj, MVMObject *indices) {
    MVMRegister r;
    at_pos_multidim(tc, obj, indices, &r, MVM_reg_num64);
    return r.n64;
}

MVMString * MVM_repr_at_pos_multidim_s(MVMThreadContext *tc, MVMObject *obj, MVMObject *indices) {
    MVMRegister r;
    at_pos_multidim(tc, obj, indices, &r, MVM_reg_str);
    return r.s;
}

MVMObject * MVM_repr_at_pos_multidim_o(MVMThreadContext *tc, MVMObject *obj, MVMObject *indices) {
    MVMRegister r;
    at_pos_multidim(tc, obj, indices, &r, MVM_reg_obj);
    return r.o;
}

static void at_pos_2d(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx1, MVMint64 idx2, MVMRegister *value, MVMuint16 kind) {
    MVMint64 c_indices[2] = { idx1, idx2 };
    REPR(obj)->pos_funcs.at_pos_multidim(tc, STABLE(obj), obj,
        OBJECT_BODY(obj), 2, c_indices, value, kind);
}

MVMint64 MVM_repr_at_pos_2d_i(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx1, MVMint64 idx2) {
    MVMRegister r;
    at_pos_2d(tc, obj, idx1, idx2, &r, MVM_reg_int64);
    return r.i64;
}

MVMuint64 MVM_repr_at_pos_2d_u(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx1, MVMint64 idx2) {
    MVMRegister r;
    at_pos_2d(tc, obj, idx1, idx2, &r, MVM_reg_uint64);
    return r.u64;
}

MVMnum64 MVM_repr_at_pos_2d_n(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx1, MVMint64 idx2) {
    MVMRegister r;
    at_pos_2d(tc, obj, idx1, idx2, &r, MVM_reg_num64);
    return r.n64;
}

MVMString * MVM_repr_at_pos_2d_s(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx1, MVMint64 idx2) {
    MVMRegister r;
    at_pos_2d(tc, obj, idx1, idx2, &r, MVM_reg_str);
    return r.s;
}

MVMObject * MVM_repr_at_pos_2d_o(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx1, MVMint64 idx2) {
    MVMRegister r;
    at_pos_2d(tc, obj, idx1, idx2, &r, MVM_reg_obj);
    return r.o;
}

static void at_pos_3d(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx1, MVMint64 idx2, MVMint64 idx3, MVMRegister *value, MVMuint16 kind) {
    MVMint64 c_indices[3] = { idx1, idx2, idx3 };
    REPR(obj)->pos_funcs.at_pos_multidim(tc, STABLE(obj), obj,
        OBJECT_BODY(obj), 3, c_indices, value, kind);
}

MVMint64 MVM_repr_at_pos_3d_i(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx1, MVMint64 idx2, MVMint64 idx3) {
    MVMRegister r;
    at_pos_3d(tc, obj, idx1, idx2, idx3, &r, MVM_reg_int64);
    return r.i64;
}

MVMuint64 MVM_repr_at_pos_3d_u(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx1, MVMint64 idx2, MVMint64 idx3) {
    MVMRegister r;
    at_pos_3d(tc, obj, idx1, idx2, idx3, &r, MVM_reg_uint64);
    return r.u64;
}

MVMnum64 MVM_repr_at_pos_3d_n(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx1, MVMint64 idx2, MVMint64 idx3) {
    MVMRegister r;
    at_pos_3d(tc, obj, idx1, idx2, idx3, &r, MVM_reg_num64);
    return r.n64;
}

MVMString * MVM_repr_at_pos_3d_s(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx1, MVMint64 idx2, MVMint64 idx3) {
    MVMRegister r;
    at_pos_3d(tc, obj, idx1, idx2, idx3, &r, MVM_reg_str);
    return r.s;
}

MVMObject * MVM_repr_at_pos_3d_o(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx1, MVMint64 idx2, MVMint64 idx3) {
    MVMRegister r;
    at_pos_3d(tc, obj, idx1, idx2, idx3, &r, MVM_reg_obj);
    return r.o;
}

void MVM_repr_bind_pos_i(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx, MVMint64 value) {
    MVMRegister val;
    val.i64 = value;
    REPR(obj)->pos_funcs.bind_pos(tc, STABLE(obj), obj, OBJECT_BODY(obj),
        idx, val, MVM_reg_int64);
}

void MVM_repr_bind_pos_u(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx, MVMuint64 value) {
    MVMRegister val;
    val.u64 = value;
    REPR(obj)->pos_funcs.bind_pos(tc, STABLE(obj), obj, OBJECT_BODY(obj),
        idx, val, MVM_reg_uint64);
}

void MVM_repr_bind_pos_n(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx, MVMnum64 value) {
    MVMRegister val;
    val.n64 = value;
    REPR(obj)->pos_funcs.bind_pos(tc, STABLE(obj), obj, OBJECT_BODY(obj),
        idx, val, MVM_reg_num64);
}

void MVM_repr_bind_pos_s(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx, MVMString *value) {
    MVMRegister val;
    val.s = value;
    REPR(obj)->pos_funcs.bind_pos(tc, STABLE(obj), obj, OBJECT_BODY(obj),
        idx, val, MVM_reg_str);
}

void MVM_repr_bind_pos_o(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx, MVMObject *value) {
    MVMRegister val;
    val.o = value;
    REPR(obj)->pos_funcs.bind_pos(tc, STABLE(obj), obj, OBJECT_BODY(obj),
        idx, val, MVM_reg_obj);
}

void MVM_repr_write_buf(MVMThreadContext *tc, MVMObject *obj, char *value, MVMint64 offset, MVMint64 size) {
    REPR(obj)->pos_funcs.write_buf(tc, STABLE(obj), obj, OBJECT_BODY(obj), value, offset, size);
}

MVMint64 MVM_repr_read_buf(MVMThreadContext *tc, MVMObject *obj, MVMint64 offset, MVMint64 size) {
    return REPR(obj)->pos_funcs.read_buf(tc, STABLE(obj), obj, OBJECT_BODY(obj), offset, size);
}

static void bind_pos_multidim(MVMThreadContext *tc, MVMObject *obj, MVMObject *indices, MVMRegister value, MVMuint16 kind) {
    MVMint64 num_indices;
    MVM_repr_populate_indices_array(tc, indices, &num_indices);
    REPR(obj)->pos_funcs.bind_pos_multidim(tc, STABLE(obj), obj,
        OBJECT_BODY(obj), num_indices, tc->multi_dim_indices, value, kind);
}

void MVM_repr_bind_pos_multidim_i(MVMThreadContext *tc, MVMObject *obj, MVMObject *indices, MVMint64 value) {
    MVMRegister r;
    r.i64 = value;
    bind_pos_multidim(tc, obj, indices, r, MVM_reg_int64);
}
void MVM_repr_bind_pos_multidim_u(MVMThreadContext *tc, MVMObject *obj, MVMObject *indices, MVMuint64 value) {
    MVMRegister r;
    r.u64 = value;
    bind_pos_multidim(tc, obj, indices, r, MVM_reg_uint64);
}
void MVM_repr_bind_pos_multidim_n(MVMThreadContext *tc, MVMObject *obj, MVMObject *indices, MVMnum64 value) {
    MVMRegister r;
    r.n64 = value;
    bind_pos_multidim(tc, obj, indices, r, MVM_reg_num64);
}
void MVM_repr_bind_pos_multidim_s(MVMThreadContext *tc, MVMObject *obj, MVMObject *indices, MVMString *value) {
    MVMRegister r;
    r.s = value;
    bind_pos_multidim(tc, obj, indices, r, MVM_reg_str);
}
void MVM_repr_bind_pos_multidim_o(MVMThreadContext *tc, MVMObject *obj, MVMObject *indices, MVMObject *value) {
    MVMRegister r;
    r.o = value;
    bind_pos_multidim(tc, obj, indices, r, MVM_reg_obj);
}

static void bind_pos_2d(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx1, MVMint64 idx2, MVMRegister value, MVMuint16 kind) {
    MVMint64 c_indices[2] = { idx1, idx2 };
    REPR(obj)->pos_funcs.bind_pos_multidim(tc, STABLE(obj), obj,
        OBJECT_BODY(obj), 2, c_indices, value, kind);
}

void MVM_repr_bind_pos_2d_i(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx1, MVMint64 idx2, MVMint64 value) {
    MVMRegister r;
    r.i64 = value;
    bind_pos_2d(tc, obj, idx1, idx2, r, MVM_reg_int64);
}
void MVM_repr_bind_pos_2d_u(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx1, MVMint64 idx2, MVMuint64 value) {
    MVMRegister r;
    r.u64 = value;
    bind_pos_2d(tc, obj, idx1, idx2, r, MVM_reg_uint64);
}
void MVM_repr_bind_pos_2d_n(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx1, MVMint64 idx2, MVMnum64 value) {
    MVMRegister r;
    r.n64 = value;
    bind_pos_2d(tc, obj, idx1, idx2, r, MVM_reg_num64);
}
void MVM_repr_bind_pos_2d_s(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx1, MVMint64 idx2, MVMString *value) {
    MVMRegister r;
    r.s = value;
    bind_pos_2d(tc, obj, idx1, idx2, r, MVM_reg_str);
}
void MVM_repr_bind_pos_2d_o(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx1, MVMint64 idx2, MVMObject *value) {
    MVMRegister r;
    r.o = value;
    bind_pos_2d(tc, obj, idx1, idx2, r, MVM_reg_obj);
}

static void bind_pos_3d(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx1, MVMint64 idx2, MVMint64 idx3, MVMRegister value, MVMuint16 kind) {
    MVMint64 c_indices[3] = { idx1, idx2, idx3 };
    REPR(obj)->pos_funcs.bind_pos_multidim(tc, STABLE(obj), obj,
        OBJECT_BODY(obj), 3, c_indices, value, kind);
}

void MVM_repr_bind_pos_3d_i(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx1, MVMint64 idx2, MVMint64 idx3, MVMint64 value) {
    MVMRegister r;
    r.i64 = value;
    bind_pos_3d(tc, obj, idx1, idx2, idx3, r, MVM_reg_int64);
}
void MVM_repr_bind_pos_3d_u(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx1, MVMint64 idx2, MVMint64 idx3, MVMuint64 value) {
    MVMRegister r;
    r.u64 = value;
    bind_pos_3d(tc, obj, idx1, idx2, idx3, r, MVM_reg_uint64);
}
void MVM_repr_bind_pos_3d_n(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx1, MVMint64 idx2, MVMint64 idx3, MVMnum64 value) {
    MVMRegister r;
    r.n64 = value;
    bind_pos_3d(tc, obj, idx1, idx2, idx3, r, MVM_reg_num64);
}
void MVM_repr_bind_pos_3d_s(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx1, MVMint64 idx2, MVMint64 idx3, MVMString *value) {
    MVMRegister r;
    r.s = value;
    bind_pos_3d(tc, obj, idx1, idx2, idx3, r, MVM_reg_str);
}
void MVM_repr_bind_pos_3d_o(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx1, MVMint64 idx2, MVMint64 idx3, MVMObject *value) {
    MVMRegister r;
    r.o = value;
    bind_pos_3d(tc, obj, idx1, idx2, idx3, r, MVM_reg_obj);
}

void MVM_repr_push_i(MVMThreadContext *tc, MVMObject *obj, MVMint64 pushee) {
    MVMRegister value;
    value.i64 = pushee;
    REPR(obj)->pos_funcs.push(tc, STABLE(obj), obj, OBJECT_BODY(obj),
        value, MVM_reg_int64);
}

void MVM_repr_push_n(MVMThreadContext *tc, MVMObject *obj, MVMnum64 pushee) {
    MVMRegister value;
    value.n64 = pushee;
    REPR(obj)->pos_funcs.push(tc, STABLE(obj), obj, OBJECT_BODY(obj),
        value, MVM_reg_num64);
}

void MVM_repr_push_s(MVMThreadContext *tc, MVMObject *obj, MVMString *pushee) {
    MVMRegister value;
    value.s = pushee;
    REPR(obj)->pos_funcs.push(tc, STABLE(obj), obj, OBJECT_BODY(obj),
        value, MVM_reg_str);
}

void MVM_repr_push_o(MVMThreadContext *tc, MVMObject *obj, MVMObject *pushee) {
    MVMRegister value;
    value.o = pushee;
    REPR(obj)->pos_funcs.push(tc, STABLE(obj), obj, OBJECT_BODY(obj),
        value, MVM_reg_obj);
}

void MVM_repr_unshift_i(MVMThreadContext *tc, MVMObject *obj, MVMint64 unshiftee) {
    MVMRegister value;
    value.i64 = unshiftee;
    REPR(obj)->pos_funcs.unshift(tc, STABLE(obj), obj, OBJECT_BODY(obj),
        value, MVM_reg_int64);
}

void MVM_repr_unshift_n(MVMThreadContext *tc, MVMObject *obj, MVMnum64 unshiftee) {
    MVMRegister value;
    value.n64 = unshiftee;
    REPR(obj)->pos_funcs.unshift(tc, STABLE(obj), obj, OBJECT_BODY(obj),
        value, MVM_reg_num64);
}

void MVM_repr_unshift_s(MVMThreadContext *tc, MVMObject *obj, MVMString *unshiftee) {
    MVMRegister value;
    value.s = unshiftee;
    REPR(obj)->pos_funcs.unshift(tc, STABLE(obj), obj, OBJECT_BODY(obj),
        value, MVM_reg_str);
}

void MVM_repr_unshift_o(MVMThreadContext *tc, MVMObject *obj, MVMObject *unshiftee) {
    MVMRegister value;
    value.o = unshiftee;
    REPR(obj)->pos_funcs.unshift(tc, STABLE(obj), obj, OBJECT_BODY(obj),
        value, MVM_reg_obj);
}

MVMint64 MVM_repr_pop_i(MVMThreadContext *tc, MVMObject *obj) {
    MVMRegister value;
    REPR(obj)->pos_funcs.pop(tc, STABLE(obj), obj, OBJECT_BODY(obj),
        &value, MVM_reg_int64);
    return value.i64;
}

MVMnum64 MVM_repr_pop_n(MVMThreadContext *tc, MVMObject *obj) {
    MVMRegister value;
    REPR(obj)->pos_funcs.pop(tc, STABLE(obj), obj, OBJECT_BODY(obj),
        &value, MVM_reg_num64);
    return value.n64;
}

MVMString * MVM_repr_pop_s(MVMThreadContext *tc, MVMObject *obj) {
    MVMRegister value;
    REPR(obj)->pos_funcs.pop(tc, STABLE(obj), obj, OBJECT_BODY(obj),
        &value, MVM_reg_str);
    return value.s;
}

MVMObject * MVM_repr_pop_o(MVMThreadContext *tc, MVMObject *obj) {
    MVMRegister value;
    REPR(obj)->pos_funcs.pop(tc, STABLE(obj), obj, OBJECT_BODY(obj),
        &value, MVM_reg_obj);
    return value.o;
}

MVMint64 MVM_repr_shift_i(MVMThreadContext *tc, MVMObject *obj) {
    MVMRegister value;
    REPR(obj)->pos_funcs.shift(tc, STABLE(obj), obj, OBJECT_BODY(obj),
        &value, MVM_reg_int64);
    return value.i64;
}

MVMnum64 MVM_repr_shift_n(MVMThreadContext *tc, MVMObject *obj) {
    MVMRegister value;
    REPR(obj)->pos_funcs.shift(tc, STABLE(obj), obj, OBJECT_BODY(obj),
        &value, MVM_reg_num64);
    return value.n64;
}

MVMString * MVM_repr_shift_s(MVMThreadContext *tc, MVMObject *obj) {
    MVMRegister value;
    REPR(obj)->pos_funcs.shift(tc, STABLE(obj), obj, OBJECT_BODY(obj),
        &value, MVM_reg_str);
    return value.s;
}

MVMObject * MVM_repr_shift_o(MVMThreadContext *tc, MVMObject *obj) {
    MVMRegister value;
    REPR(obj)->pos_funcs.shift(tc, STABLE(obj), obj, OBJECT_BODY(obj),
        &value, MVM_reg_obj);
    return value.o;
}

MVMint64 MVM_repr_at_key_i(MVMThreadContext *tc, MVMObject *obj, MVMString *key) {
    MVMRegister value;
    if (REPR(obj)->ID == MVM_REPR_ID_MVMHash) {
        MVMHash_at_key(tc, STABLE(obj), obj, OBJECT_BODY(obj),
            (MVMObject *)key, &value, MVM_reg_int64);
    }
    else {
        REPR(obj)->ass_funcs.at_key(tc, STABLE(obj), obj, OBJECT_BODY(obj),
                                (MVMObject *)key, &value, MVM_reg_int64);
    }
    return value.i64;
}

MVMuint64 MVM_repr_at_key_u(MVMThreadContext *tc, MVMObject *obj, MVMString *key) {
    MVMRegister value;
    if (REPR(obj)->ID == MVM_REPR_ID_MVMHash) {
        MVMHash_at_key(tc, STABLE(obj), obj, OBJECT_BODY(obj),
            (MVMObject *)key, &value, MVM_reg_uint64);
    }
    else {
        REPR(obj)->ass_funcs.at_key(tc, STABLE(obj), obj, OBJECT_BODY(obj),
                                (MVMObject *)key, &value, MVM_reg_uint64);
    }
    return value.u64;
}

MVMnum64 MVM_repr_at_key_n(MVMThreadContext *tc, MVMObject *obj, MVMString *key) {
    MVMRegister value;
    if (REPR(obj)->ID == MVM_REPR_ID_MVMHash) {
        MVMHash_at_key(tc, STABLE(obj), obj, OBJECT_BODY(obj),
            (MVMObject *)key, &value, MVM_reg_num64);
    }
    else {
        REPR(obj)->ass_funcs.at_key(tc, STABLE(obj), obj, OBJECT_BODY(obj),
                                (MVMObject *)key, &value, MVM_reg_num64);
    }
    return value.n64;
}

MVMString * MVM_repr_at_key_s(MVMThreadContext *tc, MVMObject *obj, MVMString *key) {
    MVMRegister value;
    if (REPR(obj)->ID == MVM_REPR_ID_MVMHash) {
        MVMHash_at_key(tc, STABLE(obj), obj, OBJECT_BODY(obj),
            (MVMObject *)key, &value, MVM_reg_str);
    }
    else {
        REPR(obj)->ass_funcs.at_key(tc, STABLE(obj), obj, OBJECT_BODY(obj),
                                (MVMObject *)key, &value, MVM_reg_str);
    }
    return value.s;
}

MVMObject * MVM_repr_at_key_o(MVMThreadContext *tc, MVMObject *obj, MVMString *key) {
    if (MVM_LIKELY(IS_CONCRETE(obj))) {
        MVMRegister value;
        if (REPR(obj)->ID == MVM_REPR_ID_MVMHash) {
            MVMHash_at_key(tc, STABLE(obj), obj, OBJECT_BODY(obj),
                (MVMObject *)key, &value, MVM_reg_obj);
        }
        else {
            REPR(obj)->ass_funcs.at_key(tc, STABLE(obj), obj, OBJECT_BODY(obj),
                                    (MVMObject *)key, &value, MVM_reg_obj);
        }
        return value.o;
    }
    return tc->instance->VMNull;
}

void MVM_repr_bind_key_i(MVMThreadContext *tc, MVMObject *obj, MVMString *key, MVMint64 val) {
    MVMRegister value;
    value.i64 = val;
    REPR(obj)->ass_funcs.bind_key(tc, STABLE(obj), obj, OBJECT_BODY(obj),
        (MVMObject *)key, value, MVM_reg_int64);
}

void MVM_repr_bind_key_n(MVMThreadContext *tc, MVMObject *obj, MVMString *key, MVMnum64 val) {
    MVMRegister value;
    value.n64 = val;
    REPR(obj)->ass_funcs.bind_key(tc, STABLE(obj), obj, OBJECT_BODY(obj),
        (MVMObject *)key, value, MVM_reg_num64);
}

void MVM_repr_bind_key_s(MVMThreadContext *tc, MVMObject *obj, MVMString *key, MVMString *val) {
    MVMRegister value;
    value.s = val;
    if (REPR(obj)->ID == MVM_REPR_ID_MVMHash) {
        MVMHash_bind_key(tc, STABLE(obj), obj, OBJECT_BODY(obj),
            (MVMObject *)key, value, MVM_reg_str);
    }
    else {
        REPR(obj)->ass_funcs.bind_key(tc, STABLE(obj), obj, OBJECT_BODY(obj),
            (MVMObject *)key, value, MVM_reg_str);
    }
}

void MVM_repr_bind_key_o(MVMThreadContext *tc, MVMObject *obj, MVMString *key, MVMObject *val) {
    MVMRegister value;
    value.o = val;
    if (REPR(obj)->ID == MVM_REPR_ID_MVMHash) {
        MVMHash_bind_key(tc, STABLE(obj), obj, OBJECT_BODY(obj),
            (MVMObject *)key, value, MVM_reg_obj);
    }
    else {
        REPR(obj)->ass_funcs.bind_key(tc, STABLE(obj), obj, OBJECT_BODY(obj),
            (MVMObject *)key, value, MVM_reg_obj);
    }
}

MVMint64 MVM_repr_exists_key(MVMThreadContext *tc, MVMObject *obj, MVMString *key) {
    return REPR(obj)->ass_funcs.exists_key(tc, STABLE(obj), obj,
        OBJECT_BODY(obj), (MVMObject *)key);
}

void MVM_repr_delete_key(MVMThreadContext *tc, MVMObject *obj, MVMString *key) {
    REPR(obj)->ass_funcs.delete_key(tc, STABLE(obj), obj,
        OBJECT_BODY(obj), (MVMObject *)key);
}

MVMuint64 MVM_repr_elems(MVMThreadContext *tc, MVMObject *obj) {
    return REPR(obj)->elems(tc, STABLE(obj), obj, OBJECT_BODY(obj));
}

MVMObject * MVM_repr_dimensions(MVMThreadContext *tc, MVMObject *obj) {
    if (IS_CONCRETE(obj)) {
        MVMint64 num_dims, i;
        MVMint64 *dims;
        MVMObject *result;
        REPR(obj)->pos_funcs.dimensions(tc, STABLE(obj), obj, OBJECT_BODY(obj),
            &num_dims, &dims);
        result = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTIntArray);
        for (i = 0; i < num_dims; i++)
            MVM_repr_bind_pos_i(tc, result, i, dims[i]);
        return result;
    }
    else {
        MVM_exception_throw_adhoc(tc, "Cannot get dimensions of a type object. Did you forget a '.new'?");
    }
}

MVMint64 MVM_repr_num_dimensions(MVMThreadContext *tc, MVMObject *obj) {
    if (IS_CONCRETE(obj)) {
        MVMint64 num_dims;
        MVMint64 *_;
        REPR(obj)->pos_funcs.dimensions(tc, STABLE(obj), obj, OBJECT_BODY(obj),
            &num_dims, &_);
        return num_dims;
    }
    else {
        MVM_exception_throw_adhoc(tc, "Cannot get number of dimensions of a type object. Did you forget a '.new'?");
    }
}

MVMint64 MVM_repr_get_int(MVMThreadContext *tc, MVMObject *obj) {
    if (!IS_CONCRETE(obj))
        MVM_exception_throw_adhoc(tc, "Cannot unbox a type object (%s) to int.", MVM_6model_get_debug_name(tc, obj));
    return REPR(obj)->box_funcs.get_int(tc, STABLE(obj), obj, OBJECT_BODY(obj));
}

MVMnum64 MVM_repr_get_num(MVMThreadContext *tc, MVMObject *obj) {
    if (!IS_CONCRETE(obj))
        MVM_exception_throw_adhoc(tc, "Cannot unbox a type object (%s) to a num.", MVM_6model_get_debug_name(tc, obj));
    return REPR(obj)->box_funcs.get_num(tc, STABLE(obj), obj, OBJECT_BODY(obj));
}

MVMString * MVM_repr_get_str(MVMThreadContext *tc, MVMObject *obj) {
    if (!IS_CONCRETE(obj))
        MVM_exception_throw_adhoc(tc, "Cannot unbox a type object (%s) to a str.", MVM_6model_get_debug_name(tc, obj));
    return REPR(obj)->box_funcs.get_str(tc, STABLE(obj), obj, OBJECT_BODY(obj));
}

MVMuint64 MVM_repr_get_uint(MVMThreadContext *tc, MVMObject *obj) {
    if (!IS_CONCRETE(obj))
        MVM_exception_throw_adhoc(tc, "Cannot unbox a type object (%s) to an unsigned int.", MVM_6model_get_debug_name(tc, obj));
    return REPR(obj)->box_funcs.get_uint(tc, STABLE(obj), obj, OBJECT_BODY(obj));
}

void MVM_repr_set_int(MVMThreadContext *tc, MVMObject *obj, MVMint64 val) {
    REPR(obj)->box_funcs.set_int(tc, STABLE(obj), obj, OBJECT_BODY(obj), val);
}

void MVM_repr_set_num(MVMThreadContext *tc, MVMObject *obj, MVMnum64 val) {
    REPR(obj)->box_funcs.set_num(tc, STABLE(obj), obj, OBJECT_BODY(obj), val);
}

void MVM_repr_set_str(MVMThreadContext *tc, MVMObject *obj, MVMString *val) {
    REPR(obj)->box_funcs.set_str(tc, STABLE(obj), obj, OBJECT_BODY(obj), val);
}

void MVM_repr_set_uint(MVMThreadContext *tc, MVMObject *obj, MVMuint64 val) {
    REPR(obj)->box_funcs.set_uint(tc, STABLE(obj), obj, OBJECT_BODY(obj), val);
}

MVMObject * MVM_repr_box_int(MVMThreadContext *tc, MVMObject *type, MVMint64 val) {
    MVMObject *res;
    res = MVM_intcache_get(tc, type, val);
    if (res == 0) {
        res = MVM_repr_alloc_init(tc, type);
        MVM_repr_set_int(tc, res, val);
    }
    return res;
}

MVMObject * MVM_repr_box_num(MVMThreadContext *tc, MVMObject *type, MVMnum64 val) {
    MVMObject *res = MVM_repr_alloc_init(tc, type);
    MVM_repr_set_num(tc, res, val);
    return res;
}

MVMObject * MVM_repr_box_str(MVMThreadContext *tc, MVMObject *type, MVMString *val) {
    MVMObject *res;
    MVMROOT(tc, val) {
        res = MVM_repr_alloc_init(tc, type);
        MVM_repr_set_str(tc, res, val);
    }
    return res;
}

MVMObject * MVM_repr_box_uint(MVMThreadContext *tc, MVMObject *type, MVMuint64 val) {
    MVMObject *res = MVM_repr_alloc_init(tc, type);
    MVM_repr_set_uint(tc, res, val);
    return res;
}

MVM_PUBLIC MVMint64 MVM_repr_get_attr_i(MVMThreadContext *tc, MVMObject *object, MVMObject *type,
                                           MVMString *name, MVMint16 hint) {
    MVMRegister result_reg;
    if (!IS_CONCRETE(object))
        MVM_exception_throw_adhoc(tc, "Cannot look up attributes in a %s type object. Did you forget a '.new'?", MVM_6model_get_debug_name(tc, object));
    REPR(object)->attr_funcs.get_attribute(tc,
            STABLE(object), object, OBJECT_BODY(object),
            type, name,
            hint, &result_reg, MVM_reg_int64);
    return result_reg.i64;
}

MVM_PUBLIC MVMuint64 MVM_repr_get_attr_u(MVMThreadContext *tc, MVMObject *object, MVMObject *type,
                                           MVMString *name, MVMint16 hint) {
    MVMRegister result_reg;
    if (!IS_CONCRETE(object))
        MVM_exception_throw_adhoc(tc, "Cannot look up attributes in a %s type object. Did you forget a '.new'?", MVM_6model_get_debug_name(tc, object));
    REPR(object)->attr_funcs.get_attribute(tc,
            STABLE(object), object, OBJECT_BODY(object),
            type, name,
            hint, &result_reg, MVM_reg_uint64);
    return result_reg.u64;
}

MVM_PUBLIC MVMnum64 MVM_repr_get_attr_n(MVMThreadContext *tc, MVMObject *object, MVMObject *type,
                                           MVMString *name, MVMint16 hint) {
    MVMRegister result_reg;
    if (!IS_CONCRETE(object))
        MVM_exception_throw_adhoc(tc, "Cannot look up attributes in a %s type object. Did you forget a '.new'?", MVM_6model_get_debug_name(tc, object));
    REPR(object)->attr_funcs.get_attribute(tc,
            STABLE(object), object, OBJECT_BODY(object),
            type, name,
            hint, &result_reg, MVM_reg_num64);
    return result_reg.n64;
}

MVM_PUBLIC MVMString * MVM_repr_get_attr_s(MVMThreadContext *tc, MVMObject *object, MVMObject *type,
                                           MVMString *name, MVMint16 hint) {
    MVMRegister result_reg;
    if (!IS_CONCRETE(object))
        MVM_exception_throw_adhoc(tc, "Cannot look up attributes in a %s type object. Did you forget a '.new'?", MVM_6model_get_debug_name(tc, object));
    REPR(object)->attr_funcs.get_attribute(tc,
            STABLE(object), object, OBJECT_BODY(object),
            type, name,
            hint, &result_reg, MVM_reg_str);
    return result_reg.s;
}

MVM_PUBLIC MVMObject * MVM_repr_get_attr_o(MVMThreadContext *tc, MVMObject *object, MVMObject *type,
                                           MVMString *name, MVMint16 hint) {
    MVMRegister result_reg;
    if (!IS_CONCRETE(object))
        MVM_exception_throw_adhoc(tc, "Cannot look up attributes in a %s type object. Did you forget a '.new'?", MVM_6model_get_debug_name(tc, object));
    REPR(object)->attr_funcs.get_attribute(tc,
            STABLE(object), object, OBJECT_BODY(object),
            type, name,
            hint, &result_reg, MVM_reg_obj);
    return result_reg.o;
}


MVM_PUBLIC void MVM_repr_bind_attr_inso(MVMThreadContext *tc, MVMObject *object, MVMObject *type,
                                           MVMString *name, MVMint16 hint, MVMRegister value_reg, MVMuint16 kind) {
    if (!IS_CONCRETE(object))
        MVM_exception_throw_adhoc(tc, "Cannot bind attributes in a %s type object. Did you forget a '.new'?", MVM_6model_get_debug_name(tc, object));
    REPR(object)->attr_funcs.bind_attribute(tc,
            STABLE(object), object, OBJECT_BODY(object),
            type, name,
            hint, value_reg, kind);
    MVM_SC_WB_OBJ(tc, object);
}

MVM_PUBLIC MVMint64 MVM_repr_attribute_inited(MVMThreadContext *tc, MVMObject *obj, MVMObject *type,
                                              MVMString *name) {
    if (!IS_CONCRETE(obj))
        MVM_exception_throw_adhoc(tc, "Cannot look up attributes in a %s type object. Did you forget a '.new'?", MVM_6model_get_debug_name(tc, obj));
    return REPR(obj)->attr_funcs.is_attribute_initialized(tc,
        STABLE(obj), OBJECT_BODY(obj),
        type, name, MVM_NO_HINT);
}

MVM_PUBLIC MVMint64    MVM_repr_compare_repr_id(MVMThreadContext *tc, MVMObject *object, MVMuint32 REPRId) {
    return object && REPR(object)->ID == REPRId ? 1 : 0;
}

MVM_PUBLIC MVMint64    MVM_repr_hint_for(MVMThreadContext *tc, MVMObject *object, MVMString *attrname) {
    return REPR(object)->attr_funcs.hint_for(tc, STABLE(object), object, attrname);
}

MVM_PUBLIC void MVM_repr_atomic_bind_attr_o(MVMThreadContext *tc, MVMObject *object,
                                            MVMObject *type, MVMString *name,
                                            MVMObject *value) {
    AO_t *target = REPR(object)->attr_funcs.attribute_as_atomic(tc, STABLE(object),
        OBJECT_BODY(object), type, name, MVM_reg_obj);
    MVM_store(target, value);
    MVM_gc_write_barrier(tc, (MVMCollectable *)object, (MVMCollectable *)value);
    MVM_SC_WB_OBJ(tc, object);
}

MVM_PUBLIC MVMObject * MVM_repr_casattr_o(MVMThreadContext *tc, MVMObject *object,
                                          MVMObject *type, MVMString *name,
                                          MVMObject *expected, MVMObject *value) {
    AO_t *target = REPR(object)->attr_funcs.attribute_as_atomic(tc, STABLE(object),
        OBJECT_BODY(object), type, name, MVM_reg_obj);
    MVMObject *witness = (MVMObject *)MVM_casptr(target, expected, value);
    MVM_gc_write_barrier(tc, (MVMCollectable *)object, (MVMCollectable *)value);
    MVM_SC_WB_OBJ(tc, object);
    return witness;
}
