The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/* -*- Mode: C -*- */

#define PERL_NO_GET_CONTEXT 1

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "ppport.h"

#if (PERL_VERSION < 7)
#include "sort.h"
#endif

static I32
ix_sv_cmp(pTHX_ SV **a, SV **b) {
    return sv_cmp(*a, *b);
}

static I32
ix_rsv_cmp(pTHX_ SV **a, SV **b) {
    return sv_cmp(*b, *a);
}

static I32
ix_lsv_cmp(pTHX_ SV **a, SV **b) {
    return sv_cmp_locale(*a, *b);
}

static I32
ix_rlsv_cmp(pTHX_ SV **a, SV **b) {
    return sv_cmp_locale(*b, *a);
}

static I32
ix_n_cmp(pTHX_ NV *a, NV *b) {
    NV nv1 = *a;
    NV nv2 = *b;
    return nv1 < nv2 ? -1 : nv1 > nv2 ? 1 : 0;
}

static I32
ix_rn_cmp(pTHX_ NV *a, NV *b) {
    NV nv1 = *b;
    NV nv2 = *a;
    return nv1 < nv2 ? -1 : nv1 > nv2 ? 1 : 0;
}

static I32
ix_i_cmp(pTHX_ IV *a, IV *b) {
    IV iv1 = *a;
    IV iv2 = *b;
    return iv1 < iv2 ? -1 : iv1 > iv2 ? 1 : 0;
}

static I32
ix_ri_cmp(pTHX_ IV *a, IV *b) {
    IV iv1 = *b;
    IV iv2 = *a;
    return iv1 < iv2 ? -1 : iv1 > iv2 ? 1 : 0;
}

static I32
ix_u_cmp(pTHX_ UV *a, UV *b) {
    UV uv1 = *a;
    UV uv2 = *b;
    return uv1 < uv2 ? -1 : uv1 > uv2 ? 1 : 0;
}

static I32
ix_ru_cmp(pTHX_ UV *a, UV *b) {
    UV uv1 = *b;
    UV uv2 = *a;
    return uv1 < uv2 ? -1 : uv1 > uv2 ? 1 : 0;
}

static void *v_alloc(pTHX_ IV n, IV lsize) {
    void *r;
    Newxc(r, n<<lsize, char, void);
    SAVEFREEPV(r);
    return r;
}

static void *av_alloc(pTHX_ IV n, IV lsize) {
    AV *av=(AV*)sv_2mortal((SV*)newAV());
    av_fill(av, n-1);
    return AvARRAY(av);
}

static void i_store(pTHX_ SV *v, void *to) {
    *((IV*)to)=SvIV(v);
}

static void u_store(pTHX_ SV *v, void *to) {
    *((UV*)to)=SvUV(v);
}

static void n_store(pTHX_ SV *v, void *to) {
    *((NV*)to)=SvNV(v);
}

static void sv_store(pTHX_ SV *v, void *to) {
    *((SV**)to)=SvREFCNT_inc(v);
}

#define lsizeof(A) (ilog2(sizeof(A)))


static int ilog2(int i) {
    if (i>256) croak("internal error");
    if (i>128) return 8;
    if (i>64) return 7;
    if (i>32) return 6;
    if (i>16) return 5;
    if (i>8) return 4;
    if (i>4) return 3;
    if (i>2) return 2;
    if (i>1) return 1;
    return 0;
}

/* sorting types:

   0 => string
   1 => locale
   2 => number
   3 => integer
   4 => unsigned_integer
   5 => single precission float - not implemented
   
   128 => reverse string
   129 => reverse locale
   130 => reverse number
   131 => reverse integer
   132 => reverse unsigned_integer
   133 => reverse s. p. float - not implemented

*/

typedef I32 (*COMPARE_t)(pTHX_ void*, void*);
typedef void (*STORE_t)(pTHX_ SV*, void*);

