NAME ^

src/mmd.c - Multimethod dispatch for binary opcode functions

SYNOPSIS ^

This system is set up to handle type-based dispatching for binary (i.e. two-arg) functions. This includes, though isn't necessarily limited to, binary operators such as addition or subtraction.

DESCRIPTION ^

The MMD system is straightforward, and currently must be explicitly invoked, for example by a vtable function. (We may reserve the right to use MMD in all circumstances, but currently do not).

API ^

For the purposes of the API, each MMD-able function is assigned a unique number which is used to find the correct function table. This is the func_num parameter in the following functions. While Parrot isn't restricted to a predefined set of functions, it does set things up so that all the binary vtable functions have a MMD table preinstalled for them, with default behaviour.

Remarks ^

binop_mmd_funcs->x and ->y are table sizes not highest type in table.

Functions ^

*/

#include "parrot/compiler.h" #include "parrot/parrot.h" #include "parrot/mmd.h" #include "parrot/oplib/ops.h" #include "mmd.str" #include <assert.h>

/* HEADER: include/parrot/mmd.h */

#define MMD_DEBUG 0

static void mmd_create_builtin_multi_meth_2(Interp *, PMC *ns, INTVAL func_nr, INTVAL type, INTVAL right, funcptr_t func_ptr);

#ifndef NDEBUG static void dump_mmd(Interp *interp /*NN*/, INTVAL function) { UINTVAL x, y; UINTVAL offset, x_funcs, y_funcs; MMD_table * const table = interp->binop_mmd_funcs + function; funcptr_t func, def; /* XXX Looks like def is never defined */

    x_funcs = table->x;
    y_funcs = table->y;
    printf("    ");
    for (x = 0; x < x_funcs; ++x) {
        if (! (x % 10))
            printf("%d", (int) x / 10);
        else
            printf(" ");
    }
    printf("\n");
    for (y = 0; y < y_funcs; ++y) {
        printf("%3d ", (int)y);
        for (x = 0; x < x_funcs; ++x) {
            offset = x_funcs * y + x;
            func = table->mmd_funcs[offset];
            printf("%c",
                    func == def ?  '.' :
                    (UINTVAL)func & 1 ?  'P' :
                    !func  ? '0' : 'F');
        }
        printf("\n");
    }
    for (y = 0; y < y_funcs; ++y) {
        for (x = 0; x < x_funcs; ++x) {
            offset = x_funcs * y + x;
            func = table->mmd_funcs[offset];
            if (func && func != def && !((UINTVAL) func & 1))
                printf("%3d %3d: %p\n", (int)x, (int)y, (void*) func);
        }
    }
}
#endif

PARROT_API funcptr_t get_mmd_dispatch_type(Interp *interp /*NN*/, INTVAL func_nr, INTVAL left_type, INTVAL right_type, int *is_pmc /*NN*/ ) /* WARN_UNUSED */ { funcptr_t func, func_; INTVAL r; MMD_table * const table = interp->binop_mmd_funcs + func_nr; const UINTVAL x_funcs = table->x; const UINTVAL y_funcs = table->y;

#if MMD_DEBUG fprintf(stderr, "running function %d with left type=%u, right type=%u\n", (int) func_nr, (unsigned) left_type, (unsigned) right_type); #endif

    func = NULL;
    assert(left_type >= 0);
    assert(right_type >=0 ||
            (right_type >= enum_type_INTVAL && right_type <= enum_type_PMC));
    r = right_type;
    if (right_type < 0)
        right_type -= enum_type_INTVAL;
    else
        right_type += 4;
    if ((UINTVAL)left_type < x_funcs && (UINTVAL)right_type < y_funcs) {
            const UINTVAL offset = x_funcs * right_type + left_type;
            func = table->mmd_funcs[offset];
    }
    if (!func) {
        const char * const meth_c = Parrot_MMD_method_name(interp, func_nr);
        STRING * const meth_s = const_string(interp, meth_c);
        PMC * const method = Parrot_MMD_search_default_infix(interp,
                meth_s, left_type, r);
        if (!method)
            real_exception(interp, 0, 1, "MMD function %s not found "
                    "for types (%d, %d)", meth_c, left_type, r);
        if (method->vtable->base_type == enum_class_NCI) {
            /* C function is at struct_val */
            func = D2FPTR(PMC_struct_val(method));
            *is_pmc = 0;
            mmd_register(interp, func_nr, left_type, r,
                    (funcptr_t)PMC_struct_val(method));
        }
        else {
            *is_pmc = 1;
            func = D2FPTR(method);
            mmd_register_sub(interp, func_nr, left_type, r, method);
        }
        return func;
    }
    *is_pmc = (UINTVAL)func & 3;
    func_ = (funcptr_t)((UINTVAL)func & ~3);
#ifndef PARROT_HAS_ALIGNED_FUNCPTR
    if (!*is_pmc) {
        return func;
    }
    else if (!is_pmc_ptr(interp, F2DPTR(func_))) {
        *is_pmc = 0;
        return func;
    }
#endif
    return func_;
}

static funcptr_t get_mmd_dispatcher(Interp *interp /*NN*/, PMC *left, PMC *right, INTVAL function, int *is_pmc /*NN*/) { const UINTVAL left_type = VTABLE_type(interp, left); const UINTVAL right_type = VTABLE_type(interp, right); return get_mmd_dispatch_type(interp, function, left_type, right_type, is_pmc); }

/*

FUNCDOC: If value is a reference-like PMC, dereference it so we can make an MMD call on the 'real' value.

*/

static PMC * mmd_deref(Interp *interp, INTVAL function, PMC *value /*NN*/) { if (VTABLE_type(interp, value) != value->vtable->base_type) return VTABLE_get_pmc(interp, value); else return value; }

/*

FUNCDOC: Make sure pmc is writable enough for function.

*/

static void mmd_ensure_writable(Interp *interp, INTVAL function, PMC *pmc /*NULLOK*/) { if (!PMC_IS_NULL(pmc) && (pmc->vtable->flags & VTABLE_IS_READONLY_FLAG)) real_exception(interp, 0, 1, "%s applied to read-only argument", Parrot_MMD_method_name(interp, function)); }

