| /** |
| This module contains support array (vector) operations |
| Copyright: Copyright Digital Mars 2000 - 2019. |
| License: Distributed under the |
| $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). |
| (See accompanying file LICENSE) |
| Source: $(DRUNTIMESRC core/_internal/_array/_operations.d) |
| */ |
| module core.internal.array.operations; |
| import core.internal.traits : Filter, staticMap, Unqual; |
| |
| version (GNU) version = GNU_OR_LDC; |
| version (LDC) version = GNU_OR_LDC; |
| |
| /** |
| * Perform array (vector) operations and store the result in `res`. Operand |
| * types and operations are passed as template arguments in Reverse Polish |
| * Notation (RPN). |
| |
| * Operands can be slices or scalar types. The element types of all |
| * slices and all scalar types must be implicitly convertible to `T`. |
| * |
| * Operations are encoded as strings, e.g. `"+"`, `"%"`, `"*="`. Unary |
| * operations are prefixed with "u", e.g. `"u-"`, `"u~"`. Only the last |
| * operation can and must be an assignment (`"="`) or op-assignment (`"op="`). |
| * |
| * All slice operands must have the same length as the result slice. |
| * |
| * Params: T[] = type of result slice |
| * Args = operand types and operations in RPN |
| * res = the slice in which to store the results |
| * args = operand values |
| * |
| * Returns: the slice containing the result |
| */ |
| T[] arrayOp(T : T[], Args...)(T[] res, Filter!(isType, Args) args) @trusted |
| { |
| alias scalarizedExp = staticMap!(toElementType, Args); |
| alias check = typeCheck!(true, T, scalarizedExp); // must support all scalar ops |
| |
| foreach (argsIdx, arg; typeof(args)) |
| { |
| static if (is(arg == U[], U)) |
| { |
| assert(res.length == args[argsIdx].length, "Mismatched array lengths for vector operation"); |
| } |
| } |
| |
| size_t pos; |
| static if (vectorizeable!(T[], Args)) |
| { |
| alias vec = .vec!T; |
| alias load = .load!(T, vec.length); |
| alias store = .store!(T, vec.length); |
| |
| // Given that there are at most as many scalars broadcast as there are |
| // operations in any `ary[] = ary[] op const op const`, it should always be |
| // worthwhile to choose vector operations. |
| if (!__ctfe && res.length >= vec.length) |
| { |
| mixin(initScalarVecs!Args); |
| |
| auto n = res.length / vec.length; |
| do |
| { |
| mixin(vectorExp!Args ~ ";"); |
| pos += vec.length; |
| } |
| while (--n); |
| } |
| } |
| for (; pos < res.length; ++pos) |
| mixin(scalarExp!Args ~ ";"); |
| |
| return res; |
| } |
| |
| private: |
| |
| // SIMD helpers |
| |
| version (DigitalMars) |
| { |
| import core.simd; |
| |
| template vec(T) |
| { |
| enum regsz = 16; // SSE2 |
| enum N = regsz / T.sizeof; |
| alias vec = __vector(T[N]); |
| } |
| |
| void store(T, size_t N)(T* p, const scope __vector(T[N]) val) |
| { |
| pragma(inline, true); |
| alias vec = __vector(T[N]); |
| |
| static if (is(T == float)) |
| cast(void) __simd_sto(XMM.STOUPS, *cast(vec*) p, val); |
| else static if (is(T == double)) |
| cast(void) __simd_sto(XMM.STOUPD, *cast(vec*) p, val); |
| else |
| cast(void) __simd_sto(XMM.STODQU, *cast(vec*) p, val); |
| } |
| |
| const(__vector(T[N])) load(T, size_t N)(const scope T* p) |
| { |
| import core.simd; |
| |
| pragma(inline, true); |
| alias vec = __vector(T[N]); |
| |
| static if (is(T == float)) |
| return cast(typeof(return)) __simd(XMM.LODUPS, *cast(const vec*) p); |
| else static if (is(T == double)) |
| return cast(typeof(return)) __simd(XMM.LODUPD, *cast(const vec*) p); |
| else |
| return cast(typeof(return)) __simd(XMM.LODDQU, *cast(const vec*) p); |
| } |
| |
| __vector(T[N]) binop(string op, T, size_t N)(const scope __vector(T[N]) a, const scope __vector(T[N]) b) |
| { |
| pragma(inline, true); |
| return mixin("a " ~ op ~ " b"); |
| } |
| |
| __vector(T[N]) unaop(string op, T, size_t N)(const scope __vector(T[N]) a) |
| if (op[0] == 'u') |
| { |
| pragma(inline, true); |
| return mixin(op[1 .. $] ~ "a"); |
| } |
| } |
| |
| // mixin gen |
| |
| /** |
| Check whether operations on operand types are supported. This |
| template recursively reduces the expression tree and determines |
| intermediate types. |
| Type checking is done here rather than in the compiler to provide more |
| detailed error messages. |
| |
| Params: |
| fail = whether to fail (static assert) with a human-friendly error message |
| T = type of result |
| Args = operand types and operations in RPN |
| Returns: |
| The resulting type of the expression |
| See_Also: |
| $(LREF arrayOp) |
| */ |
| template typeCheck(bool fail, T, Args...) |
| { |
| enum idx = staticIndexOf!(not!isType, Args); |
| static if (isUnaryOp(Args[idx])) |
| { |
| alias UT = Args[idx - 1]; |
| enum op = Args[idx][1 .. $]; |
| static if (is(typeof((UT a) => mixin(op ~ "cast(int) a")) RT == return)) |
| alias typeCheck = typeCheck!(fail, T, Args[0 .. idx - 1], RT, Args[idx + 1 .. $]); |
| else static if (fail) |
| static assert(0, "Unary `" ~ op ~ "` not supported for type `" ~ UT.stringof ~ "`."); |
| } |
| else static if (isBinaryOp(Args[idx])) |
| { |
| alias LHT = Args[idx - 2]; |
| alias RHT = Args[idx - 1]; |
| enum op = Args[idx]; |
| static if (is(typeof((LHT a, RHT b) => mixin("a " ~ op ~ " b")) RT == return)) |
| alias typeCheck = typeCheck!(fail, T, Args[0 .. idx - 2], RT, Args[idx + 1 .. $]); |
| else static if (fail) |
| static assert(0, |
| "Binary `" ~ op ~ "` not supported for types `" |
| ~ LHT.stringof ~ "` and `" ~ RHT.stringof ~ "`."); |
| } |
| else static if (Args[idx] == "=" || isBinaryAssignOp(Args[idx])) |
| { |
| alias RHT = Args[idx - 1]; |
| enum op = Args[idx]; |
| static if (is(T == __vector(ET[N]), ET, size_t N)) |
| { |
| // no `cast(T)` before assignment for vectors |
| static if (is(typeof((T res, RHT b) => mixin("res " ~ op ~ " b")) RT == return) |
| && // workaround https://issues.dlang.org/show_bug.cgi?id=17758 |
| (op != "=" || is(Unqual!T == Unqual!RHT))) |
| alias typeCheck = typeCheck!(fail, T, Args[0 .. idx - 1], RT, Args[idx + 1 .. $]); |
| else static if (fail) |
| static assert(0, |
| "Binary op `" ~ op ~ "` not supported for types `" |
| ~ T.stringof ~ "` and `" ~ RHT.stringof ~ "`."); |
| } |
| else |
| { |
| static if (is(typeof((RHT b) => mixin("cast(T) b")))) |
| { |
| static if (is(typeof((T res, T b) => mixin("res " ~ op ~ " b")) RT == return)) |
| alias typeCheck = typeCheck!(fail, T, Args[0 .. idx - 1], RT, Args[idx + 1 .. $]); |
| else static if (fail) |
| static assert(0, |
| "Binary op `" ~ op ~ "` not supported for types `" |
| ~ T.stringof ~ "` and `" ~ T.stringof ~ "`."); |
| } |
| else static if (fail) |
| static assert(0, |
| "`cast(" ~ T.stringof ~ ")` not supported for type `" ~ RHT.stringof ~ "`."); |
| } |
| } |
| else |
| static assert(0); |
| } |
| /// ditto |
| template typeCheck(bool fail, T, ResultType) |
| { |
| alias typeCheck = ResultType; |
| } |
| |
| version (GNU_OR_LDC) |
| { |
| // leave it to the auto-vectorizer |
| enum vectorizeable(E : E[], Args...) = false; |
| } |
| else |
| { |
| // check whether arrayOp is vectorizable |
| template vectorizeable(E : E[], Args...) |
| { |
| static if (is(vec!E)) |
| { |
| // type check with vector types |
| enum vectorizeable = is(typeCheck!(false, vec!E, staticMap!(toVecType, Args))); |
| } |
| else |
| enum vectorizeable = false; |
| } |
| |
| version (X86_64) unittest |
| { |
| static assert(vectorizeable!(double[], const(double)[], double[], "+", "=")); |
| static assert(!vectorizeable!(double[], const(ulong)[], double[], "+", "=")); |
| // Vector type are (atm.) not implicitly convertible and would require |
| // lots of SIMD intrinsics. Therefor leave mixed type array ops to |
| // GDC/LDC's auto-vectorizers. |
| static assert(!vectorizeable!(double[], const(uint)[], uint, "+", "=")); |
| } |
| } |
| |
| bool isUnaryOp(scope string op) pure nothrow @safe @nogc |
| { |
| return op[0] == 'u'; |
| } |
| |
| bool isBinaryOp(scope string op) pure nothrow @safe @nogc |
| { |
| if (op == "^^") |
| return true; |
| if (op.length != 1) |
| return false; |
| switch (op[0]) |
| { |
| case '+', '-', '*', '/', '%', '|', '&', '^': |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool isBinaryAssignOp(string op) |
| { |
| return op.length >= 2 && op[$ - 1] == '=' && isBinaryOp(op[0 .. $ - 1]); |
| } |
| |
| // Generate mixin expression to perform scalar arrayOp loop expression, assumes |
| // `pos` to be the current slice index, `args` to contain operand values, and |
| // `res` the target slice. |
| enum scalarExp(Args...) = |
| (){ |
| string[] stack; |
| size_t argsIdx; |
| |
| static if (is(Args[0] == U[], U)) |
| alias Type = U; |
| else |
| alias Type = Args[0]; |
| |
| foreach (i, arg; Args) |
| { |
| static if (is(arg == T[], T)) |
| stack ~= "args[" ~ argsIdx++.toString ~ "][pos]"; |
| else static if (is(arg)) |
| stack ~= "args[" ~ argsIdx++.toString ~ "]"; |
| else static if (isUnaryOp(arg)) |
| { |
| auto op = arg[0] == 'u' ? arg[1 .. $] : arg; |
| // Explicitly use the old integral promotion rules |
| // See also: https://dlang.org/changelog/2.078.0.html#fix16997 |
| static if (is(Type : int)) |
| stack[$ - 1] = "cast(typeof(" ~ stack[$ -1] ~ "))" ~ op ~ "cast(int)("~ stack[$ - 1] ~ ")"; |
| else |
| stack[$ - 1] = op ~ stack[$ - 1]; |
| } |
| else static if (arg == "=") |
| { |
| stack[$ - 1] = "res[pos] = cast(T)(" ~ stack[$ - 1] ~ ")"; |
| } |
| else static if (isBinaryAssignOp(arg)) |
| { |
| stack[$ - 1] = "res[pos] " ~ arg ~ " cast(T)(" ~ stack[$ - 1] ~ ")"; |
| } |
| else static if (isBinaryOp(arg)) |
| { |
| stack[$ - 2] = "(" ~ stack[$ - 2] ~ " " ~ arg ~ " " ~ stack[$ - 1] ~ ")"; |
| stack.length -= 1; |
| } |
| else |
| assert(0, "Unexpected op " ~ arg); |
| } |
| assert(stack.length == 1); |
| return stack[0]; |
| }(); |
| |
| // Generate mixin statement to perform vector loop initialization, assumes |
| // `args` to contain operand values. |
| enum initScalarVecs(Args...) = |
| () { |
| size_t scalarsIdx, argsIdx; |
| string res; |
| foreach (arg; Args) |
| { |
| static if (is(arg == T[], T)) |
| { |
| ++argsIdx; |
| } |
| else static if (is(arg)) |
| res ~= "immutable vec scalar" ~ scalarsIdx++.toString ~ " = args[" |
| ~ argsIdx++.toString ~ "];\n"; |
| } |
| return res; |
| }(); |
| |
| // Generate mixin expression to perform vector arrayOp loop expression, assumes |
| // `pos` to be the current slice index, `args` to contain operand values, and |
| // `res` the target slice. |
| enum vectorExp(Args...) = |
| () { |
| size_t scalarsIdx, argsIdx; |
| string[] stack; |
| foreach (arg; Args) |
| { |
| static if (is(arg == T[], T)) |
| stack ~= "load(&args[" ~ argsIdx++.toString ~ "][pos])"; |
| else static if (is(arg)) |
| { |
| ++argsIdx; |
| stack ~= "scalar" ~ scalarsIdx++.toString; |
| } |
| else static if (isUnaryOp(arg)) |
| { |
| auto op = arg[0] == 'u' ? arg[1 .. $] : arg; |
| stack[$ - 1] = "unaop!\"" ~ arg ~ "\"(" ~ stack[$ - 1] ~ ")"; |
| } |
| else static if (arg == "=") |
| { |
| stack[$ - 1] = "store(&res[pos], " ~ stack[$ - 1] ~ ")"; |
| } |
| else static if (isBinaryAssignOp(arg)) |
| { |
| stack[$ - 1] = "store(&res[pos], binop!\"" ~ arg[0 .. $ - 1] |
| ~ "\"(load(&res[pos]), " ~ stack[$ - 1] ~ "))"; |
| } |
| else static if (isBinaryOp(arg)) |
| { |
| stack[$ - 2] = "binop!\"" ~ arg ~ "\"(" ~ stack[$ - 2] ~ ", " ~ stack[$ - 1] ~ ")"; |
| stack.length -= 1; |
| } |
| else |
| assert(0, "Unexpected op " ~ arg); |
| } |
| assert(stack.length == 1); |
| return stack[0]; |
| }(); |
| |
| // other helpers |
| |
| enum isType(T) = true; |
| enum isType(alias a) = false; |
| template not(alias tmlp) |
| { |
| enum not(Args...) = !tmlp!Args; |
| } |
| /** |
| Find element in `haystack` for which `pred` is true. |
| |
| Params: |
| pred = the template predicate |
| haystack = elements to search |
| Returns: |
| The first index for which `pred!haystack[index]` is true or -1. |
| */ |
| template staticIndexOf(alias pred, haystack...) |
| { |
| static if (pred!(haystack[0])) |
| enum staticIndexOf = 0; |
| else |
| { |
| enum next = staticIndexOf!(pred, haystack[1 .. $]); |
| enum staticIndexOf = next == -1 ? -1 : next + 1; |
| } |
| } |
| /// converts slice types to their element type, preserves anything else |
| alias toElementType(E : E[]) = E; |
| alias toElementType(S) = S; |
| alias toElementType(alias op) = op; |
| /// converts slice types to their element type, preserves anything else |
| alias toVecType(E : E[]) = vec!E; |
| alias toVecType(S) = vec!S; |
| alias toVecType(alias op) = op; |
| |
| string toString(size_t num) |
| { |
| import core.internal.string : unsignedToTempString; |
| version (D_BetterC) |
| { |
| // Workaround for https://issues.dlang.org/show_bug.cgi?id=19268 |
| if (__ctfe) |
| { |
| char[20] fixedbuf = void; |
| char[] buf = unsignedToTempString(num, fixedbuf); |
| char[] result = new char[buf.length]; |
| result[] = buf[]; |
| return (() @trusted => cast(string) result)(); |
| } |
| else |
| { |
| // Failing at execution rather than during compilation is |
| // not good, but this is in `core.internal` so it should |
| // not be used by the unwary. |
| assert(0, __FUNCTION__ ~ " not available in -betterC except during CTFE."); |
| } |
| } |
| else |
| { |
| char[20] buf = void; |
| return unsignedToTempString(num, buf).idup; |
| } |
| } |
| |
| bool contains(T)(const scope T[] ary, const scope T[] vals...) |
| { |
| foreach (v1; ary) |
| foreach (v2; vals) |
| if (v1 == v2) |
| return true; |
| return false; |
| } |
| |
| // tests |
| |
| version (CoreUnittest) template TT(T...) |
| { |
| alias TT = T; |
| } |
| |
| version (CoreUnittest) template _arrayOp(Args...) |
| { |
| alias _arrayOp = arrayOp!Args; |
| } |
| |
| unittest |
| { |
| static void check(string op, TA, TB, T, size_t N)(TA a, TB b, const scope ref T[N] exp) |
| { |
| T[N] res; |
| _arrayOp!(T[], TA, TB, op, "=")(res[], a, b); |
| foreach (i; 0 .. N) |
| assert(res[i] == exp[i]); |
| } |
| |
| static void check2(string unaOp, string binOp, TA, TB, T, size_t N)(TA a, TB b, const scope ref T[N] exp) |
| { |
| T[N] res; |
| _arrayOp!(T[], TA, TB, unaOp, binOp, "=")(res[], a, b); |
| foreach (i; 0 .. N) |
| assert(res[i] == exp[i]); |
| } |
| |
| static void test(T, string op, size_t N = 16)(T a, T b, T exp) |
| { |
| T[N] va = a, vb = b, vexp = exp; |
| |
| check!op(va[], vb[], vexp); |
| check!op(va[], b, vexp); |
| check!op(a, vb[], vexp); |
| } |
| |
| static void test2(T, string unaOp, string binOp, size_t N = 16)(T a, T b, T exp) |
| { |
| T[N] va = a, vb = b, vexp = exp; |
| |
| check2!(unaOp, binOp)(va[], vb[], vexp); |
| check2!(unaOp, binOp)(va[], b, vexp); |
| check2!(unaOp, binOp)(a, vb[], vexp); |
| } |
| |
| alias UINTS = TT!(ubyte, ushort, uint, ulong); |
| alias INTS = TT!(byte, short, int, long); |
| alias FLOATS = TT!(float, double); |
| |
| foreach (T; TT!(UINTS, INTS, FLOATS)) |
| { |
| test!(T, "+")(1, 2, 3); |
| test!(T, "-")(3, 2, 1); |
| static if (__traits(compiles, { import std.math; })) |
| test!(T, "^^")(2, 3, 8); |
| |
| test2!(T, "u-", "+")(3, 2, 1); |
| } |
| |
| foreach (T; TT!(UINTS, INTS)) |
| { |
| test!(T, "|")(1, 2, 3); |
| test!(T, "&")(3, 1, 1); |
| test!(T, "^")(3, 1, 2); |
| |
| test2!(T, "u~", "+")(3, cast(T)~2, 5); |
| } |
| |
| foreach (T; TT!(INTS, FLOATS)) |
| { |
| test!(T, "-")(1, 2, -1); |
| test2!(T, "u-", "+")(-3, -2, -1); |
| test2!(T, "u-", "*")(-3, -2, -6); |
| } |
| |
| foreach (T; TT!(UINTS, INTS, FLOATS)) |
| { |
| test!(T, "*")(2, 3, 6); |
| test!(T, "/")(8, 4, 2); |
| test!(T, "%")(8, 6, 2); |
| } |
| } |
| |
| // test handling of v op= exp |
| @nogc nothrow pure @safe unittest |
| { |
| uint[32] c; |
| arrayOp!(uint[], uint, "+=")(c[], 2); |
| foreach (v; c) |
| assert(v == 2); |
| static if (__traits(compiles, { import std.math; })) |
| { |
| arrayOp!(uint[], uint, "^^=")(c[], 3); |
| foreach (v; c) |
| assert(v == 8); |
| } |
| } |
| |
| // proper error message for UDT lacking certain ops |
| @nogc nothrow pure @safe unittest |
| { |
| static assert(!is(typeof(&arrayOp!(int[4][], int[4], "+=")))); |
| static assert(!is(typeof(&arrayOp!(int[4][], int[4], "u-", "=")))); |
| |
| static struct S |
| { |
| } |
| |
| static assert(!is(typeof(&arrayOp!(S[], S, "+=")))); |
| static assert(!is(typeof(&arrayOp!(S[], S[], "*", S, "+=")))); |
| static struct S2 |
| { |
| S2 opBinary(string op)(in S2) @nogc pure nothrow |
| { |
| return this; |
| } |
| |
| ref S2 opOpAssign(string op)(in S2) @nogc pure nothrow |
| { |
| return this; |
| } |
| } |
| |
| static assert(is(typeof(&arrayOp!(S2[], S2[], S2[], S2, "*", "+", "=")))); |
| static assert(is(typeof(&arrayOp!(S2[], S2[], S2, "*", "+=")))); |
| } |
| |
| // test mixed type array op |
| @nogc nothrow pure @safe unittest |
| { |
| uint[32] a = 0xF; |
| float[32] res = 2.0f; |
| arrayOp!(float[], const(uint)[], uint, "&", "*=")(res[], a[], 12); |
| foreach (v; res[]) |
| assert(v == 24.0f); |
| } |
| |
| // test mixed type array op |
| @nogc nothrow pure @safe unittest |
| { |
| static struct S |
| { |
| float opBinary(string op)(in S) @nogc const pure nothrow |
| { |
| return 2.0f; |
| } |
| } |
| |
| float[32] res = 24.0f; |
| S[32] s; |
| arrayOp!(float[], const(S)[], const(S)[], "+", "/=")(res[], s[], s[]); |
| foreach (v; res[]) |
| assert(v == 12.0f); |
| } |
| |
| // test scalar after operation argument |
| @nogc nothrow pure @safe unittest |
| { |
| float[32] res, a = 2, b = 3; |
| float c = 4; |
| arrayOp!(float[], const(float)[], const(float)[], "*", float, "+", "=")(res[], a[], b[], c); |
| foreach (v; res[]) |
| assert(v == 2 * 3 + 4); |
| } |
| |
| @nogc nothrow pure @safe unittest |
| { |
| // https://issues.dlang.org/show_bug.cgi?id=17964 |
| uint bug(){ |
| uint[] a = [1, 2, 3, 5, 6, 7]; |
| uint[] b = [1, 2, 3, 5, 6, 7]; |
| a[] |= ~b[]; |
| return a[1]; |
| } |
| enum x = bug(); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=19796 |
| nothrow pure @safe unittest |
| { |
| double[] data = [0.5]; |
| double[] result; |
| result.length = data.length; |
| result[] = -data[]; |
| assert(result[0] == -0.5); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=21110 |
| pure unittest |
| { |
| import core.exception; |
| |
| static void assertThrown(T : Throwable, E)(lazy E expression, string msg) |
| { |
| try |
| expression; |
| catch (T) |
| return; |
| assert(0, "msg"); |
| } |
| |
| int[] dst; |
| int[] a; |
| int[] b; |
| a.length = 3; |
| b.length = 3; |
| dst.length = 4; |
| |
| void func() { dst[] = a[] + b[]; } |
| assertThrown!AssertError(func(), "Array operations with mismatched lengths must throw an error"); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=24272 |
| unittest |
| { |
| static struct B |
| { |
| B opOpAssign(string op)(B other) |
| { |
| static int g; |
| g++; |
| throw new Exception(""); |
| } |
| } |
| |
| B[] bArr; |
| bArr[] += B(); |
| } |