static void
_keysort(pTHX_ IV type, SV *keygen, SV **values, I32 offset, I32 ax, IV len) {
    dSP;
    if (len) {
	void *keys;
	void **ixkeys;
	IV i;
	SV **from, **to;

	IV lsize;
	COMPARE_t cmp;
	STORE_t store;

#if (PERL_VERSION < 9)
        int hints = PL_curcop->op_private;
#else
        int hints = CopHINTS_get(PL_curcop);
#endif

        /* fprintf (stderr, "hints=0x%x, int=0x%x, loc=0x%x\n", hints, HINT_INTEGER, HINT_LOCALE );fflush(stderr); */

	switch(type) {
	case 0:
	case 128:
	    if (hints & HINT_LOCALE) type = type | 128;
	    break;
	case 2:
	case 130:
	    if (hints & HINT_INTEGER) type = type | 1;
	    break;
	}

	switch(type) {
	case 0:
	    cmp = (COMPARE_t)&ix_sv_cmp;
	    lsize = lsizeof(SV*);
	    keys = av_alloc(aTHX_ len, lsize);
	    store = &sv_store;
	    break;
	case 1:
	    cmp = (COMPARE_t)&ix_lsv_cmp;
	    lsize = lsizeof(SV*);
	    keys = av_alloc(aTHX_ len, lsize);
	    store = &sv_store;
	    break;
	case 2:
	    cmp = (COMPARE_t)&ix_n_cmp;
	    lsize = lsizeof(NV);
	    keys = v_alloc(aTHX_ len, lsize);
	    store = &n_store;
	    break;
	case 3:
	    cmp = (COMPARE_t)&ix_i_cmp;
	    lsize = lsizeof(IV);
	    keys = v_alloc(aTHX_ len, lsize);
	    store = &i_store;
	    break;
	case 4:
	    cmp = (COMPARE_t)&ix_u_cmp;
	    lsize = lsizeof(UV);
	    keys = v_alloc(aTHX_ len, lsize);
	    store = &u_store;
	    break;
	case 128:
	    cmp = (COMPARE_t)&ix_rsv_cmp;
	    lsize = lsizeof(SV*);
	    keys = av_alloc(aTHX_ len, lsize);
	    store = &sv_store;
	    break;
	case 129:
	    cmp = (COMPARE_t)&ix_rlsv_cmp;
	    lsize = lsizeof(SV*);
	    keys = av_alloc(aTHX_ len, lsize);
	    store = &sv_store;
	    break;
	case 130:
	    cmp = (COMPARE_t)&ix_rn_cmp;
	    lsize = lsizeof(NV);
	    keys = v_alloc(aTHX_ len, lsize);
	    store = &n_store;
	    break;
	case 131:
	    cmp = (COMPARE_t)&ix_ri_cmp;
	    lsize = lsizeof(IV);
	    keys = v_alloc(aTHX_ len, lsize);
	    store = &i_store;
	    break;
	case 132:
	    cmp = (COMPARE_t)&ix_ru_cmp;
	    lsize = lsizeof(UV);
	    keys = v_alloc(aTHX_ len, lsize);
	    store = &u_store;
	    break;
	default:
	    croak("unsupported sort type %d", type);
	}

	Newx(ixkeys, len, void*);
	SAVEFREEPV(ixkeys);
	if (keygen) {
	    for (i=0; i<len; i++) {
		IV count;
		SV *current;
		SV *result;
		void *target;
		/* warn("values=%p SP=%p SP-len=%p, &ST(0)=%p\n", values, SP, SP-len, &ST(0)); */
		ENTER;
		SAVETMPS;
                SAVE_DEFSV;
		current = values ? values[i] : ST(i + offset);
		DEFSV = sv_2mortal(current ? SvREFCNT_inc(current) : newSV(0));
		PUSHMARK(SP);
		PUTBACK;
		count = call_sv(keygen, G_SCALAR);
		SPAGAIN;
		if (count != 1)
		    croak("wrong number of results returned from key generation sub");
		result = POPs;
		/* warn("key: %_\n", result); */
		ixkeys[i] = target = ((char*)keys) + (i << lsize);
		(*store)(aTHX_ result, target);
		FREETMPS;
		LEAVE;
	    }
	}
	else {
	    for (i = 0; i < len; i++) {
		void *target;
		SV *current = values ? values[i] : ST(i + offset);
		ixkeys[i] = target = ((char*)keys) + (i << lsize);

		(*store)(aTHX_
			 current ? current : sv_2mortal(newSV(0)),
			 target);
	    }
	}
	sortsv((SV**)ixkeys, len, (SVCOMPARE_t)cmp);
	if (values) {
	    from = to = values;
	}
	else {
	    from = &ST(offset);
	    to = &ST(0);
	}
	for(i = 0; i < len; i++) {
            IV j = ( ((char*)(ixkeys[i])) - ((char*)keys) )>>lsize;
	    ixkeys[i] = from[j];
	}
	for(i = 0; i < len; i++) {
	    to[i] = (SV*)ixkeys[i];
	}
    }
}