/*

FUNCDOC: mmd_dispatch_p_ppp

Dispatch to a multimethod that returns a PMC. left, right, and dest are all PMC pointers, while func_num is the MMD table that should be used to do the dispatching. If the dest pointer is NULL, it dispatches two a two-argument function that returns a new dest always.

The MMD system will figure out which function should be called based on the types of left and right and call it, passing in left, right, and possibly dest like any other binary vtable function.

FUNCDOC: mmd_dispatch_p_pip

Like above, right argument is a native INTVAL.

FUNCDOC: mmd_dispatch_p_pnp

Like above, right argument is a native FLOATVAL.

FUNCDOC: mmd_dispatch_p_psp

Like above, right argument is a native STRING *.

FUNCDOC: mmd_dispatch_v_pp

FUNCDOC: mmd_dispatch_v_pi

FUNCDOC: mmd_dispatch_v_pn

FUNCDOC: mmd_dispatch_v_ps

Inplace dispatch functions for left <op=> right.

*/

PARROT_API PMC* mmd_dispatch_p_ppp(Interp *interp, PMC *left /*NN*/, PMC *right /*NN*/, PMC *dest, INTVAL func_nr) { mmd_f_p_ppp real_function; int is_pmc;

    left = mmd_deref(interp, func_nr, left);
    right = mmd_deref(interp, func_nr, right);

    real_function = (mmd_f_p_ppp)get_mmd_dispatcher(interp,
            left, right, func_nr, &is_pmc);

    if (is_pmc) {
        PMC * const sub = (PMC*)real_function;
        if (dest)
            return Parrot_runops_fromc_args(interp, sub, "PPPP",
                    left, right, dest);
        else
            return Parrot_runops_fromc_args(interp, sub, "PPP",
                    left, right);
    }
    else {
        return (*real_function)(interp, left, right, dest);
    }
}

PARROT_API PMC* mmd_dispatch_p_pip(Interp *interp /*NN*/, PMC *left /*NN*/, INTVAL right, PMC *dest, INTVAL func_nr) { int is_pmc;

    UINTVAL left_type;
    mmd_f_p_pip real_function;

    left = mmd_deref(interp, func_nr, left);

    left_type = left->vtable->base_type;

    real_function =
        (mmd_f_p_pip)get_mmd_dispatch_type(interp, func_nr,
                                           left_type, enum_type_INTVAL,
                                           &is_pmc);

    if (is_pmc) {
        PMC * const sub = (PMC*)real_function;
        if (dest)
            return Parrot_runops_fromc_args(interp, sub, "PPIP",
                    left, right, dest);
        else
            return Parrot_runops_fromc_args(interp, sub, "PPI",
                    left, right);
    }
    else {
        return (*real_function)(interp, left, right, dest);
    }
}

PARROT_API PMC* mmd_dispatch_p_pnp(Interp *interp /*NN*/, PMC *left /*NN*/, FLOATVAL right, PMC *dest, INTVAL func_nr) { mmd_f_p_pnp real_function; int is_pmc; UINTVAL left_type;

    left = mmd_deref(interp, func_nr, left);

    left_type = left->vtable->base_type;
    real_function = (mmd_f_p_pnp)get_mmd_dispatch_type(interp,
            func_nr, left_type, enum_type_FLOATVAL, &is_pmc);
    if (is_pmc) {
        PMC * const sub = (PMC*)real_function;
        if (dest)
            return Parrot_runops_fromc_args(interp, sub, "PPNP",
                    left, right, dest);
        else
            return Parrot_runops_fromc_args(interp, sub, "PPN",
                    left, right);
    }
    else {
        return (*real_function)(interp, left, right, dest);
    }
}

PARROT_API PMC* mmd_dispatch_p_psp(Interp *interp /*NN*/, PMC *left /*NN*/, STRING *right, PMC *dest, INTVAL func_nr) { mmd_f_p_psp real_function; int is_pmc; const UINTVAL left_type = left->vtable->base_type;

    real_function = (mmd_f_p_psp)get_mmd_dispatch_type(interp,
            func_nr, left_type, enum_type_STRING, &is_pmc);
    if (is_pmc) {
        PMC * const sub = (PMC*)real_function;
        if (dest)
            return Parrot_runops_fromc_args(interp, sub, "PPSP",
                    left, right, dest);
        else
            return Parrot_runops_fromc_args(interp, sub, "PPS",
                    left, right);
    }
    else {
        return (*real_function)(interp, left, right, dest);
    }
}

/* * inplace variants */ PARROT_API void mmd_dispatch_v_pp(Interp *interp /*NN*/, PMC *left /*NN*/, PMC *right /*NN*/, INTVAL func_nr) { mmd_f_v_pp real_function; int is_pmc;

    left = mmd_deref(interp, func_nr, left);
    right = mmd_deref(interp, func_nr, right);

    mmd_ensure_writable(interp, func_nr, left);

    real_function = (mmd_f_v_pp)get_mmd_dispatcher(interp,
            left, right, func_nr, &is_pmc);

    if (is_pmc) {
        PMC * const sub = (PMC*)real_function;
        Parrot_runops_fromc_args(interp, sub, "vPP", left, right);
    }
    else {
        (*real_function)(interp, left, right);
    }
}

void mmd_dispatch_v_pi(Interp *interp /*NN*/, PMC *left /*NN*/, INTVAL right, INTVAL func_nr) { mmd_f_v_pi real_function; int is_pmc; UINTVAL left_type;

    left = mmd_deref(interp, func_nr, left);
    mmd_ensure_writable(interp, func_nr, left);

    left_type = left->vtable->base_type;
    real_function = (mmd_f_v_pi)get_mmd_dispatch_type(interp,
            func_nr, left_type, enum_type_INTVAL, &is_pmc);
    if (is_pmc) {
        PMC * const sub = (PMC*)real_function;
        Parrot_runops_fromc_args(interp, sub, "vPI", left, right);
    }
    else {
        (*real_function)(interp, left, right);
    }
}

