2 This module contains support array (vector) operations
3 Copyright: Copyright Digital Mars 2000 - 2019.
4 License: Distributed under the
5 $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0).
6 (See accompanying file LICENSE)
7 Source: $(DRUNTIMESRC core/_internal/_array/_operations.d)
9 module core
.internal
.array
.operations
;
10 import core
.internal
.traits
: Filter
, staticMap
, Unqual
;
12 version (GNU
) version = GNU_OR_LDC
;
13 version (LDC
) version = GNU_OR_LDC
;
16 * Perform array (vector) operations and store the result in `res`. Operand
17 * types and operations are passed as template arguments in Reverse Polish
20 * Operands can be slices or scalar types. The element types of all
21 * slices and all scalar types must be implicitly convertible to `T`.
23 * Operations are encoded as strings, e.g. `"+"`, `"%"`, `"*="`. Unary
24 * operations are prefixed with "u", e.g. `"u-"`, `"u~"`. Only the last
25 * operation can and must be an assignment (`"="`) or op-assignment (`"op="`).
27 * All slice operands must have the same length as the result slice.
29 * Params: T[] = type of result slice
30 * Args = operand types and operations in RPN
31 * res = the slice in which to store the results
32 * args = operand values
34 * Returns: the slice containing the result
36 T
[] arrayOp(T
: T
[], Args
...)(T
[] res
, Filter
!(isType
, Args
) args
) @trusted
38 alias scalarizedExp
= staticMap
!(toElementType
, Args
);
39 alias check
= typeCheck
!(true, T
, scalarizedExp
); // must support all scalar ops
41 foreach (argsIdx
, arg
; typeof(args
))
43 static if (is(arg
== U
[], U
))
45 assert(res
.length
== args
[argsIdx
].length
, "Mismatched array lengths for vector operation");
50 static if (vectorizeable
!(T
[], Args
))
53 alias load
= .load
!(T
, vec
.length
);
54 alias store
= .store
!(T
, vec
.length
);
56 // Given that there are at most as many scalars broadcast as there are
57 // operations in any `ary[] = ary[] op const op const`, it should always be
58 // worthwhile to choose vector operations.
59 if (!__ctfe
&& res
.length
>= vec
.length
)
61 mixin(initScalarVecs
!Args
);
63 auto n
= res
.length
/ vec
.length
;
66 mixin(vectorExp
!Args
~ ";");
72 for (; pos
< res
.length
; ++pos
)
73 mixin(scalarExp
!Args
~ ";");
88 enum regsz
= 16; // SSE2
89 enum N
= regsz
/ T
.sizeof
;
90 alias vec
= __vector(T
[N
]);
93 void store(T
, size_t N
)(T
* p
, const scope __vector(T
[N
]) val
)
96 alias vec
= __vector(T
[N
]);
98 static if (is(T
== float))
99 cast(void) __simd_sto(XMM
.STOUPS
, *cast(vec
*) p
, val
);
100 else static if (is(T
== double))
101 cast(void) __simd_sto(XMM
.STOUPD
, *cast(vec
*) p
, val
);
103 cast(void) __simd_sto(XMM
.STODQU
, *cast(vec
*) p
, val
);
106 const(__vector(T
[N
])) load(T
, size_t N
)(const scope T
* p
)
110 pragma(inline
, true);
111 alias vec
= __vector(T
[N
]);
113 static if (is(T
== float))
114 return cast(typeof(return)) __simd(XMM
.LODUPS
, *cast(const vec
*) p
);
115 else static if (is(T
== double))
116 return cast(typeof(return)) __simd(XMM
.LODUPD
, *cast(const vec
*) p
);
118 return cast(typeof(return)) __simd(XMM
.LODDQU
, *cast(const vec
*) p
);
121 __vector(T
[N
]) binop(string op
, T
, size_t N
)(const scope __vector(T
[N
]) a
, const scope __vector(T
[N
]) b
)
123 pragma(inline
, true);
124 return mixin("a " ~ op
~ " b");
127 __vector(T
[N
]) unaop(string op
, T
, size_t N
)(const scope __vector(T
[N
]) a
)
130 pragma(inline
, true);
131 return mixin(op
[1 .. $] ~ "a");
138 Check whether operations on operand types are supported. This
139 template recursively reduces the expression tree and determines
141 Type checking is done here rather than in the compiler to provide more
142 detailed error messages.
145 fail = whether to fail (static assert) with a human-friendly error message
147 Args = operand types and operations in RPN
149 The resulting type of the expression
153 template typeCheck(bool fail
, T
, Args
...)
155 enum idx
= staticIndexOf
!(not!isType
, Args
);
156 static if (isUnaryOp(Args
[idx
]))
158 alias UT
= Args
[idx
- 1];
159 enum op
= Args
[idx
][1 .. $];
160 static if (is(typeof((UT a
) => mixin(op
~ "cast(int) a")) RT
== return))
161 alias typeCheck
= typeCheck
!(fail
, T
, Args
[0 .. idx
- 1], RT
, Args
[idx
+ 1 .. $]);
162 else static if (fail
)
163 static assert(0, "Unary `" ~ op
~ "` not supported for type `" ~ UT
.stringof
~ "`.");
165 else static if (isBinaryOp(Args
[idx
]))
167 alias LHT
= Args
[idx
- 2];
168 alias RHT
= Args
[idx
- 1];
170 static if (is(typeof((LHT a
, RHT b
) => mixin("a " ~ op
~ " b")) RT
== return))
171 alias typeCheck
= typeCheck
!(fail
, T
, Args
[0 .. idx
- 2], RT
, Args
[idx
+ 1 .. $]);
172 else static if (fail
)
174 "Binary `" ~ op
~ "` not supported for types `"
175 ~ LHT
.stringof
~ "` and `" ~ RHT
.stringof
~ "`.");
177 else static if (Args
[idx
] == "=" ||
isBinaryAssignOp(Args
[idx
]))
179 alias RHT
= Args
[idx
- 1];
181 static if (is(T
== __vector(ET
[N
]), ET
, size_t N
))
183 // no `cast(T)` before assignment for vectors
184 static if (is(typeof((T res
, RHT b
) => mixin("res " ~ op
~ " b")) RT
== return)
185 && // workaround https://issues.dlang.org/show_bug.cgi?id=17758
186 (op
!= "=" ||
is(Unqual
!T
== Unqual
!RHT
)))
187 alias typeCheck
= typeCheck
!(fail
, T
, Args
[0 .. idx
- 1], RT
, Args
[idx
+ 1 .. $]);
188 else static if (fail
)
190 "Binary op `" ~ op
~ "` not supported for types `"
191 ~ T
.stringof
~ "` and `" ~ RHT
.stringof
~ "`.");
195 static if (is(typeof((RHT b
) => mixin("cast(T) b"))))
197 static if (is(typeof((T res
, T b
) => mixin("res " ~ op
~ " b")) RT
== return))
198 alias typeCheck
= typeCheck
!(fail
, T
, Args
[0 .. idx
- 1], RT
, Args
[idx
+ 1 .. $]);
199 else static if (fail
)
201 "Binary op `" ~ op
~ "` not supported for types `"
202 ~ T
.stringof
~ "` and `" ~ T
.stringof
~ "`.");
204 else static if (fail
)
206 "`cast(" ~ T
.stringof
~ ")` not supported for type `" ~ RHT
.stringof
~ "`.");
213 template typeCheck(bool fail
, T
, ResultType
)
215 alias typeCheck
= ResultType
;
220 // leave it to the auto-vectorizer
221 enum vectorizeable(E
: E
[], Args
...) = false;
225 // check whether arrayOp is vectorizable
226 template vectorizeable(E
: E
[], Args
...)
228 static if (is(vec
!E
))
230 // type check with vector types
231 enum vectorizeable
= is(typeCheck
!(false, vec
!E
, staticMap
!(toVecType
, Args
)));
234 enum vectorizeable
= false;
237 version (X86_64
) unittest
239 static assert(vectorizeable
!(double[], const(double)[], double[], "+", "="));
240 static assert(!vectorizeable
!(double[], const(ulong)[], double[], "+", "="));
241 // Vector type are (atm.) not implicitly convertible and would require
242 // lots of SIMD intrinsics. Therefor leave mixed type array ops to
243 // GDC/LDC's auto-vectorizers.
244 static assert(!vectorizeable
!(double[], const(uint)[], uint, "+", "="));
248 bool isUnaryOp(scope string op
) pure nothrow @safe @nogc
253 bool isBinaryOp(scope string op
) pure nothrow @safe @nogc
261 case '+', '-', '*', '/', '%', '|', '&', '^':
268 bool isBinaryAssignOp(string op
)
270 return op
.length
>= 2 && op
[$ - 1] == '=' && isBinaryOp(op
[0 .. $ - 1]);
273 // Generate mixin expression to perform scalar arrayOp loop expression, assumes
274 // `pos` to be the current slice index, `args` to contain operand values, and
275 // `res` the target slice.
276 enum scalarExp(Args
...) =
281 static if (is(Args
[0] == U
[], U
))
284 alias Type
= Args
[0];
286 foreach (i
, arg
; Args
)
288 static if (is(arg
== T
[], T
))
289 stack
~= "args[" ~ argsIdx
++.toString
~ "][pos]";
290 else static if (is(arg
))
291 stack
~= "args[" ~ argsIdx
++.toString
~ "]";
292 else static if (isUnaryOp(arg
))
294 auto op
= arg
[0] == 'u' ? arg
[1 .. $] : arg
;
295 // Explicitly use the old integral promotion rules
296 // See also: https://dlang.org/changelog/2.078.0.html#fix16997
297 static if (is(Type
: int))
298 stack
[$ - 1] = "cast(typeof(" ~ stack
[$ -1] ~ "))" ~ op
~ "cast(int)("~ stack
[$ - 1] ~ ")";
300 stack
[$ - 1] = op
~ stack
[$ - 1];
302 else static if (arg
== "=")
304 stack
[$ - 1] = "res[pos] = cast(T)(" ~ stack
[$ - 1] ~ ")";
306 else static if (isBinaryAssignOp(arg
))
308 stack
[$ - 1] = "res[pos] " ~ arg
~ " cast(T)(" ~ stack
[$ - 1] ~ ")";
310 else static if (isBinaryOp(arg
))
312 stack
[$ - 2] = "(" ~ stack
[$ - 2] ~ " " ~ arg
~ " " ~ stack
[$ - 1] ~ ")";
316 assert(0, "Unexpected op " ~ arg
);
318 assert(stack
.length
== 1);
322 // Generate mixin statement to perform vector loop initialization, assumes
323 // `args` to contain operand values.
324 enum initScalarVecs(Args
...) =
326 size_t scalarsIdx
, argsIdx
;
330 static if (is(arg
== T
[], T
))
334 else static if (is(arg
))
335 res
~= "immutable vec scalar" ~ scalarsIdx
++.toString
~ " = args["
336 ~ argsIdx
++.toString
~ "];\n";
341 // Generate mixin expression to perform vector arrayOp loop expression, assumes
342 // `pos` to be the current slice index, `args` to contain operand values, and
343 // `res` the target slice.
344 enum vectorExp(Args
...) =
346 size_t scalarsIdx
, argsIdx
;
350 static if (is(arg
== T
[], T
))
351 stack
~= "load(&args[" ~ argsIdx
++.toString
~ "][pos])";
352 else static if (is(arg
))
355 stack
~= "scalar" ~ scalarsIdx
++.toString
;
357 else static if (isUnaryOp(arg
))
359 auto op
= arg
[0] == 'u' ? arg
[1 .. $] : arg
;
360 stack
[$ - 1] = "unaop!\"" ~ arg
~ "\"(" ~ stack
[$ - 1] ~ ")";
362 else static if (arg
== "=")
364 stack
[$ - 1] = "store(&res[pos], " ~ stack
[$ - 1] ~ ")";
366 else static if (isBinaryAssignOp(arg
))
368 stack
[$ - 1] = "store(&res[pos], binop!\"" ~ arg
[0 .. $ - 1]
369 ~ "\"(load(&res[pos]), " ~ stack
[$ - 1] ~ "))";
371 else static if (isBinaryOp(arg
))
373 stack
[$ - 2] = "binop!\"" ~ arg
~ "\"(" ~ stack
[$ - 2] ~ ", " ~ stack
[$ - 1] ~ ")";
377 assert(0, "Unexpected op " ~ arg
);
379 assert(stack
.length
== 1);
385 enum isType(T
) = true;
386 enum isType(alias a
) = false;
387 template not(alias tmlp
)
389 enum not(Args
...) = !tmlp
!Args
;
392 Find element in `haystack` for which `pred` is true.
395 pred = the template predicate
396 haystack = elements to search
398 The first index for which `pred!haystack[index]` is true or -1.
400 template staticIndexOf(alias pred
, haystack
...)
402 static if (pred
!(haystack
[0]))
403 enum staticIndexOf
= 0;
406 enum next
= staticIndexOf
!(pred
, haystack
[1 .. $]);
407 enum staticIndexOf
= next
== -1 ?
-1 : next
+ 1;
410 /// converts slice types to their element type, preserves anything else
411 alias toElementType(E
: E
[]) = E
;
412 alias toElementType(S
) = S
;
413 alias toElementType(alias op
) = op
;
414 /// converts slice types to their element type, preserves anything else
415 alias toVecType(E
: E
[]) = vec
!E
;
416 alias toVecType(S
) = vec
!S
;
417 alias toVecType(alias op
) = op
;
419 string
toString(size_t num
)
421 import core
.internal
.string
: unsignedToTempString
;
424 // Workaround for https://issues.dlang.org/show_bug.cgi?id=19268
427 char[20] fixedbuf
= void;
428 char[] buf
= unsignedToTempString(num
, fixedbuf
);
429 char[] result
= new char[buf
.length
];
431 return (() @trusted => cast(string
) result
)();
435 // Failing at execution rather than during compilation is
436 // not good, but this is in `core.internal` so it should
437 // not be used by the unwary.
438 assert(0, __FUNCTION__
~ " not available in -betterC except during CTFE.");
444 return unsignedToTempString(num
, buf
).idup
;
448 bool contains(T
)(const scope T
[] ary
, const scope T
[] vals
...)
459 version (CoreUnittest
) template TT(T
...)
464 version (CoreUnittest
) template _arrayOp(Args
...)
466 alias _arrayOp
= arrayOp
!Args
;
471 static void check(string op
, TA
, TB
, T
, size_t N
)(TA a
, TB b
, const scope ref T
[N
] exp
)
474 _arrayOp
!(T
[], TA
, TB
, op
, "=")(res
[], a
, b
);
476 assert(res
[i
] == exp
[i
]);
479 static void check2(string unaOp
, string binOp
, TA
, TB
, T
, size_t N
)(TA a
, TB b
, const scope ref T
[N
] exp
)
482 _arrayOp
!(T
[], TA
, TB
, unaOp
, binOp
, "=")(res
[], a
, b
);
484 assert(res
[i
] == exp
[i
]);
487 static void test(T
, string op
, size_t N
= 16)(T a
, T b
, T exp
)
489 T
[N
] va
= a
, vb
= b
, vexp
= exp
;
491 check
!op(va
[], vb
[], vexp
);
492 check
!op(va
[], b
, vexp
);
493 check
!op(a
, vb
[], vexp
);
496 static void test2(T
, string unaOp
, string binOp
, size_t N
= 16)(T a
, T b
, T exp
)
498 T
[N
] va
= a
, vb
= b
, vexp
= exp
;
500 check2
!(unaOp
, binOp
)(va
[], vb
[], vexp
);
501 check2
!(unaOp
, binOp
)(va
[], b
, vexp
);
502 check2
!(unaOp
, binOp
)(a
, vb
[], vexp
);
505 alias UINTS
= TT
!(ubyte, ushort, uint, ulong);
506 alias INTS
= TT
!(byte, short, int, long);
507 alias FLOATS
= TT
!(float, double);
509 foreach (T
; TT
!(UINTS
, INTS
, FLOATS
))
511 test!(T
, "+")(1, 2, 3);
512 test!(T
, "-")(3, 2, 1);
513 static if (__traits(compiles
, { import std
.math
; }))
514 test!(T
, "^^")(2, 3, 8);
516 test2
!(T
, "u-", "+")(3, 2, 1);
519 foreach (T
; TT
!(UINTS
, INTS
))
521 test!(T
, "|")(1, 2, 3);
522 test!(T
, "&")(3, 1, 1);
523 test!(T
, "^")(3, 1, 2);
525 test2
!(T
, "u~", "+")(3, cast(T
)~2, 5);
528 foreach (T
; TT
!(INTS
, FLOATS
))
530 test!(T
, "-")(1, 2, -1);
531 test2
!(T
, "u-", "+")(-3, -2, -1);
532 test2
!(T
, "u-", "*")(-3, -2, -6);
535 foreach (T
; TT
!(UINTS
, INTS
, FLOATS
))
537 test!(T
, "*")(2, 3, 6);
538 test!(T
, "/")(8, 4, 2);
539 test!(T
, "%")(8, 6, 2);
543 // test handling of v op= exp
544 @nogc nothrow pure @safe unittest
547 arrayOp
!(uint[], uint, "+=")(c
[], 2);
550 static if (__traits(compiles
, { import std
.math
; }))
552 arrayOp
!(uint[], uint, "^^=")(c
[], 3);
558 // proper error message for UDT lacking certain ops
559 @nogc nothrow pure @safe unittest
561 static assert(!is(typeof(&arrayOp
!(int[4][], int[4], "+="))));
562 static assert(!is(typeof(&arrayOp
!(int[4][], int[4], "u-", "="))));
568 static assert(!is(typeof(&arrayOp
!(S
[], S
, "+="))));
569 static assert(!is(typeof(&arrayOp
!(S
[], S
[], "*", S
, "+="))));
572 S2
opBinary(string op
)(in S2
) @nogc pure nothrow
577 ref S2
opOpAssign(string op
)(in S2
) @nogc pure nothrow
583 static assert(is(typeof(&arrayOp
!(S2
[], S2
[], S2
[], S2
, "*", "+", "="))));
584 static assert(is(typeof(&arrayOp
!(S2
[], S2
[], S2
, "*", "+="))));
587 // test mixed type array op
588 @nogc nothrow pure @safe unittest
591 float[32] res
= 2.0f;
592 arrayOp
!(float[], const(uint)[], uint, "&", "*=")(res
[], a
[], 12);
597 // test mixed type array op
598 @nogc nothrow pure @safe unittest
602 float opBinary(string op
)(in S
) @nogc const pure nothrow
608 float[32] res
= 24.0f;
610 arrayOp
!(float[], const(S
)[], const(S
)[], "+", "/=")(res
[], s
[], s
[]);
615 // test scalar after operation argument
616 @nogc nothrow pure @safe unittest
618 float[32] res
, a
= 2, b
= 3;
620 arrayOp
!(float[], const(float)[], const(float)[], "*", float, "+", "=")(res
[], a
[], b
[], c
);
622 assert(v
== 2 * 3 + 4);
625 @nogc nothrow pure @safe unittest
627 // https://issues.dlang.org/show_bug.cgi?id=17964
629 uint[] a
= [1, 2, 3, 5, 6, 7];
630 uint[] b
= [1, 2, 3, 5, 6, 7];
637 // https://issues.dlang.org/show_bug.cgi?id=19796
638 nothrow pure @safe unittest
640 double[] data
= [0.5];
642 result
.length
= data
.length
;
644 assert(result
[0] == -0.5);
647 // https://issues.dlang.org/show_bug.cgi?id=21110
650 import core
.exception
;
652 static void assertThrown(T
: Throwable
, E
)(lazy E expression
, string msg
)
668 void func() { dst
[] = a
[] + b
[]; }
669 assertThrown
!AssertError(func(), "Array operations with mismatched lengths must throw an error");
672 // https://issues.dlang.org/show_bug.cgi?id=24272
677 B
opOpAssign(string op
)(B other
)
681 throw new Exception("");