typedef struct multikey {
    COMPARE_t cmp;
    void *data;
    IV lsize;
} MK;


static I32 _multikeycmp(pTHX_ void *a, void *b) {
    MK *keys = (MK*)PL_sortcop;
    IV r = (*(keys->cmp))(aTHX_ a, b);
    if (r) 
	return r;
    else {
	IV ixa = ( ((char*)a) - ((char*)(keys->data)) ) >> keys->lsize;
	IV ixb = ( ((char*)b) - ((char*)(keys->data)) ) >> keys->lsize;
	COMPARE_t cmp;
	while(1) {
	    keys++;
	    cmp=keys->cmp;
	    if (!cmp)
		return 0;
	    a = ((char*)(keys->data))+(ixa<<keys->lsize);
	    b = ((char*)(keys->data))+(ixb<<keys->lsize);
	    r = (*cmp)(aTHX_ a, b);
	    if (r)
		return r;
	}
    }
    return 0; /* dead code just to remove warnings from some
	       * compilers */
}

static I32 _secondkeycmp(pTHX_ void *a, void *b) {
    MK *keys = (MK*)PL_sortcop;
    IV ixa = ( ((char*)a) - ((char*)(keys->data)) ) >> keys->lsize;
    IV ixb = ( ((char*)b) - ((char*)(keys->data)) ) >> keys->lsize;
    COMPARE_t cmp;
    while(1) {
	I32 r;
	keys++;
	cmp=keys->cmp;
	if (!cmp)
	    return 0;
	a = ((char*)(keys->data))+(ixa<<keys->lsize);
	b = ((char*)(keys->data))+(ixb<<keys->lsize);
	r = (*cmp)(aTHX_ a, b);
	if (r)
	    return r;
    }
    return 0; /* dead code just to remove warnings from some
	       * compilers */
}

static I32
ix_sv_mcmp(pTHX_ SV **a, SV **b) {
    I32 r = sv_cmp(*a, *b);
    if (r) return r;
    return _secondkeycmp(aTHX_ a, b);
}

static I32
ix_rsv_mcmp(pTHX_ SV **a, SV **b) {
    I32 r = sv_cmp(*b, *a);
    if (r) return r;
    return _secondkeycmp(aTHX_ a, b);
}

static I32
ix_lsv_mcmp(pTHX_ SV **a, SV **b) {
    I32 r = sv_cmp_locale(*a, *b);
    if (r) return r;
    return _secondkeycmp(aTHX_ a, b);
}

static I32
ix_rlsv_mcmp(pTHX_ SV **a, SV **b) {
    I32 r = sv_cmp_locale(*b, *a);
    if (r) return r;
    return _secondkeycmp(aTHX_ a, b);
}

static I32
ix_n_mcmp(pTHX_ NV *a, NV *b) {
    NV nv1 = *a;
    NV nv2 = *b;
    return nv1 < nv2 ? -1 : nv1 > nv2 ? 1 : _secondkeycmp(aTHX_ a, b);
}

static I32
ix_rn_mcmp(pTHX_ NV *a, NV *b) {
    NV nv1 = *b;
    NV nv2 = *a;
    return nv1 < nv2 ? -1 : nv1 > nv2 ? 1 : _secondkeycmp(aTHX_ a, b);
}

static I32
ix_i_mcmp(pTHX_ IV *a, IV *b) {
    IV iv1 = *a;
    IV iv2 = *b;
    return iv1 < iv2 ? -1 : iv1 > iv2 ? 1 : _secondkeycmp(aTHX_ a, b);
}

