// GNU D Compiler SIMD support functions and intrinsics.
// Copyright (C) 2022-2024 Free Software Foundation, Inc.

// GCC 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; either version 3, or (at your option) any later
// version.

// GCC 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.

// Under Section 7 of GPL version 3, you are granted additional
// permissions described in the GCC Runtime Library Exception, version
// 3.1, as published by the Free Software Foundation.

// You should have received a copy of the GNU General Public License and
// a copy of the GCC Runtime Library Exception along with this program;
// see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
// <http://www.gnu.org/licenses/>.

module gcc.simd;

pure:
nothrow:
@safe:
@nogc:
pragma(inline, true):

/**
* Emit prefetch instruction.
* Params:
*    address = address to be prefetched
*    writeFetch = true for write fetch, false for read fetch
*    locality = 0..3 (0 meaning least local, 3 meaning most local)
*/
void prefetch(bool writeFetch, ubyte locality)(const(void)* address)
{
    static assert(locality < 4, "0..3 expected for locality");
    import gcc.builtins : __builtin_prefetch;
    __builtin_prefetch(address, writeFetch, locality);
}

/**
 * Load unaligned vector from address.
 * This is a compiler intrinsic.
 * Params:
 *    p = pointer to vector
 * Returns:
 *    vector
 */
V loadUnaligned(V)(const V* p) if (isVectorType!V);

/**
 * Store vector to unaligned address.
 * This is a compiler intrinsic.
 * Params:
 *    p = pointer to vector
 *    value = value to store
 * Returns:
 *    value
 */
V storeUnaligned(V)(V* p, V value) if (isVectorType!V);

/**
 * Construct a permutation of elements from one or two vectors, returning a
 * vector of the same type as the input vector(s). The `mask` is an integral
 * vector with the same width and element count as the output vector.
 * Params:
 *    op1 = input vector
 *    op2 = input vector
 *    mask = integer vector mask
 * Returns:
 *    vector with the same type as `op1` and `op2`
 * Example:
 * ---
 * int4 a = [1, 2, 3, 4];
 * int4 b = [5, 6, 7, 8];
 * int4 mask1 = [0, 1, 1, 3];
 * int4 mask2 = [0, 4, 2, 5];
 * assert(shuffle(a, mask1).array == [1, 2, 2, 4]);
 * assert(shuffle(a, b, mask2).array == [1, 5, 3, 6]);
 * ---
 */
template shuffle(V0, V1, M)
{
    static assert(isVectorType!V0, "first argument must be vector");
    static assert(isVectorType!V1, "second argument must be vector");
    static assert(is(BaseType!V0 == BaseType!V1),
                  "first and second argument vectors must have the same element type");
    static assert(isVectorType!M && is(BaseType!M : long),
                  "last argument must be an integer vector");
    static assert(numElements!V0 == numElements!M && numElements!V1 == numElements!M,
                  "argument vectors and mask vector should have the same number of elements");
    static assert(BaseType!V0.sizeof == BaseType!M.sizeof,
                  "argument vectors and mask vector should have the same element type size");

    V0 shuffle(V0 op1, V1 op2, M mask);
}

/// Ditto
template shuffle(V, M)
{
    static assert(isVectorType!V, "first argument must be a vector");
    static assert(isVectorType!M && is(BaseType!M : long),
                  "last argument must be an integer vector");
    static assert(numElements!V == numElements!M,
                  "argument vector and mask vector should have the same number of elements");
    static assert(BaseType!V.sizeof == BaseType!M.sizeof,
                  "argument vector and mask vector should have the same element type size");

    V shuffle(V op1, M mask)
    {
        return shuffle(op1, op1, mask);
    }
}