PARROT_API void mmd_dispatch_v_pn(Interp *interp /*NN*/, PMC *left /*NN*/, FLOATVAL right, INTVAL func_nr) { mmd_f_v_pn real_function; int is_pmc; UINTVAL left_type;

    left = mmd_deref(interp, func_nr, left);
    mmd_ensure_writable(interp, func_nr, left);

    left_type = left->vtable->base_type;
    real_function = (mmd_f_v_pn)get_mmd_dispatch_type(interp,
            func_nr, left_type, enum_type_FLOATVAL, &is_pmc);
    if (is_pmc) {
        PMC * const sub = (PMC*)real_function;
        Parrot_runops_fromc_args(interp, sub, "vPN", left, right);
    }
    else {
        (*real_function)(interp, left, right);
    }
}

PARROT_API void mmd_dispatch_v_ps(Interp *interp /*NN*/, PMC *left /*NN*/, STRING *right, INTVAL func_nr) { mmd_f_v_ps real_function; int is_pmc; UINTVAL left_type;

    left = mmd_deref(interp, func_nr, left);
    mmd_ensure_writable(interp, func_nr, left);

    left_type = VTABLE_type(interp, left);
    real_function = (mmd_f_v_ps)get_mmd_dispatch_type(interp,
            func_nr, left_type, enum_type_STRING, &is_pmc);
    if (is_pmc) {
        PMC * const sub = (PMC*)real_function;
        Parrot_runops_fromc_args(interp, sub, "vPS", left, right);
    }
    else {
        (*real_function)(interp, left, right);
    }
}

/*

FUNCDOC: Like mmd_dispatch_p_ppp(), only it returns an INTVAL. This is used by MMD compare functions.

*/

PARROT_API INTVAL mmd_dispatch_i_pp(Interp *interp /*NN*/, PMC *left /*NN*/, PMC *right /*NN*/, INTVAL func_nr) { mmd_f_i_pp real_function; int is_pmc; INTVAL ret;

    left = mmd_deref(interp, func_nr, left);
    right = mmd_deref(interp, func_nr, right);

    real_function = (mmd_f_i_pp)get_mmd_dispatcher(interp,
            left, right, func_nr, &is_pmc);

    if (is_pmc) {
        PMC * const sub = (PMC*)real_function;
        ret = Parrot_runops_fromc_args_reti(interp, sub, "IPP",
                left, right);
    }
    else {
        ret = (*real_function)(interp, left, right);
    }
    return ret;
}

/*

FUNCDOC: Add a new binary MMD function to the list of functions the MMD system knows of. func_num is the number of the new function. function is ignored.

TODO change this to a MMD register interface that takes a function *name*.

*/

PARROT_API void mmd_add_function(Interp *interp /*NN*/, INTVAL func_nr, funcptr_t function) { if (func_nr >= (INTVAL)interp->n_binop_mmd_funcs) { INTVAL i;

        if (interp->binop_mmd_funcs) {
            interp->binop_mmd_funcs =
                (MMD_table *)mem_sys_realloc(interp->binop_mmd_funcs,
                                (func_nr + 1) * sizeof (MMD_table));
        }
        else {
            interp->binop_mmd_funcs =
                (MMD_table *)mem_sys_allocate((func_nr + 1) * sizeof (MMD_table));
        }

        for (i = interp->n_binop_mmd_funcs; i <= func_nr; ++i)  {
            MMD_table * const table = interp->binop_mmd_funcs + i;
            table->x = 0;
            table->y = 0;
            table->mmd_funcs = NULL;
        }
        interp->n_binop_mmd_funcs = func_nr + 1;
    }

}

/*

FUNCDOC: Expands the function table in the X dimension to include new_x.

*/

static void mmd_expand_x(Interp *interp /*NN*/, INTVAL func_nr, INTVAL new_x) { funcptr_t *new_table; UINTVAL x; UINTVAL y; UINTVAL i; MMD_table * const table = interp->binop_mmd_funcs + func_nr; char *src_ptr, *dest_ptr; size_t old_dp, new_dp;

    /* Is the Y 0? If so, nothing to expand, so just set the X for
       later use */
    if (table->y == 0) {
        table->x = new_x;
        return;
    }

    /* The Y is not zero. Bleah. This means we have to expand the
       table in an unpleasant way. */

    x = table->x;
    y = table->y;

    /* First, fill in the whole new table with the default function
       pointer. We only really need to do the new part, but... */
    new_table = (funcptr_t *)mem_sys_allocate_zeroed(sizeof (funcptr_t) *
                                                     y * new_x);

    /* Then copy the old table over. We have to do this row by row,
       because the rows in the old and new tables are different
       lengths */
    src_ptr = (char*) table->mmd_funcs;
    dest_ptr = (char*) new_table;
    old_dp = sizeof (funcptr_t) * x;
    new_dp = sizeof (funcptr_t) * new_x;
    for (i = 0; i < y; i++) {
        memcpy(dest_ptr, src_ptr, sizeof (funcptr_t) * x);
        src_ptr  += old_dp;
        dest_ptr += new_dp;
    }
    if (table->mmd_funcs)
        mem_sys_free(table->mmd_funcs);
    table->x = new_x;
    /* Set the old table to point to the new table */
    table->mmd_funcs = new_table;
}

/*

FUNCDOC: Expands the function table in the Y direction.

*/

static void mmd_expand_y(Interp *interp /*NN*/, INTVAL func_nr, INTVAL new_y) { UINTVAL new_size, old_size; MMD_table * const table = interp->binop_mmd_funcs + func_nr;

    assert(table->x);

    old_size = sizeof (funcptr_t) * table->x * table->y;
    new_size = sizeof (funcptr_t) * table->x * new_y;

    if (table->mmd_funcs)
        table->mmd_funcs = (funcptr_t *)mem_sys_realloc_zeroed(
            table->mmd_funcs, new_size, old_size);
    else
        table->mmd_funcs = (funcptr_t *)mem_sys_allocate_zeroed(new_size);

    table->y = new_y;
}

/*

FUNCDOC: Add a function to the MMD table by class name, rather than class number. Handles the case where the named class isn't loaded yet.

Adds a new MMD function funcptr to the func_num function table that will be invoked when the left parameter is of class left_class and the right parameter is of class right_class. Both classes are STRING *s that hold the PMC class names for the left and right sides. If either class isn't yet loaded, Parrot will cache the information such that the function will be installed if at some point in the future both classes are available.

Currently this is done by just assigning class numbers to the classes, which the classes will pick up and use if they're later loaded, but we may later put the functions into a deferred table that we scan when PMC classes are loaded. Either way, the function will be guaranteed to be installed when it's needed.

The function table must exist, but if it is too small, it will automatically be expanded.

*/