static I32
ix_ri_mcmp(pTHX_ IV *a, IV *b) {
    IV iv1 = *b;
    IV iv2 = *a;
    return iv1 < iv2 ? -1 : iv1 > iv2 ? 1 : _secondkeycmp(aTHX_ a, b);
}

static I32
ix_u_mcmp(pTHX_ UV *a, UV *b) {
    UV uv1 = *a;
    UV uv2 = *b;
    return uv1 < uv2 ? -1 : uv1 > uv2 ? 1 : _secondkeycmp(aTHX_ a, b);
}

static I32
ix_ru_mcmp(pTHX_ UV *a, UV *b) {
    UV uv1 = *b;
    UV uv2 = *a;
    return uv1 < uv2 ? -1 : uv1 > uv2 ? 1 : _secondkeycmp(aTHX_ a, b);
}

static void
_multikeysort(pTHX_ SV *keytypes, SV *keygen, SV *post,
	      SV**values, I32 from_offset, I32 ax, I32 len) {
    dSP;
    STRLEN nkeys;
    unsigned char *types=(unsigned char *)SvPV(keytypes, nkeys);

    if (nkeys<1)
	croak("empty multikey type list passed");

    if (len) {
	IV i;
	MK *keys;
	STORE_t *store;
	void **ixkeys;
	SV **from, **to;
	COMPARE_t cmp = (COMPARE_t)&_multikeycmp;

	Newx(keys, nkeys+1, MK);
	SAVEFREEPV(keys);
	Newx(store, nkeys, STORE_t);
	SAVEFREEPV(store);
	
	for(i=0; i<nkeys; i++) {
	    MK *key = keys+i;
	    switch(types[i]) {
	    case 0:
		if (i==0) cmp = (COMPARE_t)&ix_sv_mcmp;
		key->cmp = (COMPARE_t)&ix_sv_cmp;
		key->lsize = lsizeof(SV*);
		key->data = av_alloc(aTHX_ len, key->lsize);
		store[i] = &sv_store;
		break;
	    case 1:
		if (i==0) cmp = (COMPARE_t)&ix_lsv_mcmp;
		key->cmp = (COMPARE_t)&ix_lsv_cmp;
		key->lsize = lsizeof(SV*);
		key->data = av_alloc(aTHX_ len, key->lsize);
		store[i] = &sv_store;
		break;
	    case 2:
		if (i==0) cmp = (COMPARE_t)&ix_n_mcmp;
		key->cmp = (COMPARE_t)&ix_n_cmp;
		key->lsize = lsizeof(NV);
		key->data = v_alloc(aTHX_ len, key->lsize);
		store[i] = &n_store;
		break;
	    case 3:
		if (i==0) cmp = (COMPARE_t)&ix_i_mcmp;
		key->cmp = (COMPARE_t)&ix_i_cmp;
		key->lsize = lsizeof(IV);
		key->data = v_alloc(aTHX_ len, key->lsize);
		store[i] = &i_store;
		break;
	    case 4:
		if (i==0) cmp = (COMPARE_t)&ix_u_mcmp;
		key->cmp = (COMPARE_t)&ix_u_cmp;
		key->lsize = lsizeof(UV);
		key->data = v_alloc(aTHX_ len, key->lsize);
		store[i] = &u_store;
		break;
	    case 128:
		if (i==0) cmp = (COMPARE_t)&ix_rsv_mcmp;
		key->cmp = (COMPARE_t)&ix_rsv_cmp;
		key->lsize = lsizeof(SV*);
		key->data = av_alloc(aTHX_ len, key->lsize);
		store[i] = &sv_store;
		break;
	    case 129:
		if (i==0) cmp = (COMPARE_t)&ix_rlsv_mcmp;
		key->cmp = (COMPARE_t)&ix_rlsv_cmp;
		key->lsize = lsizeof(SV*);
		key->data = av_alloc(aTHX_ len, key->lsize);
		store[i] = &sv_store;
		break;
	    case 130:
		if (i==0) cmp = (COMPARE_t)&ix_rn_mcmp;
		key->cmp = (COMPARE_t)&ix_rn_cmp;
		key->lsize = lsizeof(NV);
		key->data = v_alloc(aTHX_ len, key->lsize);
		store[i] = &n_store;
		break;
	    case 131:
		if (i==0) cmp = (COMPARE_t)&ix_ri_mcmp;
		key->cmp = (COMPARE_t)&ix_ri_cmp;
		key->lsize = lsizeof(IV);
		key->data = v_alloc(aTHX_ len, key->lsize);
		store[i] = &i_store;
                break;
	    case 132:
		if (i==0) cmp = (COMPARE_t)&ix_ru_mcmp;
		key->cmp = (COMPARE_t)&ix_ru_cmp;
		key->lsize = lsizeof(UV);
		key->data = v_alloc(aTHX_ len, key->lsize);
		store[i] = &u_store;
		break;
	    default:
		croak("unsupported sort type %d", types[i]);
	    }
	}

	keys[nkeys].cmp = 0;
	keys[nkeys].data = 0;
	keys[nkeys].lsize = 0;
	    
	Newx(ixkeys, len, void*);
	SAVEFREEPV(ixkeys);
	for (i=0; i<len; i++) {
	    IV count;
	    SV *current;
	    void *target;
	    ENTER;
	    SAVETMPS;
            SAVE_DEFSV;
	    current = values ? values[i] : ST(i+from_offset);
	    DEFSV = sv_2mortal(current ? SvREFCNT_inc(current) : newSV(0));
	    PUSHMARK(SP);
	    PUTBACK;
	    count = call_sv(keygen, G_ARRAY);
	    SPAGAIN;
	    if (post) {
		PUSHMARK(SP-count);
		PUTBACK;
		count = call_sv(post, G_ARRAY);
		SPAGAIN;
	    }
	    if (count != nkeys)
		croak("wrong number of results returned "
		      "from multikey generation sub "
		      "(%d expected, %d returned)",
		      nkeys, count);
	    while(count-- > 0) {
		SV *result = POPs;
		MK *key = keys+count;
		target = ((char*)(key->data)) + (i<<key->lsize);
		(*(store[count]))(aTHX_ result, target);
	    }
	    ixkeys[i] = target;
	    FREETMPS;
	    LEAVE;
	}
	SAVEVPTR(PL_sortcop);
	PL_sortcop = (OP*)keys;
	sortsv((SV**)ixkeys, len, (SVCOMPARE_t)cmp);
	if (values) {
	    from = to = values;
	}
	else {
	    from = &ST(from_offset);
	    to = &ST(0);
	}
	for(i=0; i<len; i++) {
	    IV j = ( ((char*)(ixkeys[i])) - ((char*)(keys->data)) )>>keys->lsize;
	    ixkeys[i] = from[j];
	}
	for(i=0; i<len; i++) {
	    to[i] = (SV*)ixkeys[i];
	}
    }
}

