| // run |
| |
| // Copyright 2026 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. |
| |
| // Issue 81266: check that we generate correct code when |
| // using the subroutine mechanism in the equality/hash generator. |
| |
| package main |
| |
| import ( |
| "fmt" |
| "unsafe" |
| ) |
| |
| func check(name string, got, want bool) { |
| if got != want { |
| panic(fmt.Sprintf("%s: got %v want %v", name, got, want)) |
| } |
| } |
| |
| // Big is large enough for subroutine treatment, and has trailing padding. |
| type Big struct { |
| Filler [2000]byte |
| S string |
| B byte |
| } |
| |
| // Outer embeds Big as a non-top-level field, and has a field after |
| // it. There is padding between the two fields. |
| type Outer struct { |
| Big Big |
| X byte |
| } |
| |
| // fill sets every byte of the size bytes at p to b. Used to poison |
| // padding bytes with garbage, so that tests which pass despite |
| // differing padding prove the padding is correctly ignored. |
| func fill(p unsafe.Pointer, size uintptr, b byte) { |
| buf := unsafe.Slice((*byte)(p), size) |
| for i := range buf { |
| buf[i] = b |
| } |
| } |
| |
| var filler [2000]byte |
| |
| func init() { |
| for i := range filler { |
| filler[i] = byte(i) |
| } |
| } |
| |
| func mkBig(s string, b byte, pad byte) Big { |
| var x Big |
| fill(unsafe.Pointer(&x), unsafe.Sizeof(x), pad) |
| x.Filler = filler |
| x.S = s |
| x.B = b |
| return x |
| } |
| |
| func testBasic() { |
| a := Outer{Big: mkBig("foo", 7, 0xAA), X: 1} |
| c := Outer{Big: mkBig("foo", 7, 0x55), X: 1} |
| check("equal despite differing padding inside Big", a == c, true) |
| |
| d := Outer{Big: mkBig("foo", 7, 0xAA), X: 2} |
| check("differ in trailing field X", a == d, false) |
| |
| e := Outer{Big: mkBig("bar", 7, 0xAA), X: 1} |
| check("differ in Big.S", a == e, false) |
| |
| g := Outer{Big: mkBig("foo", 9, 0xAA), X: 1} |
| check("differ in Big.B", a == g, false) |
| |
| h := a |
| h.Big.Filler[1000] ^= 0xFF |
| check("differ in Big.Filler", a == h, false) |
| } |
| |
| // testPadding makes sure different paddings don't prevent equality. |
| func testPadding() { |
| var a, c Outer |
| fill(unsafe.Pointer(&a), unsafe.Sizeof(a), 0x11) |
| fill(unsafe.Pointer(&c), unsafe.Sizeof(c), 0x99) |
| a.Big.Filler = filler |
| c.Big.Filler = filler |
| a.Big.S = "padding-check" |
| c.Big.S = "padding-check" |
| a.Big.B = 42 |
| c.Big.B = 42 |
| a.X = 5 |
| c.X = 5 |
| check("equal with garbage-filled padding", a == c, true) |
| } |
| |
| // testHash exercises the hash side of the subroutine mechanism. |
| func testHash() { |
| m := make(map[Outer]int) |
| k1 := Outer{Big: mkBig("k1", 1, 0), X: 1} |
| k2 := Outer{Big: mkBig("k2", 2, 0), X: 2} |
| m[k1] = 100 |
| m[k2] = 200 |
| |
| look1 := Outer{Big: mkBig("k1", 1, 0xFF), X: 1} // differing padding |
| look2 := Outer{Big: mkBig("k2", 2, 0xFF), X: 2} |
| v1, ok1 := m[look1] |
| v2, ok2 := m[look2] |
| check("map lookup k1 found", ok1, true) |
| check("map lookup k2 found", ok2, true) |
| if v1 != 100 { |
| panic(fmt.Sprintf("map k1 value: got %v want 100", v1)) |
| } |
| if v2 != 200 { |
| panic(fmt.Sprintf("map k2 value: got %v want 200", v2)) |
| } |
| |
| notFound := Outer{Big: mkBig("k1", 1, 0), X: 99} |
| _, ok3 := m[notFound] |
| check("map lookup miss", ok3, false) |
| } |
| |
| // testNested exercises two levels of subroutine nesting |
| // (Outer2 -> Level1 -> Big). |
| func testNested() { |
| // Level1 embeds Big, so it becomes a subroutine when embedded |
| // in Outer2. This produces two levels of subroutine nesting. |
| type Level1 struct { |
| B Big |
| F2 [2000]byte |
| T string |
| C byte |
| } |
| |
| type Outer2 struct { |
| L1 Level1 |
| Y byte |
| } |
| |
| mkLevel1 := func(s1, s2 string, b1, b2 byte) Level1 { |
| var l Level1 |
| l.B = mkBig(s1, b1, 0) |
| l.F2 = filler |
| l.T = s2 |
| l.C = b2 |
| return l |
| } |
| |
| a := Outer2{L1: mkLevel1("inner1", "outer1", 1, 2), Y: 9} |
| c := Outer2{L1: mkLevel1("inner1", "outer1", 1, 2), Y: 9} |
| check("nested equal", a == c, true) |
| |
| d := a |
| d.L1.B.S = "changed" |
| check("nested differ in deepest field", a == d, false) |
| |
| e := a |
| e.L1.T = "changed" |
| check("nested differ in Level1 field", a == e, false) |
| |
| g := a |
| g.Y = 10 |
| check("nested differ in outer field", a == g, false) |
| |
| mm := make(map[Outer2]string) |
| mm[a] = "a-value" |
| if got := mm[c]; got != "a-value" { |
| panic(fmt.Sprintf("nested map lookup: got %q want %q", got, "a-value")) |
| } |
| } |
| |
| // testArray exercises an array of large elements, each of which uses |
| // the subroutine mechanism. |
| func testArray() { |
| type BigArr struct { |
| A [3]Big |
| } |
| var a, c BigArr |
| for i := range a.A { |
| a.A[i] = mkBig(fmt.Sprintf("elem%d", i), byte(i), byte(i)) |
| c.A[i] = mkBig(fmt.Sprintf("elem%d", i), byte(i), byte(i+100)) // different padding |
| } |
| check("array equal despite differing padding", a == c, true) |
| |
| d := a |
| d.A[2].S = "different" |
| check("array differ in one element", a == d, false) |
| } |
| |
| // testBoundary checks types right at the maxExpandSize (1024) cutoff, |
| // on both sides. |
| func testBoundary() { |
| type Exactly1024 struct { |
| F [1024 - 16]byte // + string(16) = 1024 exactly, on a 64-bit platform |
| S string |
| } |
| type Container1024 struct { |
| V Exactly1024 |
| X byte |
| } |
| var a, c Container1024 |
| a.V.S = "same" |
| c.V.S = "same" |
| a.X = 1 |
| c.X = 1 |
| check("boundary <=1024 equal", a == c, true) |
| d := a |
| d.V.S = "diff" |
| check("boundary <=1024 differ", a == d, false) |
| |
| type Exactly1025 struct { |
| F [1025 - 16]byte |
| S string |
| } |
| type Container1025 struct { |
| V Exactly1025 |
| X byte |
| } |
| var e, g Container1025 |
| e.V.S = "same" |
| g.V.S = "same" |
| e.X = 1 |
| g.X = 1 |
| check("boundary >1024 equal", e == g, true) |
| h := e |
| h.V.S = "diff" |
| check("boundary >1024 differ", e == h, false) |
| } |
| |
| func main() { |
| testBasic() |
| testPadding() |
| testHash() |
| testNested() |
| testArray() |
| testBoundary() |
| } |