PARROT_API void mmd_add_by_class(Interp *interp /*NN*/, INTVAL functype, STRING *left_class /*NN*/, STRING *right_class /*NN*/, funcptr_t funcptr /*NULLOK*/) { INTVAL left_type = pmc_type(interp, left_class); INTVAL right_type = pmc_type(interp, right_class);

    if (left_type == enum_type_undef) {
        left_type = pmc_register(interp, left_class);
    }
    if (right_type == enum_type_undef) {
        right_type = pmc_register(interp, right_class);
    }

    mmd_register(interp, functype, left_type, right_type, funcptr);

}

/*

FUNCDOC: Register a function funcptr for MMD function table func_num for classes left_type and right_type. The left and right types are INTVALs that represent the class ID numbers.

The function table must exist, but if it is too small, it will automatically be expanded.

Adding a new function to the table can be interestingly non-trivial, so we get to be tricky.

If the left or right types are larger than anything we've seen so far, it means that we have to expand the table. Making Y larger is simple -- just realloc with some more rows. Making X larger is less simple. In either case, we punt to other functions.

TODO - Currently the MMD system doesn't handle inheritance and best match searching, as it assumes that all PMC types have no parent type. This can be considered a bug, and will be resolved at some point in the future.

*/

PARROT_API void mmd_register(Interp *interp /*NN*/, INTVAL func_nr, INTVAL left_type, INTVAL right_type, funcptr_t funcptr /*NULLOK*/) {

    INTVAL offset;
    MMD_table *table;

    assert(func_nr < (INTVAL)interp->n_binop_mmd_funcs);
    assert(left_type >= 0);
    assert(right_type >=0 ||
            (right_type >= enum_type_INTVAL && right_type <= enum_type_PMC));
    if (right_type < 0)
        right_type -= enum_type_INTVAL;
    else
        right_type += 4;
    table = interp->binop_mmd_funcs + func_nr;
    if ((INTVAL)table->x <= left_type) {
        mmd_expand_x(interp, func_nr, left_type + 1);
    }

    if ((INTVAL)table->y <= right_type) {
        mmd_expand_y(interp, func_nr, right_type + 1);
    }

    offset = table->x * right_type + left_type;
    table->mmd_funcs[offset] = funcptr;
}

PARROT_API void mmd_register_sub(Interp *interp /*NN*/, INTVAL func_nr, INTVAL left_type, INTVAL right_type, PMC *sub /*NN*/) { if (sub->vtable->base_type == enum_class_NCI) { /* returned from mmdvt_find */ mmd_register(interp, func_nr, left_type, right_type, D2FPTR(PMC_struct_val(sub))); } else { PMC * const fake = (PMC*)((UINTVAL) sub | 1); mmd_register(interp, func_nr, left_type, right_type, D2FPTR(fake)); } }

/*

FUNCDOC: Frees all the memory allocated used the MMD subsystem.

*/

PARROT_API void mmd_destroy(Interp *interp /*NN*/) { if (interp->n_binop_mmd_funcs) { UINTVAL i; for (i = 0; i <interp->n_binop_mmd_funcs; ++i) { if (interp->binop_mmd_funcs[i].mmd_funcs) { mem_sys_free(interp->binop_mmd_funcs[i].mmd_funcs); interp->binop_mmd_funcs[i].mmd_funcs = NULL; } } } mem_sys_free(interp->binop_mmd_funcs); interp->binop_mmd_funcs = NULL; }

/*

FUNCDOC: Return an MMD PMC function for the given data types. The return result is either a Sub PMC (for PASM MMD functions) or a NCI PMC holding the C function pointer in PMC_struct_val.

*/

PARROT_API PMC * mmd_vtfind(Interp *interp /*NN*/, INTVAL func_nr, INTVAL left, INTVAL right) /* WARN_UNUSED */ { int is_pmc; PMC *f; const funcptr_t func = get_mmd_dispatch_type(interp, func_nr, left, right, &is_pmc); if (func && is_pmc) { /* TODO if is_pmc == 2 a Bound_NCI is returned, which actually * should be filled with one of the wrapper functions */ return (PMC*)F2DPTR(func); } f = pmc_new(interp, enum_class_NCI); PMC_struct_val(f) = F2DPTR(func); return f; }

static PMC* mmd_arg_tuple_func(Interp *); static PMC* mmd_search_default(Interp *, STRING *meth, PMC *arg_tuple); static PMC* mmd_search_scopes(Interp *, STRING *meth, PMC *arg_tuple); static void mmd_search_classes(Interp *, STRING *meth, PMC *arg_tuple, PMC *, INTVAL start); static int mmd_search_cur_namespace(Interp *, STRING *meth, PMC *arg_tuple, PMC *); static void mmd_search_builtin(Interp *, STRING *meth, PMC *arg_tuple, PMC *); static int mmd_maybe_candidate(Interp *, PMC *pmc, PMC *arg_tuple, PMC *cl); static void mmd_sort_candidates(Interp *, PMC *arg_tuple, PMC *cl);

PARROT_API PMC * Parrot_MMD_search_default_infix(Interp *interp /*NN*/, STRING *meth, INTVAL left_type, INTVAL right_type) { PMC* const arg_tuple = pmc_new(interp, enum_class_FixedIntegerArray);

    VTABLE_set_integer_native(interp, arg_tuple, 2);
    VTABLE_set_integer_keyed_int(interp, arg_tuple, 0, left_type);
    VTABLE_set_integer_keyed_int(interp, arg_tuple, 1, right_type);
    return mmd_search_default(interp, meth, arg_tuple);
}

/*

FUNCDOC: Parrot_mmd_sort_candidate_list

Given an array PMC (usually a MultiSub) sort the mmd candidates by their manhatten distance to the current args.

*/

