const builtin = @import("builtin");
const is_test = builtin.is_test;

comptime {
    const linkage = if (is_test) builtin.GlobalLinkage.Internal else builtin.GlobalLinkage.Weak;
    const strong_linkage = if (is_test) builtin.GlobalLinkage.Internal else builtin.GlobalLinkage.Strong;

    @export("__letf2", @import("comparetf2.zig").__letf2, linkage);
    @export("__getf2", @import("comparetf2.zig").__getf2, linkage);

    if (!is_test) {
        // only create these aliases when not testing
        @export("__cmptf2", @import("comparetf2.zig").__letf2, linkage);
        @export("__eqtf2", @import("comparetf2.zig").__letf2, linkage);
        @export("__lttf2", @import("comparetf2.zig").__letf2, linkage);
        @export("__netf2", @import("comparetf2.zig").__letf2, linkage);
        @export("__gttf2", @import("comparetf2.zig").__getf2, linkage);
    }

    @export("__unordtf2", @import("comparetf2.zig").__unordtf2, linkage);

    @export("__fixunssfsi", @import("fixunssfsi.zig").__fixunssfsi, linkage);
    @export("__fixunssfdi", @import("fixunssfdi.zig").__fixunssfdi, linkage);
    @export("__fixunssfti", @import("fixunssfti.zig").__fixunssfti, linkage);

    @export("__fixunsdfsi", @import("fixunsdfsi.zig").__fixunsdfsi, linkage);
    @export("__fixunsdfdi", @import("fixunsdfdi.zig").__fixunsdfdi, linkage);
    @export("__fixunsdfti", @import("fixunsdfti.zig").__fixunsdfti, linkage);

    @export("__fixunstfsi", @import("fixunstfsi.zig").__fixunstfsi, linkage);
    @export("__fixunstfdi", @import("fixunstfdi.zig").__fixunstfdi, linkage);
    @export("__fixunstfti", @import("fixunstfti.zig").__fixunstfti, linkage);

    @export("__udivmoddi4", @import("udivmoddi4.zig").__udivmoddi4, linkage);
    @export("__udivmodti4", @import("udivmodti4.zig").__udivmodti4, linkage);

    @export("__udivti3", @import("udivti3.zig").__udivti3, linkage);
    @export("__umodti3", @import("umodti3.zig").__umodti3, linkage);

    @export("__udivsi3", __udivsi3, linkage);
    @export("__udivdi3", __udivdi3, linkage);
    @export("__umoddi3", __umoddi3, linkage);
    @export("__udivmodsi4", __udivmodsi4, linkage);

    if (isArmArch()) {
        @export("__aeabi_uldivmod", __aeabi_uldivmod, linkage);
        @export("__aeabi_uidivmod", __aeabi_uidivmod, linkage);
        @export("__aeabi_uidiv", __udivsi3, linkage);
    }
    if (builtin.os == builtin.Os.windows) {
        switch (builtin.arch) {
            builtin.Arch.i386 => {
                if (!builtin.link_libc) {
                    @export("_chkstk", _chkstk, strong_linkage);
                    @export("__chkstk_ms", __chkstk_ms, linkage);
                }
                @export("_aulldiv", @import("aulldiv.zig")._aulldiv, strong_linkage);
                @export("_aullrem", @import("aullrem.zig")._aullrem, strong_linkage);
            },
            builtin.Arch.x86_64 => {
                if (!builtin.link_libc) {
                    @export("__chkstk", __chkstk, strong_linkage);
                    @export("___chkstk_ms", ___chkstk_ms, linkage);
                }
            },
            else => {},
        }
    }
}

const assert = @import("../../index.zig").debug.assert;

const __udivmoddi4 = @import("udivmoddi4.zig").__udivmoddi4;

// Avoid dragging in the runtime safety mechanisms into this .o file,
// unless we're trying to test this file.
pub fn panic(msg: []const u8, error_return_trace: ?&builtin.StackTrace) noreturn {
    @setCold(true);
    if (is_test) {
        @import("std").debug.panic("{}", msg);
    } else {
        unreachable;
    }
}

extern fn __udivdi3(a: u64, b: u64) u64 {
    @setRuntimeSafety(is_test);
    return __udivmoddi4(a, b, null);
}

extern fn __umoddi3(a: u64, b: u64) u64 {
    @setRuntimeSafety(is_test);

    var r: u64 = undefined;
    _ = __udivmoddi4(a, b, &r);
    return r;
}