static AV *
_xclosure_defaults(pTHX_ CV *cv) {
    MAGIC *magic = mg_find((SV*)cv, '~');
    if (magic) {
	if ( magic->mg_obj
	     && SvTYPE((SV*)(magic->mg_obj)) == SVt_PVAV )
	    return (AV*)(magic->mg_obj);
	croak("internal error: bad XSUB closure");
    }
    return NULL;
}

static void
_xclosure_make(pTHX_ CV *cv, AV *defaults) {
    sv_magic((SV*)cv, (SV*)defaults, '~', "XCLOSURE", 0);
}

XS(XS_Sort__Key__multikeysort);
XS(XS_Sort__Key__multikeysort)
{
    dXSARGS;
    SV *gen=0;
    SV *post=0;
    SV *types=0;
    IV offset=0;

    AV *defaults = _xclosure_defaults(aTHX_ cv);

    if (defaults) {
	types = *(av_fetch(defaults, 0, 1));
	gen = *(av_fetch(defaults, 1, 1));
	post = *(av_fetch(defaults, 2, 1));
	if (!SvOK(post))
	    post = 0;
    }

    if (!types || !SvOK(types)) {
	if (items--)
	    types = ST(offset++);
	else
	    croak("not enough arguments");

    }
    if (!gen || !SvOK(gen)) {
	if (items--)
	    gen = ST(offset++);
	else
	    croak("not enough arguments");
    }

    _multikeysort(aTHX_ types, gen, post, 0, offset, ax, items);
    SP=&ST(items-1);
    PUTBACK;
    return;
}


