Category talk:Wren-i64

From Rosetta Code

64-bit integer arithmetic

Although this is already supported in an easy to use way by the Wren-long module, there are some RC tasks which that module (written entirely in Wren) struggles to complete in an acceptable time or to a satisfactory standard.

I have therefore written a wrapper for the C99 fixed length types, int64_t and uint64_t, to deal with such cases which are represented by the Wren classes, I64 and U64, respectively. There are also I64s and U64s classes which contain some static methods for dealing with lists of such objects.

These types are designed to behave the same as the C types regards rounding, underflow and overflow.

As it is not possible to create user-defined value types in Wren, all these classes are reference types and so the first two require all objects to be allocated on the heap and are subject therefore to automatic garbage collection when they are no longer needed.

As well as methods for basic arithmetic, I have also included some additional 'convenience' or utility methods which can be coded in a few lines of Wren code using the former.

This module is written to be as fast as possible within the confines of the Wren VM and therefore follows the model of Wren-GMP in recycling memory to minimize the number of variables and garbage collections needed in a typical application. This means that there is an inevitable trade-off between maximizing efficiency/performance and easy of use which is an important consideration for a simple language such as Wren. In an attempt to retain as much of the latter as possible:

1. I have included versions of the main functions which operate on and always return a reference to the current object which reduces verbosity and enables method chaining. These methods also sense the type of the argument eliminating the need to have separate versions for Wren-i64 types and built-in numbers (Num).

2. The two classes: I64 and U64 both inherit from the Comparable trait which means that the normal ordering operators (<, <=, ==, !=, >=, >) can be used for comparisons.

3. I have also included operator overloading for the basic arithmetic operations with the difference that these always produce a new object rather than mutating the current one. Consequently, these should be used sparingly in scripts which need to maximize performance but can be used more freely in other scripts to provide a more 'natural' programming experience.

How fast?

Early results suggest Wren-i64 will be at least 4 times faster than Wren-long for scripts requiring a lot of 64-bit integer arithmetic. However, writers of Wren-cli scripts which require file handling or other stuff which embedded Wren doesn't support 'out of the box' may prefer to stick to the former rather than make one off changes to the C executable if maximizing performance is not a major concern.

As they are optimized for dealing with much larger numbers, Wren-i64 is also an order of magnitude quicker than both the BigInt and Mpz (in Wren-GMP) classes.

However, it appears to be around 50% slower than normal 53-bit integer arithmetic in Wren and so there will still be a considerable speed differential between Wren and statically typed compiled or other languages which have native support for 64 integer arithmetic.

Source code (Wren)

/* Module "i64.wren" */

import "./trait" for Comparable

/*
    I64 represents a signed 64 bit integer which mirrors the int64_t type in C99.
    Consequently, foreign methods behave the same as regards rounding, underflow and overflow as int64_t does.
    Where Num arguments are allowed, they will be converted to integers if they are not already by the C code.
*/
foreign class I64 is Comparable {

    // Creates small commonly used I64 objects.
    static minusOne { from(-1) }
    static zero     { from(0)  }
    static one      { from(1)  }
    static two      { from(2)  }
    static three    { from(3)  }
    static four     { from(4)  }
    static five     { from(5)  }
    static six      { from(6)  }
    static seven    { from(7)  }
    static eight    { from(8)  }
    static nine     { from(9)  }
    static ten      { from(10) }

    foreign static largest
    foreign static smallest

    static maxSafe { from(9007199254740991)  }
    static minSafe { from(-9007199254740991) }

    // Returns an I64 object equal in value to 'x' raised to the power of 'n'.
    // The arguments can be either I64 objects or Nums.
    // If 'n' is less than 0, returns zero. O.pow(0) returns one.
    static pow(x, n) {
        if (x is Num) x = from(x)
        if (n is Num) n = from(n)
        if (n < 0) return zero
        if (n.isZero) return one
        if (n.isOne) return x.copy()
        if (x.isZero) return zero
        if (x.isOne) return one
        if (x == minusOne) return n.isEven ? one : minusOne
        var neg = x < 0
        var value = neg ? -(x.toNum) : x.toNum
        value = value.pow(n.toNum)
        if (value <= 9007199254740991) return neg ? from(-value) : from(value)
        x = x.copy()
        var y = one
        var z = n.copy()
        while (true) {
            if (z.isOdd) {
                y.mul(x)
                z.dec
            }
            if (z.isZero) break
            z.rsh(1)
            x.mul(x)
        }
        return y
    }

    // Returns an I64 object equal in value to the integer n'th root of 'x'
    // i.e. the precise n'th root truncated towards zero if not an integer.
    // 'x' can either be an I64 object or a Num but 'n' must be a positive integer.
    // Throws an error if 'x' is negative and 'n' is even.
    static root(x, n) {
        if (x is Num) x = from(x)
        if (!((n is Num) && n.isInteger && n > 0)) {
            Fiber.abort("'n' must be a positive integer.")
        }
        if (n == 1) return x.copy()
        var t = x.copy()
        var neg = t < 0
        if (neg) {
            if (n.isDivisible(two)) {
                Fiber.abort("Cannot take the %(n)th root of a negative number.")
            } else {
                t.neg
            }
        }
        n = n - 1
        var s = t + 1
        var u = from(t)
        while (u < s) {
            s.set(u)
            u.set(((u * n) + t / pow(u, n)) / (n + 1))
        }
        return (neg) ? -s : s
    }

    // Returns an I64 object equal in value to 'x' multiplied by 'n' modulo 'mod'.
    // The arguments can either be I64 objects or Nums but 'mod' must be non-zero.
    static modMul(x, n, mod) {
        x = from(x)
        n = from(n)
        mod = from(mod)
        if (mod.isZero) Fiber.abort("Cannot take modMul with modulus 0.")
        var sign = x.sign * n.sign
        var mag = U64.modMul(x.abs, n.abs, mod.abs).toI64
        return mag * sign
    }

    // Returns an I64 object equal in value to 'x' to the power 'exp' modulo 'mod'.
    // The arguments can either be I64 objects or Nums but 'mod' must be non-zero.
    static modPow(x, exp, mod) {
        if (x is Num) x = from(x)
        exp = from(exp)
        if (mod is Num) mod = from(mod)
        if (mod.isZero) Fiber.abort("Cannot take modPow with modulus 0.")
        var r = one
        var base = x % mod
        if (exp < 0) {
            exp.mul(minusOne)
            base.set(modInv(base, mod))
        }
        while (exp > 0) {
            if (base.isZero) return zero
            if (exp.isOdd) r.set(modMul(r, base, mod))
            exp.rsh(1)
            base.set(modMul(base, base, mod))
        }
        return r
    }

    // Returns the multiplicative inverse of 'x' modulo 'n'.
    // The arguments can either be I64 objects or Nums but must be co-prime.
    static modInv(x, n) {
        if (x is Num) x = from(x)
        if (n is Num) n = from(n)
        var r = from(n)
        var newR = from(x).abs
        var t = zero
        var newT = one
        while (!newR.isZero) {
            var q = r / newR
            var lastT = from(t)
            var lastR = from(r)
            t.set(newT)
            r.set(newR)
            newT.sub(lastT, q*newT)
            newR.sub(lastR, q*newR)
        }
        if (!r.abs.isOne) Fiber.abort("%(this) and %(n) are not co-prime.")
        if (t < 0) t.add(n)
        if (x < 0) return t.neg
        return t
    }

    // Returns the smaller of two I64 objects.
    static min(x, y) {
        if (x is Num) x = from(x)
        if (y is Num) y = from(y)
        if (x < y) return x
        return y
    }

    // Returns the greater of two I64 objects.
    static max(x, y) {
        if (x is Num) x = from(x)
        if (y is Num) y = from(y)
        if (x > y) return x
        return y
    }

    // Returns the positive difference of two I64 objects.
    static dim(x, y) {
        if (x is Num) x = from(x)
        if (y is Num) y = from(y)
        if (x >= y) return x - y
        return zero
    }

    // Returns the greatest common divisor of two I64 objects.
    static gcd(x, y) {
        x = from(x)
        y = from(y)
        while (y != zero) {
            var t = y
            y.set(x % y)
            x.set(t)
        }
        return x.abs
    }

    // Returns the least common multiple of two I64 objects.
    static lcm(x, y) {
        x = from(x)
        y = from(y)
        if (x.isZero && y.isZero) return zero
        return (x*y).abs / gcd(x, y)
    }

    // Returns whether or not 'x' is an instance of I64.
    static isInstance(x) { x is I64 }

    // Private method which aborts the script if an argument is of an invalid type or value.
    static abort_() { Fiber.abort("Argument type or value is invalid.") }

    // Creates a new I64 object with a value of zero.
    construct new() {}

    // Creates a new I64 object from:
    foreign static from(x)       // another I64 object or from a Num
    foreign static fromU64(x)    // a U64 object
    foreign static fromStr(s)    // a base 10 string
    foreign static fromStr(s, b) // a base 'b' string

    // Assigns a new value to the current instance using :
    foreign set(x)        // another I64 object or a Num
    foreign setU64(x)     // a U64 object
    foreign setStr(s)     // a base 10 string
    foreign setStr(s, b)  // a base 'b' string

    // Converts the current instance to:
    foreign toNum       // a Num
    foreign toU64       // a U64 object
    foreign toString    // a base 10 string

    toString(b) {       // a base 'b' string
        if (b < 2 || b > 36) Fiber.abort("Base must be between 2 and 36.")
        if (this.isZero) return "0"
        var digits = "0123456789abcdefghijklmnopqrstuvwxyz"
        var neg = (this < 0)
        var n = this.copy()
        if (neg) n.neg
        var res = ""
        while (n > 0) {
            res = res + "%(digits[(n%b).toNum])"
            n.div(b)
        }
        return ((neg) ? "-" : "") + res[-1..0]
    }

    /* Methods which assign their result to the current instance ('this').
       Unless otherwise noted, arguments can be either I64 objects or Nums. */

    foreign add(op1, op2)        // adds two objects
    foreign sub(op1, op2)        // subtracts one object from another
    foreign mul(op1, op2)        // multiplies two objects
    foreign addMul(op1, op2)     // multiplies two objects and adds the result to this
    foreign subMul(op1, op2)     // multiplies two objects and subtracts the result from this
    foreign div(n, d)            // divides one object by another (rounds towards zero)
    foreign rem(n, d)            // sets the remainder after dividing one object by another
    foreign and(op1, op2)        // bitwise 'and' of two objects
    foreign ior(op1, op2)        // bitwise 'inclusive or' of two objects
    foreign xor(op1, op2)        // bitwise 'exclusive or' of two objects
    foreign lsh(n, b)            // shifts n (I64 or Num) by b (Num) bits to the left
    foreign rsh(n, b)            // shifts n (I64 or Num) by b (Num) bits to the right
    foreign neg(op)              // sets to -op
    foreign abs(op)              // sets to the absolute value of op
    foreign inc(op)              // adds one to op
    foreign dec(op)              // subtracts one from op
    foreign com(op)              // one's complement of an object

    pow(op, n)  { set(I64.pow (op, n)) }  // raises op to the power n
    square(op)  { set(I64.pow (op, 2)) }  // squares op
    cube(op)    { set(I64.pow (op, 3)) }  // cubes op
    root(op, n) { set(I64.root(op, n)) }  // takes the nth integral root of op
    sqrt(op)    { set(I64.root(op, 2)) }  // takes the square integral root of op
    cbrt(op)    { set(I64.root(op, 3)) }  // takes the cube integral root of op

    /* Convenience versions of the above methods where the first (or only) argument is 'this'.
       Unless otherwise noted, any other argument must be either another I64 object or a Num. */

    foreign add(op)
    foreign sub(op)
    foreign mul(op)
    foreign addMul(op)
    foreign subMul(op)
    foreign div(op)
    foreign rem(op)
    foreign and(op)
    foreign ior(op)
    foreign xor(op)
    foreign lsh(b)    // b must be a Num
    foreign rsh(b)    // b must be a Num
    foreign neg
    foreign abs
    foreign inc
    foreign dec
    foreign com

    pow(n)   { set(I64.pow (this, n)) }
    square   { set(I64.pow (this, 2)) }
    cube     { set(I64.pow (this, 3)) }
    root(n)  { set(I64.root(this, n)) }
    sqrt     { set(I64.root(this, 2)) } 
    cbrt     { set(I64.root(this, 3)) }

    /* As above methods where the first (or only) argument is 'this'
       but return a new I64 object rather than mutating 'this'. */

    // Operators corresponding to the above foreign methods.
    +(op)  { copy().add(op) }
    -(op)  { copy().sub(op) }
    *(op)  { copy().mul(op) }
    /(op)  { copy().div(op) }
    %(op)  { copy().rem(op) }

    &(op)  { copy().and(op) }
    |(op)  { copy().ior(op) }
    ^(op)  { copy().xor(op) }

    <<(b)  { copy().lsh(b)  }
    >>(b)  { copy().rsh(b)  }

    - { copy().neg }
    ~ { copy().com }

    /* Comparison methods which return -1, 0, 1
       if this < op, this == op or this > op respectively. */

    // Needed to satisfy Comparable trait and allow relational operators to be used.
    // Argument can be either another I64 object or a Num.
    foreign compare(op)

    foreign cmpU64(op)  // compare this to a U64 object

    /* Miscellaneous methods. */

    min(op)   { (op < this) ? op : this }    // returns the minimum of this and another I64 object
    max(op)   { (op > this) ? op : this }    // returns the maximum of this and another I64 object

    clamp(min, max) {  // clamps this to the interval [min, max]
        if (min > max) Fiber.abort("Range cannot be decreasing.")
        if (this < min) set(min) else if (this > max) set(max)
        return this.copy()
    }

    copy() { I64.from(this) }  // copies 'this' to a new I64 object

    isOdd  { this % I64.two != I64.zero }                 // true if 'this' is odd
    isEven { this % I64.two == I64.zero }                 // true if 'this' is even
    isZero { this == I64.zero }                           // true if 'this' is zero
    isOne  { this == I64.one  }                           // true if 'this' is one
    isSafe { this >= I64.minSafe && this <= I64.maxSafe } // true if 'this' is safe

    isDivisible(d) { this % d == I64.zero }  // true if 'this' is divisible by d

    isRoot(n) {  // true if 'this' is an exact nth root of another I64 object
        var r = I64.root(this, n)
        return I64.pow(r, n) == this
    }

    isSquare { isRoot(2) }  // true if 'this' is an exact square root of another I64 object
    isCube   { isRoot(3) }  // true if 'this' is an exact cube root of another I64 object

    sign {  // the sign of 'this'
        if (this < I64.zero) return -1
        if (this > I64.zero) return 1
        return 0
    }
}

