// 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) } }