PARROT_API PMC * Parrot_mmd_sort_candidate_list(Interp *interp /*NN*/, PMC *candidates) { PMC *arg_tuple; INTVAL n;

    n = VTABLE_elements(interp, candidates);
    if (!n)
        return PMCNULL;

    arg_tuple  = mmd_arg_tuple_func(interp);
    candidates = VTABLE_clone(interp, candidates);
    mmd_sort_candidates(interp, arg_tuple, candidates);

    /* if there aren't any variants that match the current args, we could end
       up with an empty list */
    n = VTABLE_elements(interp, candidates);
    if (!n)
        return PMCNULL;

    return candidates;
}

/*

FUNCDOC: mmd_arg_tuple_inline

Return a list of argument types. PMC arguments are specified as function arguments.

FUNCDOC: mmd_arg_tuple_func

Return a list of argument types. PMC arguments are take from registers according to calling conventions.

*/

static PMC* mmd_arg_tuple_inline(Interp *interp /*NN*/, STRING *signature /*NN*/, va_list args) { INTVAL i; PMC *arg;

    PMC * const arg_tuple = pmc_new(interp, enum_class_FixedIntegerArray);
    const INTVAL sig_len = string_length(interp, signature);

    if (!sig_len)
        return arg_tuple;
    VTABLE_set_integer_native(interp, arg_tuple, sig_len);
    for (i = 0; i < sig_len; ++i) {
        INTVAL type = string_index(interp, signature, i);
        switch (type) {
            case 'I':
                VTABLE_set_integer_keyed_int(interp, arg_tuple,
                        i, enum_type_INTVAL);
                break;
            case 'N':
                VTABLE_set_integer_keyed_int(interp, arg_tuple,
                        i, enum_type_FLOATVAL);
                break;
            case 'S':
                VTABLE_set_integer_keyed_int(interp, arg_tuple,
                        i, enum_type_STRING);
                break;
            case 'O':
            case 'P':
                arg = va_arg(args, PMC *);
                type = VTABLE_type(interp, arg);
                VTABLE_set_integer_keyed_int(interp, arg_tuple,
                        i, type);
                break;
            default:
                internal_exception(1,
                        "Unknown signature type %d in mmd_arg_tuple", type);
                break;
        }

    }
    return arg_tuple;
}

static PMC* mmd_arg_tuple_func(Interp *interp /*NN*/) { INTVAL sig_len, i, type, idx; PMC* arg; PMC* args_array; /* from recent set_args opcode */ opcode_t *args_op; PackFile_Constant **constants;

    /*
     * if there is no signature e.g. because of
     *      m = getattribute l, "__add"
     * - we have to return the MultiSub
     * - create a BoundMulti
     * - dispatch in invoke - yeah ugly
     */

    PMC * const arg_tuple = pmc_new(interp, enum_class_ResizableIntegerArray);

    args_op = interp->current_args;
    if (!args_op)
        return arg_tuple;
    assert(*args_op == PARROT_OP_set_args_pc);
    constants = interp->code->const_table->constants;
    ++args_op;
    args_array = constants[*args_op]->u.key;
    ASSERT_SIG_PMC(args_array);
    sig_len = SIG_ELEMS(args_array);
    if (!sig_len)
        return arg_tuple;
    ++args_op;

    for (i = 0; i < sig_len; ++i, ++args_op) {
        type = SIG_ITEM(args_array, i);
        /* named don't MMD */
        if (type & PARROT_ARG_NAME)
            break;
        /* expand flattening args */
        if (type & PARROT_ARG_FLATTEN) {
            int j, n;

            idx = *args_op;
            arg = REG_PMC(idx);
            n = VTABLE_elements(interp, arg);
            for (j = 0; j < n; ++j)  {
                PMC * const elem = VTABLE_get_pmc_keyed_int(interp, arg, j);
                type = VTABLE_type(interp, elem);
                VTABLE_push_integer(interp, arg_tuple, type);
            }
            return arg_tuple;
        }
        switch (type & PARROT_ARG_TYPE_MASK) {
            case PARROT_ARG_INTVAL:
                VTABLE_push_integer(interp, arg_tuple, enum_type_INTVAL);
                break;
            case PARROT_ARG_FLOATVAL:
                VTABLE_push_integer(interp, arg_tuple, enum_type_FLOATVAL);
                break;
            case PARROT_ARG_STRING:
                VTABLE_push_integer(interp, arg_tuple, enum_type_STRING);
                break;
            case PARROT_ARG_PMC:
                idx = *args_op;
                if ((type & PARROT_ARG_CONSTANT))
                    arg = constants[idx]->u.key;
                else
                    arg = REG_PMC(idx);
                type = VTABLE_type(interp, arg);
                VTABLE_push_integer(interp, arg_tuple, type);
                break;
            default:
                internal_exception(1,
                        "Unknown signature type %d in mmd_arg_tuple", type);
                break;
        }

    }
    return arg_tuple;
}

/*

FUNCDOC: mmd_search_default

Default implementation of MMD search. Search scopes for candidates, walk the class hierarchy, sort all candidates by their Manhattan distance, and return result

*/

static PMC* mmd_search_default(Interp *interp, STRING *meth, PMC *arg_tuple) { INTVAL n;

    /*
     * 2) create a list of matching functions
     */
    PMC * const candidate_list = mmd_search_scopes(interp, meth, arg_tuple);
    /*
     * 3) if list is empty fail
     *    if the first found function is a plain Sub: finito
     */
    n = VTABLE_elements(interp, candidate_list);
    if (n == 1) {
        PMC * const pmc = VTABLE_get_pmc_keyed_int(interp, candidate_list, 0);
        STRING * const _sub = CONST_STRING(interp, "Sub");

        if (VTABLE_isa(interp, pmc, _sub)) {
            return pmc;
        }
    }
    /*
     * 4) first was a MultiSub - go through all found MultiSubs and check
     *    the first arguments MRO, add all MultiSubs and plain methods,
     *    where the first argument matches
     */
    mmd_search_classes(interp, meth, arg_tuple, candidate_list, 0);
    n = VTABLE_elements(interp, candidate_list);
    if (!n)
        return NULL;
    /*
     * 5) sort the list
     */
    if (n > 1)
        mmd_sort_candidates(interp, arg_tuple, candidate_list);
    n = VTABLE_elements(interp, candidate_list);
    if (!n)
        return NULL;
    /*
     * 6) Uff, return first one
     */
    return VTABLE_get_pmc_keyed_int(interp, candidate_list, 0);
}

