/*
 * This file was generated automatically by ExtUtils::ParseXS version 3.18 from the
 * contents of MinPerfHashTwoLevel.xs. Do not edit this file, edit MinPerfHashTwoLevel.xs instead.
 *
 *    ANY CHANGES MADE HERE WILL BE LOST!
 *
 */

#line 1 "MinPerfHashTwoLevel.xs"
#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "ppport.h"
#include "stadtx_hash.h"
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <assert.h>

struct mph_header {
    U32 magic_num;
    U32 version;
    U32 num_buckets;
    U32 state_ofs;

    U32 table_ofs;
    U32 key_flags_ofs;
    U32 val_flags_ofs;
    U32 str_buf_ofs;

    U64 table_checksum;
    U64 str_buf_checksum;
};

struct mph_bucket {
    U32 xor_val;
    U32 key_ofs;
    U32 val_ofs;
    U16 key_len;
    U16 val_len;
};

struct mph_obj {
    size_t bytes;
    struct mph_header *header;
    int fd;
};

#define sv_set_from_bucket(sv,strs,ofs,len,idx,flags,bits) \
STMT_START {                                            \
    U8 *ptr;                                            \
    U8 is_utf8;                                         \
    if (ofs) {                                          \
        ptr= (strs) + (ofs);                            \
        is_utf8= (*((flags)+(((idx)*(bits))>>3))>>(((idx)*(bits))&7))&((1<<bits)-1); \
    } else {                                            \
        ptr= 0;                                         \
        is_utf8= 0;                                     \
    }                                                   \
    sv_setpvn_mg((sv),ptr,len);                         \
    if (is_utf8 > 1) {                                  \
        sv_utf8_upgrade(sv);                            \
    }                                                   \
    else                                                \
    if (is_utf8) {                                      \
        SvUTF8_on(sv);                                  \
    }                                                   \
    else                                                \
    if (ptr) {                                          \
        SvUTF8_off(sv);                                 \
    }                                                   \
} STMT_END

int
lookup_bucket(struct mph_header *mph, U32 index, SV *key_sv, SV *val_sv)
{
    struct mph_bucket *bucket;
    U8 *strs;
    if (index >= mph->num_buckets) {
        return 0;
    }
    bucket= (struct mph_bucket *)((char *)mph + mph->table_ofs) + index;
    strs= (U8 *)mph + mph->str_buf_ofs;
    if (key_sv) {
        sv_set_from_bucket(key_sv,strs,bucket->key_ofs,bucket->key_len,index,((U8*)mph)+mph->key_flags_ofs,2);
    }
    if (val_sv) {
        sv_set_from_bucket(val_sv,strs,bucket->val_ofs,bucket->val_len,index,((U8*)mph)+mph->val_flags_ofs,1);
    }
    return 1;
}

int
lookup_key(struct mph_header *mph, SV *key_sv, SV *val_sv)
{
    U8 *strs= (U8 *)mph + mph->str_buf_ofs;
    struct mph_bucket *buckets= (struct mph_bucket *) ((char *)mph + mph->table_ofs);
    struct mph_bucket *bucket;
    U8 *state= (char *)mph + mph->state_ofs;
    STRLEN key_len;
    U8 *key_pv;
    U64 h0;
    U32 h1;
    U32 index;

    if (SvUTF8(key_sv)) {
        SV *tmp= sv_2mortal(newSVsv(key_sv));
        sv_utf8_downgrade(tmp,1);
        key_sv= tmp;
    }
    key_pv= SvPV(key_sv,key_len);
    h0= stadtx_hash_with_state(state,key_pv,key_len);
    h1= h0 >> 32;
    index= h1 % mph->num_buckets;

    bucket= buckets + index;
    if (!bucket->xor_val) {
        return 0;
    } else {
        U32 h2= h0 & 0xFFFFFFFF;
        U8 *got_key_pv;
        STRLEN got_key_len;
        index = (h2 ^ bucket->xor_val) % mph->num_buckets;
        bucket= buckets + index;
        got_key_pv= strs + bucket->key_ofs;
        if (bucket->key_len == key_len && memEQ(key_pv,got_key_pv,key_len)) {
            if (val_sv) {
                sv_set_from_bucket(val_sv,strs,bucket->val_ofs,bucket->val_len,index,((U8*)mph)+mph->val_flags_ofs,1);
            }
            return 1;
        }
        return 0;
    }
}