const AeabiUlDivModResult = extern struct {
    quot: u64,
    rem: u64,
};
extern fn __aeabi_uldivmod(numerator: u64, denominator: u64) AeabiUlDivModResult {
    @setRuntimeSafety(is_test);
    var result: AeabiUlDivModResult = undefined;
    result.quot = __udivmoddi4(numerator, denominator, &result.rem);
    return result;
}

fn isArmArch() bool {
    return switch (builtin.arch) {
        builtin.Arch.armv8_2a,
        builtin.Arch.armv8_1a,
        builtin.Arch.armv8,
        builtin.Arch.armv8r,
        builtin.Arch.armv8m_baseline,
        builtin.Arch.armv8m_mainline,
        builtin.Arch.armv7,
        builtin.Arch.armv7em,
        builtin.Arch.armv7m,
        builtin.Arch.armv7s,
        builtin.Arch.armv7k,
        builtin.Arch.armv6,
        builtin.Arch.armv6m,
        builtin.Arch.armv6k,
        builtin.Arch.armv6t2,
        builtin.Arch.armv5,
        builtin.Arch.armv5te,
        builtin.Arch.armv4t,
        builtin.Arch.armebv8_2a,
        builtin.Arch.armebv8_1a,
        builtin.Arch.armebv8,
        builtin.Arch.armebv8r,
        builtin.Arch.armebv8m_baseline,
        builtin.Arch.armebv8m_mainline,
        builtin.Arch.armebv7,
        builtin.Arch.armebv7em,
        builtin.Arch.armebv7m,
        builtin.Arch.armebv7s,
        builtin.Arch.armebv7k,
        builtin.Arch.armebv6,
        builtin.Arch.armebv6m,
        builtin.Arch.armebv6k,
        builtin.Arch.armebv6t2,
        builtin.Arch.armebv5,
        builtin.Arch.armebv5te,
        builtin.Arch.armebv4t => true,
        else => false,
    };
}

nakedcc fn __aeabi_uidivmod() void {
    @setRuntimeSafety(false);
    asm volatile (
        \\ push    { lr }
        \\ sub     sp, sp, #4
        \\ mov     r2, sp
        \\ bl      __udivmodsi4
        \\ ldr     r1, [sp]
        \\ add     sp, sp, #4
        \\ pop     { pc }
    ::: "r2", "r1");
}

// _chkstk (_alloca) routine - probe stack between %esp and (%esp-%eax) in 4k increments,
// then decrement %esp by %eax.  Preserves all registers except %esp and flags.
// This routine is windows specific
// http://msdn.microsoft.com/en-us/library/ms648426.aspx
nakedcc fn _chkstk() align(4) void {
    @setRuntimeSafety(false);

    asm volatile (
        \\         push   %%ecx
        \\         push   %%eax
        \\         cmp    $0x1000,%%eax
        \\         lea    12(%%esp),%%ecx
        \\         jb     1f
        \\ 2:
        \\         sub    $0x1000,%%ecx
        \\         test   %%ecx,(%%ecx)
        \\         sub    $0x1000,%%eax
        \\         cmp    $0x1000,%%eax
        \\         ja     2b
        \\ 1:
        \\         sub    %%eax,%%ecx
        \\         test   %%ecx,(%%ecx)
        \\         pop    %%eax
        \\         pop    %%ecx
        \\         ret
    );
}

nakedcc fn __chkstk() align(4) void {
    @setRuntimeSafety(false);

    asm volatile (
        \\        push   %%rcx
        \\        push   %%rax
        \\        cmp    $0x1000,%%rax
        \\        lea    24(%%rsp),%%rcx
        \\        jb     1f
        \\2:
        \\        sub    $0x1000,%%rcx
        \\        test   %%rcx,(%%rcx)
        \\        sub    $0x1000,%%rax
        \\        cmp    $0x1000,%%rax
        \\        ja     2b
        \\1:
        \\        sub    %%rax,%%rcx
        \\        test   %%rcx,(%%rcx)
        \\        pop    %%rax
        \\        pop    %%rcx
        \\        ret
    );
}

// _chkstk routine
// This routine is windows specific
// http://msdn.microsoft.com/en-us/library/ms648426.aspx
nakedcc fn __chkstk_ms() align(4) void {
    @setRuntimeSafety(false);

    asm volatile (
        \\         push   %%ecx
        \\         push   %%eax
        \\         cmp    $0x1000,%%eax
        \\         lea    12(%%esp),%%ecx
        \\         jb     1f
        \\ 2:
        \\         sub    $0x1000,%%ecx
        \\         test   %%ecx,(%%ecx)
        \\         sub    $0x1000,%%eax
        \\         cmp    $0x1000,%%eax
        \\         ja     2b
        \\ 1:
        \\         sub    %%eax,%%ecx
        \\         test   %%ecx,(%%ecx)
        \\         pop    %%eax
        \\         pop    %%ecx
        \\         ret
    );
}