/*
    U64 represents an unsigned 64 bit integer which mirrors the uint64_t type in C99.
    Consequently, foreign methods behave the same as regards rounding, underflow and overflow as uint64_t does.
    Where Num arguments are allowed, they will be converted to integers if they are not already by the C code.
*/
foreign class U64 is Comparable {

    // Creates small commonly used U64 objects.
    static zero     { from(0)  }
    static one      { from(1)  }
    static two      { from(2)  }
    static three    { from(3)  }
    static four     { from(4)  }
    static five     { from(5)  }
    static six      { from(6)  }
    static seven    { from(7)  }
    static eight    { from(8)  }
    static nine     { from(9)  }
    static ten      { from(10) }

    foreign static largest

    static maxSafe { from(9007199254740991) }
    static maxU32  { from(4294967295) }

    // Returns the largest prime less than 2^64.
    static largestPrime { U64.fromStr("18446744073709551557") }

    // Returns a U64 object equal in value to 'x' raised to the power of 'n'.
    // The arguments can be either U64 objects or Nums.
    // If 'n' is less than 0, returns zero. O.pow(0) returns one.
    static pow(x, n) {
        if (x is Num) x = from(x)
        if (n is Num) n = from(n)
        if (n.isZero) return one
        if (n.isOne) return x.copy()
        if (x.isZero) return zero
        if (x.isOne) return one
        var value = x.toNum.pow(n.toNum)
        if (value <= 9007199254740991) return from(value)
        x = x.copy()
        var y = one
        var z = n.copy()
        while (true) {
            if (z.isOdd) {
                y.mul(x)
                z.dec
            }
            if (z.isZero) break
            z.rsh(1)
            x.mul(x)
        }
        return y
    }

    // Returns a U64 object equal in value to the integer n'th root of 'x'
    // i.e. the precise n'th root truncated towards zero if not an integer.
    // 'x' can either be a U64 object or a Num but 'n' must be a positive integer.
    static root(x, n) {
        if (x is Num) x = from(x)
        if (!((n is Num) && n.isInteger && n > 0)) {
            Fiber.abort("'n' must be a positive integer.")
        }
        if (n == 1) return x.copy()
        var t = x.copy()
        n = n - 1
        var s = t + 1
        var u = from(t)
        while (u < s) {
            s.set(u)
            u.set(((u * n) + t / pow(u, n)) / (n + 1))
        }
        return s
    }

    // Private helper method for modMul method to avoid overflow.
    static checkedAdd_(a, b, c) {
        var room = c - U64.one - a
        if (b <= room) {
            a.add(b)
        } else {
            a.set(b - room - U64.one)
        }
    }

    // Returns a U64 object equal in value to 'x' multiplied by 'n' modulo 'mod'.
    // The arguments can either be U64 objects or Nums.
    static modMul(x, n, mod) {
        if (x is Num) x = from(x)
        n = from(n)
        mod = from(mod)
        if (mod.isZero) Fiber.abort("Cannot take modMul with modulus 0.")
        var z = zero
        var y = x % mod
        n.rem(mod)
        if (n > y) {
            var t = y
            y = n
            n = t
        }
        while (n > zero) {
            if (n.isOdd) checkedAdd_(z, y, mod)
            checkedAdd_(y, y, mod)
            n.rsh(1)
        }
        return z
    }

    // Returns a U64 object equal in value to 'x' to the power 'exp' modulo 'mod'.
    // The arguments can either be U64 objects or Nums but 'mod' must be non-zero.
    static modPow(x, exp, mod) {
        if (x is Num) x = from(x)
        exp = from(exp)
        if (mod is Num) mod = from(mod)
        if (mod.isZero) Fiber.abort("Cannot take modPow with modulus 0.")
        var r = one
        var base = x % mod
        while (!exp.isZero) {
            if (base.isZero) return zero
            if (exp.isOdd) r.set(modMul(r, base, mod))
            exp.rsh(1)
            base.set(modMul(base, base, mod))
        }
        return r
    }

    // Returns the multiplicative inverse of 'x' modulo 'n'.
    // The arguments can either be U64 objects or Nums but must be co-prime.
    static modInv(x, n) {
        if (x is Num) x = from(x)
        if (n is Num) n = from(n)
        return I64.modInv(x.toI64, n.toI64).toU64
    }

    // Returns the smaller of two U64 objects.
    static min(x, y) {
        if (x is Num) x = from(x)
        if (y is Num) y = from(y)
        if (x < y) return x
        return y
    }

    // Returns the greater of two U64 objects.
    static max(x, y) {
        if (x is Num) x = from(x)
        if (y is Num) y = from(y)
        if (x > y) return x
        return y
    }

    // Returns the positive difference of two U64 objects.
    static dim(x, y) {
        if (x is Num) x = from(x)
        if (y is Num) y = from(y)
        if (x >= y) return x - y
        return zero
    }

    // Returns the greatest common divisor of two U64 objects.
    static gcd(x, y) {
        x = from(x)
        y = from(y)
        while (y != zero) {
            var t = y
            y.set(x % y)
            x.set(t)
        }
        return x
    }

    // Returns the least common multiple of two U64 objects.
    static lcm(x, y) {
        x = from(x)
        y = from(y)
        if (x.isZero && y.isZero) return zero
        return x * y / gcd(x, y)
    }

    // Returns the factorial of 'n'.
    // Returns the factorial of 'n'.
    static factorial(n) {
        if (!((n is Num) && n >= 0 && n <= 20)) {
            Fiber.abort("Argument must be a non-negative integer <= 20.")
        }
        if (n < 2) return one
        var fact = one
        var i = 2
        while (i <= n) {
            fact.mul(i)
            i = i + 1
        }
        return fact
    }

    // Returns the multinomial coefficient of n over a list f where sum(f) == n.
    static multinomial(n, f) {
        if (!(n is Num && n >= 0 && n <= 20)) {
            Fiber.abort("First argument must be a non-negative integer <= 20.")
        }
        if (!(f is List)) Fiber.abort("Second argument must be a list.")
        var sum = f.reduce { |acc, i| acc + i }
        if (n != sum) {
            Fiber.abort("The elements of the list must sum to 'n'.")
        }
        var prod = one
        for (e in f) {
            if (e < 0) Fiber.abort("The elements of the list must be non-negative integers.")
            if (e > 1) prod.mul(factorial(e))
        }
        return factorial(n).div(prod)
    }

    // Returns the binomial coefficent of n over k.
    static binomial(n, k) { multinomial(n, [k, n-k]) }

    // Returns whether or not 'x' is an instance of U64.
    static isInstance(x) { x is U64 }

    // Private method to apply the Miller-Rabin test.
    static millerRabinTest_(n, a) {
        var nPrev = n.copy().dec
        var b = nPrev.copy()
        var r = 0
        while (b.isEven) {
            b.rsh(1)
            r = r + 1
        }
        for (i in 0...a.count) {
            if (n >= a[i]) {
                var x = (a[i] is U64) ? a[i].copy() : from(a[i])
                x.set(modPow(x, b, n))
                if (!x.isOne && x != nPrev) {
                    var d = r - 1
                    var next = false
                    while (d != 0) {
                        x.set(modMul(x, x, n))
                        if (x.isOne) return false
                        if (x == nPrev) {
                            next = true
                            break
                        }
                        d = d - 1
                    }
                    if (!next) return false
                }
            }
        }
        return true
    }

    // Private helper method for 'isPrime' method.
    // Determines whether 'x' is prime using a wheel with basis [2, 3, 5].
    // Should be faster than Miller-Rabin if 'x' is below 2^37.
    static isPrimeWheel_(x) {
        if (x < 2) return false
        var n = x.copy()
        if (n.isEven) return n == 2
        if ((n%3).isZero) return n == 3
        if ((n%5).isZero) return n == 5
        var d = U64.seven
        while (d*d <= n) {
            if ((n%d).isZero) return false
            d.add(4)
            if ((n%d).isZero) return false
            d.add(2)
            if ((n%d).isZero) return false
            d.add(4)
            if ((n%d).isZero) return false
            d.add(2)
            if ((n%d).isZero) return false 
            d.add(4)
            if ((n%d).isZero) return false
            d.add(6)
            if ((n%d).isZero) return false 
            d.add(2)
            if ((n%d).isZero) return false
            d.add(6)
            if ((n%d).isZero) return false
        }
        return true
    }

    // Returns true if 'x' is prime, false otherwise.
    static isPrime(x) {
        if (x is Num) x = from(x)
        if (x < 137438953472) return isPrimeWheel_(x)  // use wheel if below 2^37
        if (x.isEven || x.isDivisible(three) || x.isDivisible(five) || 
            x.isDivisible(seven)) return false
        var a
        if (x < 1122004669633) {
            a = [2, 13, 23, 1662803]
        } else if (x < 2152302898747) {
            a = [2, 3, 5, 7, 11]
        } else if (x < 3474749660383) {                       
            a = [2, 3, 5, 7, 11, 13]
        } else if (x < 341550071728321) {
            a = [2, 3, 5, 7, 11, 13, 17]
        } else if (x < fromStr("3825123056546413051")) {
            a = [2, 3, 5, 7, 11, 13, 17, 19, 23]
        } else {
            a = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]
        }
        return millerRabinTest_(x, a)
    }

    // Returns the next prime number greater than 'x'.
    static nextPrime(x) {
        if (x is Num) x = from(x)
        if (x < two) return U64.two
        var n = x.isEven ? x + one : x + two
        while (true) {
            if (isPrime(n)) return n
            n.add(two)
        }
    }

    // Returns the previous prime number less than 'x' or null if there isn't one.
    static prevPrime(x) {
        if (x is Num) x = from(x)
        if (x < three) return null
        if (x == three) return U64.two
        var n = x.isEven ? x - one : x - two
        while (true) {
            if (isPrime(n)) return n
            n.sub(two)
        }
    }

    // Private worker method for Pollard's Rho algorithm.
    static pollardRho_(m, seed, c) {
        var g = Fn.new { |x| (x * x).add(c).rem(m) }
        var x = from(seed)
        var y = from(seed)
        var z = one
        var d = one
        var count = 0
        while (true) {
            x.set(g.call(x))
            y.set(g.call(g.call(y)))
            if (x >= y) d.sub(x, y).rem(m) else d.sub(y, x).rem(m)
            z.mul(d)
            count = count + 1
            if (count == 100) {
                d.set(gcd(z, m))
                if (d != one) break
                z.set(one)
                count = 0
            }
        }
        if (d == m) return zero
        return d
    }

    // Returns a factor (U64) of 'm' (a U64 object or an integral Num) using the
    // Pollard's Rho algorithm. Both the 'seed' and 'c' can be set to integers.
    // Returns U64.zero in the event of failure.
    static pollardRho(m, seed, c) {
        if (m < 2) return U64.zero
        if (m is Num) m = from(m)
        if (isPrime(m)) return m.copy()
        if (m.isSquare) return m.copy().sqrt
        for (p in [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31]) {
            if (m.isDivisible(p)) return from(p)
        }
        return pollardRho_(m, seed, c)
    }

    // Convenience version of the above method which uses a seed of 2 and a value for c of 1.
    static pollardRho(m) { pollardRho(m, 2, 1) }

    // Private method for factorizing smaller numbers (U64) using a wheel with basis [2, 3, 5].
    static primeFactorsWheel_(m) {
        var n = m.copy()
        var inc = [4, 2, 4, 2, 4, 6, 2, 6]
        var factors = []
        var k = from(37)
        var i = 0
        while (k * k <= n) {
            if (n.isDivisible(k)) {
                factors.add(k.copy())
                n.div(k)
            } else {
                k.add(inc[i])
                i = (i + 1) % 8
            }
        }
        if (n > one) factors.add(n)
        return factors
    }

    // Private worker method (recursive) to obtain the prime factors of a number (U64).
    static primeFactors_(m, trialDivs) {
        if (isPrime(m)) return [m.copy()]
        var n = m.copy()
        var factors = []
        var seed = 2
        var c = 1
        var checkPrime = true
        var threshold = 1e11 // from which using PR may be advantageous
        if (trialDivs) {
            for (p in [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31]) {
                while (n.isDivisible(p)) {
                    factors.add(from(p))
                    n.div(p)
                }
            }
        }
        while (n > one) {
            if (checkPrime && isPrime(n)) {
                factors.add(n.copy())
                break
            }
            if (n >= threshold) {
                var d = pollardRho_(n, seed, c)
                if (d != zero) {
                    factors.addAll(primeFactors_(d, false))
                    n.div(d)
                    checkPrime = true
                } else if (c == 1) {
                    if (n.isSquare) {
                        n.sqrt
                        var pf = primeFactors_(n, false)
                        factors.addAll(pf)
                        factors.addAll(pf)
                        break
                    } else {
                        c = 2
                        checkPrime = false
                    }
                } else if (c < 101) {
                    c = c + 1
                } else if (seed < 101) {
                    seed = seed + 1
                } else {
                    factors.addAll(primeFactorsWheel_(n))
                    break
                }
            } else {
                factors.addAll(primeFactorsWheel_(n))
                break
            }
        }
        factors.sort()
        return factors
    }

    // Returns a list of the primes factors (U64) of 'm' (a U64 object or an integral Num)
    // using the wheel based factorization and/or Pollard's Rho algorithm as appropriate.
    static primeFactors(m) {
       if (m < 2) return []
       if (m is Num) m = from(m)
       return primeFactors_(m, true)
    }

    // Returns all the divisors of 'n' including 1 and 'n' itself.
    static divisors(n) {
        if (n < 1) return []
        if (n is Num) n = from(n)   
        var divs = []
        var divs2 = []
        var i = one
        var k = n.isEven ? one : two
        var sqrt = n.copy().sqrt
        while (i <= sqrt) {
            if (n.isDivisible(i)) {
                divs.add(i.copy())
                var j = n / i
                if (j != i) divs2.add(j)
            }
            i.add(k)
        }
        if (!divs2.isEmpty) divs = divs + divs2[-1..0]
        return divs
    }

    // Returns all the divisors of 'n' excluding 'n'.
    static properDivisors(n) {
        var d = divisors(n)
        var c = d.count
        return (c <= 1) ? [] : d[0..-2]
    }

    // As 'divisors' method but uses a different algorithm.
    // Better for larger numbers.
    static divisors2(n) {
        if (n is Num) n = from(n)
        var pf = primeFactors(n)
        if (pf.count == 0) return (n == 1) ? [one] : pf
        var arr = []
        if (pf.count == 1) {
            arr.add([pf[0].copy(), 1])
        } else {
            var prevPrime = pf[0]
            var count = 1
            for (i in 1...pf.count) {
                if (pf[i] == prevPrime) {
                    count = count + 1
                } else {
                    arr.add([prevPrime.copy(), count])
                    prevPrime = pf[i]
                    count = 1
                }
            }
            arr.add([prevPrime.copy(), count])
        }
        var divisors = []
        var generateDivs
        generateDivs = Fn.new { |currIndex, currDivisor|
            if (currIndex == arr.count) {
                divisors.add(currDivisor.copy())
                return
            }
            for (i in 0..arr[currIndex][1]) {
                generateDivs.call(currIndex+1, currDivisor)
                currDivisor = currDivisor * arr[currIndex][0]
            }
        }
        generateDivs.call(0, one)
        return divisors.sort()
    }

    // As 'properDivisors' but uses 'divisors2' method.
    static properDivisors2(n) {
        var d = divisors2(n)
        var c = d.count
        return (c <= 1) ? [] : d[0..-2]
    }

    // Returns the sum of all the divisors of 'n' including 1 and 'n' itself.
    static divisorSum(n) {
        if (n < 1) return zero
        n = from(n)
        var total = one
        var power = two
        while (n.isDivisible(two)) {
            total.add(power)
            power.mul(2)
            n.div(2)
        }
        var i = three
        while (i * i <= n) {
            var sum = one
            power.set(i)
            while (n.isDivisible(i)) {
                sum.add(power)
                power.mul(i)
                n.div(i)
            }
            total.mul(sum)
            i.add(2)
        }
        if (n > 1) total.mul(n + 1)
        return total
    }

    // Returns the number of divisors of 'n' including 1 and 'n' itself.
    static divisorCount(n) {
        if (n < 1) return 0
        n = from(n)
        var count = 0
        var prod = 1
        while (n.isDivisible(two)) {
            count = count + 1
            n.div(2)
        }
        prod = prod * (1 + count)
        var i = three
        while (i * i <= n) {
            count = 0
            while (n.isDivisible(i)) {
                count = count + 1
                n.div(i)
            }
            prod = prod * (1 + count)
            i.add(2)
        }
        if (n > 2) prod = prod * 2
        return prod
    }

    // Creates a new U64 object with a value of zero.
    construct new() {}

    // Creates a new U64 object from:
    foreign static from(x)       // another U64 object or from a Num
    foreign static fromI64(x)    // an I64 object
    foreign static fromStr(s, b) // a base 'b' string
    foreign static fromStr(s)    // a base 10 string

    // Assigns a new value to the current instance using:
    foreign set(x)        // another U64 object or a Num
    foreign setI64(x)     // an I64 object
    foreign setStr(s, b)  // a base 'b' string
    foreign setStr(s)     // a base 10 string

    // Converts the current instance to:
    foreign toNum       // a Num
    foreign toI64       // an I64 object
    foreign toString    // a base 10 string

    toString(b) {       // a base 'b' string
        if (b < 2 || b > 36) Fiber.abort("Base must be between 2 and 36.")
        if (this.isZero) return "0"
        var digits = "0123456789abcdefghijklmnopqrstuvwxyz"
        var n = this.copy()
        var res = ""
        while (n > 0) {
            res = res + "%(digits[(n%b).toNum])"
            n.div(b)
        }
        return res[-1..0]
    }

    /* Methods which assign their result to the current instance ('this').
       Unless otherwise noted, arguments can be either U64 objects or Nums. */

    foreign add(op1, op2)        // adds two objects
    foreign sub(op1, op2)        // subtracts one object from another
    foreign mul(op1, op2)        // multiplies two objects
    foreign addMul(op1, op2)     // multiplies two objects and adds the result to this
    foreign subMul(op1, op2)     // multiplies two objects and subtracts the result from this
    foreign div(n, d)            // divides one object by another (rounds towards zero)
    foreign rem(n, d)            // sets the remainder after dividing one object by another
    foreign and(op1, op2)        // bitwise 'and' of two objects
    foreign ior(op1, op2)        // bitwise 'inclusive or' of two objects
    foreign xor(op1, op2)        // bitwise 'exclusive or' of two objects
    foreign lsh(n, b)            // shifts n (U64 or Num) by b (Num) bits to the left
    foreign rsh(n, b)            // shifts n (U64 or Num) by b (Num) bits to the right
    foreign neg(op)              // sets to -op
    foreign inc(op)              // adds one to op
    foreign dec(op)              // subtracts one from op
    foreign com(op)              // one's complement of an object

    pow(op, n)  { set(U64.pow (op, n)) }  // raises op to the power n
    square(op)  { set(U64.pow (op, 2)) }  // squares op
    cube(op)    { set(U64.pow (op, 3)) }  // cubes op
    root(op, n) { set(U64.root(op, n)) }  // takes the nth integral root of op
    sqrt(op)    { set(U64.root(op, 2)) }  // takes the square integral root of op
    cbrt(op)    { set(U64.root(op, 3)) }  // takes the cube integral root of op

    /* Convenience versions of the above methods where the first (or only) argument is 'this'.
       Unless otherwise noted, any other argument must be either another U64 object or a Num. */

    foreign add(op)
    foreign sub(op)
    foreign mul(op)
    foreign addMul(op)
    foreign subMul(op)
    foreign div(op)
    foreign rem(op)
    foreign and(op)
    foreign ior(op)
    foreign xor(op)
    foreign lsh(b)    // b must be a Num
    foreign rsh(b)    // b must be a Num
    foreign neg
    foreign inc
    foreign dec
    foreign com

    pow(n)  { set(U64.pow (this, n)) }
    square  { set(U64.pow (this, 2)) }
    cube    { set(U64.pow (this, 3)) }
    root(n) { set(U64.root(this, n)) }
    sqrt    { set(U64.root(this, 2)) } 
    cbrt    { set(U64.root(this, 3)) }

    /* As above methods where the first (or only) argument is 'this'
       but return a new U64 object rather than mutating 'this'. */

    // Operators corresponding to the above foreign methods.
    +(op)  { copy().add(op) }
    -(op)  { copy().sub(op) }
    *(op)  { copy().mul(op) }
    /(op)  { copy().div(op) }
    %(op)  { copy().rem(op) }

    &(op)  { copy().and(op) }
    |(op)  { copy().ior(op) }
    ^(op)  { copy().xor(op) }

    <<(b)  { copy().lsh(b)  }
    >>(b)  { copy().rsh(b)  }

    - { copy().neg }
    ~ { copy().com }

    /* Comparison methods which return -1, 0, 1
       if this < op, this == op or this > op respectively. */

    // Needed to satisfy Comparable trait and allow relational operators to be used.
    // Argument can be either another U64 object or a Num.
    foreign compare(op)

    foreign cmpI64(op)  // compare this to an I64 object

    /* Miscellaneous methods. */

    min(op)   { (op < this) ? op : this }    // returns the minimum of this and another U64 object
    max(op)   { (op > this) ? op : this }    // returns the maximum of this and another U64 object

    clamp(min, max) {  // clamps this to the interval [min, max]
        if (min > max) Fiber.abort("Range cannot be decreasing.")
        if (this < min) set(min) else if (this > max) set(max)
        return this.copy()
    }

    copy() { U64.from(this) }  // copies 'this' to a new U64 object

    isOdd   { this % U64.two == 1 }      // true if 'this' is odd
    isEven  { this % U64.two == 0 }      // true if 'this' is even
    isZero  { this == 0 }                // true if 'this' is zero
    isOne   { this == 1 }                // true if 'this' is one
    isSafe  { this <= U64.maxSafe }      // true if 'this' is safe

    isDivisible(d) { this % d == U64.zero } // true if 'this' is divisible by d

    isRoot(n) {  // true if 'this' is an exact nth root of another U64 object
        var r = U64.root(this, n)
        return U64.pow(r, n) == this
    }

    isSquare { isRoot(2) }  // true if 'this' is an exact square root of another U64 object
    isCube   { isRoot(3) }  // true if 'this' is an exact cube root of another U64 object

    sign {  // the sign of 'this'
        if (this > U64.zero) return 1
        return 0
    }
    // Return a list of the current instance's base 10 digits
    digits   { toString.map { |d| Num.fromString(d) }.toList }

    // Returns the sum of the current instance's base 10 digits.
    digitSum {
        var sum = 0
        for (d in toString.bytes) sum = sum + d - 48
        return sum
    }
}

