/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=8 sts=4 et sw=4 tw=99:
 */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "jit/RegisterSets.h"

#include "jsapi-tests/tests.h"

using namespace js;
using namespace js::jit;

static bool
CoPrime(size_t a, size_t b)
{
    if (b <= 1)
        return a == 1 || b == 1;
    return CoPrime(b, a % b);
}

// This macros are use to iterave over all registers in a large number of
// non-looping sequences, which does not rely on the getFirst / getLast
// functions.
#define BEGIN_INDEX_WALK(RegTotal)                          \
    static const size_t Total = RegTotal;                   \
    for (size_t walk = 1; walk < RegTotal; walk += 2) {     \
        if (!CoPrime(RegTotal, walk))                       \
            continue;                                       \
        for (size_t start = 0; start < RegTotal; start++) { \
            size_t index = start;

#define END_INDEX_WALK                                      \
        }                                                   \
    }

#define BEGIN_All_WALK(RegTotal)                            \
    static const size_t Total = RegTotal;                   \
    size_t walk = 1;                                        \
    size_t start = 0;                                       \
    size_t index = start;

#define FOR_ALL_REGISTERS(Register, reg)            \
    do {                                            \
        Register reg = Register::FromCode(index);

#define END_FOR_ALL_REGISTERS                    \
        index = (index + walk) % Total;          \
    } while(index != start)

BEGIN_TEST(testJitRegisterSet_GPR)
{
    BEGIN_INDEX_WALK(Registers::Total)

    LiveGeneralRegisterSet liveRegs;
    AllocatableGeneralRegisterSet pool(GeneralRegisterSet::All());
    CHECK(liveRegs.empty());
    CHECK(pool.set() == GeneralRegisterSet::All());

    FOR_ALL_REGISTERS(Register, reg) {

        CHECK(!pool.has(reg) || !liveRegs.has(reg));
        if (pool.has(reg)) {
            CHECK(!liveRegs.has(reg));
            pool.take(reg);
            liveRegs.add(reg);
            CHECK(liveRegs.has(reg));
            CHECK(!pool.has(reg));
        }
        CHECK(!pool.has(reg) || !liveRegs.has(reg));

    } END_FOR_ALL_REGISTERS;

    CHECK(pool.empty());

    FOR_ALL_REGISTERS(Register, reg) {

        CHECK(!pool.has(reg) || !liveRegs.has(reg));
        if (liveRegs.has(reg)) {
            CHECK(!pool.has(reg));
            liveRegs.take(reg);
            pool.add(reg);
            CHECK(pool.has(reg));
            CHECK(!liveRegs.has(reg));
        }
        CHECK(!pool.has(reg) || !liveRegs.has(reg));

    } END_FOR_ALL_REGISTERS;

    CHECK(liveRegs.empty());
    CHECK(pool.set() == GeneralRegisterSet::All());

    END_INDEX_WALK
    return true;
}
END_TEST(testJitRegisterSet_GPR)

BEGIN_TEST(testJitRegisterSet_FPU)
{
    BEGIN_INDEX_WALK(FloatRegisters::Total)

    LiveFloatRegisterSet liveRegs;
    AllocatableFloatRegisterSet pool(FloatRegisterSet::All());
    CHECK(liveRegs.empty());
    CHECK(pool.set() == FloatRegisterSet::All());

    FOR_ALL_REGISTERS(FloatRegister, reg) {

        CHECK(!pool.has(reg) || !liveRegs.has(reg));
        if (pool.has(reg)) {
            CHECK(!liveRegs.has(reg));
            pool.take(reg);
            liveRegs.add(reg);
            CHECK(liveRegs.has(reg));
            CHECK(!pool.has(reg));
        }
        CHECK(!pool.has(reg) || !liveRegs.has(reg));

    } END_FOR_ALL_REGISTERS;

    CHECK(pool.empty());

    FOR_ALL_REGISTERS(FloatRegister, reg) {

        CHECK(!pool.has(reg) || !liveRegs.has(reg));
        if (liveRegs.has(reg)) {
            CHECK(!pool.has(reg));
            liveRegs.take(reg);
            pool.add(reg);
            CHECK(pool.has(reg));
            CHECK(!liveRegs.has(reg));
        }
        CHECK(!pool.has(reg) || !liveRegs.has(reg));

    } END_FOR_ALL_REGISTERS;

    CHECK(liveRegs.empty());
    CHECK(pool.set() == FloatRegisterSet::All());

    END_INDEX_WALK
    return true;
}
END_TEST(testJitRegisterSet_FPU)

void pullAllFpus(AllocatableFloatRegisterSet& set, uint32_t& max_bits, uint32_t bits) {
    FloatRegisterSet allocSet(set.bits());
    FloatRegisterSet available_f32(allocSet.allAllocatable<RegTypeName::Float32>());
    FloatRegisterSet available_f64(allocSet.allAllocatable<RegTypeName::Float64>());
    FloatRegisterSet available_v128(allocSet.allAllocatable<RegTypeName::Vector128>());
    for (FloatRegisterIterator it(available_f32); it.more(); ++it) {
        FloatRegister tmp = *it;
        set.take(tmp);
        pullAllFpus(set, max_bits, bits + 32);
        set.add(tmp);
    }
    for (FloatRegisterIterator it(available_f64); it.more(); ++it) {
        FloatRegister tmp = *it;
        set.take(tmp);
        pullAllFpus(set, max_bits, bits + 64);
        set.add(tmp);
    }
    for (FloatRegisterIterator it(available_v128); it.more(); ++it) {
        FloatRegister tmp = *it;
        set.take(tmp);
        pullAllFpus(set, max_bits, bits + 128);
        set.add(tmp);
    }
    if (bits >= max_bits)
        max_bits = bits;
}

BEGIN_TEST(testJitRegisterSet_FPU_Aliases)
{
    BEGIN_All_WALK(FloatRegisters::Total);
    FOR_ALL_REGISTERS(FloatRegister, reg) {
        AllocatableFloatRegisterSet pool;
        pool.add(reg);

        uint32_t alias_bits = 0;
        for (uint32_t i = 0; i < reg.numAlignedAliased(); i++) {
            FloatRegister alias;
            reg.alignedAliased(i, &alias);

            if (alias.isSingle()) {
                if (alias_bits <= 32)
                    alias_bits = 32;
            } else if (alias.isDouble()) {
                if (alias_bits <= 64)
                    alias_bits = 64;
            } else if (alias.isSimd128()) {
                if (alias_bits <= 128)
                    alias_bits = 128;
            }
        }

        uint32_t max_bits = 0;
        pullAllFpus(pool, max_bits, 0);

        // By adding one register, we expect that we should not be able to pull
        // more than any of its aligned aliases.  This rule should hold for both
        // x64 and ARM.
        CHECK(max_bits <= alias_bits);

        // We added one register, we expect to be able to pull it back.
        CHECK(max_bits > 0);
    } END_FOR_ALL_REGISTERS;

    return true;
}
END_TEST(testJitRegisterSet_FPU_Aliases)
