Add shift operators, remove shiftLeft, shiftRight builtins - #2989
Conversation
Part 1: Revert "Remove shift tokens and add shift built-in functions (gpuweb#2713)" This reverts commit 48efd97. Part 2: Since the removal of shift operators, the shiftLeft and shiftRight builtins added the ability to take operands that were AbstractInt or vectors of AbstractInt. We already have a rule that creation-time expressions must not overflow. For shift-left, that can only be determined if both its operands are abstract. So split the description of shift-left into all-concrete operands, and all-abstract operands. Additionally, spell out the overflow condition for the all-abstract form of shift-left. Fixes: gpuweb#2092
|
This ended up being more exciting because of the interaction with AbstractInt. This actually constrains both operands of left-shift to be concrete or both non-concrete. I think it was already problematic to have shiftLeft(AbstractInt, dynamic and concrete) because then the compiler can't tell if there will be overflow. So force the first operand to be concrete as well. |
|
cc: @ben-clayton |
|
FYI. The grammar analysis dump expands to this now: It's still LALR(1). |
| [=Component-wise=] when |T| is a vector. | ||
|
|
||
| It is a [=shader-creation error=] if the calculation would overflow the AbstractInt type. | ||
| Overflow occurs when both 0 and 1 bits would be discarded, |
There was a problem hiding this comment.
Why does this specify 0 and 1 bits specifically? How can you shift off the 0 bit without shifting off the 1 bit?
There was a problem hiding this comment.
Those are the values of the bits, not their position.
So, bits with value 0 and bits with value 1.
I'll clarify.
| It is a [=shader-creation error=] if the calculation would overflow the AbstractInt type. | ||
| Overflow occurs when both 0 and 1 bits would be discarded, | ||
| or when the most significant bit of |e1| would differ from the most | ||
| significant bit of the result. |
There was a problem hiding this comment.
I don't understand this restriction if I have bits 0100 and I << 1 I'll get 1000 and now the most significant bit of |e1| differs from the result. Is this overflow?
There was a problem hiding this comment.
So the essence of the example is you have x = 1 << 62, or 0100 followed by 60 zeroes. So it's a 64bit number, which is the minimum required for AbstractInt.
If I compute (x << 1) then it's 1 followed by 63 zeroes. That 1 bit in the most significant position is the sign bit, because numbers are twos complement. So now the author expects ( x << 1 ) to have value 1 << 63. But (x << 1) is negative. So I call that overflow.
Overflow does not occur if and only if: all the discarded bits are the same value, and are the same as the resulting sign bit. That is, when shifting by y bits, overflow does not occur if the most-significant y+1 bits are the same.
I described the overflow case instead of the not-overflow case, and I wasn't certain which would read better.
There was a problem hiding this comment.
Maybe would be easier to describe as a sign change or the size of e2 is too large (e.g. 64).
There was a problem hiding this comment.
Chatted offline and saying the top e2+1 bits must be the same makes things a bit clearer. so 1100 << 1 is valid because the top 2 bits are 1 but << 2 would be invalid as the top 3 bits differ.
| |e2|: |TS|<br> | ||
| [ALLINTEGRALDECL]<br> | ||
| |TS| is vec|N|<AbstractInt> or vec|N|<u32> when |T| is vec|N|<S>, | ||
| otherwise |TS| is AbstractInt or u32 |
There was a problem hiding this comment.
Can we write this like shift left?
|TS| is AbstractInt or u32 when |T| is |S|,
otherwise |TS| is vec|N|<: AbstractInt> or vec|N|<u32>
There was a problem hiding this comment.
Yes. I wondered if that was too much indirection. :-)
There was a problem hiding this comment.
Oh, I remember: I did it this way to make sure that the same N value is used.
There was a problem hiding this comment.
The N always has to be the same, it can't vary within a precondition (a lot of our builtins are written with that assumption I think)
There was a problem hiding this comment.
I didn't want to leave that potential ambiguity.
But I can change it; I do agree it reads better to have the simpler expression first
|
|
||
| [=Component-wise=] when |T| is a vector. | ||
|
|
||
| It is a [=shader-creation error=] if the calculation would overflow the AbstractInt type. |
There was a problem hiding this comment.
By, if the calculation do you mean if the number of bits shifted is larger then the bit-width of an AbstractInt? If so, could we just say that e2 is modulo the bit-width of an abstract int?
There was a problem hiding this comment.
We're trying to allow a future where AbstractInt has more than 64 bits. That's why we don't allow overflow.
So we don't want to have a calculation depend on "the" bit width of AbstractInt.
There was a problem hiding this comment.
Are you implying that an abstract int, in the future, could have multiple bit widths? Otherwise there is aways a bit-width for an abstract int so saying the bit-width of abstract int should always be correct regardless if abstract int is int8_t, int64_t or int256_t, no?
There was a problem hiding this comment.
You would only have one width of abstract int per translation unit.
So, imagine "abstractInt256" feature which updates only one sentence, to say basically
The AbstractInt type is the set of integers i, with -2255 ≤ i < 2255.
And nothing else. And any pre-existing shader that was valid before will still be valid with this feature, and it would mean the same thing as before.
There was a problem hiding this comment.
I see, and a shader which previous did 1 << 65 and ended up doing a 1<<1 will now actually 1 << 65
| and discarding the least significant bits. | ||
|
|
||
| The number of bits to shift is the value of |e2|. | ||
| If |e1| has a [=concrete=] type, the shift value is modulo the bit width of |e1|.<br> |
There was a problem hiding this comment.
if e1 is abstract, do we modulo the bit-width of an abstract int? Otherwise, what if I do 1 >> 65?
There was a problem hiding this comment.
No. We don't want to rely on AbstractInt having a specific bit width.
If you do 1 >> 65 then you get zero.
If you do 1 << 65 you get shader creation error.
If someone makes an extension where AbstractInt has 128 bits, then 1 << 64 can become meaningful, and strictly more programs become valid. But old programs that are valid still have the same meaning.
There was a problem hiding this comment.
Right and the need to specify this for concrete numbers is because they could happen in the shader where it may modulo. But an abstract number happens compile time and we can make it 0.
| | [=syntax/shift_expression=] | ||
|
|
||
| | [=syntax/additive_expression=] [=syntax/greater_than=] [=syntax/additive_expression=] | ||
| | [=syntax/shift_expression=] [=syntax/less_than=] [=syntax/shift_expression=] |
There was a problem hiding this comment.
Why did the ordering of >, <=, etc change?
There was a problem hiding this comment.
Artifact. I started with a plain revert of the previous removal.
| <div class='syntax' noexport='true'> | ||
| <dfn for=syntax>shift_right</dfn> : | ||
|
|
||
| | `'>>'` |
| <div class='syntax' noexport='true'> | ||
| <dfn for=syntax>shift_right_equal</dfn> : | ||
|
|
||
| | `'>>='` (Code points: `U+005E` `U+005E` `U+003D`) |
* Add shift operators, remove shiftLeft, shiftRight builtins Part 1: Revert "Remove shift tokens and add shift built-in functions (gpuweb#2713)" This reverts commit 48efd97. Part 2: Since the removal of shift operators, the shiftLeft and shiftRight builtins added the ability to take operands that were AbstractInt or vectors of AbstractInt. We already have a rule that creation-time expressions must not overflow. For shift-left, that can only be determined if both its operands are abstract. So split the description of shift-left into all-concrete operands, and all-abstract operands. Additionally, spell out the overflow condition for the all-abstract form of shift-left. Fixes: gpuweb#2092 * Apply review feedback * Split right-shift into concrete and abstract forms
Part 1:
Revert "Remove shift tokens and add shift built-in functions (#2713)"
This reverts commit 48efd97.
Part 2:
Since the removal of shift operators, the shiftLeft and shiftRight
builtins added the ability to take operands that were AbstractInt
or vectors of AbstractInt.
We already have a rule that creation-time expressions must not
overflow. For shift-left, that can only be determined if both its
operands are abstract. So split the description of shift-left
into all-concrete operands, and all-abstract operands.
Additionally, spell out the overflow condition for the all-abstract
form of shift-left.
Fixes: #2092