void
mph_mmap(char *file, struct mph_obj *obj) {
    struct stat st;
    int fd = open(file, O_RDONLY, 0);
    void *ptr;
    if (fd < 0)
        croak("failed to open '%s' for read", file);
    fstat(fd,&st);
    ptr = mmap(NULL,st.st_size,PROT_READ,MAP_SHARED | MAP_POPULATE, fd, 0);
    if (ptr == MAP_FAILED) {
        croak("failed to create mapping to file '%s'", file);
    }
    obj->bytes= st.st_size;
    obj->header= (struct mph_header*)ptr;
    obj->fd= fd;
}

void 
mph_munmap(struct mph_obj *obj) {
    munmap(obj->header,obj->bytes);
    close(obj->fd);
}


#line 165 "MinPerfHashTwoLevel.c"
#ifndef PERL_UNUSED_VAR
#  define PERL_UNUSED_VAR(var) if (0) var = var
#endif

#ifndef dVAR
#  define dVAR		dNOOP
#endif


/* This stuff is not part of the API! You have been warned. */
#ifndef PERL_VERSION_DECIMAL
#  define PERL_VERSION_DECIMAL(r,v,s) (r*1000000 + v*1000 + s)
#endif
#ifndef PERL_DECIMAL_VERSION
#  define PERL_DECIMAL_VERSION \
	  PERL_VERSION_DECIMAL(PERL_REVISION,PERL_VERSION,PERL_SUBVERSION)
#endif
#ifndef PERL_VERSION_GE
#  define PERL_VERSION_GE(r,v,s) \
	  (PERL_DECIMAL_VERSION >= PERL_VERSION_DECIMAL(r,v,s))
#endif
#ifndef PERL_VERSION_LE
#  define PERL_VERSION_LE(r,v,s) \
	  (PERL_DECIMAL_VERSION <= PERL_VERSION_DECIMAL(r,v,s))
#endif

/* XS_INTERNAL is the explicit static-linkage variant of the default
 * XS macro.
 *
 * XS_EXTERNAL is the same as XS_INTERNAL except it does not include
 * "STATIC", ie. it exports XSUB symbols. You probably don't want that
 * for anything but the BOOT XSUB.
 *
 * See XSUB.h in core!
 */


/* TODO: This might be compatible further back than 5.10.0. */
#if PERL_VERSION_GE(5, 10, 0) && PERL_VERSION_LE(5, 15, 1)
#  undef XS_EXTERNAL
#  undef XS_INTERNAL
#  if defined(__CYGWIN__) && defined(USE_DYNAMIC_LOADING)
#    define XS_EXTERNAL(name) __declspec(dllexport) XSPROTO(name)
#    define XS_INTERNAL(name) STATIC XSPROTO(name)
#  endif
#  if defined(__SYMBIAN32__)
#    define XS_EXTERNAL(name) EXPORT_C XSPROTO(name)
#    define XS_INTERNAL(name) EXPORT_C STATIC XSPROTO(name)
#  endif
#  ifndef XS_EXTERNAL
#    if defined(HASATTRIBUTE_UNUSED) && !defined(__cplusplus)
#      define XS_EXTERNAL(name) void name(pTHX_ CV* cv __attribute__unused__)
#      define XS_INTERNAL(name) STATIC void name(pTHX_ CV* cv __attribute__unused__)
#    else
#      ifdef __cplusplus
#        define XS_EXTERNAL(name) extern "C" XSPROTO(name)
#        define XS_INTERNAL(name) static XSPROTO(name)
#      else
#        define XS_EXTERNAL(name) XSPROTO(name)
#        define XS_INTERNAL(name) STATIC XSPROTO(name)
#      endif
#    endif
#  endif
#endif

/* perl >= 5.10.0 && perl <= 5.15.1 */


/* The XS_EXTERNAL macro is used for functions that must not be static
 * like the boot XSUB of a module. If perl didn't have an XS_EXTERNAL
 * macro defined, the best we can do is assume XS is the same.
 * Dito for XS_INTERNAL.
 */