/**
 * Construct a permutation of elements from two vectors, returning a vector with
 * the same element type as the input vector(s), and same length as the `mask`.
 * Params:
 *    op1 = input vector
 *    op2 = input vector
 *    index = elements indices of the vectors that should be extracted and returned
 * Returns:
 *    vector with the same element type as `op1` and `op2`, but has an element count
 *    equal to the number of indices in `index`.
 * Example:
 * ---
 * int8 a = [1, -2, 3, -4, 5, -6, 7, -8];
 * int4 b = shufflevector(a, a, 0, 2, 4, 6);
 * assert(b.array == [1, 3, 5, 7]);
 * int4 c = [-2, -4, -6, -8];
 * int d = shufflevector(c, b, 4, 0, 5, 1, 6, 2, 7, 3);
 * assert(d.array == a.array);
 * ---
 */
template shufflevector(V1, V2, M...)
{
    static assert(isVectorType!V1, "first argument must be vector");
    static assert(isVectorType!V2, "second argument must be vector");
    static assert(is(BaseType!V1 == BaseType!V2),
                  "first and second argument vectors must have the same element type");
    static assert(isPowerOf2!(M.length),
                  "number of index arguments must be a power of 2");

    __vector(BaseType!V1[M.length]) shufflevector(V1 op1, V2 op2, M index);
}

/// Ditto
template shufflevector(V, index...)
{
    // Defined for compatibility with LDC.
    static assert(isVectorType!V, "first argument must be a vector type");
    static assert(numElements!V == index.length,
                  "number of index arguments must be the same number of vector elements");

    private template ctfeConstants(m...)
    {
        static if (m.length == 0) enum ctfeConstants = 1;
        else enum ctfeConstants = m[0] | ctfeConstants!(m[1 .. $]);
    }
    static assert(__traits(compiles, ctfeConstants!index),
                  "all index arguments must be compile time constants");

    private template validIndexes(m...)
    {
        static if (m.length == 0) enum validIndexes = true;
        else enum validIndexes = (cast(long)m[0] > -1) && validIndexes!(m[1 .. $]);
    }
    static assert(validIndexes!index,
                  "all index arguments must be greater than or equal to 0");

    V shufflevector(V op1, V op2)
    {
        return shufflevector(op1, op2, index);
    }
}

/**
 * Extracts a single scalar element from a vector at a specified index.
 * Defined for compatibility with LDC.
 * Params:
 *    val = vector to extract element from
 *    idx = index indicating the position from which to extract the element
 * Returns:
 *    scalar of the same type as the element type of val
 * Example:
 * ---
 * int4 a = [0, 10, 20, 30];
 * int k = extractelement!(int4, 2)(a);
 * assert(k == 20);
 * ---
 */
BaseType!V extractelement(V, int idx)(V val)
    if (isVectorType!V && idx < numElements!V)
{
    return val[idx];
}

/**
 * Inserts a scalar element into a vector at a specified index.
 * Defined for compatibility with LDC.
 * Params:
 *    val = vector to assign element to
 *    elt = scalar whose type is the element type of val
 *    idx = index indicating the position from which to extract the element
 * Returns:
 *    vector of the same type as val
 * Example:
 * ---
 * int4 a = [0, 10, 20, 30];
 * int4 b = insertelement!(int4, 2)(a, 50);
 * assert(b.array == [0, 10, 50, 30]);
 * ---
 */
V insertelement(V, int idx)(V val, BaseType!V elt)
    if (isVectorType!V && idx < numElements!V)
{
    val[idx] = elt;
    return val;
}

/**
 * Convert a vector from one integral or floating vector type to another.
 * The result is an integral or floating vector that has had every element
 * cast to the element type of the return type.
 * Params:
 *    from = input vector
 * Returns:
 *    converted vector
 * Example:
 * ---
 * int4 a = [1, -2, 3, -4];
 * float4 b = [1.5, -2.5, 3, 7];
 * assert(convertvector!float4(a).array == [1, -2, 3, -4]);
 * assert(convertvector!double4(a).array == [1, -2, 3, -4]);
 * assert(convertvector!double4(b).array == [1.5, -2.5, 3, 7]);
 * assert(convertvector!int4(b).array == [1, -2, 3, 7]);
 * ---
 */

