KhronosGroup / OpenCL-Docs

OpenCL API, OpenCL C, Extensions, SPIR-V Environment Specs, Ref page, and C++ for OpenCL doc sources.
Other
359 stars 112 forks source link

Specify truthiness for consumption of vector components for logical operators #177

Open StuartDBrady opened 4 years ago

StuartDBrady commented 4 years ago

The OpenCL C specification does not define the semantics for the consumption of integer vector components as logic values for the operands of the binary logical operators (i.e. && and ||). I.e. although it is not stated, it must be the case that ((int2)(-1, -1) || (int2)(x, y)) == (int2)(-1, -1) and that ((int2)(0, 0) && (int2)(x, y)) == (int2)(0, 0), but it is not clear how the binary logical operators are defined for integer vector component values other than 0 and -1. (The version of Clang that I have tested constant-folds ((int2)(1, 1) || (int2)(x, y)) into (int2)(-1, -1).)

The specification also does not explicitly define the semantics of implicit casts from bool for the integer vector components of vector literals. I.e. is (int2)((bool)true, (bool)true) equivalent to (int2)(1, 1) or (int2)(-1, -1)? (The version of Clang that I have tested uses (int2)(1, 1).) [Note: this is incorrect: the semantics are well-defined.]

It would be good to clarify these semantics, if possible.

Note: the semantics for the representation of logic values as integer vector components for results are clearly defined for all operators including casts from bool scalars to the integer vector types, and for the relational built-in functions. The semantics for the consumption of integer vector components as logic values are clearly defined for the unary logical (negation) operator (i.e. !), the select() built-in function, the ternary selection operator (?), and the any() and all() built-in functions.

The OpenCL C conformance test suite has code in the integer_ops test to check that the binary and unary logical operators determine truth for integer vector components by comparing against 0. (Somewhat confusingly, the test for ! has been implemented as a binary operator test.)

bashbaug commented 4 years ago

Hi Stuart,

I think the semantics above are reasonably well-specified in the latest OpenCL C specs. In particular, for the logical operators and vector types:

https://www.khronos.org/registry/OpenCL/specs/2.2/html/OpenCL_C.html#operators-logical

For scalar types, the logical operators shall return 0 if the result of the operation is false and 1 if the result is true. For vector types, the logical operators shall return 0 if the result of the operation is false and -1 (i.e. all bits set) if the result is true.

For casts from bool to integer vector components:

https://www.khronos.org/registry/OpenCL/specs/2.2/html/OpenCL_C.html#built-in-scalar-data-types

bool: A conditional data type which is either true or false. The value true expands to the integer constant 1 and the value false expands to the integer constant 0.

These descriptions sound consistent with the version of Clang you are using.

Do you have suggestions to improve these descriptions further? Thanks!

StuartDBrady commented 4 years ago

Hi Stuart,

I think the semantics above are reasonably well-specified in the latest OpenCL C specs. In particular, for the logical operators and vector types:

https://www.khronos.org/registry/OpenCL/specs/2.2/html/OpenCL_C.html#operators-logical

For scalar types, the logical operators shall return 0 if the result of the operation is false and 1 if the result is true. For vector types, the logical operators shall return 0 if the result of the operation is false and -1 (i.e. all bits set) if the result is true.

Hi Ben,

While it's true that the result for each component must be -1 or 0, the specification does not state how integer vector components are consumed. The conformance tests seem to expect the same semantics as for scalars (i.e. all non-zero values are considered true). If we agree, then I could submit something to clarify this. However, it seems possible to me that the conformance tests might be too strict. (I.e. would there be benefit to allowing only the most significant bit of each integer vector component to be tested, or would this add needless complication?)

For casts from bool to integer vector components:

https://www.khronos.org/registry/OpenCL/specs/2.2/html/OpenCL_C.html#built-in-scalar-data-types

bool: A conditional data type which is either true or false. The value true expands to the integer constant 1 and the value false expands to the integer constant 0.

These descriptions sound consistent with the version of Clang you are using.

Agreed. However, in 6.2.2. Explicit Casts, we have:

When casting a bool to a vector integer data type, the vector components will be set to -1 (i.e. all bits set) if the bool value is true and 0 otherwise.

It seems reasonable to expect similar semantics when using bools within vector literals, i.e. one might expect (int2)((bool)true, (bool)true) to be equivalent to (int2)(bool)true, but this is inconsistent with what I see from Clang.

Note: the explicit cast to bool in my examples above might be unnecessary. I have found that in Clang, (int2)true and (int2)(bool)true are equivalent. However, the specification only goes as far as stating that true expands to 1 and false expands to 0. It does not state whether the integer constants that true and false expand to are actually of type bool (which I presume is the intent), and whether bool is therefore an integer type (i.e. can I increment false by 1 and expect to get true)?

Do you have suggestions to improve these descriptions further? Thanks!

I would be happy to submit improvements, but first I need a clear view of the intended, desired and relied-upon semantics.

bashbaug commented 4 years ago

the specification does not state how integer vector components are consumed.

Ah, I think I get it now. If we have:

int2 a = ...;
int2 b = ...;
int2 c = a || b;

The question is less about what c contains and more about how a and b are interpreted to compute c. One specific question is whether a component of a is considered true if it is non-zero, true if the MSB is set, or true based on some other condition. Is that correct?

If so, I've always assumed that a component of a is considered true if it is non-zero, and I think this is somewhat implied by the statement that (emphasis mine) "For built-in vector types, both operands are evaluated and the operators are applied component-wise", but perhaps this could be clarified further.

Regarding casting bool to a vector of integers, I agree that it is somewhat non-intuitive that (int2)((bool)true, (bool)true) is not equivalent to (int2)(bool)true, but I think this is well-specified.

I'm guessing the bool casting semantics are driven by the select() built-in function, since otherwise the behavior of select(a, b, true) would be VERY non-intuitive for vector values of a and b.

StuartDBrady commented 4 years ago

Ah, I think I get it now. If we have:

int2 a = ...;
int2 b = ...;
int2 c = a || b;

The question is less about what c contains and more about how a and b are interpreted to compute c. One specific question is whether a component of a is considered true if it is non-zero, true if the MSB is set, or true based on some other condition. Is that correct?

Yeah, exactly! This seems as though it ought to be straightforward to specify, even if the behavior is explicitly made undefined.

If so, I've always assumed that a component of a is considered true if it is non-zero, and I think this is somewhat implied by the statement that (emphasis mine) "For built-in vector types, both operands are evaluated and the operators are applied component-wise", but perhaps this could be clarified further.

Perhaps, but even if vectors components were considered true(*) based on some other criteria, the statement that "for built-in vector types, both operands are evaluated and the operators are applied component-wise" would be equally valid. The question here is whether "the operators" in this sentence refers to the exact same operators as are applied for scalar types.

Given that the ? operator works differently for vectors and that casts from scalar bool works differently, there doesn't seem to be an especially strong implication in the specification that the scalar and vector operators have the same semantics. I.e. the implication that is there seems weak enough to that there could be a risk of specification being interpreted alternatively. I would agree, though, that it does seem to suggest that the original intent was for the semantics to be the same.

If we change this to "the operators are applied component-wise with the same semantics as for scalar types" or some equivalent wording, that would be sufficient, in my view. However, I'd welcome alternative suggestions if there are any differing views on this.

(* Note: I'm avoiding formatting this as true here as I'm referring to the internal notion of truth as it applies within the && and || operators, considered independently of the true and false literals.)

Regarding casting bool to a vector of integers, I agree that it is somewhat non-intuitive that (int2)((bool)true, (bool)true) is not equivalent to (int2)(bool)true, but I think this is well-specified.

I'm guessing the bool casting semantics are driven by the select() built-in function, since otherwise the behavior of select(a, b, true) would be VERY non-intuitive for vector values of a and b.

I see: the statement that "a vector literal operates as an overloaded function" is sufficient to indicate that the usual implicit type conversion rules apply to the function parameters (i.e. the vector elements). I agree, then, that it is well-specified, albeit somewhat unfortunately so. Thank you for the correction. I've updated the title and description of this issue accordingly.