#ifndef XS_EXTERNAL
#  define XS_EXTERNAL(name) XS(name)
#endif
#ifndef XS_INTERNAL
#  define XS_INTERNAL(name) XS(name)
#endif

/* Now, finally, after all this mess, we want an ExtUtils::ParseXS
 * internal macro that we're free to redefine for varying linkage due
 * to the EXPORT_XSUB_SYMBOLS XS keyword. This is internal, use
 * XS_EXTERNAL(name) or XS_INTERNAL(name) in your code if you need to!
 */

#undef XS_EUPXS
#if defined(PERL_EUPXS_ALWAYS_EXPORT)
#  define XS_EUPXS(name) XS_EXTERNAL(name)
#else
   /* default to internal */
#  define XS_EUPXS(name) XS_INTERNAL(name)
#endif

#ifndef PERL_ARGS_ASSERT_CROAK_XS_USAGE
#define PERL_ARGS_ASSERT_CROAK_XS_USAGE assert(cv); assert(params)

/* prototype to pass -Wmissing-prototypes */
STATIC void
S_croak_xs_usage(pTHX_ const CV *const cv, const char *const params);

STATIC void
S_croak_xs_usage(pTHX_ const CV *const cv, const char *const params)
{
    const GV *const gv = CvGV(cv);

    PERL_ARGS_ASSERT_CROAK_XS_USAGE;

    if (gv) {
        const char *const gvname = GvNAME(gv);
        const HV *const stash = GvSTASH(gv);
        const char *const hvname = stash ? HvNAME(stash) : NULL;

        if (hvname)
            Perl_croak(aTHX_ "Usage: %s::%s(%s)", hvname, gvname, params);
        else
            Perl_croak(aTHX_ "Usage: %s(%s)", gvname, params);
    } else {
        /* Pants. I don't think that it should be possible to get here. */
        Perl_croak(aTHX_ "Usage: CODE(0x%"UVxf")(%s)", PTR2UV(cv), params);
    }
}
#undef  PERL_ARGS_ASSERT_CROAK_XS_USAGE

#ifdef PERL_IMPLICIT_CONTEXT
#define croak_xs_usage(a,b)    S_croak_xs_usage(aTHX_ a,b)
#else
#define croak_xs_usage        S_croak_xs_usage
#endif

#endif

/* NOTE: the prototype of newXSproto() is different in versions of perls,
 * so we define a portable version of newXSproto()
 */
#ifdef newXS_flags
#define newXSproto_portable(name, c_impl, file, proto) newXS_flags(name, c_impl, file, proto, 0)
#else
#define newXSproto_portable(name, c_impl, file, proto) (PL_Sv=(SV*)newXS(name, c_impl, file), sv_setpv(PL_Sv, proto), (CV*)PL_Sv)
#endif /* !defined(newXS_flags) */

#line 307 "MinPerfHashTwoLevel.c"

XS_EUPXS(XS_Algorithm__MinPerfHashTwoLevel_hash_with_state); /* prototype to pass -Wmissing-prototypes */
XS_EUPXS(XS_Algorithm__MinPerfHashTwoLevel_hash_with_state)
{
    dVAR; dXSARGS;
    if (items != 2)
       croak_xs_usage(cv,  "str_sv, state_sv");
    {
	SV*	str_sv = ST(0)
;
	SV*	state_sv = ST(1)
;
	UV	RETVAL;
	dXSTARG;
#line 163 "MinPerfHashTwoLevel.xs"
{
    STRLEN str_len;
    STRLEN state_len;
    U8 *state_pv;
    U8 *str_pv= (U8 *)SvPV(str_sv,str_len);
    state_pv= (U8 *)SvPV(state_sv,state_len);
    if (state_len != STADTX_STATE_BYTES) {
        croak("state vector must be at exactly %d bytes",STADTX_SEED_BYTES);
    }
    RETVAL= stadtx_hash_with_state(state_pv,str_pv,str_len);
}
#line 334 "MinPerfHashTwoLevel.c"
	XSprePUSH; PUSHu((UV)RETVAL);
    }
    XSRETURN(1);
}