nakedcc fn ___chkstk_ms() align(4) void {
    @setRuntimeSafety(false);

    asm volatile (
        \\        push   %%rcx
        \\        push   %%rax
        \\        cmp    $0x1000,%%rax
        \\        lea    24(%%rsp),%%rcx
        \\        jb     1f
        \\2:
        \\        sub    $0x1000,%%rcx
        \\        test   %%rcx,(%%rcx)
        \\        sub    $0x1000,%%rax
        \\        cmp    $0x1000,%%rax
        \\        ja     2b
        \\1:
        \\        sub    %%rax,%%rcx
        \\        test   %%rcx,(%%rcx)
        \\        pop    %%rax
        \\        pop    %%rcx
        \\        ret
    );
}

extern fn __udivmodsi4(a: u32, b: u32, rem: &u32) u32 {
    @setRuntimeSafety(is_test);

    const d = __udivsi3(a, b);
    *rem = u32(i32(a) -% (i32(d) * i32(b)));
    return d;
}


extern fn __udivsi3(n: u32, d: u32) u32 {
    @setRuntimeSafety(is_test);

    const n_uword_bits: c_uint = u32.bit_count;
    // special cases
    if (d == 0)
        return 0; // ?!
    if (n == 0)
        return 0;
    var sr = @bitCast(c_uint, c_int(@clz(d)) - c_int(@clz(n)));
    // 0 <= sr <= n_uword_bits - 1 or sr large
    if (sr > n_uword_bits - 1)  // d > r
        return 0;
    if (sr == n_uword_bits - 1)  // d == 1
        return n;
    sr += 1;
    // 1 <= sr <= n_uword_bits - 1
    // Not a special case
    var q: u32 = n << u5(n_uword_bits - sr);
    var r: u32 = n >> u5(sr);
    var carry: u32 = 0;
    while (sr > 0) : (sr -= 1) {
        // r:q = ((r:q)  << 1) | carry
        r = (r << 1) | (q >> u5(n_uword_bits - 1));
        q = (q << 1) | carry;
        // carry = 0;
        // if (r.all >= d.all)
        // {
        //      r.all -= d.all;
        //      carry = 1;
        // }
        const s = i32(d -% r -% 1) >> u5(n_uword_bits - 1);
        carry = u32(s & 1);
        r -= d & @bitCast(u32, s);
    }
    q = (q << 1) | carry;
    return q;
}

test "test_umoddi3" {
    test_one_umoddi3(0, 1, 0);
    test_one_umoddi3(2, 1, 0);
    test_one_umoddi3(0x8000000000000000, 1, 0x0);
    test_one_umoddi3(0x8000000000000000, 2, 0x0);
    test_one_umoddi3(0xFFFFFFFFFFFFFFFF, 2, 0x1);
}

fn test_one_umoddi3(a: u64, b: u64, expected_r: u64) void {
    const r = __umoddi3(a, b);
    assert(r == expected_r);
}