/*

FUNCDOC: mmd_search_classes

Search all the classes in all MultiSubs of the candidates cl and return a list of all candidates. start_at_parent is 0 to start at the class itself or 1 to search from the first parent class.

*/

static void mmd_search_classes(Interp *interp /*NN*/, STRING *meth, PMC *arg_tuple, PMC *cl, INTVAL start_at_parent) { INTVAL type1;

    /*
     * get the class of the first argument
     */
    if (!VTABLE_elements(interp, arg_tuple))
        return;
    type1 = VTABLE_get_integer_keyed_int(interp, arg_tuple, 0);
    if (type1 < 0) {
        return;
        internal_exception(1, "unimplemented native MMD type");
        /* TODO create some class namespace */
    }
    else {
        PMC * const mro = interp->vtables[type1]->mro;
        const INTVAL n = VTABLE_elements(interp, mro);
        INTVAL i;

        for (i = start_at_parent; i < n; ++i) {
            PMC * const _class = VTABLE_get_pmc_keyed_int(interp, mro, i);
            PMC * const pmc = Parrot_find_method_with_cache(interp, _class, meth);
            if (!PMC_IS_NULL(pmc)) {
                /*
                 * mmd_is_hidden would consider all previous candidates
                 * XXX pass current n so that only candidates from this
                 *     mro are used?
                 */
                if (mmd_maybe_candidate(interp, pmc, arg_tuple, cl))
                    break;
            }
        }
    }
}

static INTVAL distance_cmp(Interp *interp /*NULLOK*/, INTVAL a, INTVAL b) { short da = (short)a & 0xffff; short db = (short)b & 0xffff; /* sort first by distance */ if (da > db) return 1; if (da < db) return -1; /* end then by index in candidate list */ da = (short)(a >> 16); db = (short)(b >> 16); return da > db ? 1 : da < db ? -1 : 0; }

extern void Parrot_FixedPMCArray_nci_sort(Interp* , PMC* pmc, PMC *cmp_func);

/*

mmd_distance

Create Manhattan Distance of sub pmc against given argument types. 0xffff is the maximum distance

*/

static PMC* mmd_cvt_to_types(Interp* interp, PMC *multi_sig) { const INTVAL n = VTABLE_elements(interp, multi_sig); INTVAL i;

    PMC * const ar = pmc_new(interp, enum_class_FixedIntegerArray);
    VTABLE_set_integer_native(interp, ar, n);
    for (i = 0; i < n; ++i) {
        PMC * const sig_elem = VTABLE_get_pmc_keyed_int(interp, multi_sig, i);
        INTVAL type;

        if (sig_elem->vtable->base_type == enum_class_String) {
            STRING * const sig = VTABLE_get_string(interp, sig_elem);
            if (memcmp(sig->strstart, "__VOID", 6) == 0) {
                PMC_int_val(ar)--;  /* XXX */
                break;
            }
            type = pmc_type(interp, sig);
        }
        else {
            type = pmc_type_p(interp, sig_elem);
        }
        VTABLE_set_integer_keyed_int(interp, ar, i, type);
    }
    return ar;
}

#define MMD_BIG_DISTANCE 0x7fff

static UINTVAL mmd_distance(Interp *interp, PMC *pmc /*NN*/, PMC *arg_tuple) { PMC *multi_sig, *mro; INTVAL i, n, args, dist, j, m;

    if (pmc->vtable->base_type == enum_class_NCI) {
        /* has to be a builtin multi method */
        multi_sig = PMC_pmc_val(pmc);
    }
    else if (pmc->vtable->base_type == enum_class_Sub ||
             pmc->vtable->base_type == enum_class_Closure) {
        multi_sig = PMC_sub(pmc)->multi_signature;
        if (!multi_sig) {
            /* some method */
            return 0;
        }
        if (multi_sig->vtable->base_type == enum_class_FixedPMCArray) {
            multi_sig = PMC_sub(pmc)->multi_signature =
                mmd_cvt_to_types(interp, multi_sig);
        }
    }
    else
        return MMD_BIG_DISTANCE;
    n = VTABLE_elements(interp, multi_sig);
    args = VTABLE_elements(interp, arg_tuple);
    /*
     * arg_tuple may have more arguments - only the
     * n multi_sig invocants are counted
     */
    if (args < n)
        return MMD_BIG_DISTANCE;
    dist = 0;
    if (args > n)
        dist = 1000;   /* XXX arbitrary > max_class_depth * n */
    /*
     * now go through args
     */
    for (i = 0; i < n; ++i) {
        const INTVAL type_sig  = VTABLE_get_integer_keyed_int(interp, multi_sig, i);
        const INTVAL type_call = VTABLE_get_integer_keyed_int(interp, arg_tuple, i);
        if (type_sig == type_call)
            continue;
        /*
         * different native types are very different, except a PMC
         * which matches any PMC
         */
        if (type_call <= 0 && type_sig == enum_type_PMC) {
            dist++;
            continue;
        }
        if ((type_sig <= 0 && type_sig != enum_type_PMC) || type_call <= 0) {
            dist = MMD_BIG_DISTANCE;
            break;
        }
        /*
         * now consider MRO of types the signature type has to be somewhere
         * in the MRO of the type_call
         */
        mro = interp->vtables[type_call]->mro;
        m = VTABLE_elements(interp, mro);
        for (j = 0; j < m; ++j) {
            const PMC * const cl = VTABLE_get_pmc_keyed_int(interp, mro, j);
            if (cl->vtable->base_type == type_sig)
                break;
            ++dist;
        }
        /*
         * if the type wasn't in MRO check, if any PMC matches
         * in that case use the distance + 1 (of an any PMC parent)
         */
        if (j == m && type_sig != enum_type_PMC) {
            dist = MMD_BIG_DISTANCE;
            break;
        }
        ++dist;
#if MMD_DEBUG
        {
            STRING *s1, *s2;
            if (type_sig < 0)
                s1 = Parrot_get_datatype_name(interp, type_sig);
            else {
                s1 = interp->vtables[type_sig]->whoami;
            }
            if (type_call < 0)
                s2 = Parrot_get_datatype_name(interp, type_call);
            else {
                s2 = interp->vtables[type_call]->whoami;
            }
            PIO_eprintf(interp, "arg %d: dist %d sig %Ss arg %Ss\n",
                i, dist, s1, s2);
        }
#endif
    }
    return dist;
}

