Skip to content

Commit 4a459cb

Browse files
committed
cmd/compile: tweak inliners handling of coverage counter updates
This patch fixes up a bug in the inliner's special case code for coverage counter updates, which was not properly working for -covermode=atomic compilations. Updates #56044. Change-Id: I9e309312b123121c3df02862623bdbab1f6c6a4b Reviewed-on: https://go-review.googlesource.com/c/go/+/441858 Reviewed-by: David Chase <[email protected]> Run-TryBot: Than McIntosh <[email protected]> Reviewed-by: Cherry Mui <[email protected]>
1 parent 742e0a9 commit 4a459cb

File tree

3 files changed

+114
-9
lines changed

3 files changed

+114
-9
lines changed

Diff for: src/cmd/compile/internal/inline/inl.go

+52-9
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ import (
3737
"cmd/compile/internal/typecheck"
3838
"cmd/compile/internal/types"
3939
"cmd/internal/obj"
40-
"cmd/internal/objabi"
4140
"cmd/internal/src"
4241
)
4342

@@ -284,6 +283,19 @@ func (v *hairyVisitor) doNode(n ir.Node) bool {
284283
break
285284
}
286285
}
286+
// Special case for coverage counter updates; although
287+
// these correspond to real operations, we treat them as
288+
// zero cost for the moment. This is due to the existence
289+
// of tests that are sensitive to inlining-- if the
290+
// insertion of coverage instrumentation happens to tip a
291+
// given function over the threshold and move it from
292+
// "inlinable" to "not-inlinable", this can cause changes
293+
// in allocation behavior, which can then result in test
294+
// failures (a good example is the TestAllocations in
295+
// crypto/ed25519).
296+
if isAtomicCoverageCounterUpdate(n) {
297+
break
298+
}
287299
}
288300
if n.X.Op() == ir.OMETHEXPR {
289301
if meth := ir.MethodExprName(n.X); meth != nil {
@@ -485,14 +497,8 @@ func (v *hairyVisitor) doNode(n ir.Node) bool {
485497
// then result in test failures (a good example is the
486498
// TestAllocations in crypto/ed25519).
487499
n := n.(*ir.AssignStmt)
488-
if n.X.Op() == ir.OINDEX {
489-
n := n.X.(*ir.IndexExpr)
490-
if n.X.Op() == ir.ONAME && n.X.Type().IsArray() {
491-
n := n.X.(*ir.Name)
492-
if n.Linksym().Type == objabi.SCOVERAGE_COUNTER {
493-
return false
494-
}
495-
}
500+
if n.X.Op() == ir.OINDEX && isIndexingCoverageCounter(n) {
501+
return false
496502
}
497503
}
498504

@@ -1539,3 +1545,40 @@ func doList(list []ir.Node, do func(ir.Node) bool) bool {
15391545
}
15401546
return false
15411547
}
1548+
1549+
// isIndexingCoverageCounter returns true if the specified node 'n' is indexing
1550+
// into a coverage counter array.
1551+
func isIndexingCoverageCounter(n ir.Node) bool {
1552+
if n.Op() != ir.OINDEX {
1553+
return false
1554+
}
1555+
ixn := n.(*ir.IndexExpr)
1556+
if ixn.X.Op() != ir.ONAME || !ixn.X.Type().IsArray() {
1557+
return false
1558+
}
1559+
nn := ixn.X.(*ir.Name)
1560+
return nn.CoverageCounter()
1561+
}
1562+
1563+
// isAtomicCoverageCounterUpdate examines the specified node to
1564+
// determine whether it represents a call to sync/atomic.AddUint32 to
1565+
// increment a coverage counter.
1566+
func isAtomicCoverageCounterUpdate(cn *ir.CallExpr) bool {
1567+
if cn.X.Op() != ir.ONAME {
1568+
return false
1569+
}
1570+
name := cn.X.(*ir.Name)
1571+
if name.Class != ir.PFUNC {
1572+
return false
1573+
}
1574+
fn := name.Sym().Name
1575+
if name.Sym().Pkg.Path != "sync/atomic" || fn != "AddUint32" {
1576+
return false
1577+
}
1578+
if len(cn.Args) != 2 || cn.Args[0].Op() != ir.OADDR {
1579+
return false
1580+
}
1581+
adn := cn.Args[0].(*ir.AddrExpr)
1582+
v := isIndexingCoverageCounter(adn.X)
1583+
return v
1584+
}

Diff for: src/cmd/compile/internal/test/inl_test.go

+55
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package test
77
import (
88
"bufio"
99
"internal/buildcfg"
10+
"internal/goexperiment"
1011
"internal/testenv"
1112
"io"
1213
"math/bits"
@@ -332,3 +333,57 @@ func TestIntendedInlining(t *testing.T) {
332333
t.Errorf("%s was not inlined: %s", fullName, reason)
333334
}
334335
}
336+
337+
func collectInlCands(msgs string) map[string]struct{} {
338+
rv := make(map[string]struct{})
339+
lines := strings.Split(msgs, "\n")
340+
re := regexp.MustCompile(`^\S+\s+can\s+inline\s+(\S+)`)
341+
for _, line := range lines {
342+
m := re.FindStringSubmatch(line)
343+
if m != nil {
344+
rv[m[1]] = struct{}{}
345+
}
346+
}
347+
return rv
348+
}
349+
350+
func TestIssue56044(t *testing.T) {
351+
if testing.Short() {
352+
t.Skipf("skipping test: too long for short mode")
353+
}
354+
if !goexperiment.CoverageRedesign {
355+
t.Skipf("skipping new coverage tests (experiment not enabled)")
356+
}
357+
358+
testenv.MustHaveGoBuild(t)
359+
360+
modes := []string{"-covermode=set", "-covermode=atomic"}
361+
362+
for _, mode := range modes {
363+
// Build the Go runtime with "-m", capturing output.
364+
args := []string{"build", "-gcflags=runtime=-m", "runtime"}
365+
cmd := exec.Command(testenv.GoToolPath(t), args...)
366+
b, err := cmd.CombinedOutput()
367+
if err != nil {
368+
t.Fatalf("build failed (%v): %s", err, b)
369+
}
370+
mbase := collectInlCands(string(b))
371+
372+
// Redo the build with -cover, also with "-m".
373+
args = []string{"build", "-gcflags=runtime=-m", mode, "runtime"}
374+
cmd = exec.Command(testenv.GoToolPath(t), args...)
375+
b, err = cmd.CombinedOutput()
376+
if err != nil {
377+
t.Fatalf("build failed (%v): %s", err, b)
378+
}
379+
mcov := collectInlCands(string(b))
380+
381+
// Make sure that there aren't any functions that are marked
382+
// as inline candidates at base but not with coverage.
383+
for k := range mbase {
384+
if _, ok := mcov[k]; !ok {
385+
t.Errorf("error: did not find %s in coverage -m output", k)
386+
}
387+
}
388+
}
389+
}

Diff for: src/runtime/coverage/emitdata_test.go

+7
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,13 @@ func TestApisOnNocoverBinary(t *testing.T) {
406406
}
407407

408408
func TestIssue56006EmitDataRaceCoverRunningGoroutine(t *testing.T) {
409+
if testing.Short() {
410+
t.Skipf("skipping test: too long for short mode")
411+
}
412+
if !goexperiment.CoverageRedesign {
413+
t.Skipf("skipping new coverage tests (experiment not enabled)")
414+
}
415+
409416
// This test requires "go test -race -cover", meaning that we need
410417
// go build, go run, and "-race" support.
411418
testenv.MustHaveGoRun(t)

0 commit comments

Comments
 (0)