diff --git a/src/math/big/arith.go b/src/math/big/arith.go
index e2cd99f602bf5e1d0a2fa8c9b3ff0682cce34fc8..bc27ca6a562e44147007a218c4f2b492aac7da25 100644
--- a/src/math/big/arith.go
+++ b/src/math/big/arith.go
@@ -26,17 +26,13 @@ const (
 	_M = _B - 1        // digit mask
 )
 
-// Many of the loops in this file are of the form
-//   for i := 0; i < len(z) && i < len(x) && i < len(y); i++
-// i < len(z) is the real condition.
-// However, checking i < len(x) && i < len(y) as well is faster than
-// having the compiler do a bounds check in the body of the loop;
-// remarkably it is even faster than hoisting the bounds check
-// out of the loop, by doing something like
-//   _, _ = x[len(z)-1], y[len(z)-1]
-// There are other ways to hoist the bounds check out of the loop,
-// but the compiler's BCE isn't powerful enough for them (yet?).
-// See the discussion in CL 164966.
+// In these routines, it is the caller's responsibility to arrange for
+// x, y, and z to all have the same length. We check this and panic.
+// The assembly versions of these routines do not include that check.
+//
+// The check+panic also has the effect of teaching the compiler that
+// “i in range for z” implies “i in range for x and y”, eliminating all
+// bounds checks in loops from 0 to len(z) and vice versa.
 
 // ----------------------------------------------------------------------------
 // Elementary operations on words