test "test_udivsi3" {
    const cases = [][3]u32 {
        []u32{0x00000000, 0x00000001, 0x00000000},
        []u32{0x00000000, 0x00000002, 0x00000000},
        []u32{0x00000000, 0x00000003, 0x00000000},
        []u32{0x00000000, 0x00000010, 0x00000000},
        []u32{0x00000000, 0x078644FA, 0x00000000},
        []u32{0x00000000, 0x0747AE14, 0x00000000},
        []u32{0x00000000, 0x7FFFFFFF, 0x00000000},
        []u32{0x00000000, 0x80000000, 0x00000000},
        []u32{0x00000000, 0xFFFFFFFD, 0x00000000},
        []u32{0x00000000, 0xFFFFFFFE, 0x00000000},
        []u32{0x00000000, 0xFFFFFFFF, 0x00000000},
        []u32{0x00000001, 0x00000001, 0x00000001},
        []u32{0x00000001, 0x00000002, 0x00000000},
        []u32{0x00000001, 0x00000003, 0x00000000},
        []u32{0x00000001, 0x00000010, 0x00000000},
        []u32{0x00000001, 0x078644FA, 0x00000000},
        []u32{0x00000001, 0x0747AE14, 0x00000000},
        []u32{0x00000001, 0x7FFFFFFF, 0x00000000},
        []u32{0x00000001, 0x80000000, 0x00000000},
        []u32{0x00000001, 0xFFFFFFFD, 0x00000000},
        []u32{0x00000001, 0xFFFFFFFE, 0x00000000},
        []u32{0x00000001, 0xFFFFFFFF, 0x00000000},
        []u32{0x00000002, 0x00000001, 0x00000002},
        []u32{0x00000002, 0x00000002, 0x00000001},
        []u32{0x00000002, 0x00000003, 0x00000000},
        []u32{0x00000002, 0x00000010, 0x00000000},
        []u32{0x00000002, 0x078644FA, 0x00000000},
        []u32{0x00000002, 0x0747AE14, 0x00000000},
        []u32{0x00000002, 0x7FFFFFFF, 0x00000000},
        []u32{0x00000002, 0x80000000, 0x00000000},
        []u32{0x00000002, 0xFFFFFFFD, 0x00000000},
        []u32{0x00000002, 0xFFFFFFFE, 0x00000000},
        []u32{0x00000002, 0xFFFFFFFF, 0x00000000},
        []u32{0x00000003, 0x00000001, 0x00000003},
        []u32{0x00000003, 0x00000002, 0x00000001},
        []u32{0x00000003, 0x00000003, 0x00000001},
        []u32{0x00000003, 0x00000010, 0x00000000},
        []u32{0x00000003, 0x078644FA, 0x00000000},
        []u32{0x00000003, 0x0747AE14, 0x00000000},
        []u32{0x00000003, 0x7FFFFFFF, 0x00000000},
        []u32{0x00000003, 0x80000000, 0x00000000},
        []u32{0x00000003, 0xFFFFFFFD, 0x00000000},
        []u32{0x00000003, 0xFFFFFFFE, 0x00000000},
        []u32{0x00000003, 0xFFFFFFFF, 0x00000000},
        []u32{0x00000010, 0x00000001, 0x00000010},
        []u32{0x00000010, 0x00000002, 0x00000008},
        []u32{0x00000010, 0x00000003, 0x00000005},
        []u32{0x00000010, 0x00000010, 0x00000001},
        []u32{0x00000010, 0x078644FA, 0x00000000},
        []u32{0x00000010, 0x0747AE14, 0x00000000},
        []u32{0x00000010, 0x7FFFFFFF, 0x00000000},
        []u32{0x00000010, 0x80000000, 0x00000000},
        []u32{0x00000010, 0xFFFFFFFD, 0x00000000},
        []u32{0x00000010, 0xFFFFFFFE, 0x00000000},
        []u32{0x00000010, 0xFFFFFFFF, 0x00000000},
        []u32{0x078644FA, 0x00000001, 0x078644FA},
        []u32{0x078644FA, 0x00000002, 0x03C3227D},
        []u32{0x078644FA, 0x00000003, 0x028216FE},
        []u32{0x078644FA, 0x00000010, 0x0078644F},
        []u32{0x078644FA, 0x078644FA, 0x00000001},
        []u32{0x078644FA, 0x0747AE14, 0x00000001},
        []u32{0x078644FA, 0x7FFFFFFF, 0x00000000},
        []u32{0x078644FA, 0x80000000, 0x00000000},
        []u32{0x078644FA, 0xFFFFFFFD, 0x00000000},
        []u32{0x078644FA, 0xFFFFFFFE, 0x00000000},
        []u32{0x078644FA, 0xFFFFFFFF, 0x00000000},
        []u32{0x0747AE14, 0x00000001, 0x0747AE14},
        []u32{0x0747AE14, 0x00000002, 0x03A3D70A},
        []u32{0x0747AE14, 0x00000003, 0x026D3A06},
        []u32{0x0747AE14, 0x00000010, 0x00747AE1},
        []u32{0x0747AE14, 0x078644FA, 0x00000000},
        []u32{0x0747AE14, 0x0747AE14, 0x00000001},
        []u32{0x0747AE14, 0x7FFFFFFF, 0x00000000},
        []u32{0x0747AE14, 0x80000000, 0x00000000},
        []u32{0x0747AE14, 0xFFFFFFFD, 0x00000000},
        []u32{0x0747AE14, 0xFFFFFFFE, 0x00000000},
        []u32{0x0747AE14, 0xFFFFFFFF, 0x00000000},
        []u32{0x7FFFFFFF, 0x00000001, 0x7FFFFFFF},
        []u32{0x7FFFFFFF, 0x00000002, 0x3FFFFFFF},
        []u32{0x7FFFFFFF, 0x00000003, 0x2AAAAAAA},
        []u32{0x7FFFFFFF, 0x00000010, 0x07FFFFFF},
        []u32{0x7FFFFFFF, 0x078644FA, 0x00000011},
        []u32{0x7FFFFFFF, 0x0747AE14, 0x00000011},
        []u32{0x7FFFFFFF, 0x7FFFFFFF, 0x00000001},
        []u32{0x7FFFFFFF, 0x80000000, 0x00000000},
        []u32{0x7FFFFFFF, 0xFFFFFFFD, 0x00000000},
        []u32{0x7FFFFFFF, 0xFFFFFFFE, 0x00000000},
        []u32{0x7FFFFFFF, 0xFFFFFFFF, 0x00000000},
        []u32{0x80000000, 0x00000001, 0x80000000},
        []u32{0x80000000, 0x00000002, 0x40000000},
        []u32{0x80000000, 0x00000003, 0x2AAAAAAA},
        []u32{0x80000000, 0x00000010, 0x08000000},
        []u32{0x80000000, 0x078644FA, 0x00000011},
        []u32{0x80000000, 0x0747AE14, 0x00000011},
        []u32{0x80000000, 0x7FFFFFFF, 0x00000001},
        []u32{0x80000000, 0x80000000, 0x00000001},
        []u32{0x80000000, 0xFFFFFFFD, 0x00000000},
        []u32{0x80000000, 0xFFFFFFFE, 0x00000000},
        []u32{0x80000000, 0xFFFFFFFF, 0x00000000},
        []u32{0xFFFFFFFD, 0x00000001, 0xFFFFFFFD},
        []u32{0xFFFFFFFD, 0x00000002, 0x7FFFFFFE},
        []u32{0xFFFFFFFD, 0x00000003, 0x55555554},
        []u32{0xFFFFFFFD, 0x00000010, 0x0FFFFFFF},
        []u32{0xFFFFFFFD, 0x078644FA, 0x00000022},
        []u32{0xFFFFFFFD, 0x0747AE14, 0x00000023},
        []u32{0xFFFFFFFD, 0x7FFFFFFF, 0x00000001},
        []u32{0xFFFFFFFD, 0x80000000, 0x00000001},
        []u32{0xFFFFFFFD, 0xFFFFFFFD, 0x00000001},
        []u32{0xFFFFFFFD, 0xFFFFFFFE, 0x00000000},
        []u32{0xFFFFFFFD, 0xFFFFFFFF, 0x00000000},
        []u32{0xFFFFFFFE, 0x00000001, 0xFFFFFFFE},
        []u32{0xFFFFFFFE, 0x00000002, 0x7FFFFFFF},
        []u32{0xFFFFFFFE, 0x00000003, 0x55555554},
        []u32{0xFFFFFFFE, 0x00000010, 0x0FFFFFFF},
        []u32{0xFFFFFFFE, 0x078644FA, 0x00000022},
        []u32{0xFFFFFFFE, 0x0747AE14, 0x00000023},
        []u32{0xFFFFFFFE, 0x7FFFFFFF, 0x00000002},
        []u32{0xFFFFFFFE, 0x80000000, 0x00000001},
        []u32{0xFFFFFFFE, 0xFFFFFFFD, 0x00000001},
        []u32{0xFFFFFFFE, 0xFFFFFFFE, 0x00000001},
        []u32{0xFFFFFFFE, 0xFFFFFFFF, 0x00000000},
        []u32{0xFFFFFFFF, 0x00000001, 0xFFFFFFFF},
        []u32{0xFFFFFFFF, 0x00000002, 0x7FFFFFFF},
        []u32{0xFFFFFFFF, 0x00000003, 0x55555555},
        []u32{0xFFFFFFFF, 0x00000010, 0x0FFFFFFF},
        []u32{0xFFFFFFFF, 0x078644FA, 0x00000022},
        []u32{0xFFFFFFFF, 0x0747AE14, 0x00000023},
        []u32{0xFFFFFFFF, 0x7FFFFFFF, 0x00000002},
        []u32{0xFFFFFFFF, 0x80000000, 0x00000001},
        []u32{0xFFFFFFFF, 0xFFFFFFFD, 0x00000001},
        []u32{0xFFFFFFFF, 0xFFFFFFFE, 0x00000001},
        []u32{0xFFFFFFFF, 0xFFFFFFFF, 0x00000001},
    };

    for (cases) |case| {
        test_one_udivsi3(case[0], case[1], case[2]);
    }
}

fn test_one_udivsi3(a: u32, b: u32, expected_q: u32) void {
    const q: u32 = __udivsi3(a, b);
    assert(q == expected_q);
}