XS_EUPXS(XS_Algorithm__MinPerfHashTwoLevel_seed_state); /* prototype to pass -Wmissing-prototypes */
XS_EUPXS(XS_Algorithm__MinPerfHashTwoLevel_seed_state)
{
    dVAR; dXSARGS;
    if (items != 1)
       croak_xs_usage(cv,  "base_seed_sv");
    {
	SV*	base_seed_sv = ST(0)
;
	SV *	RETVAL;
#line 182 "MinPerfHashTwoLevel.xs"
{
    STRLEN seed_len;
    STRLEN state_len;
    U8 *seed_pv;
    U8 *state_pv;
    SV *seed_sv;
    if (!SvOK(base_seed_sv))
        croak("seed must be defined");
    if (SvROK(base_seed_sv))
        croak("seed should not be a reference");
    seed_sv= base_seed_sv;
    seed_pv= (U8 *)SvPV(seed_sv,seed_len);

    if (seed_len != STADTX_SEED_BYTES) {
        if (SvREADONLY(base_seed_sv)) {
            if (seed_len < STADTX_SEED_BYTES) {
                warn("seed passed into seed_state() is readonly and too short, argument has been right padded with %d nulls",
                    (int)(STADTX_SEED_BYTES - seed_len));
            }
            else if (seed_len > STADTX_SEED_BYTES) {
                warn("seed passed into seed_state() is readonly and too long, using only the first %d bytes",
                    (int)STADTX_SEED_BYTES);
            }
            seed_sv= sv_2mortal(newSVsv(base_seed_sv)); 
        }
        if (seed_len < STADTX_SEED_BYTES) {
            sv_grow(seed_sv,STADTX_SEED_BYTES+1);
            while (seed_len < STADTX_SEED_BYTES) {
                seed_pv[seed_len] = 0;
                seed_len++;
            }
        }
        SvCUR_set(seed_sv,STADTX_SEED_BYTES);
        seed_pv= (U8 *)SvPV(seed_sv,seed_len);
    } else {
        seed_sv= base_seed_sv;
    }

    RETVAL= newSV(STADTX_STATE_BYTES+1);
    SvCUR_set(RETVAL,STADTX_STATE_BYTES);
    SvPOK_on(RETVAL);
    state_pv= (U8 *)SvPV(RETVAL,state_len);
    stadtx_seed_state(seed_pv,state_pv);
}
#line 396 "MinPerfHashTwoLevel.c"
	ST(0) = RETVAL;
	sv_2mortal(ST(0));
    }
    XSRETURN(1);
}


XS_EUPXS(XS_Algorithm__MinPerfHashTwoLevel_calc_xor_val); /* prototype to pass -Wmissing-prototypes */
XS_EUPXS(XS_Algorithm__MinPerfHashTwoLevel_calc_xor_val)
{
    dVAR; dXSARGS;
    if (items != 4)
       croak_xs_usage(cv,  "max_xor_val, h2_sv, idx_sv, used_sv");
    {
	U32	max_xor_val = (unsigned long)SvUV(ST(0))
;
	SV *	h2_sv = ST(1)
;
	SV *	idx_sv = ST(2)
;
	SV *	used_sv = ST(3)
;
	U32	RETVAL;
	dXSTARG;
#line 237 "MinPerfHashTwoLevel.xs"
{
    U32 xor_val= 0;
    STRLEN h2_strlen;
    STRLEN bucket_count;
    char *used= SvPV(used_sv,bucket_count);
    U32 *h2_start= (U32 *)SvPV(h2_sv,h2_strlen);
    STRLEN h2_count= h2_strlen / sizeof(U32);
    U32 *h2_end= h2_start + h2_count;
    U32 *idx_start;

    sv_grow(idx_sv,h2_strlen);
    SvCUR_set(idx_sv,h2_strlen);
    SvPOK_on(idx_sv);
    idx_start= (U32 *)SvPVX(idx_sv);

    next_xor_val:
    while (1) {
        U32 *h2_ptr= h2_start;
        U32 *idx_ptr= idx_start;
        if (xor_val == max_xor_val) {
            RETVAL= 0;
            break;
        } else {
            xor_val++;
        }
        while (h2_ptr < h2_end) {
            U32 i= (*h2_ptr ^ xor_val) % bucket_count;
            U32 *check_idx;
            if (used[i])
                goto next_xor_val;
            for (check_idx= idx_start; check_idx < idx_ptr; check_idx++) {
                if (*check_idx == i)
                    goto next_xor_val;
            }
            *idx_ptr= i;
            h2_ptr++;
            idx_ptr++;
        }
        RETVAL= xor_val;
        break;
    }
}
#line 464 "MinPerfHashTwoLevel.c"
	XSprePUSH; PUSHu((UV)RETVAL);
    }
    XSRETURN(1);
}


