luaur-vm 0.1.1

The Luau register virtual machine and standard library (Rust).
Documentation
use crate::functions::mul_192_hi::mul_192_hi;
use crate::functions::roundodd::roundodd;
use crate::records::decimal::Decimal;
use luaur_common::macros::luau_assert::LUAU_ASSERT;

const K_POW_10_TABLE_MIN: i32 = -292;
const K_POW_10_TABLE_MAX: i32 = 324;

const K_POW_5_TABLE: [u64; 16] = [
    0x8000000000000000,
    0xa000000000000000,
    0xc800000000000000,
    0xfa00000000000000,
    0x9c40000000000000,
    0xc350000000000000,
    0xf424000000000000,
    0x9896800000000000,
    0xbebc200000000000,
    0xee6b280000000000,
    0x9502f90000000000,
    0xba43b74000000000,
    0xe8d4a51000000000,
    0x9184e72a00000000,
    0xb5e620f480000000,
    0xe35fa931a0000000,
];

const K_POW_10_TABLE: [[u64; 3]; 39] = [
    [0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b, 0x333443443333443b],
    [0x8dd01fad907ffc3b, 0xae3da7d97f6792e4, 0xbbb3ab3cb3ba3cbc],
    [0x9d71ac8fada6c9b5, 0x6f773fc3603db4aa, 0x4ba4bc4bb4bb4bcc],
    [0xaecc49914078536d, 0x58fae9f773886e19, 0x3ba3bc33b43b43bb],
    [0xc21094364dfb5636, 0x985915fc12f542e5, 0x33b43b43a33b33cb],
    [0xd77485cb25823ac7, 0x7d633293366b828c, 0x34b44c444343443c],
    [0xef340a98172aace4, 0x86fb897116c87c35, 0x333343333343334b],
    [0x84c8d4dfd2c63f3b, 0x29ecd9f40041e074, 0xccaccbbcbcbb4bbc],
    [0x936b9fcebb25c995, 0xcab10dd900beec35, 0x3ab3ab3ab3bb3bbb],
    [0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebbb, 0x4cc3dc4db4db4dbb],
    [0xb5b5ada8aaff80b8, 0x0d819992132456bb, 0x33b33a34c33b34ab],
    [0xc9bcff6034c13052, 0xfc89b393dd02f0b6, 0x33c33b44b43c34bc],
    [0xdff9772470297ebd, 0x59787e2b93bc56f8, 0x43b444444443434c],
    [0xf8a95fcf88747d94, 0x75a44c6397ce912b, 0x443334343443343b],
    [0x8a08f0f8bf0f156b, 0x1b8e9ecb641b5900, 0xbbabab3aa3ab4ccc],
    [0x993fe2c6d07b7fab, 0xe546a8038efe402a, 0x4cb4bc4db4db4bcc],
    [0xaa242499697392d2, 0xdde50bd1d5d0b9ea, 0x3ba3ba3bb33b33bc],
    [0xbce5086492111aea, 0x88f4bb1ca6bcf585, 0x44b44c44c44c43cb],
    [0xd1b71758e219652b, 0xd3c36113404ea4a9, 0x44c44c44c444443b],
    [0xe8d4a51000000000, 0x0000000000000000, 0x444444444444444c],
    [0x813f3978f8940984, 0x4000000000000000, 0xcccccccccccccccc],
    [0x8f7e32ce7bea5c6f, 0xe4820023a2000000, 0xbba3bc4cc4cc4ccc],
    [0x9f4f2726179a2245, 0x01d762422c946591, 0x4aa3bb3aa3ba3bab],
    [0xb0de65388cc8ada8, 0x3b25a55f43294bcc, 0x3ca33b33b44b43bc],
    [0xc45d1df942711d9a, 0x3ba5d0bd324f8395, 0x44c44c34c44b44cb],
    [0xda01ee641a708de9, 0xe80e6f4820cc9496, 0x33b33b343333333c],
    [0xf209787bb47d6b84, 0xc0678c5dbd23a49b, 0x443444444443443b],
    [0x865b86925b9bc5c2, 0x0b8a2392ba45a9b3, 0xdbccbcccb4cb3bbb],
    [0x952ab45cfa97a0b2, 0xdd945a747bf26184, 0x3bc4bb4ab3ca3cbc],
    [0xa59bc234db398c25, 0x43fab9837e699096, 0x3bb3ac3ab3bb33ac],
    [0xb7dcbf5354e9bece, 0x0c11ed6d538aeb30, 0x33b43b43b34c34dc],
    [0xcc20ce9bd35c78a5, 0x31ec038df7b441f5, 0x34c44c43c44b44cb],
    [0xe2a0b5dc971f303a, 0x2e44ae64840fd61e, 0x333333333333333c],
    [0xfb9b7cd9a4a7443c, 0x169840ef017da3b2, 0x433344443333344c],
    [0x8bab8eefb6409c1a, 0x1ad089b6c2f7548f, 0xdcbdcc3cc4cc4bcb],
    [0x9b10a4e5e9913128, 0xca7cf2b4191c8327, 0x3ab3cb3bc3bb4bbb],
    [0xac2820d9623bf429, 0x546345fa9fbdcd45, 0x3bb3cc43c43c43cb],
    [0xbf21e44003acdd2c, 0xe0470a63e6bd56c4, 0x44b34a43b44c44bc],
    [0xd433179d9c8cb841, 0x5fa60692a46151ec, 0x43a33a33a333333c],
];