template convertvector(V, T)
{
    static assert(isVectorType!V && (is(BaseType!V : long) || is(BaseType!V : real)),
                  "first argument must be an integer or floating vector type");
    static assert(isVectorType!T && (is(BaseType!T : long) || is(BaseType!T : real)),
                  "second argument must be an integer or floating vector");
    static assert(numElements!V == numElements!T,
                  "first and second argument vectors should have the same number of elements");

    V convertvector(T);
}

/**
 * Construct a conditional merge of elements from two vectors, returning a
 * vector of the same type as the input vector(s). The `mask` is an integral
 * vector with the same width and element count as the output vector.
 * Params:
 *    op1 = input vector
 *    op2 = input vector
 *    mask = integer vector mask
 * Returns:
 *    vector with the same type as `op1` and `op2`
 * Example:
 * ---
 * int4 a = [1, 2, 3, 4];
 * int4 b = [5, 6, 7, 8];
 * int4 mask1 = [0, 1, 1, 3];
 * int4 mask2 = [0, 4, 2, 5];
 * assert(shuffle(a, mask1).array == [1, 2, 2, 4]);
 * assert(shuffle(a, b, mask2).array == [1, 5, 3, 6]);
 * ---
 */
template blendvector(V0, V1, M)
{
    static assert(isVectorType!V0, "first argument must be vector");
    static assert(isVectorType!V1, "second argument must be vector");
    static assert(is(BaseType!V0 == BaseType!V1),
                  "first and second argument vectors must have the same element type");
    static assert(isVectorType!M && is(BaseType!M : long),
                  "last argument must be an integer vector");
    static assert(numElements!V0 == numElements!M && numElements!V1 == numElements!M,
                  "argument vectors and mask vector should have the same number of elements");
    static assert(BaseType!V0.sizeof == BaseType!M.sizeof,
                  "argument vectors and mask vector should have the same element type size");

    V0 blendvector(V0 op1, V1 op2, M mask);
}

/**
 * Perform an element-wise comparison between two vectors, producing `0` when
 * the comparison is false and `-1` (all bits are set to 1) otherwise.
 * Params:
 *    op1 = input vector
 *    op2 = input vector
 * Returns:
 *    vector of the same width and number of elements as the comparison
 *    operands with a signed integral element type
 * Example:
 * ---
 * float4 a = [1, 3, 5, 7];
 * float4 b = [2, 3, 4, 5];
 * int4 c = greaterMask!float4(a, b);
 * assert(c.array == [0, 0, -1, -1]);
 * ---
 */
V equalMask(V)(V op1, V op2) if (isVectorType!V)
{
    return op1 == op2;
}
/// Ditto
V notEqualMask(V)(V op1, V op2) if (isVectorType!V)
{
    return op1 != op2;
}
/// Ditto
V greaterMask(V)(V op1, V op2) if (isVectorType!V)
{
    return op1 > op2;
}
/// Ditto
V greaterOrEqualMask(V)(V op1, V op2) if (isVectorType!V)
{
    return op1 >= op2;
}

/**
 * Perform an element-wise logical comparison between two vectors, producing
 * `0` when the comparison is false and `-1` (all bits are set to 1) otherwise.
 * Params:
 *    op1 = input vector
 *    op2 = input vector
 * Returns:
 *    vector of the same width and number of elements as the comparison
 *    operands with a signed integral element type
 */
V notMask(V)(V op1) if (isVectorType!V)
{
    return op1 == 0;
}

/// Ditto
V andAndMask(V)(V op1, V op2) if (isVectorType!V)
{
    return (op1 != 0) & (op2 != 0);
}

/// Ditto
V orOrMask(V)(V op1, V op2) if (isVectorType!V)
{
    return (op1 != 0) | (op2 != 0);
}

// Private helper templates.
private:

enum bool isVectorType(T) = is(T : __vector(V[N]), V, size_t N);

template BaseType(V)
{
    alias typeof(V.array[0]) BaseType;
}

template numElements(V)
{
    enum numElements = V.sizeof / BaseType!(V).sizeof;
}

enum bool isPowerOf2(int Y) = Y && (Y & -Y) == Y;