XS_EUPXS(XS_Tie__Hash__MinPerfHashTwoLevel__OnDisk_mount_file); /* prototype to pass -Wmissing-prototypes */
XS_EUPXS(XS_Tie__Hash__MinPerfHashTwoLevel__OnDisk_mount_file)
{
    dVAR; dXSARGS;
    if (items != 1)
       croak_xs_usage(cv,  "file_sv");
    {
	SV*	file_sv = ST(0)
;
	SV *	RETVAL;
#line 289 "MinPerfHashTwoLevel.xs"
{
    struct mph_obj obj;
    STRLEN file_len;
    char *file_pv= SvPV(file_sv,file_len);
    mph_mmap(file_pv,&obj);
    RETVAL= newSVpvn((char *)&obj,sizeof(struct mph_obj));
    SvPOK_on(RETVAL);
    SvREADONLY_on(RETVAL);
}
#line 491 "MinPerfHashTwoLevel.c"
	ST(0) = RETVAL;
	sv_2mortal(ST(0));
    }
    XSRETURN(1);
}


XS_EUPXS(XS_Tie__Hash__MinPerfHashTwoLevel__OnDisk_unmount_file); /* prototype to pass -Wmissing-prototypes */
XS_EUPXS(XS_Tie__Hash__MinPerfHashTwoLevel__OnDisk_unmount_file)
{
    dVAR; dXSARGS;
    if (items != 1)
       croak_xs_usage(cv,  "mount_sv");
    {
	SV*	mount_sv = ST(0)
;
#line 306 "MinPerfHashTwoLevel.xs"
{
    struct mph_obj *obj= (struct mph_obj *)SvPV_nolen(mount_sv);
    mph_munmap(obj);
    SvOK_off(mount_sv);
}
#line 514 "MinPerfHashTwoLevel.c"
    }
    XSRETURN_EMPTY;
}


XS_EUPXS(XS_Tie__Hash__MinPerfHashTwoLevel__OnDisk_num_buckets); /* prototype to pass -Wmissing-prototypes */
XS_EUPXS(XS_Tie__Hash__MinPerfHashTwoLevel__OnDisk_num_buckets)
{
    dVAR; dXSARGS;
    if (items != 1)
       croak_xs_usage(cv,  "mount_sv");
    {
	SV*	mount_sv = ST(0)
;
	UV	RETVAL;
	dXSTARG;
#line 318 "MinPerfHashTwoLevel.xs"
{
    struct mph_obj *obj= (struct mph_obj *)SvPV_nolen(mount_sv);
    RETVAL= obj->header->num_buckets;
}
#line 536 "MinPerfHashTwoLevel.c"
	XSprePUSH; PUSHu((UV)RETVAL);
    }
    XSRETURN(1);
}


XS_EUPXS(XS_Tie__Hash__MinPerfHashTwoLevel__OnDisk_fetch_by_index); /* prototype to pass -Wmissing-prototypes */
XS_EUPXS(XS_Tie__Hash__MinPerfHashTwoLevel__OnDisk_fetch_by_index)
{
    dVAR; dXSARGS;
    if (items < 2)
       croak_xs_usage(cv,  "mount_sv, index, ...");
    {
	SV*	mount_sv = ST(0)
;
	U32	index = (unsigned long)SvUV(ST(1))
;
	int	RETVAL;
	dXSTARG;
#line 331 "MinPerfHashTwoLevel.xs"
{
    struct mph_obj *obj= (struct mph_obj *)SvPV_nolen(mount_sv);
    SV* key_sv= items > 2 ? ST(2) : NULL;
    SV* val_sv= items > 3 ? ST(3) : NULL;
    if (items > 4)
       croak_xs_usage(cv,  "mount_sv, index, key_sv, val_sv");
    RETVAL= lookup_bucket(obj->header,index,key_sv,val_sv);    
}
#line 565 "MinPerfHashTwoLevel.c"
	XSprePUSH; PUSHi((IV)RETVAL);
    }
    XSRETURN(1);
}