@@ -65,8 +61,11 @@ func nlz(x Word) uint {
 
 // The resulting carry c is either 0 or 1.
 func addVV_g(z, x, y []Word) (c Word) {
-	// The comment near the top of this file discusses this for loop condition.
-	for i := 0; i < len(z) && i < len(x) && i < len(y); i++ {
+	if len(x) != len(z) || len(y) != len(z) {
+		panic("addVV len")
+	}
+
+	for i := range z {
 		zi, cc := bits.Add(uint(x[i]), uint(y[i]), uint(c))
 		z[i] = Word(zi)
 		c = Word(cc)
@@ -76,8 +75,11 @@ func addVV_g(z, x, y []Word) (c Word) {
 
 // The resulting carry c is either 0 or 1.
 func subVV_g(z, x, y []Word) (c Word) {
-	// The comment near the top of this file discusses this for loop condition.
-	for i := 0; i < len(z) && i < len(x) && i < len(y); i++ {
+	if len(x) != len(z) || len(y) != len(z) {
+		panic("subVV len")
+	}
+
+	for i := range z {
 		zi, cc := bits.Sub(uint(x[i]), uint(y[i]), uint(c))
 		z[i] = Word(zi)
 		c = Word(cc)
@@ -99,7 +101,10 @@ func subVV_g(z, x, y []Word) (c Word) {
 //
 //go:linkname addVW
 func addVW(z, x []Word, y Word) (c Word) {
-	x = x[:len(z)]
+	if len(x) != len(z) {
+		panic("addVW len")
+	}
+
 	if len(z) == 0 {
 		return y
 	}
@@ -150,7 +155,10 @@ func addVW_ref(z, x []Word, y Word) (c Word) {
 //
 //go:linkname subVW
 func subVW(z, x []Word, y Word) (c Word) {
-	x = x[:len(z)]
+	if len(x) != len(z) {
+		panic("subVW len")
+	}
+
 	if len(z) == 0 {
 		return y
 	}
@@ -188,6 +196,10 @@ func subVW_ref(z, x []Word, y Word) (c Word) {
 }
 
 func lshVU_g(z, x []Word, s uint) (c Word) {
+	if len(x) != len(z) {
+		panic("lshVU len")
+	}
+
 	if s == 0 {
 		copy(z, x)
 		return
@@ -207,6 +219,10 @@ func lshVU_g(z, x []Word, s uint) (c Word) {
 }
 
 func rshVU_g(z, x []Word, s uint) (c Word) {
+	if len(x) != len(z) {
+		panic("rshVU len")
+	}
+
 	if s == 0 {
 		copy(z, x)
 		return
@@ -214,10 +230,6 @@ func rshVU_g(z, x []Word, s uint) (c Word) {
 	if len(z) == 0 {
 		return
 	}
-	if len(x) != len(z) {
-		// This is an invariant guaranteed by the caller.
-		panic("len(x) != len(z)")
-	}
 	s &= _W - 1 // hint to the compiler that shifts by s don't need guard code
 	ŝ := _W - s
 	ŝ &= _W - 1 // ditto
@@ -230,18 +242,23 @@ func rshVU_g(z, x []Word, s uint) (c Word) {
 }
 
 func mulAddVWW_g(z, x []Word, y, r Word) (c Word) {
+	if len(x) != len(z) {
+		panic("mulAddVWW len")
+	}
 	c = r
-	// The comment near the top of this file discusses this for loop condition.
-	for i := 0; i < len(z) && i < len(x); i++ {
+	for i := range z {
 		c, z[i] = mulAddWWW_g(x[i], y, c)
 	}
 	return
 }
 
 func addMulVVWW_g(z, x, y []Word, m, a Word) (c Word) {
+	if len(x) != len(z) || len(y) != len(z) {
+		panic("rshVU len")
+	}
+
 	c = a
-	// The comment near the top of this file discusses this for loop condition.
-	for i := 0; i < len(z) && i < len(x) && i < len(y); i++ {
+	for i := range z {
 		z1, z0 := mulAddWWW_g(y[i], m, x[i])
 		lo, cc := bits.Add(uint(z0), uint(c), 0)
 		c, z[i] = Word(cc), Word(lo)
diff --git a/src/math/big/nat.go b/src/math/big/nat.go
index feff4835da4c3eccf7fb71a6db6c904c525d82bf..43e36d309389f525bd34759d68528a5b219a2f32 100644
--- a/src/math/big/nat.go
+++ b/src/math/big/nat.go
@@ -111,7 +111,7 @@ func (z nat) add(x, y nat) nat {
 	// m > 0
 
 	z = z.make(m + 1)
-	c := addVV(z[0:n], x, y)
+	c := addVV(z[:n], x[:n], y[:n])
 	if m > n {
 		c = addVW(z[n:m], x[n:], c)
 	}
@@ -137,7 +137,7 @@ func (z nat) sub(x, y nat) nat {
 	// m > 0
 
 	z = z.make(m)
-	c := subVV(z[0:n], x, y)
+	c := subVV(z[:n], x[:n], y[:n])
 	if m > n {
 		c = subVW(z[n:], x[n:], c)
 	}
@@ -232,7 +232,7 @@ func alias(x, y nat) bool {
 // slice, and we don't need to normalize z after each addition)
 func addTo(z, x nat) {
 	if n := len(x); n > 0 {
-		if c := addVV(z[:n], z, x); c != 0 {
+		if c := addVV(z[:n], z[:n], x[:n]); c != 0 {
 			if n < len(z) {
 				addVW(z[n:], z[n:], c)
 			}
diff --git a/src/math/big/natdiv.go b/src/math/big/natdiv.go
index c9b7f4e3556d829ec9b7fbd6b11f2ccd2ae45d4c..88cb5d9e2e322cfc81d1f46f9b863df428e9d71a 100644
--- a/src/math/big/natdiv.go
+++ b/src/math/big/natdiv.go
@@ -699,9 +699,9 @@ func (q nat) divBasic(stk *stack, u, v nat) {
 		// Subtract q̂·v from the current section of u.
 		// If it underflows, q̂·v > u, which we fix up
 		// by decrementing q̂ and adding v back.
-		c := subVV(u[j:j+qhl], u[j:], qhatv)
+		c := subVV(u[j:j+qhl], u[j:j+qhl], qhatv[:qhl])
 		if c != 0 {
-			c := addVV(u[j:j+n], u[j:], v)
+			c := addVV(u[j:j+n], u[j:j+n], v)
 			// If n == qhl, the carry from subVV and the carry from addVV
 			// cancel out and don't affect u[j+n].
 			if n < qhl {