// order of evaluation. Makes walk easier, because it
// can (after this runs) reorder at will within an expression.
//
-// Rewrite x op= y into x = x op y.
+// Rewrite m[k] op= r into m[k] = m[k] op r if op is / or %.
//
// Introduce temporaries as needed by runtime routines.
// For example, the map runtime routines take the map key
default:
Fatalf("ordermapassign %v", n.Op)
- case OAS:
+ case OAS, OASOP:
if n.Left.Op == OINDEXMAP {
// Make sure we evaluate the RHS before starting the map insert.
// We need to make sure the RHS won't panic. See issue 22881.
o.cleanTemp(t)
case OASOP:
- // Special: rewrite l op= r into l = l op r.
- // This simplifies quite a few operations;
- // most important is that it lets us separate
- // out map read from map write when l is
- // a map index expression.
t := o.markTemp()
n.Left = o.expr(n.Left, nil)
n.Right = o.expr(n.Right, nil)
- n.Left = o.safeExpr(n.Left)
- tmp1 := treecopy(n.Left, src.NoXPos)
- if tmp1.Op == OINDEXMAP {
- tmp1.SetIndexMapLValue(false)
+ if instrumenting || n.Left.Op == OINDEXMAP && (n.SubOp() == ODIV || n.SubOp() == OMOD) {
+ // Rewrite m[k] op= r into m[k] = m[k] op r so
+ // that we can ensure that if op panics
+ // because r is zero, the panic happens before
+ // the map assignment.
+
+ n.Left = o.safeExpr(n.Left)
+
+ l := treecopy(n.Left, src.NoXPos)
+ if l.Op == OINDEXMAP {
+ l.SetIndexMapLValue(false)
+ }
+ l = o.copyExpr(l, n.Left.Type, false)
+ n.Right = nod(n.SubOp(), l, n.Right)
+ n.Right = typecheck(n.Right, Erv)
+ n.Right = o.expr(n.Right, nil)
+
+ n.Op = OAS
+ n.ResetAux()
}
- tmp1 = o.copyExpr(tmp1, n.Left.Type, false)
- n.Right = nod(n.SubOp(), tmp1, n.Right)
- n.Right = typecheck(n.Right, Erv)
- n.Right = o.expr(n.Right, nil)
- n.Op = OAS
- n.ResetAux()
+
o.mapAssign(n)
o.cleanTemp(t)
}
}
-// nan is a good test because nan != nan, and nan has
-// a randomized hash value.
-func TestNan(t *testing.T) {
- m := make(map[float64]int, 0)
- nan := math.NaN()
- m[nan] = 1
- m[nan] = 2
- m[nan] = 4
+func testMapNan(t *testing.T, m map[float64]int) {
if len(m) != 3 {
t.Error("length wrong")
}
}
}
+// nan is a good test because nan != nan, and nan has
+// a randomized hash value.
+func TestMapAssignmentNan(t *testing.T) {
+ m := make(map[float64]int, 0)
+ nan := math.NaN()
+
+ // Test assignment.
+ m[nan] = 1
+ m[nan] = 2
+ m[nan] = 4
+ testMapNan(t, m)
+}
+
+// nan is a good test because nan != nan, and nan has
+// a randomized hash value.
+func TestMapOperatorAssignmentNan(t *testing.T) {
+ m := make(map[float64]int, 0)
+ nan := math.NaN()
+
+ // Test assignment operations.
+ m[nan] += 1
+ m[nan] += 2
+ m[nan] += 4
+ testMapNan(t, m)
+}
+
+func TestMapOperatorAssignment(t *testing.T) {
+ m := make(map[int]int, 0)
+
+ // "m[k] op= x" is rewritten into "m[k] = m[k] op x"
+ // differently when op is / or % than when it isn't.
+ // Simple test to make sure they all work as expected.
+ m[0] = 12345
+ m[0] += 67890
+ m[0] /= 123
+ m[0] %= 456
+
+ const want = (12345 + 67890) / 123 % 456
+ if got := m[0]; got != want {
+ t.Errorf("got %d, want %d", got, want)
+ }
+}
+
// Maps aren't actually copied on assignment.
func TestAlias(t *testing.T) {
m := make(map[int]int, 0)
func TestGrowWithNaN(t *testing.T) {
m := make(map[float64]int, 4)
nan := math.NaN()
+
+ // Use both assignment and assignment operations as they may
+ // behave differently.
m[nan] = 1
m[nan] = 2
- m[nan] = 4
+ m[nan] += 4
+
cnt := 0
s := 0
growflag := true
for k, v := range m {
if growflag {
// force a hashtable resize
- for i := 0; i < 100; i++ {
+ for i := 0; i < 50; i++ {
m[float64(i)] = i
}
+ for i := 50; i < 100; i++ {
+ m[float64(i)] += i
+ }
growflag = false
}
if k != k {
negzero := math.Copysign(0.0, -1.0)
m := make(map[FloatInt]int, 4)
m[FloatInt{0.0, 0}] = 1
- m[FloatInt{0.0, 1}] = 2
- m[FloatInt{0.0, 2}] = 4
+ m[FloatInt{0.0, 1}] += 2
+ m[FloatInt{0.0, 2}] += 4
m[FloatInt{0.0, 3}] = 8
growflag := true
s := 0
// an iterator is still using them.
func TestIterGrowWithGC(t *testing.T) {
m := make(map[int]int, 4)
- for i := 0; i < 16; i++ {
+ for i := 0; i < 8; i++ {
m[i] = i
}
+ for i := 8; i < 16; i++ {
+ m[i] += i
+ }
growflag := true
bitmask := 0
for k := range m {
}
}
+func benchmarkMapOperatorAssignInt32(b *testing.B, n int) {
+ a := make(map[int32]int)
+ for i := 0; i < b.N; i++ {
+ a[int32(i&(n-1))] += i
+ }
+}
+
func benchmarkMapDeleteInt32(b *testing.B, n int) {
a := make(map[int32]int, n)
b.ResetTimer()
}
}
+func benchmarkMapOperatorAssignInt64(b *testing.B, n int) {
+ a := make(map[int64]int)
+ for i := 0; i < b.N; i++ {
+ a[int64(i&(n-1))] += i
+ }
+}
+
func benchmarkMapDeleteInt64(b *testing.B, n int) {
a := make(map[int64]int, n)
b.ResetTimer()
}
}
+func benchmarkMapOperatorAssignStr(b *testing.B, n int) {
+ k := make([]string, n)
+ for i := 0; i < len(k); i++ {
+ k[i] = strconv.Itoa(i)
+ }
+ b.ResetTimer()
+ a := make(map[string]string)
+ for i := 0; i < b.N; i++ {
+ key := k[i&(n-1)]
+ a[key] += key
+ }
+}
+
func benchmarkMapDeleteStr(b *testing.B, n int) {
i2s := make([]string, n)
for i := 0; i < n; i++ {
b.Run("Str", runWith(benchmarkMapAssignStr, 1<<8, 1<<16))
}
+func BenchmarkMapOperatorAssign(b *testing.B) {
+ b.Run("Int32", runWith(benchmarkMapOperatorAssignInt32, 1<<8, 1<<16))
+ b.Run("Int64", runWith(benchmarkMapOperatorAssignInt64, 1<<8, 1<<16))
+ b.Run("Str", runWith(benchmarkMapOperatorAssignStr, 1<<8, 1<<16))
+}
+
func BenchmarkMapDelete(b *testing.B) {
b.Run("Int32", runWith(benchmarkMapDeleteInt32, 100, 1000, 10000))
b.Run("Int64", runWith(benchmarkMapDeleteInt64, 100, 1000, 10000))
return nil
}
+func addInt(m map[interface{}]int, key interface{}) (err error) {
+ defer func() {
+ if r := recover(); r != nil {
+ err = fmt.Errorf("addInt failed: %v", r)
+ }
+ }()
+ m[key] += 2018
+ return nil
+}
+
+func addStr(m map[interface{}]string, key interface{}) (err error) {
+ defer func() {
+ if r := recover(); r != nil {
+ err = fmt.Errorf("addStr failed: %v", r)
+ }
+ }()
+ m[key] += "hello, go"
+ return nil
+}
+
func main() {
m := make(map[interface{}]interface{})
set(m, []int{1, 2, 3})
set(m, "abc") // used to throw
del(m, []int{1, 2, 3})
del(m, "abc") // used to throw
+
+ mi := make(map[interface{}]int)
+ addInt(mi, []int{1, 2, 3})
+ addInt(mi, "abc") // used to throw
+
+ ms := make(map[interface{}]string)
+ addStr(ms, []int{1, 2, 3})
+ addStr(ms, "abc") // used to throw
}