XS_EUPXS(XS_Tie__Hash__MinPerfHashTwoLevel__OnDisk_fetch_by_key); /* prototype to pass -Wmissing-prototypes */
XS_EUPXS(XS_Tie__Hash__MinPerfHashTwoLevel__OnDisk_fetch_by_key)
{
    dVAR; dXSARGS;
    if (items < 2)
       croak_xs_usage(cv,  "mount_sv, key_sv, ...");
    {
	SV*	mount_sv = ST(0)
;
	SV*	key_sv = ST(1)
;
	int	RETVAL;
	dXSTARG;
#line 348 "MinPerfHashTwoLevel.xs"
{
    SV* val_sv= items > 2 ? ST(2) : NULL;
    struct mph_obj *obj= (struct mph_obj *)SvPV_nolen(mount_sv);
    if (items > 3)
       croak_xs_usage(cv,  "mount_sv, index, key_sv");
    RETVAL= lookup_key(obj->header,key_sv,val_sv);
}
#line 593 "MinPerfHashTwoLevel.c"
	XSprePUSH; PUSHi((IV)RETVAL);
    }
    XSRETURN(1);
}

#ifdef __cplusplus
extern "C"
#endif
XS_EXTERNAL(boot_Algorithm__MinPerfHashTwoLevel); /* prototype to pass -Wmissing-prototypes */
XS_EXTERNAL(boot_Algorithm__MinPerfHashTwoLevel)
{
    dVAR; dXSARGS;
#if (PERL_REVISION == 5 && PERL_VERSION < 9)
    char* file = __FILE__;
#else
    const char* file = __FILE__;
#endif

    PERL_UNUSED_VAR(cv); /* -W */
    PERL_UNUSED_VAR(items); /* -W */
#ifdef XS_APIVERSION_BOOTCHECK
    XS_APIVERSION_BOOTCHECK;
#endif
    XS_VERSION_BOOTCHECK;

        (void)newXSproto_portable("Algorithm::MinPerfHashTwoLevel::hash_with_state", XS_Algorithm__MinPerfHashTwoLevel_hash_with_state, file, "$$");
        (void)newXSproto_portable("Algorithm::MinPerfHashTwoLevel::seed_state", XS_Algorithm__MinPerfHashTwoLevel_seed_state, file, "$");
        (void)newXSproto_portable("Algorithm::MinPerfHashTwoLevel::calc_xor_val", XS_Algorithm__MinPerfHashTwoLevel_calc_xor_val, file, "$$$$");
        (void)newXSproto_portable("Tie::Hash::MinPerfHashTwoLevel::OnDisk::mount_file", XS_Tie__Hash__MinPerfHashTwoLevel__OnDisk_mount_file, file, "$");
        (void)newXSproto_portable("Tie::Hash::MinPerfHashTwoLevel::OnDisk::unmount_file", XS_Tie__Hash__MinPerfHashTwoLevel__OnDisk_unmount_file, file, "$");
        (void)newXSproto_portable("Tie::Hash::MinPerfHashTwoLevel::OnDisk::num_buckets", XS_Tie__Hash__MinPerfHashTwoLevel__OnDisk_num_buckets, file, "$");
        (void)newXSproto_portable("Tie::Hash::MinPerfHashTwoLevel::OnDisk::fetch_by_index", XS_Tie__Hash__MinPerfHashTwoLevel__OnDisk_fetch_by_index, file, "$$;$$");
        (void)newXSproto_portable("Tie::Hash::MinPerfHashTwoLevel::OnDisk::fetch_by_key", XS_Tie__Hash__MinPerfHashTwoLevel__OnDisk_fetch_by_key, file, "$$;$");
#if (PERL_REVISION == 5 && PERL_VERSION >= 9)
  if (PL_unitcheckav)
       call_list(PL_scopestack_ix, PL_unitcheckav);
#endif
    XSRETURN_YES;
}