/* I64s contains various routines applicable to a sequence of I64 objects. */
class I64s  {
    static sum(sz)  { sz.reduce(I64.zero) { |acc, x| acc.add(x) } }
    static prod(sz) { sz.reduce(I64.one)  { |acc, x| acc.mul(x) } }
    static min(sz)  { sz.reduce { |acc, x| (x > acc) ? x : acc  } }
    static max(sz)  { sz.reduce { |acc, x| (x < acc) ? x : acc  } }
}

/* U64s contains various routines applicable to a sequence of U64 objects. */
class U64s  {
    static sum(sz)  { sz.reduce(U64.zero) { |acc, x| acc.add(x) } }
    static prod(sz) { sz.reduce(U64.one)  { |acc, x| acc.mul(x) } }
    static min(sz)  { sz.reduce { |acc, x| (x > acc) ? x : acc  } }
    static max(sz)  { sz.reduce { |acc, x| (x < acc) ? x : acc  } }
}

Source code (C)

/* gcc -O3 wren-i64.c -o wren-i64 -lwren -lm  */

#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <string.h>
#include "wren.h"

/* I64 functions */

void I64_allocate(WrenVM* vm) {
    int64_t *pi = (int64_t*)wrenSetSlotNewForeign(vm, 0, 0, 8);
    *pi = 0;
}

