1
0
mirror of https://github.com/golang/go synced 2024-11-26 15:06:52 -07:00

cmd/compile: implement shifts by signed amounts

Allow shifts by signed amounts. Panic if the shift amount is negative.

TODO: We end up doing two compares per shift, see Ian's comment
https://github.com/golang/go/issues/19113#issuecomment-443241799 that
we could do it with a single comparison in the normal case.

The prove pass mostly handles this code well. For instance, it removes the
<0 check for cases like this:
    if s >= 0 { _ = x << s }
    _ = x << len(a)

This case isn't handled well yet:
    _ = x << (y & 0xf)
I'll do followon CLs for unhandled cases as needed.

Update #19113

R=go1.13

Change-Id: I839a5933d94b54ab04deb9dd5149f32c51c90fa1
Reviewed-on: https://go-review.googlesource.com/c/158719
Run-TryBot: Keith Randall <khr@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Josh Bleecher Snyder <josharian@gmail.com>
This commit is contained in:
Keith Randall 2019-01-20 10:52:11 -08:00 committed by Keith Randall
parent e1acd854f7
commit 585c9e8412
10 changed files with 144 additions and 15 deletions

View File

@ -13,6 +13,7 @@ var runtimeDecls = [...]struct {
{"panicindex", funcTag, 5}, {"panicindex", funcTag, 5},
{"panicslice", funcTag, 5}, {"panicslice", funcTag, 5},
{"panicdivide", funcTag, 5}, {"panicdivide", funcTag, 5},
{"panicshift", funcTag, 5},
{"panicmakeslicelen", funcTag, 5}, {"panicmakeslicelen", funcTag, 5},
{"throwinit", funcTag, 5}, {"throwinit", funcTag, 5},
{"panicwrap", funcTag, 5}, {"panicwrap", funcTag, 5},

View File

@ -18,6 +18,7 @@ func newobject(typ *byte) *any
func panicindex() func panicindex()
func panicslice() func panicslice()
func panicdivide() func panicdivide()
func panicshift()
func panicmakeslicelen() func panicmakeslicelen()
func throwinit() func throwinit()
func panicwrap() func panicwrap()

View File

@ -296,6 +296,7 @@ var (
msanwrite, msanwrite,
newproc, newproc,
panicdivide, panicdivide,
panicshift,
panicdottypeE, panicdottypeE,
panicdottypeI, panicdottypeI,
panicindex, panicindex,

View File

@ -84,6 +84,7 @@ func initssaconfig() {
panicnildottype = sysfunc("panicnildottype") panicnildottype = sysfunc("panicnildottype")
panicoverflow = sysfunc("panicoverflow") panicoverflow = sysfunc("panicoverflow")
panicslice = sysfunc("panicslice") panicslice = sysfunc("panicslice")
panicshift = sysfunc("panicshift")
raceread = sysfunc("raceread") raceread = sysfunc("raceread")
racereadrange = sysfunc("racereadrange") racereadrange = sysfunc("racereadrange")
racewrite = sysfunc("racewrite") racewrite = sysfunc("racewrite")
@ -2128,7 +2129,13 @@ func (s *state) expr(n *Node) *ssa.Value {
case OLSH, ORSH: case OLSH, ORSH:
a := s.expr(n.Left) a := s.expr(n.Left)
b := s.expr(n.Right) b := s.expr(n.Right)
return s.newValue2(s.ssaShiftOp(n.Op, n.Type, n.Right.Type), a.Type, a, b) bt := b.Type
if bt.IsSigned() {
cmp := s.newValue2(s.ssaOp(OGE, bt), types.Types[TBOOL], b, s.zeroVal(bt))
s.check(cmp, panicshift)
bt = bt.ToUnsigned()
}
return s.newValue2(s.ssaShiftOp(n.Op, n.Type, bt), a.Type, a, b)
case OANDAND, OOROR: case OANDAND, OOROR:
// To implement OANDAND (and OOROR), we introduce a // To implement OANDAND (and OOROR), we introduce a
// new temporary variable to hold the result. The // new temporary variable to hold the result. The

View File

@ -660,8 +660,8 @@ func typecheck1(n *Node, top int) (res *Node) {
r = defaultlit(r, types.Types[TUINT]) r = defaultlit(r, types.Types[TUINT])
n.Right = r n.Right = r
t := r.Type t := r.Type
if !t.IsInteger() || t.IsSigned() { if !t.IsInteger() {
yyerror("invalid operation: %v (shift count type %v, must be unsigned integer)", n, r.Type) yyerror("invalid operation: %v (shift count type %v, must be integer)", n, r.Type)
n.Type = nil n.Type = nil
return n return n
} }

View File

@ -1115,7 +1115,8 @@ func needRaceCleanup(sym interface{}, v *Value) bool {
case OpStaticCall: case OpStaticCall:
switch v.Aux.(fmt.Stringer).String() { switch v.Aux.(fmt.Stringer).String() {
case "runtime.racefuncenter", "runtime.racefuncexit", "runtime.panicindex", case "runtime.racefuncenter", "runtime.racefuncexit", "runtime.panicindex",
"runtime.panicslice", "runtime.panicdivide", "runtime.panicwrap": "runtime.panicslice", "runtime.panicdivide", "runtime.panicwrap",
"runtime.panicshift":
// Check for racefuncenter will encounter racefuncexit and vice versa. // Check for racefuncenter will encounter racefuncexit and vice versa.
// Allow calls to panic* // Allow calls to panic*
default: default:

View File

@ -968,7 +968,7 @@ func isZeroArgRuntimeCall(s *obj.LSym) bool {
return false return false
} }
switch s.Name { switch s.Name {
case "runtime.panicindex", "runtime.panicslice", "runtime.panicdivide", "runtime.panicwrap": case "runtime.panicindex", "runtime.panicslice", "runtime.panicdivide", "runtime.panicwrap", "runtime.panicshift":
return true return true
} }
return false return false

View File

@ -23,16 +23,16 @@ func panicCheckMalloc(err error) {
var indexError = error(errorString("index out of range")) var indexError = error(errorString("index out of range"))
// The panicindex, panicslice, and panicdivide functions are called by // The panic{index,slice,divide,shift} functions are called by
// code generated by the compiler for out of bounds index expressions, // code generated by the compiler for out of bounds index expressions,
// out of bounds slice expressions, and division by zero. The // out of bounds slice expressions, division by zero, and shift by negative.
// panicdivide (again), panicoverflow, panicfloat, and panicmem // The panicdivide (again), panicoverflow, panicfloat, and panicmem
// functions are called by the signal handler when a signal occurs // functions are called by the signal handler when a signal occurs
// indicating the respective problem. // indicating the respective problem.
// //
// Since panicindex and panicslice are never called directly, and // Since panic{index,slice,shift} are never called directly, and
// since the runtime package should never have an out of bounds slice // since the runtime package should never have an out of bounds slice
// or array reference, if we see those functions called from the // or array reference or negative shift, if we see those functions called from the
// runtime package we turn the panic into a throw. That will dump the // runtime package we turn the panic into a throw. That will dump the
// entire runtime stack for easier debugging. // entire runtime stack for easier debugging.
@ -68,6 +68,16 @@ func panicoverflow() {
panic(overflowError) panic(overflowError)
} }
var shiftError = error(errorString("negative shift amount"))
func panicshift() {
if hasPrefix(funcname(findfunc(getcallerpc())), "runtime.") {
throw(string(shiftError.(errorString)))
}
panicCheckMalloc(shiftError)
panic(shiftError)
}
var floatError = error(errorString("floating point error")) var floatError = error(errorString("floating point error"))
func panicfloat() { func panicfloat() {

View File

@ -1,4 +1,4 @@
// errorcheck // compile
// Copyright 2009 The Go Authors. All rights reserved. // Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
@ -7,8 +7,8 @@
package main package main
func main() { func main() {
var s int = 0; var s int = 0
var x int = 0; var x int = 0
x = x << s; // ERROR "illegal|inval|shift" x = x << s // as of 1.13, these are ok
x = x >> s; // ERROR "illegal|inval|shift" x = x >> s // as of 1.13, these are ok
} }

View File

@ -0,0 +1,108 @@
// run
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import "reflect"
var tests = []interface{}{
func(x int, s int) int {
return x << s
},
func(x int, s int64) int {
return x << s
},
func(x int, s int32) int {
return x << s
},
func(x int, s int16) int {
return x << s
},
func(x int, s int8) int {
return x << s
},
func(x int, s int) int {
return x >> s
},
func(x int, s int64) int {
return x >> s
},
func(x int, s int32) int {
return x >> s
},
func(x int, s int16) int {
return x >> s
},
func(x int, s int8) int {
return x >> s
},
func(x uint, s int) uint {
return x << s
},
func(x uint, s int64) uint {
return x << s
},
func(x uint, s int32) uint {
return x << s
},
func(x uint, s int16) uint {
return x << s
},
func(x uint, s int8) uint {
return x << s
},
func(x uint, s int) uint {
return x >> s
},
func(x uint, s int64) uint {
return x >> s
},
func(x uint, s int32) uint {
return x >> s
},
func(x uint, s int16) uint {
return x >> s
},
func(x uint, s int8) uint {
return x >> s
},
}
func main() {
for _, t := range tests {
runTest(reflect.ValueOf(t))
}
}
func runTest(f reflect.Value) {
xt := f.Type().In(0)
st := f.Type().In(1)
for _, x := range []int{1, 0, -1} {
for _, s := range []int{-99, -64, -63, -32, -31, -16, -15, -8, -7, -1, 0, 1, 7, 8, 15, 16, 31, 32, 63, 64, 99} {
args := []reflect.Value{
reflect.ValueOf(x).Convert(xt),
reflect.ValueOf(s).Convert(st),
}
if s < 0 {
shouldPanic(func() {
f.Call(args)
})
} else {
f.Call(args) // should not panic
}
}
}
}
func shouldPanic(f func()) {
defer func() {
if recover() == nil {
panic("did not panic")
}
}()
f()
}