XS(XS_Sort__Key__multikeysort_inplace);
XS(XS_Sort__Key__multikeysort_inplace)
{
    dXSARGS;
    SV *gen = 0;
    SV *post = 0;
    SV *types = 0;
    AV *values;

    AV *magic_values=0;
    I32 len;
    I32 offset=0;

    AV *defaults = _xclosure_defaults(aTHX_ cv);

    if (defaults) {
	types = *(av_fetch(defaults, 0, 1));
	gen = *(av_fetch(defaults, 1, 1));
	post = *(av_fetch(defaults, 2, 1));
	if (!SvOK(post))
	    post = 0;
    }

    SP-=items;

    if (!types || !SvOK(types)) {
	if (items--)
	    types = ST(offset++);
	else
	    croak("not enough arguments, packed multikey type descriptor required");
    }
    if (!gen || !SvOK(gen)) {
	if (items--)
	    gen = ST(offset++);
	else
	    croak("not enough arguments, reference to multikey generation subroutine required");
    }

    if(!(SvROK(gen) && SvTYPE(SvRV(gen))==SVt_PVCV))
       croak("wrong argument type, subroutine reference required");

    if (items != 1)
	croak("not enough arguments, array reference required");

    if (SvROK(ST(offset)) && SvTYPE(SvRV(ST(offset)))==SVt_PVAV)
	values = (AV*)SvRV(ST(offset));
    else croak("wrong argument type, array reference required");

    if ((len=av_len(values)+1)) {
	/* warn("ix=%d\n", ix); */
	if (SvMAGICAL(values) || AvREIFY(values)) {
	    int i;
	    magic_values = values;
	    values = (AV*)sv_2mortal((SV*)newAV());
	    av_extend(values, len-1);
	    for (i=0; i<len; i++) {
		SV **currentp = av_fetch(magic_values, i, 0);
		av_store( values, i,
			  ( currentp
			    ? SvREFCNT_inc(*currentp)
			    : newSV(0) ) );
	    }
	}
	
	_multikeysort(aTHX_ types, gen, post, AvARRAY(values), 0, 0, len);
	
	if (magic_values) {
	    int i;
	    SV **values_array = AvARRAY(values);
	    for(i=0; i<len; i++) {
		SV *current = values_array[i];
		if (!current) current = &PL_sv_undef;
		if (!av_store(magic_values, i, SvREFCNT_inc(current)))
		    SvREFCNT_dec(current);
	    }
	}
    }
    PUTBACK;
}


MODULE = Sort::Key		PACKAGE = Sort::Key		
PROTOTYPES: ENABLE

void
keysort(SV *keygen, ...)
PROTOTYPE: &@
ALIAS:
    lkeysort = 1
    nkeysort = 2
    ikeysort = 3
    ukeysort = 4
    rkeysort = 128
    rlkeysort = 129
    rnkeysort = 130
    rikeysort = 131
    rukeysort = 132
PPCODE:
    items--;
    if (items) {
	_keysort(aTHX_ ix, keygen, 0, 1, ax, items);
        SPAGAIN;
	SP = &ST(items-1);
    }


void
keysort_inplace(SV *keygen, AV *values)
PROTOTYPE: &\@
PREINIT:
    AV *magic_values=0;
    int len;
ALIAS:
    lkeysort_inplace = 1
    nkeysort_inplace = 2
    ikeysort_inplace = 3
    ukeysort_inplace = 4
    rkeysort_inplace = 128
    rlkeysort_inplace = 129
    rnkeysort_inplace = 130
    rikeysort_inplace = 131
    rukeysort_inplace = 132