void I64_largest(WrenVM* vm) {
    int64_t *pi = (int64_t*)wrenSetSlotNewForeign(vm, 0, 0, 8);
    *pi = INT64_MAX;
}

void I64_smallest(WrenVM* vm) {
    int64_t *pi = (int64_t*)wrenSetSlotNewForeign(vm, 0, 0, 8);
    *pi = INT64_MIN;
}

void I64_from(WrenVM* vm) {
    int64_t *pi = (int64_t*)wrenSetSlotNewForeign(vm, 0, 0, 8);
    int wt = wrenGetSlotType(vm, 1);
    int64_t val = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 1) : (int64_t)wrenGetSlotDouble(vm, 1);
    *pi = val;
}

void I64_fromU64(WrenVM* vm) {
    int64_t *pi  = (int64_t*)wrenSetSlotNewForeign(vm, 0, 0, 8);
    uint64_t val = *(uint64_t*)wrenGetSlotForeign(vm, 1);
    *pi = (int64_t)val;
}

void I64_fromStr(WrenVM* vm) {
    int64_t *pi = (int64_t*)wrenSetSlotNewForeign(vm, 0, 0, 8);
    const char *str = wrenGetSlotString(vm, 1);
    int64_t val = (int64_t)strtoll(str, NULL, 10);
    *pi = val;
}

