diff --git a/src/cmd/compile/internal/gc/reproduciblebuilds_test.go b/src/cmd/compile/internal/gc/reproduciblebuilds_test.go index 59d1edb9e8..8101e44079 100644 --- a/src/cmd/compile/internal/gc/reproduciblebuilds_test.go +++ b/src/cmd/compile/internal/gc/reproduciblebuilds_test.go @@ -60,3 +60,53 @@ func TestReproducibleBuilds(t *testing.T) { }) } } + +func TestIssue38068(t *testing.T) { + testenv.MustHaveGoBuild(t) + t.Parallel() + + // Compile a small package with and without the concurrent + // backend, then check to make sure that the resulting archives + // are identical. Note: this uses "go tool compile" instead of + // "go build" since the latter will generate differnent build IDs + // if it sees different command line flags. + scenarios := []struct { + tag string + args string + libpath string + }{ + {tag: "serial", args: "-c=1"}, + {tag: "concurrent", args: "-c=2"}} + + tmpdir, err := ioutil.TempDir("", "TestIssue38068") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpdir) + + src := filepath.Join("testdata", "reproducible", "issue38068.go") + for i := range scenarios { + s := &scenarios[i] + s.libpath = filepath.Join(tmpdir, s.tag+".a") + // Note: use of "-p" required in order for DWARF to be generated. + cmd := exec.Command(testenv.GoToolPath(t), "tool", "compile", "-trimpath", "-p=issue38068", "-buildid=", s.args, "-o", s.libpath, src) + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("%v: %v:\n%s", cmd.Args, err, out) + } + } + + readBytes := func(fn string) []byte { + payload, err := ioutil.ReadFile(fn) + if err != nil { + t.Fatalf("failed to read executable '%s': %v", fn, err) + } + return payload + } + + b1 := readBytes(scenarios[0].libpath) + b2 := readBytes(scenarios[1].libpath) + if !bytes.Equal(b1, b2) { + t.Fatalf("concurrent and serial builds produced different output") + } +} diff --git a/src/cmd/compile/internal/gc/testdata/reproducible/issue38068.go b/src/cmd/compile/internal/gc/testdata/reproducible/issue38068.go new file mode 100644 index 0000000000..db5ca7dcbe --- /dev/null +++ b/src/cmd/compile/internal/gc/testdata/reproducible/issue38068.go @@ -0,0 +1,70 @@ +// Copyright 2020 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 issue38068 + +// A type with a couple of inlinable, non-pointer-receiver methods +// that have params and local variables. +type A struct { + s string + next *A + prev *A +} + +// Inlinable, value-received method with locals and parms. +func (a A) double(x string, y int) string { + if y == 191 { + a.s = "" + } + q := a.s + "a" + r := a.s + "b" + return q + r +} + +// Inlinable, value-received method with locals and parms. +func (a A) triple(x string, y int) string { + q := a.s + if y == 998877 { + a.s = x + } + r := a.s + a.s + return q + r +} + +type methods struct { + m1 func(a *A, x string, y int) string + m2 func(a *A, x string, y int) string +} + +// Now a function that makes references to the methods via pointers, +// which should trigger the wrapper generation. +func P(a *A, ms *methods) { + if a != nil { + defer func() { println("done") }() + } + println(ms.m1(a, "a", 2)) + println(ms.m2(a, "b", 3)) +} + +func G(x *A, n int) { + if n <= 0 { + println(n) + return + } + // Address-taken local of type A, which will insure that the + // compiler's dtypesym() routine will create a method wrapper. + var a, b A + a.next = x + a.prev = &b + x = &a + G(x, n-2) +} + +var M methods + +func F() { + M.m1 = (*A).double + M.m2 = (*A).triple + G(nil, 100) +}