/* -*- Mode: C -*- */

#define PERL_NO_GET_CONTEXT 1

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

#include <stdint.h>
#include "perl_math_int64.h"

#ifdef INT128_TI
typedef int int128_t __attribute__ ((__mode__ (TI)));
typedef unsigned int uint128_t __attribute__ ((__mode__ (TI)));
#define HAVE_INT128
#endif

#ifdef __INT128
typedef __int128 int128_t;
typedef unsigned __int128 uint128_t;
#define HAVE_INT128
#endif

#ifndef HAVE_INT128
#error "No int128 type define was passed to the compiler!"
#endif

/* perl memory allocator does not guarantee 16-byte alignment */
typedef int128_t int128_t_a8 __attribute__ ((aligned(8)));
typedef uint128_t uint128_t_a8 __attribute__ ((aligned(8)));

#define I128LEN sizeof(int128_t)

#define INT128_MAX ((int128_t)((~(uint128_t)0)>>1))
#define INT128_MIN (~INT128_MAX)
#define UINT128_MAX ((uint128_t)(~(uint128_t)0))

static int may_die_on_overflow = 0;

#if (PERL_VERSION >= 10)

#ifndef cop_hints_fetch_pvs
#define cop_hints_fetch_pvs(cop, key, flags) \
    Perl_refcounted_he_fetch(aTHX_ (cop)->cop_hints_hash, NULL, STR_WITH_LEN(key), (flags), 0)
#endif

static int
check_die_on_overflow_hint(pTHX) {
    SV *hint = cop_hints_fetch_pvs(PL_curcop, "Math::Int128::die_on_overflow", 0);
    return (hint && SvTRUE(hint));
}

#else

static int
check_die_on_overflow_hint(pTHX) {
    return 1;
}

#endif

static void
overflow(pTHX_ const char *msg) {
    if (check_die_on_overflow_hint(aTHX))
        Perl_croak(aTHX_ "Math::Int128 overflow: %s", msg);
}

#define get_int128_stash_uncached() gv_stashpvs("Math::Int128", 1)
#define get_uint128_stash_uncached() gv_stashpvs("Math::UInt128", 1)

#ifdef MULTIPLICITY
#  if defined(I_PTHREAD) && defined(PTHREAD_MUTEX_INITIALIZER)
#    define CACHE_STASHES
#  endif
#else
#  define CACHE_STASHES
#endif

#ifdef CACHE_STASHES
static HV * volatile int128_stash;
static HV * volatile uint128_stash;

#  ifdef MULTIPLICITY
static pthread_mutex_t stash_mutex = PTHREAD_MUTEX_INITIALIZER;
static int too_many_threads = 0;

static void init_stash_cache(pTHX) {
    pthread_mutex_lock(&stash_mutex);
    if (too_many_threads) {
        int128_stash = NULL;
        uint128_stash = NULL;
    }
    else {
        too_many_threads = 1;
        int128_stash = get_int128_stash_uncached();
        uint128_stash = get_uint128_stash_uncached();
    }
    pthread_mutex_unlock(&stash_mutex);
}

#  else

static void init_stash_cache(pTHX) {
    int128_stash = get_int128_stash_uncached();
    uint128_stash = get_uint128_stash_uncached();
}

#  endif

#define get_int128_stash() (int128_stash ? int128_stash : get_int128_stash_uncached())
#define get_uint128_stash() (uint128_stash ? uint128_stash : get_uint128_stash_uncached())

#else

static void init_stash_cache(pTHX) { }

#define get_int128_stash get_int128_stash_uncached
#define get_uint128_stash get_uint128_stash_uncached

#endif

static char *out_of_bounds_error_s = "Number is out of bounds for int128_t conversion";
static char *out_of_bounds_error_u = "Number is out of bounds for uint128_t conversion";
static char *mul_error             = "Multiplication overflows";
static char *pow_error             = "Exponentiation overflows";
static char *add_error             = "Addition overflows";
static char *sub_error             = "Subtraction overflows";
static char *inc_error             = "Increment operation wraps";
static char *dec_error             = "Decrement operation wraps";
static char *div_by_0_error        = "Illegal division by zero";

static void croak_string(pTHX_ const char *str) {
    Perl_croak(aTHX_ "%s", str);
}

#include "strtoint128.h"

#define SvI128Y(sv) (*((int128_t_a8*)SvPVX(sv)))
#define SvU128Y(sv) (*((uint128_t_a8*)SvPVX(sv)))
#define SVt_I128 SVt_PV

static SV *
new_si128(pTHX) {
    SV *si128 = newSV(I128LEN);
    SvPOK_on(si128);
    SvCUR_set(si128, I128LEN);
    return si128;
}

#define new_su128 new_si128

static SV *
newSVi128(pTHX_ int128_t i128) {
    HV *stash = get_int128_stash();
    SV *si128 = new_si128(aTHX);
    SV *sv;
    SvI128Y(si128) = i128;
    sv = newRV_noinc(si128);
    sv_bless(sv, stash);
    SvREADONLY_on(si128);
    return sv;
}

static SV *
newSVu128(pTHX_ uint128_t u128) {
    HV *stash = get_uint128_stash();
    SV *su128 = new_su128(aTHX);
    SV *sv;
    SvI128Y(su128) = u128;
    sv = newRV_noinc(su128);
    sv_bless(sv, stash);
    SvREADONLY_on(su128);
    return sv;
}

static int
SvI128OK(pTHX_ SV *sv) {
    if (SvROK(sv)) {
        SV *si128 = SvRV(sv);
        return (si128 && (SvTYPE(si128) >= SVt_I128) && sv_isa(sv, "Math::Int128"));
    }
    return 0;
}

static int
SvU128OK(pTHX_ SV *sv) {
    if (SvROK(sv)) {
        SV *su128 = SvRV(sv);
        return (su128 && (SvTYPE(su128) >= SVt_I128) && sv_isa(sv, "Math::UInt128"));
    }
    return 0;
}

#define SvI128X(sv) (SvI128Y(SvRV(sv)))
#define SvU128X(sv) (SvU128Y(SvRV(sv)))

static SV *
SvSI128(pTHX_ SV *sv) {
    if (SvROK(sv)) {
        SV *si128 = SvRV(sv);
        if (SvPOK(si128) && (SvCUR(si128) == I128LEN))
            return si128;
    }
    croak_string(aTHX_ "internal error: reference to int128_t expected");
    return NULL; /* never happens */
}

static SV *
SvSU128(pTHX_ SV *sv) {
    if (SvROK(sv)) {
        SV *su128 = SvRV(sv);
        if (SvPOK(su128) && (SvCUR(su128) == I128LEN))
            return su128;
    }
    croak_string(aTHX_ "internal error: reference to uint128_t expected");
    return NULL; /* never happens */
}

#define SvI128x(sv) SvI128Y(SvSI128(aTHX_ sv))
#define SvU128x(sv) SvU128Y(SvSU128(aTHX_ sv))