void I64_fromStr2(WrenVM* vm) {
    int64_t *pi = (int64_t*)wrenSetSlotNewForeign(vm, 0, 0, 8);
    const char *str = wrenGetSlotString(vm, 1);
    int base = (int)wrenGetSlotDouble(vm, 2);
    int64_t val = (int64_t)strtoll(str, NULL, base);
    *pi = val;
}

void I64_set(WrenVM* vm) {
    int64_t *pi = (int64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    int64_t val = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 1) : (int64_t)wrenGetSlotDouble(vm, 1);
    *pi = val;
}

void I64_setU64(WrenVM* vm) {
    int64_t *pi = (int64_t*)wrenGetSlotForeign(vm, 0);
    uint64_t val = *(uint64_t*)wrenGetSlotForeign(vm, 1);
    *pi = (int64_t)val;
}

void I64_setStr(WrenVM* vm) {
    int64_t *pi = (int64_t*)wrenGetSlotForeign(vm, 0);
    const char *str = wrenGetSlotString(vm, 1);
    int64_t val = (int64_t)strtoll(str, NULL, 10);
    *pi = val;
}

void I64_setStr2(WrenVM* vm) {
    int64_t *pi = (int64_t*)wrenGetSlotForeign(vm, 0);
    const char *str = wrenGetSlotString(vm, 1);
    int base = (int)wrenGetSlotDouble(vm, 2);
    int64_t val = (int64_t)strtoll(str, NULL, base);
    *pi = val;
}

void I64_toNum(WrenVM* vm) {
    int64_t val = *(int64_t*)wrenGetSlotForeign(vm, 0);
    wrenSetSlotDouble(vm, 0, (double)val);
}

void I64_toU64(WrenVM* vm) {
    int64_t val = *(int64_t*)wrenGetSlotForeign(vm, 0);
    wrenGetVariable(vm, "./i64", "U64", 0);
    uint64_t *pu = wrenSetSlotNewForeign(vm, 0, 0, 8);
    *pu = (uint64_t)val;
}

void I64_toString(WrenVM* vm) {
    int64_t val = *(int64_t*)wrenGetSlotForeign(vm, 0);
    char buf[21];
    snprintf(buf, 21, "%lld", (long long int)val);
    wrenSetSlotString(vm, 0, (const char*)buf);
}

void I64_add2(WrenVM* vm) {
    int64_t *pv1 = (int64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    int64_t v2 = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 1) : (int64_t)wrenGetSlotDouble(vm, 1); 
    wt = wrenGetSlotType(vm, 2);
    int64_t v3 = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 2) : (int64_t)wrenGetSlotDouble(vm, 2);  
    *pv1 = v2 + v3;
}

void I64_sub2(WrenVM* vm) {
    int64_t *pv1 = (int64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    int64_t v2 = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 1) : (int64_t)wrenGetSlotDouble(vm, 1); 
    wt = wrenGetSlotType(vm, 2);
    int64_t v3 = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 2) : (int64_t)wrenGetSlotDouble(vm, 2);  
    *pv1 = v2 - v3;
}

void I64_mul2(WrenVM* vm) {
    int64_t *pv1 = (int64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    int64_t v2 = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 1) : (int64_t)wrenGetSlotDouble(vm, 1); 
    wt = wrenGetSlotType(vm, 2);
    int64_t v3 = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 2) : (int64_t)wrenGetSlotDouble(vm, 2);  
    *pv1 = v2 * v3;
}

void I64_addMul2(WrenVM* vm) {
    int64_t *pv1 = (int64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    int64_t v2 = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 1) : (int64_t)wrenGetSlotDouble(vm, 1); 
    wt = wrenGetSlotType(vm, 2);
    int64_t v3 = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 2) : (int64_t)wrenGetSlotDouble(vm, 2);
    *pv1 += v2 * v3;
}

void I64_subMul2(WrenVM* vm) {
    int64_t *pv1 = (int64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    int64_t v2 = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 1) : (int64_t)wrenGetSlotDouble(vm, 1); 
    wt = wrenGetSlotType(vm, 2);
    int64_t v3 = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 2) : (int64_t)wrenGetSlotDouble(vm, 2);  
    *pv1 -= v2 * v3;
}

void I64_div2(WrenVM* vm) {
    int64_t *pv1 = (int64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    int64_t v2 = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 1) : (int64_t)wrenGetSlotDouble(vm, 1); 
    wt = wrenGetSlotType(vm, 2);
    int64_t v3 = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 2) : (int64_t)wrenGetSlotDouble(vm, 2);  
    *pv1 = v2 / v3;
}

void I64_rem2(WrenVM* vm) {
    int64_t *pv1 = (int64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    int64_t v2 = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 1) : (int64_t)wrenGetSlotDouble(vm, 1); 
    wt = wrenGetSlotType(vm, 2);
    int64_t v3 = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 2) : (int64_t)wrenGetSlotDouble(vm, 2);  
    *pv1 = v2 % v3;
}

void I64_and2(WrenVM* vm) {
    int64_t *pv1 = (int64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    int64_t v2 = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 1) : (int64_t)wrenGetSlotDouble(vm, 1); 
    wt = wrenGetSlotType(vm, 2);
    int64_t v3 = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 2) : (int64_t)wrenGetSlotDouble(vm, 2);  
    *pv1 = v2 & v3;
}

void I64_ior2(WrenVM* vm) {
    int64_t *pv1 = (int64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    int64_t v2 = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 1) : (int64_t)wrenGetSlotDouble(vm, 1); 
    wt = wrenGetSlotType(vm, 2);
    int64_t v3 = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 2) : (int64_t)wrenGetSlotDouble(vm, 2);  
    *pv1 = v2 | v3;
}

void I64_xor2(WrenVM* vm) {
    int64_t *pv1 = (int64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    int64_t v2 = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 1) : (int64_t)wrenGetSlotDouble(vm, 1); 
    wt = wrenGetSlotType(vm, 2);
    int64_t v3 = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 2) : (int64_t)wrenGetSlotDouble(vm, 2);
    *pv1 = v2 ^ v3;
}

void I64_lsh2(WrenVM* vm) {
    int64_t *pi = (int64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    int64_t n = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 1) : (int64_t)wrenGetSlotDouble(vm, 1); 
    int b = (int)wrenGetSlotDouble(vm, 2);
    *pi = n << b;
}

void I64_rsh2(WrenVM* vm) {
    int64_t *pi = (int64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    int64_t n = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 1) : (int64_t)wrenGetSlotDouble(vm, 1);
    int b = (int)wrenGetSlotDouble(vm, 2);
    *pi = n >> b;
}

void I64_neg(WrenVM* vm) {
    int64_t *pi = (int64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    int64_t val = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 1) : (int64_t)wrenGetSlotDouble(vm, 1); 
    *pi = -val;
}

void I64_abs(WrenVM* vm) {
    int64_t *pi = (int64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    int64_t val = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 1) : (int64_t)wrenGetSlotDouble(vm, 1); 
    if (val < 0) *pi = -val;
}

void I64_inc(WrenVM* vm) {
    int64_t *pi = (int64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    int64_t val = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 1) : (int64_t)wrenGetSlotDouble(vm, 1); 
    *pi = ++val;
}

void I64_dec(WrenVM* vm) {
    int64_t *pi = (int64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    int64_t val = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 1) : (int64_t)wrenGetSlotDouble(vm, 1); 
    *pi = --val;
}

void I64_com(WrenVM* vm) {
    int64_t *pi = (int64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    int64_t val = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 1) : (int64_t)wrenGetSlotDouble(vm, 1);
    *pi = ~val;
}

void I64_add(WrenVM* vm) {
    int64_t *pv1 = (int64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    int64_t v2 = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 1) : (int64_t)wrenGetSlotDouble(vm, 1);  
    *pv1 += v2;
}

void I64_sub(WrenVM* vm) {
    int64_t *pv1 = (int64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    int64_t v2 = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 1) : (int64_t)wrenGetSlotDouble(vm, 1);  
    *pv1 -= v2;
}

void I64_mul(WrenVM* vm) {
    int64_t *pv1 = (int64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    int64_t v2 = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 1) : (int64_t)wrenGetSlotDouble(vm, 1);  
    *pv1 *= v2;
}

