/** @file
 * IPRT - CPU Set.
 */

/*
 * Copyright (C) 2008-2023 Oracle and/or its affiliates.
 *
 * This file is part of VirtualBox base platform packages, as
 * available from https://www.virtualbox.org.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation, in version 3 of the
 * License.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <https://www.gnu.org/licenses>.
 *
 * The contents of this file may alternatively be used under the terms
 * of the Common Development and Distribution License Version 1.0
 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
 * in the VirtualBox distribution, in which case the provisions of the
 * CDDL are applicable instead of those of the GPL.
 *
 * You may elect to license modified versions of this file under the
 * terms and conditions of either the GPL or the CDDL or both.
 *
 * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
 */

#ifndef IPRT_INCLUDED_cpuset_h
#define IPRT_INCLUDED_cpuset_h
#ifndef RT_WITHOUT_PRAGMA_ONCE
# pragma once
#endif

#include <iprt/types.h>
#include <iprt/mp.h> /* RTMpCpuIdToSetIndex */
#include <iprt/asm.h>


RT_C_DECLS_BEGIN

/** @defgroup grp_rt_cpuset RTCpuSet - CPU Set
 * @ingroup grp_rt
 * @{
 */


/**
 * Clear all CPUs.
 *
 * @returns pSet.
 * @param   pSet    Pointer to the set.
 */
DECLINLINE(PRTCPUSET) RTCpuSetEmpty(PRTCPUSET pSet)
{
    size_t i;
    for (i = 0; i < RT_ELEMENTS(pSet->bmSet); i++)
        pSet->bmSet[i] = 0;
    return pSet;
}


/**
 * Set all CPUs.
 *
 * @returns pSet.
 * @param   pSet    Pointer to the set.
 */
DECLINLINE(PRTCPUSET) RTCpuSetFill(PRTCPUSET pSet)
{
    size_t i;
    for (i = 0; i < RT_ELEMENTS(pSet->bmSet); i++)
        pSet->bmSet[i] = UINT64_MAX;
    return pSet;
}


/**
 * Copies one set to another.
 *
 * @param   pDst    Pointer to the destination set.
 * @param   pSrc    Pointer to the source set.
 */
DECLINLINE(void) RTCpuSetCopy(PRTCPUSET pDst, PRTCPUSET pSrc)
{
    size_t i;
    for (i = 0; i < RT_ELEMENTS(pDst->bmSet); i++)
        pDst->bmSet[i] = pSrc->bmSet[i];
}


/**
 * ANDs the given CPU set with another.
 *
 * @returns pSet.
 * @param   pSet          Pointer to the set.
 * @param   pAndMaskSet   Pointer to the AND-mask set.
 */
DECLINLINE(PRTCPUSET) RTCpuSetAnd(PRTCPUSET pSet, PRTCPUSET pAndMaskSet)
{
    size_t i;
    for (i = 0; i < RT_ELEMENTS(pSet->bmSet); i++)
        ASMAtomicAndU64((volatile uint64_t *)&pSet->bmSet[i], pAndMaskSet->bmSet[i]);
    return pSet;
}


/**
 * Adds a CPU given by its identifier to the set.
 *
 * @returns 0 on success, -1 if idCpu isn't valid.
 * @param   pSet    Pointer to the set.
 * @param   idCpu   The identifier of the CPU to add.
 * @remarks The modification is atomic.
 */
DECLINLINE(int) RTCpuSetAdd(PRTCPUSET pSet, RTCPUID idCpu)
{
    int iCpu = RTMpCpuIdToSetIndex(idCpu);
    if (RT_LIKELY(iCpu >= 0))
    {
        ASMAtomicBitSet(pSet, iCpu);
        return 0;
    }
    return -1;
}


/**
 * Adds a CPU given by its identifier to the set.
 *
 * @returns 0 on success, -1 if iCpu isn't valid.
 * @param   pSet    Pointer to the set.
 * @param   iCpu    The index of the CPU to add.
 * @remarks The modification is atomic.
 */
DECLINLINE(int) RTCpuSetAddByIndex(PRTCPUSET pSet, int iCpu)
{
    if (RT_LIKELY((unsigned)iCpu < RTCPUSET_MAX_CPUS))
    {
        ASMAtomicBitSet(pSet, iCpu);
        return 0;
    }
    return -1;
}


/**
 * Removes a CPU given by its identifier from the set.
 *
 * @returns 0 on success, -1 if idCpu isn't valid.
 * @param   pSet    Pointer to the set.
 * @param   idCpu   The identifier of the CPU to delete.
 * @remarks The modification is atomic.
 */
DECLINLINE(int) RTCpuSetDel(PRTCPUSET pSet, RTCPUID idCpu)
{
    int iCpu = RTMpCpuIdToSetIndex(idCpu);
    if (RT_LIKELY(iCpu >= 0))
    {
        ASMAtomicBitClear(pSet, iCpu);
        return 0;
    }
    return -1;
}