static int128_t
SvI128(pTHX_ SV *sv) {
    STRLEN len;
    char *pv;
    if (SvROK(sv)) {
        SV *si128 = SvRV(sv);
        if (si128 && SvOBJECT(si128)) {
            HV *stash = SvSTASH(si128);
#ifdef CACHE_STASHES
            if (stash == int128_stash) {
                return SvI128Y(si128);
            }
            else if (stash == uint128_stash) {
                int128_t u128 = SvU128Y(si128);
                if (may_die_on_overflow && (u128 > INT128_MAX))
                    overflow(aTHX_ out_of_bounds_error_s);
                return u128;
            }
#else
            if (0);
#endif
            else {
                GV *method;
                char const * classname = HvNAME_get(stash);
                if (memcmp(classname, "Math::", 6) == 0) {
                    int u;
                    if (classname[6] == 'U') {
                        classname += 7;
                        u = 1;
                    }
                    else {
                        classname += 6;
                        u = 0;
                    }
                    if (memcmp(classname, "Int", 3) == 0) {
                        classname += 3;
                        if (memcmp(classname, "128", 4) == 0) {
                            if (!SvPOK(si128) || (SvCUR(si128) != I128LEN))
                                Perl_croak(aTHX_ "Wrong internal representation for %s object", HvNAME_get(stash));
                            if (u) {
                                int128_t u128 = SvU128Y(si128);
                                if (may_die_on_overflow && (u128 > INT128_MAX))
                                    overflow(aTHX_ out_of_bounds_error_s);
                                return u128;
                            }
                            return SvI128Y(si128);
                        }
                        if (memcmp(classname, "64", 3) == 0) {
                            if (u) {
                                return SvU64(sv);
                            }
                            return SvI64(sv);
                        }
                    }
                }
                method = gv_fetchmethod(stash, "as_int128");
                if (method) {
                    SV *result;
                    int count;
                    dSP;
                    ENTER;
                    SAVETMPS;
                    PUSHSTACKi(PERLSI_MAGIC);
                    PUSHMARK(SP);
                    XPUSHs(sv);
                    PUTBACK;
                    count = perl_call_sv( (SV*)method, G_SCALAR );
                    SPAGAIN;
                    if (count != 1)
                        Perl_croak(aTHX_ "internal error: method call returned %d values, 1 expected", count);
                    result = newSVsv(POPs);
                    PUTBACK;
                    POPSTACK;
                    SPAGAIN;
                    FREETMPS;
                    LEAVE;
                    return SvI128(aTHX_ sv_2mortal(result));
                }
            }
        }
    }
    else {
        SvGETMAGIC(sv);
        if (SvIOK(sv)) {
            if (SvIOK_UV(sv))
                return SvUV(sv);
            return SvIV(sv);
        }
        if (SvNOK(sv)) {
            NV nv = SvNV(sv);
            if (may_die_on_overflow &&
                ((nv >= 0x1p127) || (nv < -0x1p127))) overflow(aTHX_ out_of_bounds_error_s);
            return nv;
        }
    }
    pv = SvPV(sv, len);
    return strtoint128(aTHX_ pv, len, 10, 1);
}

static uint128_t
SvU128(pTHX_ SV *sv) {
    STRLEN len;
    char *pv;
    if (SvROK(sv)) {
        SV *su128 = SvRV(sv);
        if (su128 && SvOBJECT(su128)) {
            HV *stash = SvSTASH(su128);
#ifdef CACHE_STASHES
            if (stash == uint128_stash)
                return SvU128Y(su128);
            else if (stash == int128_stash) {
                int128_t i128 = SvI128Y(su128);
                if (may_die_on_overflow && (i128 < 0))
                    overflow(aTHX_ out_of_bounds_error_u);
                return i128;
            }
#else
            if (0);
#endif
            else {
                GV *method;
                char const * classname = HvNAME_get(stash);
                if (memcmp(classname, "Math::", 6) == 0) {
                    int u;
                    if (classname[6] == 'U') {
                        classname += 7;
                        u = 1;
                    }
                    else {
                        classname += 6;
                        u = 0;
                    }
                    if (memcmp(classname, "Int", 3) == 0) {
                        classname += 3;
                        if (memcmp(classname, "128", 4) == 0) {
                            if (!SvPOK(su128) || (SvCUR(su128) != I128LEN))
                                Perl_croak(aTHX_ "Wrong internal representation for %s object", HvNAME_get(stash));
                            if (u)
                                return SvU128Y(su128);
                            else {
                                int128_t i128 = SvI128Y(su128);
                                if (may_die_on_overflow && (i128 < 0)) overflow(aTHX_ out_of_bounds_error_u);
                                return i128;
                            }
                        }
                        if (memcmp(classname, "64", 3) == 0) {
                            if (u) {
                                return SvU64(sv);
                            }
                            else {
                                int64_t i64 = SvI64(sv);
                                if (may_die_on_overflow && (i64 < 0)) overflow(aTHX_ out_of_bounds_error_u);
                                return i64;
                            }
                        }
                    }
                }
                method = gv_fetchmethod(stash, "as_uint128");
                if (method) {
                    SV *result;
                    int count;
                    dSP;
                    ENTER;
                    SAVETMPS;
                    PUSHSTACKi(PERLSI_MAGIC);
                    PUSHMARK(SP);
                    XPUSHs(sv);
                    PUTBACK;
                    count = perl_call_sv( (SV*)method, G_SCALAR );
                    SPAGAIN;
                    if (count != 1)
                        Perl_croak(aTHX_ "internal error: method call returned %d values, 1 expected", count);
                    result = newSVsv(POPs);
                    PUTBACK;
                    POPSTACK;
                    SPAGAIN;
                    FREETMPS;
                    LEAVE;
                    return SvU128(aTHX_ sv_2mortal(result));
                }
            }
        }
    }
    else {
        SvGETMAGIC(sv);
        if (SvIOK(sv)) {
            if (SvIOK_UV(sv))
                return SvUV(sv);
            else {
                IV iv = SvIV(sv);
                if (may_die_on_overflow && (iv < 0)) overflow(aTHX_ out_of_bounds_error_u);
                return iv;
            }
        }
        if (SvNOK(sv)) {
            NV nv = SvNV(sv);
            if (may_die_on_overflow && ((nv < 0) || (nv >= 0x1p128))) overflow(aTHX_ out_of_bounds_error_u);
            return nv;
        }
    }
    pv = SvPV(sv, len);
    return strtoint128(aTHX_ pv, len, 10, 0);
}

static SV *
si128_to_number(pTHX_ SV *sv) {
    int128_t i128 = SvI128(aTHX_ sv);
    if (i128 < 0) {
        IV iv = i128;
        if (iv == i128)
            return newSViv(iv);
    }
    else {
        UV uv = i128;
        if (uv == i128)
            return newSVuv(uv);
    }
    return newSVnv(i128);
}

static SV *
su128_to_number(pTHX_ SV *sv) {
    uint128_t u128 = SvU128(aTHX_ sv);
    UV uv;
    uv = u128;
    if (uv == u128)
        return newSVuv(uv);
    return newSVnv(u128);
}

#define I128STRLEN 44

static STRLEN
u128_to_string(uint128_t u128, char *to) {
    char str[I128STRLEN];
    int i, len = 0;
    while (u128) {
        str[len++] = '0' + u128 % 10;
        u128 /= 10;
    }
    if (len) {
        for (i = len; i--;) *(to++) = str[i];
        return len;
    }
    else {
        to[0] = '0';
        return 1;
    }
}

static STRLEN
i128_to_string(int128_t i128, char *to) {
    if (i128 < 0) {
        *(to++) = '-';
        return u128_to_string(-i128, to) + 1;
    }
    return u128_to_string(i128, to);
}