void I64_addMul(WrenVM* vm) {
    int64_t *pv1 = (int64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    int64_t v2 = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 1) : (int64_t)wrenGetSlotDouble(vm, 1);  
    *pv1 += *pv1 * v2;
}

void I64_subMul(WrenVM* vm) {
    int64_t *pv1 = (int64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    int64_t v2 = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 1) : (int64_t)wrenGetSlotDouble(vm, 1);  
    *pv1 -= *pv1 * v2;
}

void I64_div(WrenVM* vm) {
    int64_t *pv1 = (int64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    int64_t v2 = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 1) : (int64_t)wrenGetSlotDouble(vm, 1);  
    *pv1 /= v2;
}

void I64_rem(WrenVM* vm) {
    int64_t *pv1 = (int64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    int64_t v2 = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 1) : (int64_t)wrenGetSlotDouble(vm, 1);  
    *pv1 %= v2;
}

void I64_and(WrenVM* vm) {
    int64_t *pv1 = (int64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    int64_t v2 = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 1) : (int64_t)wrenGetSlotDouble(vm, 1);  
    *pv1 &= v2;
}

void I64_ior(WrenVM* vm) {
    int64_t *pv1 = (int64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    int64_t v2 = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 1) : (int64_t)wrenGetSlotDouble(vm, 1);  
    *pv1 |= v2;
}

void I64_xor(WrenVM* vm) {
    int64_t *pv1 = (int64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    int64_t v2 = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 1) : (int64_t)wrenGetSlotDouble(vm, 1);  
    *pv1 ^= v2;
}

void I64_lsh(WrenVM* vm) {
    int64_t *pi = (int64_t*)wrenGetSlotForeign(vm, 0);
    int b = (int)wrenGetSlotDouble(vm, 1);
    *pi <<= b;
}

void I64_rsh(WrenVM* vm) {
    int64_t *pi = (int64_t*)wrenGetSlotForeign(vm, 0);
    int b = (int)wrenGetSlotDouble(vm, 1);
    *pi >>= b;
}

void I64_neg0(WrenVM* vm) {
    int64_t *pi = (int64_t*)wrenGetSlotForeign(vm, 0);
    *pi = -(*pi);
}

void I64_abs0(WrenVM* vm) {
    int64_t *pi = (int64_t*)wrenGetSlotForeign(vm, 0);
    if (*pi < 0) *pi = -(*pi);
}

void I64_inc0(WrenVM* vm) {
    int64_t *pi = (int64_t*)wrenGetSlotForeign(vm, 0);
    *pi += 1;
}

void I64_dec0(WrenVM* vm) {
    int64_t *pi = (int64_t*)wrenGetSlotForeign(vm, 0);
    *pi -= 1;
}

void I64_com0(WrenVM* vm) {
    int64_t *pi = (int64_t*)wrenGetSlotForeign(vm, 0);
    *pi = ~(*pi);
}

void I64_compare(WrenVM* vm) {
    int64_t v1 = *(int64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    int64_t v2 = (wt == 2) ? *(int64_t*)wrenGetSlotForeign(vm, 1) : (int64_t)wrenGetSlotDouble(vm, 1);
    int res = (v1 < v2) ? -1 : (v1 > v2) ? 1 : 0;
    wrenSetSlotDouble(vm, 0, (double)res);
}

void I64_cmpU64(WrenVM* vm) {
    int64_t v1 = *(int64_t*)wrenGetSlotForeign(vm, 0);
    uint64_t v2 = *(uint64_t*)wrenGetSlotForeign(vm, 1);
    int64_t v3 = (int64_t)v2;
    int res = (v1 < v3) ? -1 : (v1 > v3) ? 1 : 0;
    wrenSetSlotDouble(vm, 0, (double)res);
}

/* U64 functions */

void U64_allocate(WrenVM* vm) {
    uint64_t *pu = (uint64_t*)wrenSetSlotNewForeign(vm, 0, 0, 8);
    *pu = 0;
}

void U64_largest(WrenVM* vm) {
    uint64_t *pu = (uint64_t*)wrenSetSlotNewForeign(vm, 0, 0, 8);
    *pu = UINT64_MAX;
}

void U64_from(WrenVM* vm) {
    uint64_t *pu = (uint64_t*)wrenSetSlotNewForeign(vm, 0, 0, 8);
    int wt = wrenGetSlotType(vm, 1);
    uint64_t val = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 1) : (uint64_t)wrenGetSlotDouble(vm, 1);
    *pu = val;
}

void U64_fromI64(WrenVM* vm) {
    uint64_t *pu = (uint64_t*)wrenSetSlotNewForeign(vm, 0, 0, 8);
    int64_t val = *(int64_t*)wrenGetSlotForeign(vm, 1);
    *pu = (uint64_t)val;
}

void U64_fromStr(WrenVM* vm) {
    uint64_t *pu = (uint64_t*)wrenSetSlotNewForeign(vm, 0, 0, 8);
    const char *str = wrenGetSlotString(vm, 1);
    uint64_t val = (uint64_t)strtoull(str, NULL, 10);
    *pu = val;
}

void U64_fromStr2(WrenVM* vm) {
    uint64_t *pu = (uint64_t*)wrenSetSlotNewForeign(vm, 0, 0, 8);
    const char *str = wrenGetSlotString(vm, 1);
    int base = (int)wrenGetSlotDouble(vm, 2);
    uint64_t val = (uint64_t)strtoull(str, NULL, base);
    *pu = val;
}

void U64_set(WrenVM* vm) {
    uint64_t *pu = (uint64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    uint64_t val = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 1) : (uint64_t)wrenGetSlotDouble(vm, 1);
    *pu = val;
}

void U64_setI64(WrenVM* vm) {
    uint64_t *pu = (uint64_t*)wrenGetSlotForeign(vm, 0);
    int64_t val  = *(int64_t*)wrenGetSlotForeign(vm, 1);
    *pu = (uint64_t)val;
}

void U64_setStr(WrenVM* vm) {
    uint64_t *pu = (uint64_t*)wrenGetSlotForeign(vm, 0);
    const char *str = wrenGetSlotString(vm, 1);
    uint64_t val = (uint64_t)strtoll(str, NULL, 10);
    *pu = val;
}

void U64_setStr2(WrenVM* vm) {
    uint64_t *pu = (uint64_t*)wrenGetSlotForeign(vm, 0);
    const char *str = wrenGetSlotString(vm, 1);
    int base = (int)wrenGetSlotDouble(vm, 2);
    uint64_t val = (uint64_t)strtoll(str, NULL, base);
    *pu = val;
}

void U64_toNum(WrenVM* vm) {
    uint64_t val = *(uint64_t*)wrenGetSlotForeign(vm, 0);
    wrenSetSlotDouble(vm, 0, (double)val);
}

void U64_toI64(WrenVM* vm) {
    uint64_t val = *(uint64_t*)wrenGetSlotForeign(vm, 0);
    wrenGetVariable(vm, "./i64", "I64", 0);
    int64_t *pi = wrenSetSlotNewForeign(vm, 0, 0, 8);
    *pi = (int64_t)val;
}

void U64_toString(WrenVM* vm) {
    uint64_t val = *(uint64_t*)wrenGetSlotForeign(vm, 0);
    char buf[21];
    snprintf(buf, 21, "%llu", (unsigned long long int)val);
    wrenSetSlotString(vm, 0, (const char*)buf);
}

void U64_add2(WrenVM* vm) {
    uint64_t *pv1 = (uint64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    uint64_t v2 = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 1) : (uint64_t)wrenGetSlotDouble(vm, 1); 
    wt = wrenGetSlotType(vm, 2);
    uint64_t v3 = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 2) : (uint64_t)wrenGetSlotDouble(vm, 2);  
    *pv1 = v2 + v3;
}

void U64_sub2(WrenVM* vm) {
    uint64_t *pv1 = (uint64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    uint64_t v2 = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 1) : (uint64_t)wrenGetSlotDouble(vm, 1); 
    wt = wrenGetSlotType(vm, 2);
    uint64_t v3 = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 2) : (uint64_t)wrenGetSlotDouble(vm, 2);  
    *pv1 = v2 - v3;
}

void U64_mul2(WrenVM* vm) {
    uint64_t *pv1 = (uint64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    uint64_t v2 = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 1) : (uint64_t)wrenGetSlotDouble(vm, 1); 
    wt = wrenGetSlotType(vm, 2);
    uint64_t v3 = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 2) : (uint64_t)wrenGetSlotDouble(vm, 2);  
    *pv1 = v2 * v3;
}

void U64_addMul2(WrenVM* vm) {
    uint64_t *pv1 = (uint64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    uint64_t v2 = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 1) : (uint64_t)wrenGetSlotDouble(vm, 1); 
    wt = wrenGetSlotType(vm, 2);
    uint64_t v3 = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 2) : (uint64_t)wrenGetSlotDouble(vm, 2);  
    *pv1 += v2 * v3;
}

void U64_subMul2(WrenVM* vm) {
    uint64_t *pv1 = (uint64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    uint64_t v2 = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 1) : (uint64_t)wrenGetSlotDouble(vm, 1); 
    wt = wrenGetSlotType(vm, 2);
    uint64_t v3 = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 2) : (uint64_t)wrenGetSlotDouble(vm, 2);  
    *pv1 -= v2 * v3;
}

void U64_div2(WrenVM* vm) {
    uint64_t *pv1 = (uint64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    uint64_t v2 = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 1) : (uint64_t)wrenGetSlotDouble(vm, 1); 
    wt = wrenGetSlotType(vm, 2);
    uint64_t v3 = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 2) : (uint64_t)wrenGetSlotDouble(vm, 2);  
    *pv1 = v2 / v3;
}