/**
 * Removes a CPU given by its index from the set.
 *
 * @returns 0 on success, -1 if iCpu isn't valid.
 * @param   pSet    Pointer to the set.
 * @param   iCpu    The index of the CPU to delete.
 * @remarks The modification is atomic.
 */
DECLINLINE(int) RTCpuSetDelByIndex(PRTCPUSET pSet, int iCpu)
{
    if (RT_LIKELY((unsigned)iCpu < RTCPUSET_MAX_CPUS))
    {
        ASMAtomicBitClear(pSet, iCpu);
        return 0;
    }
    return -1;
}


/**
 * Checks if a CPU given by its identifier is a member of the set.
 *
 * @returns true / false accordingly.
 * @param   pSet    Pointer to the set.
 * @param   idCpu   The identifier of the CPU to look for.
 * @remarks The test is atomic.
 */
DECLINLINE(bool) RTCpuSetIsMember(PCRTCPUSET pSet, RTCPUID idCpu)
{
    int iCpu = RTMpCpuIdToSetIndex(idCpu);
    if (RT_LIKELY(iCpu >= 0))
        return ASMBitTest((volatile void *)pSet, iCpu);
    return false;
}


/**
 * Checks if a CPU given by its index is a member of the set.
 *
 * @returns true / false accordingly.
 * @param   pSet    Pointer to the set.
 * @param   iCpu    The index of the CPU in the set.
 * @remarks The test is atomic.
 */
DECLINLINE(bool) RTCpuSetIsMemberByIndex(PCRTCPUSET pSet, int iCpu)
{
    if (RT_LIKELY((unsigned)iCpu < RTCPUSET_MAX_CPUS))
        return ASMBitTest((volatile void *)pSet, iCpu);
    return false;
}


/**
 * Checks if the two sets match or not.
 *
 * @returns true / false accordingly.
 * @param   pSet1       The first set.
 * @param   pSet2       The second set.
 */
DECLINLINE(bool) RTCpuSetIsEqual(PCRTCPUSET pSet1, PCRTCPUSET pSet2)
{
    size_t i;
    for (i = 0; i < RT_ELEMENTS(pSet1->bmSet); i++)
        if (pSet1->bmSet[i] != pSet2->bmSet[i])
            return false;
    return true;
}


/**
 * Checks if the CPU set is empty or not.
 *
 * @returns true / false accordingly.
 * @param   pSet    Pointer to the set.
 */
DECLINLINE(bool) RTCpuSetIsEmpty(PRTCPUSET pSet)
{
    size_t i;
    for (i = 0; i < RT_ELEMENTS(pSet->bmSet); i++)
        if (pSet->bmSet[i])
            return false;
    return true;
}


/**
 * Converts the CPU set to a 64-bit mask.
 *
 * @returns The mask.
 * @param   pSet    Pointer to the set.
 * @remarks Use with extreme care as it may lose information!
 */
DECLINLINE(uint64_t) RTCpuSetToU64(PCRTCPUSET pSet)
{
    return pSet->bmSet[0];
}


/**
 * Initializes the CPU set from a 64-bit mask.
 *
 * @param   pSet    Pointer to the set.
 * @param   fMask   The mask.
 */
DECLINLINE(PRTCPUSET) RTCpuSetFromU64(PRTCPUSET pSet, uint64_t fMask)
{
    size_t i;

    pSet->bmSet[0] = fMask;
    for (i = 1; i < RT_ELEMENTS(pSet->bmSet); i++)
        pSet->bmSet[i] = 0;

    return pSet;
}


/**
 * Count the CPUs in the set.
 *
 * @returns CPU count.
 * @param   pSet    Pointer to the set.
 */
DECLINLINE(int) RTCpuSetCount(PCRTCPUSET pSet)
{
    int         cCpus = 0;
    size_t      i;

    for (i = 0; i < RT_ELEMENTS(pSet->bmSet); i++)
    {
        uint64_t u64 = pSet->bmSet[i];
        if (u64 != 0)
        {
            unsigned iCpu = 64;
            while (iCpu-- > 0)
            {
                if (u64 & 1)
                    cCpus++;
                u64 >>= 1;
            }
        }
    }
    return cCpus;
}


/**
 * Get the highest set index.
 *
 * @returns The higest set index, -1 if all bits are clear.
 * @param   pSet    Pointer to the set.
 */
DECLINLINE(int) RTCpuLastIndex(PCRTCPUSET pSet)
{
    size_t i = RT_ELEMENTS(pSet->bmSet);
    while (i-- > 0)
    {
        uint64_t u64 = pSet->bmSet[i];
        if (u64)
        {
            /* There are more efficient ways to do this in asm.h... */
            unsigned iBit;
            for (iBit = 63; iBit > 0; iBit--)
            {
                if (u64 & RT_BIT_64(63))
                    break;
                u64 <<= 1;
            }
            return (int)i * 64 + (int)iBit;
        }
    }
    return 0;
}


/** @} */

RT_C_DECLS_END

#endif /* !IPRT_INCLUDED_cpuset_h */

