blob: 843044d08447984facc3fd28869e604bc741e652 [file] [log] [blame]
// Copyright 2024 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 test
import (
"cmd/compile/internal/ir"
"cmd/compile/internal/liveness"
"cmd/compile/internal/typecheck"
"cmd/compile/internal/types"
"cmd/internal/src"
"internal/testenv"
"path/filepath"
"sort"
"strings"
"testing"
)
func mkiv(name string) *ir.Name {
i32 := types.Types[types.TINT32]
s := typecheck.Lookup(name)
v := ir.NewNameAt(src.NoXPos, s, i32)
return v
}
func TestMergeLocalState(t *testing.T) {
v1 := mkiv("v1")
v2 := mkiv("v2")
v3 := mkiv("v3")
testcases := []struct {
vars []*ir.Name
partition map[*ir.Name][]int
experr bool
}{
{
vars: []*ir.Name{v1, v2, v3},
partition: map[*ir.Name][]int{
v1: []int{0, 1, 2},
v2: []int{0, 1, 2},
v3: []int{0, 1, 2},
},
experr: false,
},
{
// invalid mls.v slot -1
vars: []*ir.Name{v1, v2, v3},
partition: map[*ir.Name][]int{
v1: []int{-1, 0},
v2: []int{0, 1, 2},
v3: []int{0, 1, 2},
},
experr: true,
},
{
// duplicate var in v
vars: []*ir.Name{v1, v2, v2},
partition: map[*ir.Name][]int{
v1: []int{0, 1, 2},
v2: []int{0, 1, 2},
v3: []int{0, 1, 2},
},
experr: true,
},
{
// single element in partition
vars: []*ir.Name{v1, v2, v3},
partition: map[*ir.Name][]int{
v1: []int{0},
v2: []int{0, 1, 2},
v3: []int{0, 1, 2},
},
experr: true,
},
{
// missing element 2
vars: []*ir.Name{v1, v2, v3},
partition: map[*ir.Name][]int{
v1: []int{0, 1},
v2: []int{0, 1},
v3: []int{0, 1},
},
experr: true,
},
{
// partitions disagree for v1 vs v2
vars: []*ir.Name{v1, v2, v3},
partition: map[*ir.Name][]int{
v1: []int{0, 1, 2},
v2: []int{1, 0, 2},
v3: []int{0, 1, 2},
},
experr: true,
},
}
for k, testcase := range testcases {
mls, err := liveness.MakeMergeLocalsState(testcase.partition, testcase.vars)
t.Logf("tc %d err is %v\n", k, err)
if testcase.experr && err == nil {
t.Fatalf("tc:%d missing error mls %v", k, mls)
} else if !testcase.experr && err != nil {
t.Fatalf("tc:%d unexpected error mls %v", k, err)
}
if mls != nil {
t.Logf("tc %d: mls: %v\n", k, mls.String())
}
}
}
func TestMergeLocalsIntegration(t *testing.T) {
testenv.MustHaveGoBuild(t)
// This test does a build of a specific canned package to
// check whether merging of stack slots is taking place.
// The idea is to do the compile with a trace option turned
// on and then pick up on the frame offsets of specific
// variables.
//
// Stack slot merging is a greedy algorithm, and there can
// be many possible ways to overlap a given set of candidate
// variables, all of them legal. Rather than locking down
// a specific set of overlappings or frame offsets, this
// tests just verifies that there is a decent-sized clump of 4+ vars that
// get overlapped.
//
// The expected output blob we're interested might look like
// this (for amd64):
//
// =-= stack layout for ABC:
// 2: "p1" frameoff -8200 ...
// 3: "s" frameoff -8200 ...
// 4: "v2" frameoff -8200 ...
// 5: "v3" frameoff -8200 ...
// 6: "xp3" frameoff -8200 ...
// 7: "xp4" frameoff -8200 ...
// 8: "p2" frameoff -16400 ...
// 9: "r" frameoff -16408 ...
//
tmpdir := t.TempDir()
src := filepath.Join("testdata", "mergelocals", "integration.go")
obj := filepath.Join(tmpdir, "p.a")
out, err := testenv.Command(t, testenv.GoToolPath(t), "tool", "compile",
"-p=p", "-c", "1", "-o", obj, "-d=mergelocalstrace=2,mergelocals=1",
src).CombinedOutput()
if err != nil {
t.Fatalf("failed to compile: %v\n%s", err, out)
}
vars := make(map[string]string)
lines := strings.Split(string(out), "\n")
prolog := true
varsAtFrameOffset := make(map[string]int)
for _, line := range lines {
if line == "=-= stack layout for ABC:" {
prolog = false
continue
} else if prolog || line == "" {
continue
}
fields := strings.Fields(line)
wantFields := 9
if len(fields) != wantFields {
t.Logf(string(out))
t.Fatalf("bad trace output line, wanted %d fields got %d: %s",
wantFields, len(fields), line)
}
vname := fields[1]
frameoff := fields[3]
varsAtFrameOffset[frameoff] = varsAtFrameOffset[frameoff] + 1
vars[vname] = frameoff
}
wantvnum := 8
gotvnum := len(vars)
if wantvnum != gotvnum {
t.Logf(string(out))
t.Fatalf("expected trace output on %d vars got %d\n", wantvnum, gotvnum)
}
// Expect at least one clump of at least 3.
n3 := 0
got := []int{}
for _, v := range varsAtFrameOffset {
if v > 2 {
n3++
}
got = append(got, v)
}
sort.Ints(got)
if n3 == 0 {
t.Logf("%s\n", string(out))
t.Fatalf("expected at least one clump of 3, got: %+v", got)
}
}