Source file src/cmd/compile/internal/test/mergelocals_test.go

     1  // Copyright 2024 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package test
     6  
     7  import (
     8  	"cmd/compile/internal/ir"
     9  	"cmd/compile/internal/liveness"
    10  	"cmd/compile/internal/typecheck"
    11  	"cmd/compile/internal/types"
    12  	"cmd/internal/src"
    13  	"internal/testenv"
    14  	"path/filepath"
    15  	"sort"
    16  	"strings"
    17  	"testing"
    18  )
    19  
    20  func mkiv(name string) *ir.Name {
    21  	i32 := types.Types[types.TINT32]
    22  	s := typecheck.Lookup(name)
    23  	v := ir.NewNameAt(src.NoXPos, s, i32)
    24  	return v
    25  }
    26  
    27  func TestMergeLocalState(t *testing.T) {
    28  	v1 := mkiv("v1")
    29  	v2 := mkiv("v2")
    30  	v3 := mkiv("v3")
    31  
    32  	testcases := []struct {
    33  		vars      []*ir.Name
    34  		partition map[*ir.Name][]int
    35  		experr    bool
    36  	}{
    37  		{
    38  			vars: []*ir.Name{v1, v2, v3},
    39  			partition: map[*ir.Name][]int{
    40  				v1: []int{0, 1, 2},
    41  				v2: []int{0, 1, 2},
    42  				v3: []int{0, 1, 2},
    43  			},
    44  			experr: false,
    45  		},
    46  		{
    47  			// invalid mls.v slot -1
    48  			vars: []*ir.Name{v1, v2, v3},
    49  			partition: map[*ir.Name][]int{
    50  				v1: []int{-1, 0},
    51  				v2: []int{0, 1, 2},
    52  				v3: []int{0, 1, 2},
    53  			},
    54  			experr: true,
    55  		},
    56  		{
    57  			// duplicate var in v
    58  			vars: []*ir.Name{v1, v2, v2},
    59  			partition: map[*ir.Name][]int{
    60  				v1: []int{0, 1, 2},
    61  				v2: []int{0, 1, 2},
    62  				v3: []int{0, 1, 2},
    63  			},
    64  			experr: true,
    65  		},
    66  		{
    67  			// single element in partition
    68  			vars: []*ir.Name{v1, v2, v3},
    69  			partition: map[*ir.Name][]int{
    70  				v1: []int{0},
    71  				v2: []int{0, 1, 2},
    72  				v3: []int{0, 1, 2},
    73  			},
    74  			experr: true,
    75  		},
    76  		{
    77  			// missing element 2
    78  			vars: []*ir.Name{v1, v2, v3},
    79  			partition: map[*ir.Name][]int{
    80  				v1: []int{0, 1},
    81  				v2: []int{0, 1},
    82  				v3: []int{0, 1},
    83  			},
    84  			experr: true,
    85  		},
    86  		{
    87  			// partitions disagree for v1 vs v2
    88  			vars: []*ir.Name{v1, v2, v3},
    89  			partition: map[*ir.Name][]int{
    90  				v1: []int{0, 1, 2},
    91  				v2: []int{1, 0, 2},
    92  				v3: []int{0, 1, 2},
    93  			},
    94  			experr: true,
    95  		},
    96  	}
    97  
    98  	for k, testcase := range testcases {
    99  		mls, err := liveness.MakeMergeLocalsState(testcase.partition, testcase.vars)
   100  		t.Logf("tc %d err is %v\n", k, err)
   101  		if testcase.experr && err == nil {
   102  			t.Fatalf("tc:%d missing error mls %v", k, mls)
   103  		} else if !testcase.experr && err != nil {
   104  			t.Fatalf("tc:%d unexpected error mls %v", k, err)
   105  		}
   106  		if mls != nil {
   107  			t.Logf("tc %d: mls: %v\n", k, mls.String())
   108  		}
   109  	}
   110  }
   111  
   112  func TestMergeLocalsIntegration(t *testing.T) {
   113  	testenv.MustHaveGoBuild(t)
   114  
   115  	// This test does a build of a specific canned package to
   116  	// check whether merging of stack slots is taking place.
   117  	// The idea is to do the compile with a trace option turned
   118  	// on and then pick up on the frame offsets of specific
   119  	// variables.
   120  	//
   121  	// Stack slot merging is a greedy algorithm, and there can
   122  	// be many possible ways to overlap a given set of candidate
   123  	// variables, all of them legal. Rather than locking down
   124  	// a specific set of overlappings or frame offsets, this
   125  	// tests just verifies that there is a decent-sized clump of 4+ vars that
   126  	// get overlapped.
   127  	//
   128  	// The expected output blob we're interested might look like
   129  	// this (for amd64):
   130  	//
   131  	// =-= stack layout for ABC:
   132  	// 2: "p1" frameoff -8200 ...
   133  	// 3: "s" frameoff -8200 ...
   134  	// 4: "v2" frameoff -8200 ...
   135  	// 5: "v3" frameoff -8200 ...
   136  	// 6: "xp3" frameoff -8200 ...
   137  	// 7: "xp4" frameoff -8200 ...
   138  	// 8: "p2" frameoff -16400 ...
   139  	// 9: "r" frameoff -16408 ...
   140  	//
   141  	tmpdir := t.TempDir()
   142  	src := filepath.Join("testdata", "mergelocals", "integration.go")
   143  	obj := filepath.Join(tmpdir, "p.a")
   144  	out, err := testenv.Command(t, testenv.GoToolPath(t), "tool", "compile",
   145  		"-p=p", "-c", "1", "-o", obj, "-d=mergelocalstrace=2,mergelocals=1",
   146  		src).CombinedOutput()
   147  	if err != nil {
   148  		t.Fatalf("failed to compile: %v\n%s", err, out)
   149  	}
   150  	vars := make(map[string]string)
   151  	lines := strings.Split(string(out), "\n")
   152  	prolog := true
   153  	varsAtFrameOffset := make(map[string]int)
   154  	for _, line := range lines {
   155  		if line == "=-= stack layout for ABC:" {
   156  			prolog = false
   157  			continue
   158  		} else if prolog || line == "" {
   159  			continue
   160  		}
   161  		fields := strings.Fields(line)
   162  		wantFields := 9
   163  		if len(fields) != wantFields {
   164  			t.Log(string(out))
   165  			t.Fatalf("bad trace output line, wanted %d fields got %d: %s",
   166  				wantFields, len(fields), line)
   167  		}
   168  		vname := fields[1]
   169  		frameoff := fields[3]
   170  		varsAtFrameOffset[frameoff] = varsAtFrameOffset[frameoff] + 1
   171  		vars[vname] = frameoff
   172  	}
   173  	wantvnum := 8
   174  	gotvnum := len(vars)
   175  	if wantvnum != gotvnum {
   176  		t.Log(string(out))
   177  		t.Fatalf("expected trace output on %d vars got %d\n", wantvnum, gotvnum)
   178  	}
   179  
   180  	// Expect at least one clump of at least 3.
   181  	n3 := 0
   182  	got := []int{}
   183  	for _, v := range varsAtFrameOffset {
   184  		if v > 2 {
   185  			n3++
   186  		}
   187  		got = append(got, v)
   188  	}
   189  	sort.Ints(got)
   190  	if n3 == 0 {
   191  		t.Logf("%s\n", string(out))
   192  		t.Fatalf("expected at least one clump of 3, got: %+v", got)
   193  	}
   194  }
   195  

View as plain text