/*

FUNCDOC: mmd_sort_candidates

Sort the candidate list cl by Manhattan Distance

*/

static void mmd_sort_candidates(Interp *interp, PMC *arg_tuple, PMC *cl) { INTVAL i; PMC *nci; INTVAL *helper; PMC **data;

    const INTVAL n = VTABLE_elements(interp, cl);
    /*
     * create a helper structure:
     * bits 0..15  = distance
     * bits 16..31 = idx in candidate list
     *
     * TODO use half of available INTVAL bits
     */
    PMC * const sort = pmc_new(interp, enum_class_FixedIntegerArray);
    VTABLE_set_integer_native(interp, sort, n);
    helper = (INTVAL *)PMC_data(sort);
    for (i = 0; i < n; ++i) {
        PMC * const pmc = VTABLE_get_pmc_keyed_int(interp, cl, i);
        const INTVAL d = mmd_distance(interp, pmc, arg_tuple);
        helper[i] = i << 16 | (d & 0xffff);
    }
    /*
     * need an NCI function pointer
     */
    nci = pmc_new(interp, enum_class_NCI);
    PMC_struct_val(nci) = F2DPTR(distance_cmp);
    /*
     * sort it
     */
    Parrot_FixedPMCArray_nci_sort(interp, sort, nci);
    /*
     * now helper has a sorted list of indices in the upper 16 bits
     * fill helper with sorted candidates
     */
    data = (PMC **)PMC_data(cl);
    for (i = 0; i < n; ++i) {
        const INTVAL idx = helper[i] >> 16;
        /*
         * if the distance is big stop
         */
        if ((helper[i] & 0xffff) == MMD_BIG_DISTANCE) {
            PMC_int_val(cl) = i;
            break;
        }
        helper[i] = (INTVAL)data[idx];
    }
    /*
     * use helper structure
     */
    PMC_data(cl) = helper;
    PMC_data(sort) = data;
}

/*

FUNCDOC: mmd_search_scopes

Search all scopes for MMD candidates matching the arguments given in arg_tuple.

*/

static PMC* mmd_search_scopes(Interp *interp, STRING *meth, PMC *arg_tuple) { PMC * const candidate_list = pmc_new(interp, enum_class_ResizablePMCArray);

    const int stop = mmd_search_cur_namespace(interp, meth, arg_tuple, candidate_list);
    if (stop)
        return candidate_list;
    mmd_search_builtin(interp, meth, arg_tuple, candidate_list);
    return candidate_list;
}

/*

FUNCDOC: mmd_is_hidden

Check if the given multi sub is hidden by any inner multi sub (already in the candidate list cl.

*/

static int mmd_is_hidden(Interp *interp, PMC *multi, PMC *cl) { /* * if the candidate list already has the a sub with the same * signature (long name), the outer multi is hidden * * TODO */ return 0; }

/*

FUNCDOC: mmd_maybe_candidate

If the candidate pmc is a Sub PMC, push it on the candidate list and return TRUE to stop further search.

If the candidate is a MultiSub remember all matching Subs and return FALSE to continue searching outer scopes.

*/

static int mmd_maybe_candidate(Interp *interp, PMC *pmc, PMC *arg_tuple, PMC *cl) { INTVAL i, n;

    STRING * const _sub = CONST_STRING(interp, "Sub");
    STRING * const _multi_sub = CONST_STRING(interp, "MultiSub");

    if (VTABLE_isa(interp, pmc, _sub)) {
        /* a plain sub stops outer searches */
        /* TODO check arity of sub */

        VTABLE_push_pmc(interp, cl, pmc);
        return 1;
    }
    if (!VTABLE_isa(interp, pmc, _multi_sub)) {
        /* not a Sub or MultiSub - ignore */
        return 0;
    }
    /*
     * ok we have a multi sub pmc, which is an array of candidates
     */
    n = VTABLE_elements(interp, pmc);
    for (i = 0; i < n; ++i) {
        PMC * const multi_sub = VTABLE_get_pmc_keyed_int(interp, pmc, i);

        if (!mmd_is_hidden(interp, multi_sub, cl))
            VTABLE_push_pmc(interp, cl, multi_sub);
    }
    return 0;
}

/*

FUNCDOC: mmd_search_cur_namespace

Search the current package namespace for matching candidates. Return TRUE if the MMD search should stop.

*/

static int mmd_search_cur_namespace(Interp *interp, STRING *meth, PMC *arg_tuple, PMC *cl) { PMC * const pmc = Parrot_find_global_cur(interp, meth);

    return pmc && mmd_maybe_candidate(interp, pmc, arg_tuple, cl);
}

static PMC* mmd_get_ns(Interp *interp /*NN*/) { STRING * const ns_name = CONST_STRING(interp, "__parrot_core"); PMC * const ns = Parrot_get_namespace_keyed_str(interp, interp->root_namespace, ns_name); return ns; }

static PMC* mmd_make_ns(Interp *interp /*NN*/) { STRING * const ns_name = CONST_STRING(interp, "__parrot_core"); PMC * const ns = Parrot_make_namespace_keyed_str(interp, interp->root_namespace, ns_name); return ns; }

/*

FUNCDOC: mmd_search_builtin

Search the builtin namespace for matching candidates. This is the last search in all the namespaces.

*/

static void mmd_search_builtin(Interp *interp, STRING *meth, PMC *arg_tuple, PMC *cl) { PMC * const ns = mmd_get_ns(interp); PMC * const pmc = Parrot_find_global_n(interp, ns, meth); if (pmc) mmd_maybe_candidate(interp, pmc, arg_tuple, cl); }

static PMC * mmd_create_builtin_multi_stub(Interp *interp, PMC* ns, INTVAL func_nr) { const char * name = Parrot_MMD_method_name(interp, func_nr); /* create in constant pool */ STRING * const s = const_string(interp, name); PMC * multi = constant_pmc_new(interp, enum_class_MultiSub);

    VTABLE_set_pmc_keyed_str(interp, ns, s, multi);
    return ns;
}