void U64_rem2(WrenVM* vm) {
    uint64_t *pv1 = (uint64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    uint64_t v2 = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 1) : (uint64_t)wrenGetSlotDouble(vm, 1); 
    wt = wrenGetSlotType(vm, 2);
    uint64_t v3 = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 2) : (uint64_t)wrenGetSlotDouble(vm, 2);  
    *pv1 = v2 % v3;
}

void U64_and2(WrenVM* vm) {
    uint64_t *pv1 = (uint64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    uint64_t v2 = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 1) : (uint64_t)wrenGetSlotDouble(vm, 1); 
    wt = wrenGetSlotType(vm, 2);
    uint64_t v3 = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 2) : (uint64_t)wrenGetSlotDouble(vm, 2);
    *pv1 = v2 & v3;
}

void U64_ior2(WrenVM* vm) {
    uint64_t *pv1 = (uint64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    uint64_t v2 = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 1) : (uint64_t)wrenGetSlotDouble(vm, 1); 
    wt = wrenGetSlotType(vm, 2);
    uint64_t v3 = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 2) : (uint64_t)wrenGetSlotDouble(vm, 2);  
    *pv1 = v2 | v3;
}

void U64_xor2(WrenVM* vm) {
    uint64_t *pv1 = (uint64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    uint64_t v2 = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 1) : (uint64_t)wrenGetSlotDouble(vm, 1); 
    wt = wrenGetSlotType(vm, 2);
    uint64_t v3 = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 2) : (uint64_t)wrenGetSlotDouble(vm, 2);
    *pv1 = v2 ^ v3;
}

void U64_lsh2(WrenVM* vm) {
    uint64_t *pu = (uint64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    uint64_t n = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 1) : (uint64_t)wrenGetSlotDouble(vm, 1); 
    int b = (int)wrenGetSlotDouble(vm, 2);
    *pu = n << b;
}

void U64_rsh2(WrenVM* vm) {
    uint64_t *pu = (uint64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    uint64_t n = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 1) : (uint64_t)wrenGetSlotDouble(vm, 1);
    int b = (int)wrenGetSlotDouble(vm, 2);
    *pu = n >> b;
}

void U64_neg(WrenVM* vm) {
    uint64_t *pu = (uint64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    uint64_t val = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 1) : (uint64_t)wrenGetSlotDouble(vm, 1); 
    *pu = -val;
}

void U64_inc(WrenVM* vm) {
    uint64_t *pu = (uint64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    uint64_t val = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 1) : (uint64_t)wrenGetSlotDouble(vm, 1);
    *pu = ++val;
}

void U64_dec(WrenVM* vm) {
    uint64_t *pu = (uint64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    uint64_t val = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 1) : (uint64_t)wrenGetSlotDouble(vm, 1);
    *pu = --val;
}

void U64_com(WrenVM* vm) {
    uint64_t *pu = (uint64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    uint64_t val = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 1) : (uint64_t)wrenGetSlotDouble(vm, 1);
    *pu = ~val;
}

void U64_add(WrenVM* vm) {
    uint64_t *pv1 = (uint64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    uint64_t v2 = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 1) : (uint64_t)wrenGetSlotDouble(vm, 1);  
    *pv1 += v2;
}

void U64_sub(WrenVM* vm) {
    uint64_t *pv1 = (uint64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    uint64_t v2 = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 1) : (uint64_t)wrenGetSlotDouble(vm, 1);
    *pv1 -= v2;
}

void U64_mul(WrenVM* vm) {
    uint64_t *pv1 = (uint64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    uint64_t v2 = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 1) : (uint64_t)wrenGetSlotDouble(vm, 1);  
    *pv1 *= v2;
}

void U64_addMul(WrenVM* vm) {
    uint64_t *pv1 = (uint64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    uint64_t v2 = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 1) : (uint64_t)wrenGetSlotDouble(vm, 1);  
    *pv1 += *pv1 * v2;
}

void U64_subMul(WrenVM* vm) {
    uint64_t *pv1 = (uint64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    uint64_t v2 = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 1) : (uint64_t)wrenGetSlotDouble(vm, 1);  
    *pv1 -= *pv1 * v2;
}

void U64_div(WrenVM* vm) {
    uint64_t *pv1 = (uint64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    uint64_t v2 = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 1) : (uint64_t)wrenGetSlotDouble(vm, 1);  
    *pv1 /= v2;
}

void U64_rem(WrenVM* vm) {
    uint64_t *pv1 = (uint64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    uint64_t v2 = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 1) : (uint64_t)wrenGetSlotDouble(vm, 1);  
    *pv1 %= v2;
}

void U64_and(WrenVM* vm) {
    uint64_t *pv1 = (uint64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    uint64_t v2 = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 1) : (uint64_t)wrenGetSlotDouble(vm, 1);
    *pv1 &= v2;
}

void U64_ior(WrenVM* vm) {
    uint64_t *pv1 = (uint64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    uint64_t v2 = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 1) : (uint64_t)wrenGetSlotDouble(vm, 1);  
    *pv1 |= v2;
}

void U64_xor(WrenVM* vm) {
    uint64_t *pv1 = (uint64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    uint64_t v2 = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 1) : (uint64_t)wrenGetSlotDouble(vm, 1);
    *pv1 ^= v2;
}

void U64_lsh(WrenVM* vm) {
    uint64_t *pu = (uint64_t*)wrenGetSlotForeign(vm, 0);
    int b = (int)wrenGetSlotDouble(vm, 1);
    *pu <<= b;
}

void U64_rsh(WrenVM* vm) {
    uint64_t *pu = (uint64_t*)wrenGetSlotForeign(vm, 0);
    int b = (int)wrenGetSlotDouble(vm, 1);
    *pu >>= b;
}

void U64_neg0(WrenVM* vm) {
    uint64_t *pu = (uint64_t*)wrenGetSlotForeign(vm, 0);
    *pu = -(*pu);
}

void U64_inc0(WrenVM* vm) {
    uint64_t *pu = (uint64_t*)wrenGetSlotForeign(vm, 0);
    *pu += 1;
}

void U64_dec0(WrenVM* vm) {
    uint64_t *pu = (uint64_t*)wrenGetSlotForeign(vm, 0);
    *pu -= 1;
} 

void U64_com0(WrenVM* vm) {
    uint64_t *pu = (uint64_t*)wrenGetSlotForeign(vm, 0);
    *pu = ~(*pu);
}

void U64_compare(WrenVM* vm) {
    uint64_t v1 = *(uint64_t*)wrenGetSlotForeign(vm, 0);
    int wt = wrenGetSlotType(vm, 1);
    uint64_t v2 = (wt == 2) ? *(uint64_t*)wrenGetSlotForeign(vm, 1) : (uint64_t)wrenGetSlotDouble(vm, 1);
    int res = (v1 < v2) ? -1 : (v1 > v2) ? 1 : 0;
    wrenSetSlotDouble(vm, 0, (double)res);
}

void U64_cmpI64(WrenVM* vm) {
    uint64_t v1 = *(uint64_t*)wrenGetSlotForeign(vm, 0);
    int64_t  v2 = *(int64_t*)wrenGetSlotForeign(vm, 1);
    uint64_t v3 = (uint64_t)v2;
    int res = (v1 < v3) ? -1 : (v1 > v3) ? 1 : 0;
    wrenSetSlotDouble(vm, 0, (double)res);
}

WrenForeignClassMethods bindForeignClass(WrenVM* vm, const char* module, const char* className) {
    WrenForeignClassMethods methods;
    methods.allocate = NULL;
    methods.finalize = NULL;
    if (strcmp(module, "./i64") == 0) {
        if (strcmp(className, "I64") == 0) {
            methods.allocate = I64_allocate;
        } else if (strcmp(className, "U64") == 0) {
            methods.allocate = U64_allocate;
        }
    }
    return methods;
}

