github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/compile/loopvar/loopvar_test.go (about) 1 // Copyright 2023 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 loopvar_test 6 7 import ( 8 "os/exec" 9 "path/filepath" 10 "regexp" 11 "runtime" 12 "strings" 13 "testing" 14 15 "github.com/go-asm/go/testenv" 16 ) 17 18 type testcase struct { 19 lvFlag string // ==-2, -1, 0, 1, 2 20 buildExpect string // message, if any 21 expectRC int 22 files []string 23 } 24 25 var for_files = []string{ 26 "for_esc_address.go", // address of variable 27 "for_esc_closure.go", // closure of variable 28 "for_esc_minimal_closure.go", // simple closure of variable 29 "for_esc_method.go", // method value of variable 30 "for_complicated_esc_address.go", // modifies loop index in body 31 } 32 33 var range_files = []string{ 34 "range_esc_address.go", // address of variable 35 "range_esc_closure.go", // closure of variable 36 "range_esc_minimal_closure.go", // simple closure of variable 37 "range_esc_method.go", // method value of variable 38 } 39 40 var cases = []testcase{ 41 {"-1", "", 11, for_files[:1]}, 42 {"0", "", 0, for_files[:1]}, 43 {"1", "", 0, for_files[:1]}, 44 {"2", "loop variable i now per-iteration,", 0, for_files}, 45 46 {"-1", "", 11, range_files[:1]}, 47 {"0", "", 0, range_files[:1]}, 48 {"1", "", 0, range_files[:1]}, 49 {"2", "loop variable i now per-iteration,", 0, range_files}, 50 51 {"1", "", 0, []string{"for_nested.go"}}, 52 } 53 54 // TestLoopVar checks that the GOEXPERIMENT and debug flags behave as expected. 55 func TestLoopVarGo1_21(t *testing.T) { 56 switch runtime.GOOS { 57 case "linux", "darwin": 58 default: 59 t.Skipf("Slow test, usually avoid it, os=%s not linux or darwin", runtime.GOOS) 60 } 61 switch runtime.GOARCH { 62 case "amd64", "arm64": 63 default: 64 t.Skipf("Slow test, usually avoid it, arch=%s not amd64 or arm64", runtime.GOARCH) 65 } 66 67 testenv.MustHaveGoBuild(t) 68 gocmd := testenv.GoToolPath(t) 69 tmpdir := t.TempDir() 70 output := filepath.Join(tmpdir, "foo.exe") 71 72 for i, tc := range cases { 73 for _, f := range tc.files { 74 source := f 75 cmd := testenv.Command(t, gocmd, "build", "-o", output, "-gcflags=-lang=go1.21 -d=loopvar="+tc.lvFlag, source) 76 cmd.Env = append(cmd.Env, "GOEXPERIMENT=loopvar", "HOME="+tmpdir) 77 cmd.Dir = "testdata" 78 t.Logf("File %s loopvar=%s expect '%s' exit code %d", f, tc.lvFlag, tc.buildExpect, tc.expectRC) 79 b, e := cmd.CombinedOutput() 80 if e != nil { 81 t.Error(e) 82 } 83 if tc.buildExpect != "" { 84 s := string(b) 85 if !strings.Contains(s, tc.buildExpect) { 86 t.Errorf("File %s test %d expected to match '%s' with \n-----\n%s\n-----", f, i, tc.buildExpect, s) 87 } 88 } 89 // run what we just built. 90 cmd = testenv.Command(t, output) 91 b, e = cmd.CombinedOutput() 92 if tc.expectRC != 0 { 93 if e == nil { 94 t.Errorf("Missing expected error, file %s, case %d", f, i) 95 } else if ee, ok := (e).(*exec.ExitError); !ok || ee.ExitCode() != tc.expectRC { 96 t.Error(e) 97 } else { 98 // okay 99 } 100 } else if e != nil { 101 t.Error(e) 102 } 103 } 104 } 105 } 106 107 func TestLoopVarInlinesGo1_21(t *testing.T) { 108 switch runtime.GOOS { 109 case "linux", "darwin": 110 default: 111 t.Skipf("Slow test, usually avoid it, os=%s not linux or darwin", runtime.GOOS) 112 } 113 switch runtime.GOARCH { 114 case "amd64", "arm64": 115 default: 116 t.Skipf("Slow test, usually avoid it, arch=%s not amd64 or arm64", runtime.GOARCH) 117 } 118 119 testenv.MustHaveGoBuild(t) 120 gocmd := testenv.GoToolPath(t) 121 tmpdir := t.TempDir() 122 123 root := "github.com/go-asm/go/cmd/compile/loopvar/testdata/inlines" 124 125 f := func(pkg string) string { 126 // This disables the loopvar change, except for the specified package. 127 // The effect should follow the package, even though everything (except "c") 128 // is inlined. 129 cmd := testenv.Command(t, gocmd, "run", "-gcflags="+root+"/...=-lang=go1.21", "-gcflags="+pkg+"=-d=loopvar=1", root) 130 cmd.Env = append(cmd.Env, "GOEXPERIMENT=noloopvar", "HOME="+tmpdir) 131 cmd.Dir = filepath.Join("testdata", "inlines") 132 133 b, e := cmd.CombinedOutput() 134 if e != nil { 135 t.Error(e) 136 } 137 return string(b) 138 } 139 140 a := f(root + "/a") 141 b := f(root + "/b") 142 c := f(root + "/c") 143 m := f(root) 144 145 t.Logf(a) 146 t.Logf(b) 147 t.Logf(c) 148 t.Logf(m) 149 150 if !strings.Contains(a, "f, af, bf, abf, cf sums = 100, 45, 100, 100, 100") { 151 t.Errorf("Did not see expected value of a") 152 } 153 if !strings.Contains(b, "f, af, bf, abf, cf sums = 100, 100, 45, 45, 100") { 154 t.Errorf("Did not see expected value of b") 155 } 156 if !strings.Contains(c, "f, af, bf, abf, cf sums = 100, 100, 100, 100, 45") { 157 t.Errorf("Did not see expected value of c") 158 } 159 if !strings.Contains(m, "f, af, bf, abf, cf sums = 45, 100, 100, 100, 100") { 160 t.Errorf("Did not see expected value of m") 161 } 162 } 163 164 func countMatches(s, re string) int { 165 slice := regexp.MustCompile(re).FindAllString(s, -1) 166 return len(slice) 167 } 168 169 func TestLoopVarHashes(t *testing.T) { 170 // This behavior does not depend on Go version (1.21 or greater) 171 switch runtime.GOOS { 172 case "linux", "darwin": 173 default: 174 t.Skipf("Slow test, usually avoid it, os=%s not linux or darwin", runtime.GOOS) 175 } 176 switch runtime.GOARCH { 177 case "amd64", "arm64": 178 default: 179 t.Skipf("Slow test, usually avoid it, arch=%s not amd64 or arm64", runtime.GOARCH) 180 } 181 182 testenv.MustHaveGoBuild(t) 183 gocmd := testenv.GoToolPath(t) 184 tmpdir := t.TempDir() 185 186 root := "github.com/go-asm/go/cmd/compile/loopvar/testdata/inlines" 187 188 f := func(hash string) string { 189 // This disables the loopvar change, except for the specified hash pattern. 190 // -trimpath is necessary so we get the same answer no matter where the 191 // Go repository is checked out. This is not normally a concern since people 192 // do not normally rely on the meaning of specific hashes. 193 cmd := testenv.Command(t, gocmd, "run", "-trimpath", root) 194 cmd.Env = append(cmd.Env, "GOCOMPILEDEBUG=loopvarhash="+hash, "HOME="+tmpdir) 195 cmd.Dir = filepath.Join("testdata", "inlines") 196 197 b, _ := cmd.CombinedOutput() 198 // Ignore the error, sometimes it's supposed to fail, the output test will catch it. 199 return string(b) 200 } 201 202 for _, arg := range []string{"v001100110110110010100100", "vx336ca4"} { 203 m := f(arg) 204 t.Logf(m) 205 206 mCount := countMatches(m, "loopvarhash triggered github.com/go-asm/go/cmd/compile/loopvar/testdata/inlines/main.go:27:6: .* 001100110110110010100100") 207 otherCount := strings.Count(m, "loopvarhash") 208 if mCount < 1 { 209 t.Errorf("%s: did not see triggered main.go:27:6", arg) 210 } 211 if mCount != otherCount { 212 t.Errorf("%s: too many matches", arg) 213 } 214 mCount = countMatches(m, "github.com/go-asm/go/cmd/compile/loopvar/testdata/inlines/main.go:27:6: .* \\[bisect-match 0x7802e115b9336ca4\\]") 215 otherCount = strings.Count(m, "[bisect-match ") 216 if mCount < 1 { 217 t.Errorf("%s: did not see bisect-match for main.go:27:6", arg) 218 } 219 if mCount != otherCount { 220 t.Errorf("%s: too many matches", arg) 221 } 222 223 // This next test carefully dodges a bug-to-be-fixed with inlined locations for ir.Names. 224 if !strings.Contains(m, ", 100, 100, 100, 100") { 225 t.Errorf("%s: did not see expected value of m run", arg) 226 } 227 } 228 } 229 230 // TestLoopVarVersionEnableFlag checks for loopvar transformation enabled by command line flag (1.22). 231 func TestLoopVarVersionEnableFlag(t *testing.T) { 232 switch runtime.GOOS { 233 case "linux", "darwin": 234 default: 235 t.Skipf("Slow test, usually avoid it, os=%s not linux or darwin", runtime.GOOS) 236 } 237 switch runtime.GOARCH { 238 case "amd64", "arm64": 239 default: 240 t.Skipf("Slow test, usually avoid it, arch=%s not amd64 or arm64", runtime.GOARCH) 241 } 242 243 testenv.MustHaveGoBuild(t) 244 gocmd := testenv.GoToolPath(t) 245 246 // loopvar=3 logs info but does not change loopvarness 247 cmd := testenv.Command(t, gocmd, "run", "-gcflags=-lang=go1.22 -d=loopvar=3", "opt.go") 248 cmd.Dir = filepath.Join("testdata") 249 250 b, err := cmd.CombinedOutput() 251 m := string(b) 252 253 t.Logf(m) 254 255 yCount := strings.Count(m, "opt.go:16:6: loop variable private now per-iteration, heap-allocated (loop inlined into ./opt.go:29)") 256 nCount := strings.Count(m, "shared") 257 258 if yCount != 1 { 259 t.Errorf("yCount=%d != 1", yCount) 260 } 261 if nCount > 0 { 262 t.Errorf("nCount=%d > 0", nCount) 263 } 264 if err != nil { 265 t.Errorf("err=%v != nil", err) 266 } 267 } 268 269 // TestLoopVarVersionEnableGoBuild checks for loopvar transformation enabled by go:build version (1.22). 270 func TestLoopVarVersionEnableGoBuild(t *testing.T) { 271 switch runtime.GOOS { 272 case "linux", "darwin": 273 default: 274 t.Skipf("Slow test, usually avoid it, os=%s not linux or darwin", runtime.GOOS) 275 } 276 switch runtime.GOARCH { 277 case "amd64", "arm64": 278 default: 279 t.Skipf("Slow test, usually avoid it, arch=%s not amd64 or arm64", runtime.GOARCH) 280 } 281 282 testenv.MustHaveGoBuild(t) 283 gocmd := testenv.GoToolPath(t) 284 285 // loopvar=3 logs info but does not change loopvarness 286 cmd := testenv.Command(t, gocmd, "run", "-gcflags=-lang=go1.21 -d=loopvar=3", "opt-122.go") 287 cmd.Dir = filepath.Join("testdata") 288 289 b, err := cmd.CombinedOutput() 290 m := string(b) 291 292 t.Logf(m) 293 294 yCount := strings.Count(m, "opt-122.go:18:6: loop variable private now per-iteration, heap-allocated (loop inlined into ./opt-122.go:31)") 295 nCount := strings.Count(m, "shared") 296 297 if yCount != 1 { 298 t.Errorf("yCount=%d != 1", yCount) 299 } 300 if nCount > 0 { 301 t.Errorf("nCount=%d > 0", nCount) 302 } 303 if err != nil { 304 t.Errorf("err=%v != nil", err) 305 } 306 } 307 308 // TestLoopVarVersionDisableFlag checks for loopvar transformation DISABLED by command line version (1.21). 309 func TestLoopVarVersionDisableFlag(t *testing.T) { 310 switch runtime.GOOS { 311 case "linux", "darwin": 312 default: 313 t.Skipf("Slow test, usually avoid it, os=%s not linux or darwin", runtime.GOOS) 314 } 315 switch runtime.GOARCH { 316 case "amd64", "arm64": 317 default: 318 t.Skipf("Slow test, usually avoid it, arch=%s not amd64 or arm64", runtime.GOARCH) 319 } 320 321 testenv.MustHaveGoBuild(t) 322 gocmd := testenv.GoToolPath(t) 323 324 // loopvar=3 logs info but does not change loopvarness 325 cmd := testenv.Command(t, gocmd, "run", "-gcflags=-lang=go1.21 -d=loopvar=3", "opt.go") 326 cmd.Dir = filepath.Join("testdata") 327 328 b, err := cmd.CombinedOutput() 329 m := string(b) 330 331 t.Logf(m) // expect error 332 333 yCount := strings.Count(m, "opt.go:16:6: loop variable private now per-iteration, heap-allocated (loop inlined into ./opt.go:29)") 334 nCount := strings.Count(m, "shared") 335 336 if yCount != 0 { 337 t.Errorf("yCount=%d != 0", yCount) 338 } 339 if nCount > 0 { 340 t.Errorf("nCount=%d > 0", nCount) 341 } 342 if err == nil { // expect error 343 t.Errorf("err=%v == nil", err) 344 } 345 } 346 347 // TestLoopVarVersionDisableGoBuild checks for loopvar transformation DISABLED by go:build version (1.21). 348 func TestLoopVarVersionDisableGoBuild(t *testing.T) { 349 switch runtime.GOOS { 350 case "linux", "darwin": 351 default: 352 t.Skipf("Slow test, usually avoid it, os=%s not linux or darwin", runtime.GOOS) 353 } 354 switch runtime.GOARCH { 355 case "amd64", "arm64": 356 default: 357 t.Skipf("Slow test, usually avoid it, arch=%s not amd64 or arm64", runtime.GOARCH) 358 } 359 360 testenv.MustHaveGoBuild(t) 361 gocmd := testenv.GoToolPath(t) 362 363 // loopvar=3 logs info but does not change loopvarness 364 cmd := testenv.Command(t, gocmd, "run", "-gcflags=-lang=go1.22 -d=loopvar=3", "opt-121.go") 365 cmd.Dir = filepath.Join("testdata") 366 367 b, err := cmd.CombinedOutput() 368 m := string(b) 369 370 t.Logf(m) // expect error 371 372 yCount := strings.Count(m, "opt-121.go:18:6: loop variable private now per-iteration, heap-allocated (loop inlined into ./opt-121.go:31)") 373 nCount := strings.Count(m, "shared") 374 375 if yCount != 0 { 376 t.Errorf("yCount=%d != 0", yCount) 377 } 378 if nCount > 0 { 379 t.Errorf("nCount=%d > 0", nCount) 380 } 381 if err == nil { // expect error 382 t.Errorf("err=%v == nil", err) 383 } 384 }