static void
u128_to_hex(uint128_t i128, char *to) {
    int i = I128LEN * 2;
    while (i--) {
        int v = i128 & 15;
        to[i] = v + ((v > 9) ? ('A' - 10) : '0');
        i128 >>= 4;
    }
}

static void
mul_check_overflow(pTHX_ uint128_t a, uint128_t b, const char *error_str) {
    if (a < b) {
        uint128_t tmp = a;
        a = b; b = tmp;
    }
    if (b > UINT64_MAX) overflow(aTHX_ error_str);
    else {
        uint128_t rl, rh;
        rl = (a & UINT64_MAX) * b;
        rh = (a >> 64) * b + (rl >> 64);
        if (rh > UINT64_MAX) overflow(aTHX_ error_str);
    }
}

static uint128_t
powU128(pTHX_ uint128_t a, uint128_t b) {
    uint128_t r;
    int mdoo = may_die_on_overflow;
    if (b == 0) return 1;
    if (b == 1) return a;
    if (b == 2) {
        if (mdoo && (a > UINT64_MAX)) overflow(aTHX_ pow_error);
        return a*a;
    }
    if (a == 0) return 0;
    if (a == 1) return 1;
    if (a == 2) {
        if (b > 127) {
            if (mdoo) overflow(aTHX_ pow_error);
            return 0;
        }
        return (((uint128_t)1) << b);
    }
    if (mdoo) {
        r = ((b & 1) ? a : 1);
        while ((b >>= 1)) {
            if (a > UINT64_MAX) overflow(aTHX_ pow_error);
            a *= a;
            if (b & 1) {
                mul_check_overflow(aTHX_ r, a, pow_error);
                r *= a;
            }
        }
    }
    else {
        r = 1;
        while (b) {
            if (b & 1) r *= a;
            a *= a;
            b >>= 1;
        }
    }
    return r;
}

#include "c_api.h"

MODULE = Math::Int128		PACKAGE = Math::Int128			PREFIX=miu128_	

BOOT:
    init_stash_cache(aTHX);
    PERL_MATH_INT64_LOAD_OR_CROAK;
    INIT_C_API;

int
CLONE(...)
CODE:
    init_stash_cache(aTHX);
    RETVAL = 1;
OUTPUT:
    RETVAL

void
miu128__set_may_die_on_overflow(v)
    int v
CODE:
    may_die_on_overflow = v;

SV *
miu128_int128(value=0)
    SV *value;
CODE:
    RETVAL = newSVi128(aTHX_ (value ? SvI128(aTHX_ value) : 0));
OUTPUT:
    RETVAL

SV *
miu128_uint128(value=0)
    SV *value;
CODE:
    RETVAL = newSVu128(aTHX_ (value ? SvU128(aTHX_ value) : 0));
OUTPUT:
    RETVAL

SV *
miu128_int128_to_number(self)
    SV *self
CODE:
    RETVAL = si128_to_number(aTHX_ self);
OUTPUT:
    RETVAL

SV *
miu128_uint128_to_number(self)
    SV *self
CODE:
    RETVAL = su128_to_number(aTHX_ self);
OUTPUT:
    RETVAL

SV *
miu128_net_to_int128(net)
    SV *net;
PREINIT:
    STRLEN len;
    unsigned char *pv = (unsigned char *)SvPV(net, len);
CODE:
    if (len != 16)
        croak_string(aTHX_ "Invalid length for int128_t");
    RETVAL = newSVi128(aTHX_
                       (((((((((((((((((((((((((((((((int128_t)pv[0]) << 8)
                                                   + (int128_t)pv[1]) << 8)
                                                 + (int128_t)pv[2]) << 8)
                                               + (int128_t)pv[3]) << 8)
                                             + (int128_t)pv[4]) << 8)
                                           + (int128_t)pv[5]) << 8)
                                         + (int128_t)pv[6]) << 8)
                                       + (int128_t)pv[7]) << 8)
                                     + (int128_t)pv[8]) << 8)
                                   + (int128_t)pv[9]) << 8)
                                 + (int128_t)pv[10]) << 8)
                               + (int128_t)pv[11]) << 8)
                             + (int128_t)pv[12]) << 8)
                           + (int128_t)pv[13]) << 8)
                         + (int128_t)pv[14]) << 8)
                       + (int128_t)pv[15]);
OUTPUT:
    RETVAL

SV *
miu128_net_to_uint128(net)
    SV *net;
PREINIT:
    STRLEN len;
    unsigned char *pv = (unsigned char *)SvPV(net, len);
CODE:
    if (len != 16)
        croak_string(aTHX_ "Invalid length for uint128_t");
    RETVAL = newSVu128(aTHX_
                       (((((((((((((((((((((((((((((((uint128_t)pv[0]) << 8)
                                                   + (uint128_t)pv[1]) << 8)
                                                 + (uint128_t)pv[2]) << 8)
                                               + (uint128_t)pv[3]) << 8)
                                             + (uint128_t)pv[4]) << 8)
                                           + (uint128_t)pv[5]) << 8)
                                         + (uint128_t)pv[6]) << 8)
                                       + (uint128_t)pv[7]) << 8)
                                     + (uint128_t)pv[8]) << 8)
                                   + (uint128_t)pv[9]) << 8)
                                 + (uint128_t)pv[10]) << 8)
                               + (uint128_t)pv[11]) << 8)
                             + (uint128_t)pv[12]) << 8)
                           + (uint128_t)pv[13]) << 8)
                         + (uint128_t)pv[14]) << 8)
                       + (uint128_t)pv[15]);
OUTPUT:
    RETVAL

SV *
miu128_int128_to_net(self)
    SV *self
PREINIT:
    char *pv;
    int128_t i128 = SvI128(aTHX_ self);
    int i;
CODE:
    RETVAL = newSV(I128LEN);
    SvPOK_on(RETVAL);
    SvCUR_set(RETVAL, I128LEN);
    pv = SvPVX(RETVAL);
    pv[I128LEN] = '\0';
    for (i = I128LEN - 1; i >= 0; i--, i128 >>= 8)
        pv[i] = i128;
OUTPUT:
    RETVAL

SV *
miu128_uint128_to_net(self)
    SV *self
PREINIT:
    char *pv;
    uint128_t u128 = SvU128(aTHX_ self);
    int i;
CODE:
    RETVAL = newSV(I128LEN);
    SvPOK_on(RETVAL);
    SvCUR_set(RETVAL, I128LEN);
    pv = SvPVX(RETVAL);
    pv[I128LEN] = '\0';
    for (i = I128LEN - 1; i >= 0; i--, u128 >>= 8)
        pv[i] = u128;
OUTPUT:
    RETVAL

SV *
miu128_native_to_int128(native)
    SV *native
PREINIT:
    STRLEN len;
    char *pv = SvPV(native, len);
CODE:
    if (len != I128LEN)
        croak_string(aTHX_ "Invalid length for int128_t");
    RETVAL = newSVi128(aTHX_ 0);
    Copy(pv, &(SvI128X(RETVAL)), I128LEN, char);
OUTPUT:
    RETVAL

SV *
miu128_native_to_uint128(native)
    SV *native
PREINIT:
    STRLEN len;
    char *pv = SvPV(native, len);
CODE:
    if (len != I128LEN)
        croak_string(aTHX_ "Invalid length for uint128_t");
    RETVAL = newSVu128(aTHX_ 0);
    Copy(pv, &(SvU128X(RETVAL)), I128LEN, char);
OUTPUT:
    RETVAL