PPCODE:
    if ((len=av_len(values)+1)) {
	/* warn("ix=%d\n", ix); */
	if (SvMAGICAL(values) || AvREIFY(values)) {
	    int i;
	    magic_values = values;
	    values = (AV*)sv_2mortal((SV*)newAV());
	    av_extend(values, len-1);
	    for (i=0; i<len; i++) {
		SV **currentp = av_fetch(magic_values, i, 0);
		av_store( values, i,
			  ( currentp
			    ? SvREFCNT_inc(*currentp)
			    : newSV(0) ) );
	    }
	}
	_keysort(aTHX_ ix, keygen, AvARRAY(values), 0, 0, len);
        SPAGAIN;
	if (magic_values) {
	    int i;
	    SV **values_array = AvARRAY(values);
	    for(i=0; i<len; i++) {
		SV *current = values_array[i];
		if (!current) current = &PL_sv_undef;
		if (!av_store(magic_values, i, SvREFCNT_inc(current)))
		    SvREFCNT_dec(current);
	    }
	}
    }

void
_sort(...)
PROTOTYPE: @
ALIAS:
    lsort = 1
    nsort = 2
    isort = 3
    usort = 4
    rsort = 128
    rlsort = 129
    rnsort = 130
    risort = 131
    rusort = 132
PPCODE:
    if (items) {
	_keysort(aTHX_ ix, 0, 0, 0, ax, items);
        SPAGAIN;
	SP = &ST(items-1);
    }

void
_sort_inplace(AV *values)
PROTOTYPE: \@
PREINIT:
    AV *magic_values=0;
    int len;
ALIAS:
    lsort_inplace = 1
    nsort_inplace = 2
    isort_inplace = 3
    usort_inplace = 4
    rsort_inplace = 128
    rlsort_inplace = 129
    rnsort_inplace = 130
    risort_inplace = 131
    rusort_inplace = 132
PPCODE:
    if ((len=av_len(values)+1)) {
	/* warn("ix=%d\n", ix); */
	if (SvMAGICAL(values) || AvREIFY(values)) {
	    int i;
	    magic_values = values;
	    values = (AV*)sv_2mortal((SV*)newAV());
	    av_extend(values, len-1);
	    for (i=0; i<len; i++) {
		SV **currentp = av_fetch(magic_values, i, 0);
		av_store( values, i,
			  ( currentp
			    ? SvREFCNT_inc(*currentp)
			    : newSV(0) ) );
	    }
	}

	_keysort(aTHX_ ix, 0, AvARRAY(values), 0, 0, len);
        SPAGAIN;
	if (magic_values) {
	    int i;
	    SV **values_array = AvARRAY(values);
	    for(i=0; i<len; i++) {
		SV *current = values_array[i];
		if (!current) current = &PL_sv_undef;
		if (!av_store(magic_values, i, SvREFCNT_inc(current)))
		    SvREFCNT_dec(current);
	    }
	}
    }


PROTOTYPES: DISABLE

CV *
_multikeysorter(SV *types, SV *gen, SV *post)
PREINIT:
    AV *defaults;
CODE:
    if (!SvOK(types) || sv_len(types)<1)
	croak("invalid packed types argument");
    RETVAL = newXS(0, &XS_Sort__Key__multikeysort, __FILE__);
    defaults = (AV*)sv_2mortal((SV*)newAV());
    av_store(defaults, 0, newSVsv(types));
    av_store(defaults, 1, newSVsv(gen));
    av_store(defaults, 2, newSVsv(post));
    _xclosure_make(aTHX_ RETVAL, defaults);
    if (!SvOK(gen))
	sv_setpv((SV*)RETVAL, "&@");
OUTPUT:
    RETVAL

CV *
_multikeysorter_inplace(SV *types, SV *gen, SV *post)
PREINIT:
    AV *defaults;
CODE:
    if (!SvOK(types) || sv_len(types)<1)
	croak("invalid packed types argument");
    RETVAL = newXS(0, &XS_Sort__Key__multikeysort_inplace, __FILE__);
    defaults = (AV*)sv_2mortal((SV*)newAV());
    av_store(defaults, 0, newSVsv(types));
    av_store(defaults, 1, newSVsv(gen));
    av_store(defaults, 2, newSVsv(post));
    _xclosure_make(aTHX_ RETVAL, defaults);
    if (!SvOK(gen))
	sv_setpv((SV*)RETVAL, "&\\@");
    else
	sv_setpv((SV*)RETVAL, "\\@");
OUTPUT:
    RETVAL