WrenForeignMethodFn bindForeignMethod(
    WrenVM* vm,
    const char* module,
    const char* className,
    bool isStatic,
    const char* signature) {
    if (strcmp(module, "./i64") == 0) {
        if (strcmp(className, "I64") == 0) {
            if( isStatic && strcmp(signature, "largest") == 0)      return I64_largest;
            if( isStatic && strcmp(signature, "smallest") == 0)     return I64_smallest;
            if( isStatic && strcmp(signature, "from(_)") == 0)      return I64_from;
            if( isStatic && strcmp(signature, "fromU64(_)") == 0)   return I64_fromU64;
            if( isStatic && strcmp(signature, "fromStr(_)") == 0)   return I64_fromStr;
            if( isStatic && strcmp(signature, "fromStr(_,_)") == 0) return I64_fromStr2;

            if(!isStatic && strcmp(signature, "set(_)") == 0)       return I64_set;
            if(!isStatic && strcmp(signature, "setU64(_)") == 0)    return I64_setU64;
            if(!isStatic && strcmp(signature, "setStr(_)") == 0)    return I64_setStr;
            if(!isStatic && strcmp(signature, "setStr(_,_)") == 0)  return I64_setStr2;
            if(!isStatic && strcmp(signature, "toNum") == 0)        return I64_toNum;
            if(!isStatic && strcmp(signature, "toU64") == 0)        return I64_toU64;
            if(!isStatic && strcmp(signature, "toString") == 0)     return I64_toString;

            if(!isStatic && strcmp(signature, "add(_,_)") == 0)     return I64_add2;
            if(!isStatic && strcmp(signature, "sub(_,_)") == 0)     return I64_sub2;
            if(!isStatic && strcmp(signature, "mul(_,_)") == 0)     return I64_mul2;
            if(!isStatic && strcmp(signature, "addMul(_,_)") == 0)  return I64_addMul2;
            if(!isStatic && strcmp(signature, "subMul(_,_)") == 0)  return I64_subMul2;
            if(!isStatic && strcmp(signature, "div(_,_)") == 0)     return I64_div2;
            if(!isStatic && strcmp(signature, "rem(_,_)") == 0)     return I64_rem2;
            if(!isStatic && strcmp(signature, "and(_,_)") == 0)     return I64_and2;
            if(!isStatic && strcmp(signature, "ior(_,_)") == 0)     return I64_ior2;
            if(!isStatic && strcmp(signature, "xor(_,_)") == 0)     return I64_xor2;
            if(!isStatic && strcmp(signature, "lsh(_,_)") == 0)     return I64_lsh2;
            if(!isStatic && strcmp(signature, "rsh(_,_)") == 0)     return I64_rsh2;
            if(!isStatic && strcmp(signature, "neg(_)") == 0)       return I64_neg;
            if(!isStatic && strcmp(signature, "abs(_)") == 0)       return I64_abs;
            if(!isStatic && strcmp(signature, "inc(_)") == 0)       return I64_inc;
            if(!isStatic && strcmp(signature, "dec(_)") == 0)       return I64_dec;
            if(!isStatic && strcmp(signature, "com(_)") == 0)       return I64_com;

            if(!isStatic && strcmp(signature, "add(_)") == 0)       return I64_add;
            if(!isStatic && strcmp(signature, "sub(_)") == 0)       return I64_sub;
            if(!isStatic && strcmp(signature, "mul(_)") == 0)       return I64_mul;
            if(!isStatic && strcmp(signature, "addMul(_)") == 0)    return I64_addMul;
            if(!isStatic && strcmp(signature, "subMul(_)") == 0)    return I64_subMul;
            if(!isStatic && strcmp(signature, "div(_)") == 0)       return I64_div;
            if(!isStatic && strcmp(signature, "rem(_)") == 0)       return I64_rem;
            if(!isStatic && strcmp(signature, "and(_)") == 0)       return I64_and;
            if(!isStatic && strcmp(signature, "ior(_)") == 0)       return I64_ior;
            if(!isStatic && strcmp(signature, "xor(_)") == 0)       return I64_xor;
            if(!isStatic && strcmp(signature, "lsh(_)") == 0)       return I64_lsh;
            if(!isStatic && strcmp(signature, "rsh(_)") == 0)       return I64_rsh;
            if(!isStatic && strcmp(signature, "neg") == 0)          return I64_neg0;
            if(!isStatic && strcmp(signature, "abs") == 0)          return I64_abs0;
            if(!isStatic && strcmp(signature, "inc") == 0)          return I64_inc0;
            if(!isStatic && strcmp(signature, "dec") == 0)          return I64_dec0;
            if(!isStatic && strcmp(signature, "com") == 0)          return I64_com0;

            if(!isStatic && strcmp(signature, "compare(_)") == 0)   return I64_compare;
            if(!isStatic && strcmp(signature, "cmpU64(_)") == 0)    return I64_cmpU64;
        } else if (strcmp(className, "U64") == 0) {
            if( isStatic && strcmp(signature, "largest") == 0)      return U64_largest;
            if( isStatic && strcmp(signature, "from(_)") == 0)      return U64_from;
            if( isStatic && strcmp(signature, "fromI64(_)") == 0)   return U64_fromI64;
            if( isStatic && strcmp(signature, "fromStr(_)") == 0)   return U64_fromStr;
            if( isStatic && strcmp(signature, "fromStr(_,_)") == 0) return U64_fromStr2;

            if(!isStatic && strcmp(signature, "set(_)") == 0)       return U64_set;
            if(!isStatic && strcmp(signature, "setI64(_)") == 0)    return U64_setI64;
            if(!isStatic && strcmp(signature, "setStr(_)") == 0)    return U64_setStr;
            if(!isStatic && strcmp(signature, "setStr(_,_)") == 0)  return U64_setStr2;
            if(!isStatic && strcmp(signature, "toNum") == 0)        return U64_toNum;
            if(!isStatic && strcmp(signature, "toI64") == 0)        return U64_toI64;
            if(!isStatic && strcmp(signature, "toString") == 0)     return U64_toString;

            if(!isStatic && strcmp(signature, "add(_,_)") == 0)     return U64_add2;
            if(!isStatic && strcmp(signature, "sub(_,_)") == 0)     return U64_sub2;
            if(!isStatic && strcmp(signature, "mul(_,_)") == 0)     return U64_mul2;
            if(!isStatic && strcmp(signature, "addMul(_,_)") == 0)  return U64_addMul2;
            if(!isStatic && strcmp(signature, "subMul(_,_)") == 0)  return U64_subMul2;
            if(!isStatic && strcmp(signature, "div(_,_)") == 0)     return U64_div2;
            if(!isStatic && strcmp(signature, "rem(_,_)") == 0)     return U64_rem2;
            if(!isStatic && strcmp(signature, "and(_,_)") == 0)     return U64_and2;
            if(!isStatic && strcmp(signature, "ior(_,_)") == 0)     return U64_ior2;
            if(!isStatic && strcmp(signature, "xor(_,_)") == 0)     return U64_xor2;
            if(!isStatic && strcmp(signature, "lsh(_,_)") == 0)     return U64_lsh2;
            if(!isStatic && strcmp(signature, "rsh(_,_)") == 0)     return U64_rsh2;
            if(!isStatic && strcmp(signature, "neg(_)") == 0)       return U64_neg;
            if(!isStatic && strcmp(signature, "inc(_)") == 0)       return U64_inc;
            if(!isStatic && strcmp(signature, "dec(_)") == 0)       return U64_dec;
            if(!isStatic && strcmp(signature, "com(_)") == 0)       return U64_com;

            if(!isStatic && strcmp(signature, "add(_)") == 0)       return U64_add;
            if(!isStatic && strcmp(signature, "sub(_)") == 0)       return U64_sub;
            if(!isStatic && strcmp(signature, "mul(_)") == 0)       return U64_mul;
            if(!isStatic && strcmp(signature, "addMul(_)") == 0)    return U64_addMul;
            if(!isStatic && strcmp(signature, "subMul(_)") == 0)    return U64_subMul;
            if(!isStatic && strcmp(signature, "div(_)") == 0)       return U64_div;
            if(!isStatic && strcmp(signature, "rem(_)") == 0)       return U64_rem;
            if(!isStatic && strcmp(signature, "and(_)") == 0)       return U64_and;
            if(!isStatic && strcmp(signature, "ior(_)") == 0)       return U64_ior;
            if(!isStatic && strcmp(signature, "xor(_)") == 0)       return U64_xor;
            if(!isStatic && strcmp(signature, "lsh(_)") == 0)       return U64_lsh;
            if(!isStatic && strcmp(signature, "rsh(_)") == 0)       return U64_rsh;
            if(!isStatic && strcmp(signature, "neg") == 0)          return U64_neg0;
            if(!isStatic && strcmp(signature, "inc") == 0)          return U64_inc0;
            if(!isStatic && strcmp(signature, "dec") == 0)          return U64_dec0;
            if(!isStatic && strcmp(signature, "com") == 0)          return U64_com0;

            if(!isStatic && strcmp(signature, "compare(_)") == 0)   return U64_compare;
            if(!isStatic && strcmp(signature, "cmpI64(_)") == 0)    return U64_cmpI64;
        }
    }
    return NULL;
}

static void writeFn(WrenVM* vm, const char* text) {
    printf("%s", text);
}

void errorFn(WrenVM* vm, WrenErrorType errorType, const char* module, const int line, const char* msg) {
    switch (errorType) {
        case WREN_ERROR_COMPILE:
            printf("[%s line %d] [Error] %s\n", module, line, msg);
            break;
        case WREN_ERROR_STACK_TRACE:
            printf("[%s line %d] in %s\n", module, line, msg);
            break;
        case WREN_ERROR_RUNTIME:
            printf("[Runtime Error] %s\n", msg);
            break;
    }
}

char *readFile(const char *fileName) {
    FILE *f = fopen(fileName, "r");
    fseek(f, 0, SEEK_END);
    long fsize = ftell(f);
    rewind(f);
    char *script = malloc(fsize + 1);
    size_t ret = fread(script, 1, fsize, f);
    if (ret != fsize) printf("Error reading %s\n", fileName);
    fclose(f);
    script[fsize] = 0;
    return script;
}

static void loadModuleComplete(WrenVM* vm, const char* module, WrenLoadModuleResult result) {
    if( result.source) free((void*)result.source);
}

WrenLoadModuleResult loadModule(WrenVM* vm, const char* name) {
    WrenLoadModuleResult result = {0};
    if (strcmp(name, "random") != 0 && strcmp(name, "meta") != 0) {
        result.onComplete = loadModuleComplete;
        char fullName[strlen(name) + 6];
        strcpy(fullName, name);
        strcat(fullName, ".wren");
        result.source = readFile(fullName);
    }
    return result;
}

int main(int argc, char **argv) {
    if (argc != 2) {
        printf("Please pass the name of the Wren file to be executed.\n");
        return 1;
    }
    WrenConfiguration config;
    wrenInitConfiguration(&config);
    config.writeFn = &writeFn;
    config.errorFn = &errorFn;
    config.bindForeignClassFn = &bindForeignClass;
    config.bindForeignMethodFn = &bindForeignMethod;
    config.loadModuleFn = &loadModule;
    WrenVM* vm = wrenNewVM(&config);
    const char* module = "main";
    const char* fileName = argv[1];
    char *script = readFile(fileName);
    WrenInterpretResult result = wrenInterpret(vm, module, script);
    switch (result) {
        case WREN_RESULT_COMPILE_ERROR:
            printf("Compile Error!\n");
            break;
        case WREN_RESULT_RUNTIME_ERROR:
            printf("Runtime Error!\n");
            break;
        case WREN_RESULT_SUCCESS:
            break;
    }
    wrenFreeVM(vm);
    free(script);
    return 0;
}