SV *
miu128_int128_to_native(self)
    SV *self
PREINIT:
    char *pv;
    int128_t i128 = SvI128(aTHX_ self);
CODE:
    RETVAL = newSV(I128LEN);
    SvPOK_on(RETVAL);
    SvCUR_set(RETVAL, I128LEN);
    pv = SvPVX(RETVAL);
    Copy(&i128, pv, I128LEN, char);
    pv[I128LEN] = '\0';
OUTPUT:
    RETVAL

SV *
miu128_uint128_to_native(self)
    SV *self
PREINIT:
    char *pv;
    uint128_t u128 = SvU128(aTHX_ self);
CODE:
    RETVAL = newSV(I128LEN);
    SvPOK_on(RETVAL);
    SvCUR_set(RETVAL, I128LEN);
    pv = SvPVX(RETVAL);
    Copy(&u128, pv, I128LEN, char);
    pv[I128LEN] = '\0';
OUTPUT:
    RETVAL

SV *
miu128_uint128_to_hex(self)
    SV *self
PREINIT:
    char *pv;
    uint128_t u128 = SvU128(aTHX_ self);
CODE:
    RETVAL = newSV(I128LEN * 2);
    SvPOK_on(RETVAL);
    SvCUR_set(RETVAL, I128LEN * 2);
    pv = SvPVX(RETVAL);
    u128_to_hex(u128, pv);
OUTPUT:
    RETVAL

SV *
miu128_int128_to_hex(self)
    SV *self
PREINIT:
    char *pv;
    uint128_t u128 = SvI128(aTHX_ self);
CODE:
    RETVAL = newSV(I128LEN * 2);
    SvPOK_on(RETVAL);
    SvCUR_set(RETVAL, I128LEN * 2);
    pv = SvPVX(RETVAL);
    u128_to_hex(u128, pv);
OUTPUT:
    RETVAL

SV *
miu128_string_to_int128(sv, base = 0)
    SV *sv
    int base;
PREINIT:
    const char *pv;
    STRLEN len;
CODE:
    pv = SvPV(sv, len);
    RETVAL = newSVi128(aTHX_ strtoint128(aTHX_ pv, len, base, 1));
OUTPUT:
    RETVAL

SV *
miu128_string_to_uint128(sv, base = 0)
    SV *sv
    int base
PREINIT:
    const char *pv;
    STRLEN len;
CODE:
    pv = SvPV(sv, len);
    RETVAL = newSVu128(aTHX_ strtoint128(aTHX_ pv, len, base, 0));
OUTPUT:
    RETVAL

MODULE = Math::Int128		PACKAGE = Math::Int128		PREFIX=mi128
PROTOTYPES: DISABLE

SV *
mi128_inc(self, ...)
    SV *self
PREINIT:
    int128_t i128 = SvI128x(self);
CODE:
    if (may_die_on_overflow && (i128 == INT128_MAX)) overflow(aTHX_ inc_error);
    SvI128x(self) = i128 + 1;
    RETVAL = self;
    SvREFCNT_inc(RETVAL);
OUTPUT:
    RETVAL

SV *
mi128_dec(self, ...)
    SV *self
PREINIT:
    int128_t i128 = SvI128x(self);
CODE:
    if (may_die_on_overflow && (i128 == 0)) overflow(aTHX_ dec_error);
    SvI128x(self) = i128 - 1;
    RETVAL = self;
    SvREFCNT_inc(RETVAL);
OUTPUT:
    RETVAL

SV *
mi128_add(self, other, rev)
    SV *self
    SV *other
    SV *rev
PREINIT:
    int128_t a = SvI128x(self);
    int128_t b = SvI128(aTHX_ other);
CODE:
    if ( may_die_on_overflow &&
         ( a > 0
           ? ( (b > 0) && (INT128_MAX - a < b) )
           : ( (b < 0) && (INT128_MIN - a > b) ) ) ) overflow(aTHX_ add_error);
    if (SvOK(rev)) 
        RETVAL = newSVi128(aTHX_ a + b);
    else {
        RETVAL = self;
        SvREFCNT_inc(RETVAL);
        SvI128x(self) = a + b;
    }
OUTPUT:
    RETVAL

SV *
mi128_sub(self, other, rev)
    SV *self
    SV *other
    SV *rev
PREINIT:
    int128_t a = SvI128x(self);
    int128_t b = SvI128(aTHX_ other);
CODE:
    if (SvTRUE(rev)) {
        int128_t tmp = a;
        a = b; b = tmp;
    }
    if ( may_die_on_overflow &&
         ( a > 0
           ? ( (b < 0) && (a - INT128_MAX > b) )
           : ( (b > 0) && (a - INT128_MIN < b) ) ) ) overflow(aTHX_ sub_error);
    if (SvOK(rev))
        RETVAL = newSVi128(aTHX_ a - b);
    else {
        RETVAL = self;
        SvREFCNT_inc(RETVAL);
        SvI128x(self) = a - b;
    }
OUTPUT:
    RETVAL

SV *
mi128_mul(self, other, rev)
    SV *self
    SV *other
    SV *rev
PREINIT:
    int128_t a1 = SvI128x(self);
    int128_t b1 = SvI128(aTHX_ other);
CODE:
    if (may_die_on_overflow) {
        int neg = 0;
        uint128_t a, b;
        if (a1 < 0) {
            a = -a1;
            neg ^= 1;
        }
        else a = a1;
        if (b1 < 0) {
            b = -b1;
            neg ^= 1;
        }
        else b = b1;
        mul_check_overflow(aTHX_ a, b, mul_error);
        if (a * b > (neg ? (~(uint128_t)INT128_MIN + 1) : INT128_MAX)) overflow(aTHX_ mul_error);
    }
    if (SvOK(rev))
        RETVAL = newSVi128(aTHX_ a1 * b1);
    else {
        RETVAL = self;
        SvREFCNT_inc(RETVAL);
        SvI128x(self) = a1 * b1;
    }
OUTPUT:
    RETVAL

SV *
mi128_div(self, other, rev)
    SV *self
    SV *other
    SV *rev
PREINIT:
    int128_t up;
    int128_t down;
CODE:
    if (SvOK(rev)) {
        if (SvTRUE(rev)) {
            up = SvI128(aTHX_ other);
            down = SvI128x(self);
        }
        else {
            up = SvI128x(self);
            down = SvI128(aTHX_ other);
        }
        if (!down)
            croak_string(aTHX_ div_by_0_error);
        RETVAL = newSVi128(aTHX_ up/down);
    }
    else {
        down = SvI128(aTHX_ other);
        if (!down)
            croak_string(aTHX_ div_by_0_error);
        RETVAL = self;
        SvREFCNT_inc(RETVAL);
        SvI128x(self) /= down;
    }
OUTPUT:
    RETVAL

SV *
mi128_remainder(self, other, rev)
    SV *self
    SV *other
    SV *rev
PREINIT:
    int128_t up;
    int128_t down;
CODE:
    if (SvOK(rev)) {
        if (SvTRUE(rev)) {
            up = SvI128(aTHX_ other);
            down = SvI128x(self);
        }
        else {
            up = SvI128x(self);
            down = SvI128(aTHX_ other);
        }
        if (!down)
            croak_string(aTHX_ div_by_0_error);
        RETVAL = newSVi128(aTHX_ up % down);
    }
    else {
        down = SvI128(aTHX_ other);
        if (!down)
            croak_string(aTHX_ div_by_0_error);
        RETVAL = self;
        SvREFCNT_inc(RETVAL);
        SvI128x(self) %= down;
    }
