| # Proposal: Permit Signed Integers as Shift Counts for Go 2 | |
| Robert Griesemer | |
| Last updated: January 17, 2019 | |
| Discussion at [golang.org/issue/19113](https://golang.org/issue/19113). | |
| ## Summary | |
| We propose to change the language spec such that the shift count | |
| (the rhs operand in a `<<` or `>>` operation) | |
| may be a _signed_ or unsigned (non-constant) integer, | |
| or any non-negative constant value that can be represented as an integer. | |
| ## Background | |
| See **Rationale** section below. | |
| ## Proposal | |
| We change the language spec regarding shift operations as follows: | |
| In the section on [Operators](https://golang.org/ref/spec#Operators), the text: | |
| > The right operand in a shift expression must have unsigned integer type | |
| > or be an untyped constant that can be converted to unsigned integer type. | |
| to | |
| > The right operand in a shift expression must have integer type | |
| > or be an untyped constant that can be converted to an integer type. | |
| > If the right operand is constant, it must not be negative. | |
| Furthermore, in the section on [Integer operators](https://golang.org/ref/spec#Arithmetic_operators), we change the text: | |
| > The shift operators shift the left operand by the shift count specified by the right operand. | |
| to | |
| > The shift operators shift the left operand by the shift count specified by the right operand. | |
| > A run-time panic occurs if a non-constant shift count is negative. | |
| ## Rationale | |
| Since Go's inception, shift counts had to be of unsigned integer type | |
| (or a non-negative constant representable as an unsigned integer). | |
| The idea behind this rule was that | |
| (a) the spec didn't have to explain what happened for negative values, | |
| and (b) the implementation didn't have to deal with negative values | |
| possibly occurring at run-time. | |
| In retrospect, this may have been a mistake; | |
| for example see | |
| [this comment by Russ Cox](https://github.com/golang/go/issues/18616#issuecomment-278852766) | |
| during the development of | |
| [`math/bits`](https://golang.org/pkg/math/bits). | |
| It turns out that we could actually change the spec | |
| in a backward-compatible way in this regard, | |
| and this proposal is suggesting that we do exactly that. | |
| There are other language features where the result (`len(x)`), | |
| argument (`n` in `make([]T, n)`) or constant (`n` in `[n]T`) | |
| are known to be never negative or must not be negative, | |
| yet we return an `int` (for `len`, `cap`) or permit any integer type. | |
| Requiring an unsigned integer type for shift counts is frequently | |
| a non-issue because the shift count is constant (see below); | |
| but in some cases explicit `uint` conversions are needed, | |
| or the code around the shift is carefully crafted to use unsigned integers. | |
| In either case, readability is slightly compromised, | |
| and more decision making is required when crafting the code: | |
| Should we use a conversion or type other variables as unsigned integers? | |
| Finally, and perhaps most importantly, there may be cases | |
| where we simply convert an integer to an unsigned integer | |
| and in the process inadvertently make an (invalid) negative value | |
| positive in the process, possibly hiding a bug that way | |
| (resulting in a shift by a very large number, | |
| leading to 0 or -1 depending on the shifted value). | |
| If we permit any integer type, the existing code will continue to work. | |
| Places where we currently use a `uint` conversion won't need it anymore, | |
| and code that is crafted for an unsigned shift count | |
| may not require unsigned integers elsewhere. | |
| (There’s a remote chance that some code relies on the | |
| fact that a negative value becomes a large positive value | |
| with a uint conversion; such code would continue to need the uint conversion. | |
| We cannot remove the uint conversions without testing.) | |
| An investigation of shifts in the current standard library and tests | |
| as of 2/15/2017 (excluding package-external tests) found: | |
| - 8081 shifts total; 5457 (68%) right shifts vs 2624 (32%) left shifts | |
| - 6151 (76%) of those are shifts by a (typed or untyped) constant | |
| - 1666 (21%) shifts are in tests (_test.go files) | |
| - 253 (3.1%) shifts use an explicit uint conversion for the shift count | |
| If we only look at shifts outside of test files we have: | |
| - 6415 shifts total; 4548 (71%) right shifts vs 1867 (29%) left shifts | |
| - 5759 (90%) of those are shifts by a (typed or untyped) constant | |
| - 243 (3.8%) shifts use an explicit uint conversion for the shift count | |
| The overwhelming majority (90%) of shifts | |
| outside of testing code is by untyped constant values, | |
| and none of those turns out to require a conversion. | |
| This proposal won't affect that code. | |
| From the remaining 10% of all shifts, | |
| 38% (3.8% of the total number of shifts) require a `uint` conversion. | |
| That's a significant number. | |
| In the remaining 62% of non-constant shifts, | |
| the shift count expression must be using a variable | |
| that's of unsigned integer type, and often a conversion is required there. | |
| A typical example is [archive/tar/strconv.go:88](https://golang.org/src/archive/tar/strconv.go#L88): | |
| ```Go | |
| func fitsInBase256(n int, x int64) bool { | |
| var binBits = uint(n-1) * 8 // <<<< uint cast | |
| return n >= 9 || (x >= -1<<binBits && x < 1<<binBits) | |
| } | |
| ``` | |
| In this case, `n` is an incoming argument, | |
| and we can't be sure that `n > 1` without further analysis of the callers, | |
| and thus there's a possibility that `n - 1` is negative. | |
| The `uint` conversions hides that error silently. | |
| Another one is [cmd/compile/internal/gc/esc.go:1460](https://golang.org/src/cmd/compile/internal/gc/esc.go#L1460): | |
| ```Go | |
| shift := uint(bitsPerOutputInTag*(vargen-1) + EscReturnBits) // <<<< uint cast | |
| old := (e >> shift) & bitsMaskForTag | |
| ``` | |
| Or [src/fmt/scan.go:604](https://golang.org/src/fmt/scan.go#L604): | |
| ```Go | |
| n := uint(bitSize) // uint cast | |
| x := (r << (64 - n)) >> (64 - n) | |
| ``` | |
| Many (most?) of the non-constant shifts | |
| that don't use an explicit `uint` conversion in the shift expression itself | |
| appear to have a `uint` conversion before that expression. | |
| Most (all?) of these conversions wouldn't be necessary anymore. | |
| The drawback of permitting signed integers | |
| where negative values are not permitted is that we need to check | |
| for negative values at run-time and panic as needed, | |
| as we do elsewhere (e.g., for `make`). | |
| This requires a bit more code; an estimated minimum of | |
| two extra instructions per non-constant shift: a test and a branch). | |
| However, none of the existing code will incur that cost | |
| because all shift counts are unsigned integers at this point, | |
| thus the compiler can omit the check. | |
| For new code using non-constant integer shift counts, | |
| often the compiler may be able to prove that | |
| the operand is non-negative and then also avoid the extra instructions. | |
| The compiler can already often prove that a value is non-negative | |
| (done anyway for automatic bounds check elimination), | |
| and in that case it can avoid the new branch entirely. | |
| Of course, as a last resort, | |
| an explicit `uint` conversion or mask in the source code | |
| will allow programmers to force the removal of the check, | |
| just as an explicit mask of the shift count today | |
| avoids the oversize shift check. | |
| On the plus side, almost all code that used a `uint` conversion | |
| before won't need it anymore, and it will be safer for that | |
| since possibly negative values will not be silently converted into positive ones. | |
| ## Compatibility | |
| This is a backward-compatible language change: | |
| Any valid program will continue to be valid, | |
| and will continue to run exactly the same, | |
| without any performance impact. | |
| New programs may be using non-constant integer shift counts | |
| as right operands in shift operations. | |
| Except for fairly small changes to the spec, | |
| the compiler, and go/types, | |
| (and possibly go/vet and golint if they look at shift operations), | |
| no other code needs to be changed. | |
| There's a (remote) chance that some code makes intentional | |
| use of negative shift count values converted to unsigned: | |
| ```Go | |
| var shift int = <some expression> // use negative value to indicate that we want a 0 result | |
| result := x << uint(shift) | |
| ``` | |
| Here, `uint(shift)` will produce a very large positive | |
| value if `shift` is negative, resulting in `x << uint(shift)` becoming 0. | |
| Because such code required an explicit conversion | |
| and will continue to have an explicit conversion, it will continue to work. | |
| Programmers removing uint conversions from their code will | |
| need to keep this in mind. Most of the time, however, a panic | |
| resulting from removing the conversion will indicate a bug. | |
| ## Implementation | |
| The implementation requires: | |
| - Adjusting the compiler’s type-checker to allow signed integer shift counts | |
| - Adjusting the compiler’s back-end to generate the extra test | |
| - Possibly some (minimal) runtime work to support the new runtime panic | |
| - Adjusting go/types to allow signed integer shift counts | |
| - Adjusting the Go spec as outlined earlier in this proposal | |
| - Adjusting gccgo accordingly (type-checker and back-end) | |
| - Testing the new changes by adding new tests | |
| No library changes will be needed as this is a 100% backward-compatible change. | |
| Robert Griesemer and Keith Randall plan to split the work | |
| and aim to have all the changes ready at the start of the Go 1.13 cycle, | |
| around February 1. Ian Lance Taylor will look into the gccgo changes. | |
| As noted in our | |
| [“Go 2, here we come!” blog post](https://blog.golang.org/go2-here-we-come), | |
| the development cycle will serve as a way to collect experience about | |
| these new features and feedback from (very) early adopters. | |
| At the release freeze, May 1, we will revisit the proposed features | |
| and decide whether to include them in Go 1.13. |