#[allow(non_snake_case)]
pub fn schubfach(exponent: i32, fraction: u64) -> Decimal {
    // Extract c & q such that c*2^q == |v|
    let mut c: u64 = fraction;
    let mut q: i32 = exponent - 1023 - 51;

    if exponent != 0 {
        // normal numbers have implicit leading 1
        c |= 1u64 << 52;
        q -= 1;
    }

    // 8.3. Fast path for integers
    if (-(q as i64) as u64) < 53 && (c & ((1u64 << (-(q as i64) as u32)) - 1)) == 0 {
        return Decimal {
            s: c >> (-(q as i64) as u32),
            k: 0,
        };
    }

    // 5. Rounding interval
    let irr: i32 = if c == (1u64 << 52) && q != -1074 {
        1
    } else {
        0
    };
    let out: i32 = (c & 1) as i32;

    // 9.8.1. Boundaries for c
    let cbl: u64 = 4u64
        .wrapping_mul(c)
        .wrapping_sub(2)
        .wrapping_add(irr as u64);
    let cb: u64 = 4u64.wrapping_mul(c);
    let cbr: u64 = 4u64.wrapping_mul(c).wrapping_add(2);

    // 9.1. Computing k and h
    const Q: i32 = 20;
    const C: i32 = 315652; // floor(2^Q * log10(2))
    const A: i32 = -131008; // floor(2^Q * log10(3/4))
    const C2: i32 = 3483294; // floor(2^Q * log2(10))

    let k: i32 = ((q * C + if irr != 0 { A } else { 0 }) >> Q) as i32;
    let h: i32 = q + ((-k * C2) >> Q) + 1; // see (9) in 9.9

    // 9.8.2. Overestimates of powers of 10
    // Recover 10^-k fraction using compact tables generated by tools/numutils.py
    // The 128-bit fraction is encoded as 128-bit baseline * power-of-5 * scale + offset
    LUAU_ASSERT!(-k >= K_POW_10_TABLE_MIN && -k <= K_POW_10_TABLE_MAX);

    let gtoff: i32 = -k - K_POW_10_TABLE_MIN;
    let gt_index = (gtoff >> 4) as usize;
    let gt = &K_POW_10_TABLE[gt_index];

    let mut ghi: u64 = 0;
    let glo: u64 = mul_192_hi(gt[0], gt[1], K_POW_5_TABLE[(gtoff & 15) as usize], &mut ghi);

    // Apply 1-bit scale + 3-bit offset; note, offset is intentionally applied without carry
    let gterr: i32 = ((gt[2] >> (((gtoff & 15) as u32) * 4)) & 15) as i32;
    let gtscale: i32 = gterr >> 3;

    let mut ghi_shifted = ghi.wrapping_shl(gtscale as u32);
    ghi_shifted = ghi_shifted.wrapping_add(((glo >> 63) & (gtscale as u64)) as u64);

    let mut glo_shifted = glo.wrapping_shl(gtscale as u32);
    glo_shifted = glo_shifted.wrapping_sub(((gterr & 7) - 4) as u64);

    let vbl: u64 = roundodd(ghi_shifted, glo_shifted, cbl << h);
    let vb: u64 = roundodd(ghi_shifted, glo_shifted, cb << h);
    let vbr: u64 = roundodd(ghi_shifted, glo_shifted, cbr << h);

    // Main algorithm; see figure 7 + figure 9
    let s: u64 = vb / 4;

    if s >= 10 {
        let sp: u64 = s / 10;

        let upin: bool = vbl + (out as u64) <= 40u64.wrapping_mul(sp);
        let wpin: bool = vbr >= 40u64.wrapping_mul(sp) + 40u64 + (out as u64);

        if upin != wpin {
            return Decimal {
                s: sp + if wpin { 1 } else { 0 },
                k: k + 1,
            };
        }
    }

    // Figure 7 contains the algorithm to select between u (s) and w (s+1)
    let uin: bool = vbl + (out as u64) <= 4u64.wrapping_mul(s);
    let win: bool = 4u64.wrapping_mul(s) + 4u64 + (out as u64) <= vbr;
    let rup: bool = vb >= 4u64.wrapping_mul(s) + 2u64 + 1u64 - ((s & 1) as u64);

    Decimal {
        s: s + if uin != win {
            if win {
                1
            } else {
                0
            }
        } else {
            if rup {
                1
            } else {
                0
            }
        },
        k,
    }
}