OUTPUT:
    RETVAL

SV *mi128_left(self, other, rev)
    SV *self
    SV *other
    SV *rev
CODE:
    if (SvOK(rev)) {
        uint128_t a, b;
        if (SvTRUE(rev)) {
            a = SvU128(aTHX_ other);
            b = SvU128x(self);
        }
        else {
            b = SvU128(aTHX_ other);
            a = SvI128x(self);
        }
        RETVAL = newSVi128(aTHX_ (uint128_t)(b > 127 ? 0 : a << b));
    }
    else {
        uint128_t b = SvU128(aTHX_ other);
        RETVAL = SvREFCNT_inc(self);
        SvI128x(self) = (b > 127 ? 0 : SvI128x(self) << b);
    }
OUTPUT:
    RETVAL

SV *mi128_right(self, other, rev)
    SV *self
    SV *other
    SV *rev
PREINIT:
    int128_t a;
    uint128_t b;
CODE:
    if (SvOK(rev)) {
        if (SvTRUE(rev)) {
            a = SvI128(aTHX_ other);
            b = SvU128x(self);
        }
        else {
            b = SvU128(aTHX_ other);
            a = SvI128x(self);
        }
    }
    else {
        a = SvI128x(self);
        b = SvU128(aTHX_ other);
    }
    RETVAL = newSVi128(aTHX_ (b > 127 ? (a >= 0 ? 0 : -1) : a >> b));
OUTPUT:
    RETVAL

SV *mi128_pow(self, other, rev = &PL_sv_no)
    SV *self
    SV *other
    SV *rev
PREINIT:
    int sign;
    uint128_t r;
    int128_t a, b;
CODE:
    if (SvTRUE(rev)) {
        a = SvI128(aTHX_ other);
        b = SvI128x(self);
    }
    else {
        a = SvI128x(self);
        b = SvI128(aTHX_ other);
    }
    if (a < 0) {
        sign = ((b & 1) ? -1 : 1);
        a = -a;
    }
    else sign = 1;
    if (b < 0) {
        if      (a == 0) croak_string(aTHX_ div_by_0_error);
        else if (a == 1) r = sign;
        else             r = 0;
    }
    else {
        uint128_t u = powU128(aTHX_ a, b);
        if (may_die_on_overflow && (u > ((sign < 0) ? (~(uint128_t)INT128_MIN + 1) : INT128_MAX))) overflow(aTHX_ pow_error);
        r = ((sign > 0) ? u : -u);
    }
    if (SvOK(rev))
        RETVAL = newSVi128(aTHX_ r);
    else {
        RETVAL = self;
        SvREFCNT_inc(RETVAL);
        SvI128x(self) = r;
    }
OUTPUT:
    RETVAL

int
mi128_spaceship(self, other, rev)
    SV *self
    SV *other
    SV *rev
PREINIT:
    int128_t left;
    int128_t right;
CODE:
    if (SvTRUE(rev)) {
        left = SvI128(aTHX_ other);
        right = SvI128x(self);
    }
    else {
        left = SvI128x(self);
        right = SvI128(aTHX_ other);
    }
    RETVAL = (left < right ? -1 : left > right ? 1 : 0);
OUTPUT:
    RETVAL

SV *
mi128_eqn(self, other, ...)
    SV *self
    SV *other
CODE:
    RETVAL = ( SvI128x(self) == SvI128(aTHX_ other)
               ? &PL_sv_yes
               : &PL_sv_no );
OUTPUT:
    RETVAL

SV *
mi128_nen(self, other, ...)
    SV *self
    SV *other
CODE:
    RETVAL = ( SvI128x(self) != SvI128(aTHX_ other)
               ? &PL_sv_yes
               : &PL_sv_no );
OUTPUT:
    RETVAL

SV *
mi128_gtn(self, other, rev)
    SV *self
    SV *other
    SV *rev
CODE:
    if (SvTRUE(rev))
        RETVAL = SvI128x(self) < SvI128(aTHX_ other) ? &PL_sv_yes : &PL_sv_no;
    else
        RETVAL = SvI128x(self) > SvI128(aTHX_ other) ? &PL_sv_yes : &PL_sv_no;
OUTPUT:
    RETVAL

SV *
mi128_ltn(self, other, rev)
    SV *self
    SV *other
    SV *rev
CODE:
    if (SvTRUE(rev))
        RETVAL = SvI128x(self) > SvI128(aTHX_ other) ? &PL_sv_yes : &PL_sv_no;
    else
        RETVAL = SvI128x(self) < SvI128(aTHX_ other) ? &PL_sv_yes : &PL_sv_no;
OUTPUT:
    RETVAL

SV *
mi128_gen(self, other, rev)
    SV *self
    SV *other
    SV *rev
CODE:
    if (SvTRUE(rev))
        RETVAL = SvI128x(self) <= SvI128(aTHX_ other) ? &PL_sv_yes : &PL_sv_no;
    else
        RETVAL = SvI128x(self) >= SvI128(aTHX_ other) ? &PL_sv_yes : &PL_sv_no;
OUTPUT:
    RETVAL

SV *
mi128_len(self, other, rev)
    SV *self
    SV *other
    SV *rev
CODE:
    if (SvTRUE(rev))
        RETVAL = SvI128x(self) >= SvI128(aTHX_ other) ? &PL_sv_yes : &PL_sv_no;
    else
        RETVAL = SvI128x(self) <= SvI128(aTHX_ other) ? &PL_sv_yes : &PL_sv_no;
OUTPUT:
    RETVAL

SV *
mi128_and(self, other, rev)
    SV *self
    SV *other
    SV *rev
CODE:
    if (SvOK(rev))
        RETVAL = newSVi128(aTHX_ SvI128x(self) & SvI128(aTHX_ other));
    else {
        RETVAL = self;
        SvREFCNT_inc(RETVAL);
        SvI128x(self) &= SvI128(aTHX_ other);
    }
OUTPUT:
    RETVAL

SV *
mi128_or(self, other, rev)
    SV *self
    SV *other
    SV *rev
CODE:
    if (SvOK(rev))
        RETVAL = newSVi128(aTHX_ SvI128x(self) | SvI128(aTHX_ other));
    else {
        RETVAL = self;
        SvREFCNT_inc(RETVAL);
        SvI128x(self) |= SvI128(aTHX_ other);
    }
OUTPUT:
    RETVAL

SV *
mi128_xor(self, other, rev)
    SV *self
    SV *other
    SV *rev
CODE:
    if (SvOK(rev))
        RETVAL = newSVi128(aTHX_ SvI128x(self) ^ SvI128(aTHX_ other));
    else {
        RETVAL = self;
        SvREFCNT_inc(RETVAL);
        SvI128x(self) ^= SvI128(aTHX_ other);
    }
OUTPUT:
    RETVAL

SV *
mi128_not(self, ...)
    SV *self
CODE:
    RETVAL = SvI128x(self) ? &PL_sv_no : &PL_sv_yes;
OUTPUT:
    RETVAL

SV *
mi128_bnot(self, ...)
    SV *self
CODE:
    RETVAL = newSVi128(aTHX_ ~SvI128x(self));
OUTPUT:
    RETVAL    

SV *
mi128_neg(self, ...)
    SV *self