static void mmd_create_builtin_multi_meth_2(Interp *interp, PMC *ns, INTVAL func_nr, INTVAL type, INTVAL right, funcptr_t func_ptr) { const char *short_name; char signature[6], val_sig; STRING *meth_name, *_sub; PMC *method, *multi, *_class, *multi_sig;

    assert(type != enum_class_Null && type != enum_class_delegate &&
            type != enum_class_Ref  && type != enum_class_SharedRef &&
            type != enum_class_deleg_pmc && type != enum_class_ParrotClass &&
            type != enum_class_ParrotObject);
    short_name = Parrot_MMD_method_name(interp, func_nr);
    /*
     * _int, _float, _str are just native variants of the base
     * multi
     */
    val_sig = 'P';
    if (right == enum_type_INTVAL)
        val_sig = 'I';
    else if (right == enum_type_STRING)
        val_sig = 'S';
    else if (right == enum_type_FLOATVAL)
        val_sig = 'N';

    /*
     * create NCI method in left class
     */
    strcpy(signature, "PJP.P");
    signature[3] = val_sig;
    if (func_nr >= MMD_EQ && func_nr <= MMD_STRCMP) {
        signature[0] = 'I';
        signature[4] = '\0';
    }
    /* implace infix like __i_add don't return a result */
    if (memcmp(short_name, "__i_", 4) == 0)
        signature[0] = 'v';
    meth_name = const_string(interp, short_name);
    _class = interp->vtables[type]->pmc_class;
    method = Parrot_find_method_direct(interp, _class, meth_name);
    if (PMC_IS_NULL(method)) {
        /* first method */
        method = constant_pmc_new(interp, enum_class_NCI);
        VTABLE_set_pointer_keyed_str(interp, method,
                const_string(interp, signature),
                F2DPTR(func_ptr));
        VTABLE_add_method(interp, _class, meth_name, method);
    }
    else {
        _sub = CONST_STRING(interp, "Sub");
        /* multiple methods with that same name */
        if (method->vtable->base_type == enum_class_NCI) {
            /* convert first to a multi */
            multi = constant_pmc_new(interp, enum_class_MultiSub);
            VTABLE_add_method(interp, _class, meth_name, multi);
            VTABLE_push_pmc(interp, multi, method);
        }
        else {
            assert(method->vtable->base_type == enum_class_MultiSub);
            multi = method;
        }
        method = constant_pmc_new(interp, enum_class_NCI);
        VTABLE_set_pointer_keyed_str(interp, method,
                const_string(interp, signature),
                F2DPTR(func_ptr));
        VTABLE_push_pmc(interp, multi, method);
    }
    /* mark MMD */
    PObj_get_FLAGS(method) |= PObj_private0_FLAG;
    /*
     * attach the multi_signature array to PMC_pmc_val
     */
    multi_sig = constant_pmc_new(interp, enum_class_FixedIntegerArray);
    VTABLE_set_integer_native(interp, multi_sig, 2);
    VTABLE_set_integer_keyed_int(interp, multi_sig, 0, type);
    VTABLE_set_integer_keyed_int(interp, multi_sig, 1, right);
    PMC_pmc_val(method) = multi_sig;

    /*
     * push method onto core multi_sub
     * TODO cache the namespace
     */
    multi = Parrot_find_global_n(interp, ns,
                                 const_string(interp, short_name));
    assert(multi);
    VTABLE_push_pmc(interp, multi, method);
}

static void mmd_create_builtin_multi_meth(Interp *interp, PMC *ns, INTVAL type, const MMD_init *entry /*NN*/) { mmd_create_builtin_multi_meth_2(interp, ns, entry->func_nr, type, entry->right, entry->func_ptr); }

/*

FUNCDOC: Register MMD functions for this PMC type.

*/

PARROT_API void Parrot_mmd_register_table(Interp* interp, INTVAL type, const MMD_init *mmd_table, INTVAL n) { MMD_table * const table = interp->binop_mmd_funcs; PMC * const ns = mmd_make_ns(interp); INTVAL i;

    if ((INTVAL)table->x < type && type < enum_class_core_max) {
        /*
         * pre-allocate the function table
         */
        for (i = 0; i < MMD_USER_FIRST; ++i) {
            mmd_register(interp, i, enum_class_core_max - 1,
                    enum_class_core_max - 1, NULL);
            /*
             * create a MultiSub stub
             */
            mmd_create_builtin_multi_stub(interp, ns, i);
        }
    }
    /*
     * register default mmds for this type
     */
    for (i = 0; i < n; ++i) {
        /* The following always fails for Intel C++ for unknown reasons,
         * but I'm assuming it's optimizer related.
         */
#ifndef __INTEL_COMPILER
        assert((PTR2UINTVAL(mmd_table[i].func_ptr) & 3) == 0);
#endif
        mmd_register(interp,
                mmd_table[i].func_nr, type,
                mmd_table[i].right, mmd_table[i].func_ptr);
        mmd_create_builtin_multi_meth(interp, ns, type, mmd_table + i);
    }
}

/*

FUNCDOC: Parrot_mmd_rebuild_table

Rebuild the static MMD_table for the given class type and MMD function number. If type is negative all classes are rebuilt. If func_nr is negative all MMD functions are rebuilt.

*/

PARROT_API void Parrot_mmd_rebuild_table(Interp* interp /*NN*/, INTVAL type, INTVAL func_nr) { MMD_table *table; UINTVAL i;

    if (!interp->binop_mmd_funcs)
        return;
    table = interp->binop_mmd_funcs + func_nr;
    if (!table)
        return;

    /* TODO specific parts of table
     * the type and it's mro and
     * all classes that inherit from type
     */
    for (i = 0; i < table->x * table->y; ++i)
        table->mmd_funcs[i] = NULL;
}

/*

SEE ALSO ^

include/parrot/mmd.h, http://svn.perl.org/perl6/doc/trunk/design/apo/A12.pod, http://svn.perl.org/perl6/doc/trunk/design/syn/S12.pod

*/

/* * Local variables: * c-file-style: "parrot" * End: * vim: expandtab shiftwidth=4: */


parrot