CODE:
    RETVAL = newSVi128(aTHX_ -SvI128x(self));
OUTPUT:
    RETVAL

SV *
mi128_bool(self, ...)
    SV *self
CODE:
    RETVAL = SvI128x(self) ? &PL_sv_yes : &PL_sv_no;
OUTPUT:
    RETVAL

SV *
mi128_number(self, ...)
    SV *self
CODE:
    RETVAL = si128_to_number(aTHX_ self);
OUTPUT:
    RETVAL

SV *
mi128_clone(self, ...)
    SV *self
CODE:
    RETVAL = newSVi128(aTHX_ SvI128x(self));
OUTPUT:
    RETVAL

SV *
mi128_string(self, ...)
    SV *self
CODE:
    RETVAL = newSV(I128STRLEN);
    SvPOK_on(RETVAL);
    SvCUR_set(RETVAL, i128_to_string(SvI128x(self), SvPVX(RETVAL)));
OUTPUT:
    RETVAL


MODULE = Math::Int128		PACKAGE = Math::UInt128		PREFIX=mu128
PROTOTYPES: DISABLE

SV *
mu128_inc(self, ...)
    SV *self
CODE:
    if (may_die_on_overflow && (SvU128x(self) == UINT128_MAX)) overflow(aTHX_ inc_error);
    SvU128x(self)++;
    RETVAL = SvREFCNT_inc(self);
OUTPUT:
    RETVAL

SV *
mu128_dec(self, ...)
    SV *self
CODE:
    if (may_die_on_overflow && (SvU128x(self) == 0)) overflow(aTHX_ dec_error);
    SvU128x(self)--;
    RETVAL = SvREFCNT_inc(self);
OUTPUT:
    RETVAL

SV *
mu128_add(self, other, rev)
    SV *self
    SV *other
    SV *rev
PREINIT:
    uint128_t a = SvU128x(self);
    uint128_t b = SvU128(aTHX_ other);
CODE:
    if (may_die_on_overflow && (UINT128_MAX - a < b)) overflow(aTHX_ add_error);
    if (SvOK(rev)) 
        RETVAL = newSVu128(aTHX_ a + b);
    else {
        RETVAL = self;
        SvREFCNT_inc(RETVAL);
        SvU128x(self) = a + b;
    }
OUTPUT:
    RETVAL

SV *
mu128_sub(self, other, rev)
    SV *self
    SV *other
    SV *rev
PREINIT:
    uint128_t a, b;
CODE:
    if (SvTRUE(rev)) {
        a = SvU128(aTHX_ other);
        b = SvU128x(self);
    }
    else {
        a = SvU128x(self);
        b = SvU128(aTHX_ other);
    }
    if (may_die_on_overflow && (b > a)) overflow(aTHX_ sub_error);
    if (SvOK(rev))
        RETVAL = newSVu128(aTHX_ a - b);
    else {
        RETVAL = SvREFCNT_inc(self);
        SvU128x(self) = a - b;
    }
OUTPUT:
    RETVAL

SV *
mu128_mul(self, other, rev)
    SV *self
    SV *other
    SV *rev
PREINIT:
    uint128_t a = SvU128x(self);
    uint128_t b = SvU128(aTHX_ other);
CODE:
    if (may_die_on_overflow)
        mul_check_overflow(aTHX_ a, b, mul_error);
    if (SvOK(rev))
        RETVAL = newSVu128(aTHX_ a * b);
    else {
        RETVAL = SvREFCNT_inc(self);
        SvU128x(self) = a * b;
    }
OUTPUT:
    RETVAL

SV *
mu128_div(self, other, rev)
    SV *self
    SV *other
    SV *rev
PREINIT:
    uint128_t up;
    uint128_t down;
CODE:
    if (SvOK(rev)) {
        if (SvTRUE(rev)) {
            up = SvU128(aTHX_ other);
            down = SvU128x(self);
        }
        else {
            up = SvU128x(self);
            down = SvU128(aTHX_ other);
        }
        if (!down)
            croak_string(aTHX_ div_by_0_error);
        RETVAL = newSVu128(aTHX_ up/down);
    }
    else {
        down = SvU128(aTHX_ other);
        if (!down)
            croak_string(aTHX_ div_by_0_error);
        RETVAL = self;
        SvREFCNT_inc(RETVAL);
        SvU128x(self) /= down;
    }
OUTPUT:
    RETVAL

SV *
mu128_remainder(self, other, rev)
    SV *self
    SV *other
    SV *rev
PREINIT:
    uint128_t up;
    uint128_t down;
CODE:
    if (SvOK(rev)) {
        if (SvTRUE(rev)) {
            up = SvU128(aTHX_ other);
            down = SvU128x(self);
        }
        else {
            up = SvU128x(self);
            down = SvU128(aTHX_ other);
        }
        if (!down)
            croak_string(aTHX_ div_by_0_error);
        RETVAL = newSVu128(aTHX_ up % down);
    }
    else {
        down = SvU128(aTHX_ other);
        if (!down)
            croak_string(aTHX_ div_by_0_error);
        RETVAL = self;
        SvREFCNT_inc(RETVAL);
        SvU128x(self) %= down;
    }
OUTPUT:
    RETVAL

SV *mu128_left(self, other, rev)
    SV *self
    SV *other
    SV *rev
CODE:
    if (SvOK(rev)) {
        uint128_t a, b;
        if (SvTRUE(rev)) {
            a = SvU128(aTHX_ other);
            b = SvU128x(self);
        }
        else {
            b = SvU128(aTHX_ other);
            a = SvU128x(self);
        }
        RETVAL = newSVu128(aTHX_ (b > 128 ? 0 : a << b));
    }
    else {
        uint128_t b = SvU128(aTHX_ other);
        RETVAL = SvREFCNT_inc(self);
        SvU128x(self) = (b > 127 ? 0 : SvU128x(self) << b);
    }
OUTPUT:
    RETVAL

SV *mu128_right(self, other, rev)
    SV *self
    SV *other
    SV *rev
CODE:
    if (SvOK(rev)) {
        uint128_t a, b;
        if (SvTRUE(rev)) {
            a = SvU128(aTHX_ other);
            b = SvU128x(self);
        }
        else {
            b = SvU128(aTHX_ other);
            a = SvU128x(self);
        }
        RETVAL = newSVu128(aTHX_ (b > 127 ? 0 : a >> b));
    }
    else {
        uint128_t b = SvU128(aTHX_ other);
        RETVAL = SvREFCNT_inc(self);
        SvU128x(self) = (b > 127 ? 0 : SvU128x(self) >> b);
    }
OUTPUT:
    RETVAL

SV *mu128_pow(self, other, rev = &PL_sv_no)
    SV *self
    SV *other
    SV *rev
PREINIT:
    uint128_t r;
    int128_t a, b;
CODE:
    if (SvTRUE(rev)) {
        a = SvU128(aTHX_ other);
        b = SvU128x(self);
    }
    else {
        a = SvU128x(self);
        b = SvU128(aTHX_ other);
    }
    r = powU128(aTHX_ a, b);
    if (SvOK(rev))
        RETVAL = newSVu128(aTHX_ r);
    else {
        RETVAL = self;
        SvREFCNT_inc(RETVAL);
        SvU128x(self) = r;
    }
OUTPUT:
    RETVAL

int
mu128_spaceship(self, other, rev)
    SV *self
    SV *other
    SV *rev
PREINIT:
    uint128_t left;
    uint128_t right;
CODE:
    if (SvTRUE(rev)) {
        left = SvU128(aTHX_ other);
        right = SvU128x(self);
    }
    else {
        left = SvU128x(self);
        right = SvU128(aTHX_ other);
    }
    RETVAL = (left < right ? -1 : left > right ? 1 : 0);
OUTPUT:
    RETVAL

SV *
mu128_eqn(self, other, ...)
    SV *self
    SV *other
CODE:
    RETVAL = ( SvU128x(self) == SvU128(aTHX_ other)
               ? &PL_sv_yes
               : &PL_sv_no );
OUTPUT:
    RETVAL

SV *
mu128_nen(self, other, ...)
    SV *self
    SV *other
CODE:
    RETVAL = ( SvU128x(self) != SvU128(aTHX_ other)
               ? &PL_sv_yes
               : &PL_sv_no );
OUTPUT:
    RETVAL

SV *
mu128_gtn(self, other, rev)
    SV *self
    SV *other
    SV *rev
CODE:
    if (SvTRUE(rev))
        RETVAL = SvU128x(self) < SvU128(aTHX_ other) ? &PL_sv_yes : &PL_sv_no;
    else
        RETVAL = SvU128x(self) > SvU128(aTHX_ other) ? &PL_sv_yes : &PL_sv_no;
OUTPUT:
    RETVAL

SV *
mu128_ltn(self, other, rev)
    SV *self
    SV *other
    SV *rev
CODE:
    if (SvTRUE(rev))
        RETVAL = SvU128x(self) > SvU128(aTHX_ other) ? &PL_sv_yes : &PL_sv_no;
    else
        RETVAL = SvU128x(self) < SvU128(aTHX_ other) ? &PL_sv_yes : &PL_sv_no;
OUTPUT:
    RETVAL

SV *
mu128_gen(self, other, rev)
    SV *self
    SV *other
    SV *rev
CODE:
    if (SvTRUE(rev))
        RETVAL = SvU128x(self) <= SvU128(aTHX_ other) ? &PL_sv_yes : &PL_sv_no;
    else
        RETVAL = SvU128x(self) >= SvU128(aTHX_ other) ? &PL_sv_yes : &PL_sv_no;
OUTPUT:
    RETVAL

SV *
mu128_len(self, other, rev)
    SV *self
    SV *other
    SV *rev
CODE:
    if (SvTRUE(rev))
        RETVAL = SvU128x(self) >= SvU128(aTHX_ other) ? &PL_sv_yes : &PL_sv_no;
    else
        RETVAL = SvU128x(self) <= SvU128(aTHX_ other) ? &PL_sv_yes : &PL_sv_no;
OUTPUT:
    RETVAL

SV *
mu128_and(self, other, rev)
    SV *self
    SV *other
    SV *rev
CODE:
    if (SvOK(rev))
        RETVAL = newSVu128(aTHX_ SvU128x(self) & SvU128(aTHX_ other));
    else {
        RETVAL = self;
        SvREFCNT_inc(RETVAL);
        SvU128x(self) &= SvU128(aTHX_ other);
    }
OUTPUT:
    RETVAL

SV *
mu128_or(self, other, rev)
    SV *self
    SV *other
    SV *rev
CODE:
    if (SvOK(rev))
        RETVAL = newSVu128(aTHX_ SvU128x(self) | SvU128(aTHX_ other));
    else {
        RETVAL = self;
        SvREFCNT_inc(RETVAL);
        SvU128x(self) |= SvU128(aTHX_ other);
    }
OUTPUT:
    RETVAL

SV *
mu128_xor(self, other, rev)
    SV *self
    SV *other
    SV *rev
CODE:
    if (SvOK(rev))
        RETVAL = newSVu128(aTHX_ SvU128x(self) ^ SvU128(aTHX_ other));
    else {
        RETVAL = self;
        SvREFCNT_inc(RETVAL);
        SvU128x(self) ^= SvU128(aTHX_ other);
    }
OUTPUT:
    RETVAL

SV *
mu128_not(self, ...)
    SV *self
CODE:
    RETVAL = SvU128x(self) ? &PL_sv_no : &PL_sv_yes;
OUTPUT:
    RETVAL

SV *
mu128_bnot(self, ...)
    SV *self
CODE:
    RETVAL = newSVu128(aTHX_ ~SvU128x(self));
OUTPUT:
    RETVAL    

SV *
mu128_neg(self, ...)
    SV *self
CODE:
    RETVAL = newSVu128(aTHX_ -SvU128x(self));
OUTPUT:
    RETVAL

SV *
mu128_bool(self, ...)
    SV *self
CODE:
    RETVAL = SvU128x(self) ? &PL_sv_yes : &PL_sv_no;
OUTPUT:
    RETVAL

SV *
mu128_number(self, ...)
    SV *self
CODE:
    RETVAL = su128_to_number(aTHX_ self);
OUTPUT:
    RETVAL

SV *
mu128_clone(self, ...)
    SV *self
CODE:
    RETVAL = newSVu128(aTHX_ SvU128x(self));
OUTPUT:
    RETVAL

SV *
mu128_string(self, ...)
    SV *self
CODE:
    RETVAL = newSV(I128STRLEN);
    SvPOK_on(RETVAL);
    SvCUR_set(RETVAL, u128_to_string(SvU128x(self), SvPVX(RETVAL)));
OUTPUT:
    RETVAL


MODULE = Math::Int128		PACKAGE = Math::Int128		PREFIX=mi128_
PROTOTYPES: DISABLE

void
mi128_int128_set(self, a=NULL)
    SV *self
    SV *a
CODE:
    SvI128x(self) = (a ? SvI128(aTHX_ a) : 0);

void
mi128_int128_inc(self, a)
    SV *self
    int128_t a
CODE:
    if ( may_die_on_overflow && (a == INT128_MAX)) overflow(aTHX_ inc_error);
    SvI128x(self) = a + 1;

void
mi128_int128_dec(self, a)
    SV *self
    int128_t a
CODE:
    if ( may_die_on_overflow && (a == INT128_MIN)) overflow(aTHX_ dec_error);
    SvI128x(self) = a - 1;

void
mi128_int128_add(self, a, b)
    SV *self
    int128_t a
    int128_t b
CODE:
    if ( may_die_on_overflow &&
         ( a > 0
           ? ( (b > 0) && (INT128_MAX - a < b) )
           : ( (b < 0) && (INT128_MIN - a > b) ) ) ) overflow(aTHX_ add_error);
    SvI128x(self) = a + b;

void
mi128_int128_sub(self, a, b)
    SV *self
    int128_t a
    int128_t b
CODE:
    if ( may_die_on_overflow &&
         ( a > 0
           ? ( (b < 0) && (a - INT128_MAX > b) )
           : ( (b > 0) && (a - INT128_MIN < b) ) ) ) overflow(aTHX_ sub_error);
    SvI128x(self) = a - b;

void
mi128_int128_mul(self, a1, b1)
    SV *self
    int128_t a1
    int128_t b1
CODE:
    if (may_die_on_overflow) {
        int neg = 0;
        uint128_t a, b;
        if (a1 < 0) {
            a = -a1;
            neg ^= 1;
        }
        else a = a1;
        if (b1 < 0) {
            b = -b1;
            neg ^= 1;
        }
        else b = b1;
        if (a < b) {
            uint128_t tmp = a;
            a = b; b = tmp;
        }
        mul_check_overflow(aTHX_ a, b, mul_error);
        if (a * b > (neg ? (~(uint128_t)INT128_MIN + 1) : INT128_MAX)) overflow(aTHX_ mul_error);
    }
    SvI128x(self) = a1 * b1;

void
mi128_int128_pow(self, a, b)
    SV *self
    int128_t a
    int128_t b
PREINIT:
    int sign;
    uint128_t r;
CODE:
    if (a < 0) {
        sign = ((b & 1) ? -1 : 1);
        a = -a;
    }
    else sign = 1;
    if (b < 0) {
        if      (a == 0) croak_string(aTHX_ div_by_0_error);
        else if (a == 1) r = sign;
        else             r = 0;
    }
    else {
        uint128_t u = powU128(aTHX_ a, b);
        if (may_die_on_overflow && (u > ((sign < 0) ? (~(uint128_t)INT128_MIN + 1) : INT128_MAX))) overflow(aTHX_ pow_error);
        r = ((sign > 0) ? u : -u);
    }
    SvI128x(self) = r;

void
mi128_int128_div(self, a, b)
    SV *self
    int128_t a
    int128_t b
CODE:
    if (!b) croak_string(aTHX_ div_by_0_error);
    SvI128x(self) = a / b;

void
mi128_int128_mod(self, a, b)
    SV *self
    int128_t a
    int128_t b
CODE:
    if (!b) croak_string(aTHX_ div_by_0_error);
    SvI128x(self) = a % b;

void
mi128_int128_divmod(self, rem, a, b)
    SV *self
    SV *rem
    int128_t a
    int128_t b
PREINIT:
    int128_t d;
CODE:
    if (!b) croak_string(aTHX_ div_by_0_error);
    SvI128x(self) = d = a / b;
    SvI128x(rem) = a - b * d;

void
mi128_int128_not(self, a)
    SV *self
    int128_t a
CODE:
    SvI128x(self) = ~a;

void
mi128_int128_neg(self, a)
    SV *self
    int128_t a
CODE:
    SvI128x(self) = -a;

void
mi128_int128_and(self, a, b)
    SV *self
    int128_t a
    int128_t b
CODE:
    SvI128x(self) = a & b;

void
mi128_int128_or(self, a, b)
    SV *self
    int128_t a
    int128_t b
CODE:
    SvI128x(self) = a | b;

void
mi128_int128_xor(self, a, b)
    SV *self
    int128_t a
    int128_t b
CODE:
    SvI128x(self) = a ^ b;

void
mi128_int128_left(self, a, b)
    SV *self
    int128_t a
    uint128_t b
CODE:
    SvI128x(self) = (b > 127 ? 0 : a << b);

void
mi128_int128_right(self, a, b)
    SV *self
    int128_t a
    uint128_t b
CODE:
    SvI128x(self) = (b > 127 ? (a < 0 ? - 1 : 0) : a >> b);
                  
void
mi128_int128_average(self, a, b)
    SV *self
    int128_t a
    int128_t b
CODE:
    SvI128x(self) = (a & b) + ((a ^ b) / 2);

void
mi128_int128_min(self, a, b)
    SV *self
    int128_t a
    int128_t b
CODE:
    SvI128x(self) = (a > b ? b : a);

void
mi128_int128_max(self, a, b)
    SV *self
    int128_t a
    int128_t b
CODE:
    SvI128x(self) = (a > b ? a : b);


MODULE = Math::Int128		PACKAGE = Math::Int128		PREFIX=mu128_
PROTOTYPES: DISABLE

void
mu128_uint128_set(self, a=0)
    SV *self
    uint128_t a;
CODE:
    SvU128x(self) = a;

void
mu128_uint128_inc(self, a)
    SV *self
    uint128_t a
CODE:
    if ( may_die_on_overflow && (a == INT128_MAX)) overflow(aTHX_ inc_error);
    SvU128x(self) = a + 1;

void
mu128_uint128_dec(self, a)
    SV *self
    uint128_t a
CODE:
    if ( may_die_on_overflow && (a == 0)) overflow(aTHX_ dec_error);
    SvU128x(self) = a - 1;

void
mu128_uint128_add(self, a, b)
    SV *self
    uint128_t a
    uint128_t b
CODE:
    if (may_die_on_overflow && (UINT128_MAX - a < b)) overflow(aTHX_ add_error);
    SvU128x(self) = a + b;

void
mu128_uint128_sub(self, a, b)
    SV *self
    uint128_t a
    uint128_t b
CODE:
    if (may_die_on_overflow && (b > a)) overflow(aTHX_ sub_error);
    SvU128x(self) = a - b;

void
mu128_uint128_mul(self, a, b)
    SV *self
    uint128_t a
    uint128_t b
CODE:
    if (may_die_on_overflow)
        mul_check_overflow(aTHX_ a, b, mul_error);
    SvU128x(self) = a * b;

void
mu128_uint128_pow(self, a, b)
    SV *self
    uint128_t a
    uint128_t b
CODE:
    SvU128x(self) = powU128(aTHX_ a, b);

void
mu128_uint128_div(self, a, b)
    SV *self
    uint128_t a
    uint128_t b
CODE:
    if (!b) croak_string(aTHX_ div_by_0_error);
    SvU128x(self) = a / b;

void
mu128_uint128_mod(self, a, b)
    SV *self
    uint128_t a
    uint128_t b
CODE:
    if (!b) croak_string(aTHX_ div_by_0_error);
    SvU128x(self) = a % b;

void
mu128_uint128_divmod(self, rem, a, b)
    SV *self
    SV *rem
    uint128_t a
    uint128_t b
PREINIT:
    uint128_t d;
CODE:
    if (!b) croak_string(aTHX_ div_by_0_error);
    SvU128x(self) = d = a / b;
    SvU128x(rem) = a - b * d;

void
mu128_uint128_not(self, a)
    SV *self
    uint128_t a
CODE:
    SvU128x(self) = ~a;

void
mu128_uint128_neg(self, a)
    SV *self
    uint128_t a
CODE:
    SvU128x(self) = -a;

void
mu128_uint128_and(self, a, b)
    SV *self
    uint128_t a
    uint128_t b
CODE:
    SvU128x(self) = a & b;

void
mu128_uint128_or(self, a, b)
    SV *self
    uint128_t a
    uint128_t b
CODE:
    SvU128x(self) = a | b;

void
mu128_uint128_xor(self, a, b)
    SV *self
    uint128_t a
    uint128_t b
CODE:
    SvU128x(self) = a ^ b;

void
mu128_uint128_left(self, a, b)
    SV *self
    uint128_t a
    uint128_t b
CODE:
    SvU128x(self) = (b > 127 ? 0 : a << b);

void
mu128_uint128_right(self, a, b)
    SV *self
    uint128_t a
    uint128_t b
CODE:
    SvU128x(self) = (b > 127 ? 0 : a >> b);

void
mu128_uint128_average(self, a, b)
    SV *self
    uint128_t a
    uint128_t b
CODE:
    SvU128x(self) = (a & b) + ((a ^ b) >> 1);

void
mi128_uint128_min(self, a, b)
    SV *self
    uint128_t a
    uint128_t b
CODE:
    SvU128x(self) = (a > b ? b : a);

void
mi128_uint128_max(self, a, b)
    SV *self
    uint128_t a
    uint128_t b
CODE:
    SvU128x(self) = (a > b ? a : b);