1 // +build !nacl,!js,!aix,!gcflags_noopt
4 // Copyright 2014 The Go Authors. All rights reserved.
5 // Use of this source code is governed by a BSD-style
6 // license that can be found in the LICENSE file.
25 # These are test cases for the linker analysis that detects chains of
26 # nosplit functions that would cause a stack overflow.
28 # Lines beginning with # are comments.
30 # Each test case describes a sequence of functions, one per line.
31 # Each function definition is the function name, then the frame size,
32 # then optionally the keyword 'nosplit', then the body of the function.
33 # The body is assembly code, with some shorthands.
34 # The shorthand 'call x' stands for CALL x(SB).
35 # The shorthand 'callind' stands for 'CALL R0', where R0 is a register.
36 # Each test case must define a function named main, and it must be first.
37 # That is, a line beginning "main " indicates the start of a new test case.
38 # Within a stanza, ; can be used instead of \n to separate lines.
40 # After the function definition, the test case ends with an optional
41 # REJECT line, specifying the architectures on which the case should
42 # be rejected. "REJECT" without any architectures means reject on all architectures.
43 # The linker should accept the test case on systems not explicitly rejected.
45 # 64-bit systems do not attempt to execute test cases with frame sizes
46 # that are only 32-bit aligned.
48 # Ordinary function should work
51 # Large frame marked nosplit is always wrong.
55 # Calling a large frame is okay.
59 # But not if the frame is nosplit.
67 # Recursive nosplit runs out of space.
68 main 0 nosplit call main
71 # Chains of ordinary functions okay.
76 # Chains of nosplit must fit in the stack limit, 128 bytes.
100 f6 16 nosplit call f7
101 f7 16 nosplit call f8
102 f8 16 nosplit call end
106 # Test cases near the 128-byte limit.
108 # Ordinary stack split frame is always okay.
117 # A nosplit leaf can use the whole 128-CallSize bytes available on entry.
118 # (CallSize is 32 on ppc64, 8 on amd64 for frame pointer.)
120 main 100 nosplit; REJECT ppc64 ppc64le
121 main 104 nosplit; REJECT ppc64 ppc64le arm64
122 main 108 nosplit; REJECT ppc64 ppc64le
123 main 112 nosplit; REJECT ppc64 ppc64le arm64
124 main 116 nosplit; REJECT ppc64 ppc64le
125 main 120 nosplit; REJECT ppc64 ppc64le amd64 arm64
126 main 124 nosplit; REJECT ppc64 ppc64le amd64
127 main 128 nosplit; REJECT
128 main 132 nosplit; REJECT
129 main 136 nosplit; REJECT
131 # Calling a nosplit function from a nosplit function requires
132 # having room for the saved caller PC and the called frame.
133 # Because ARM doesn't save LR in the leaf, it gets an extra 4 bytes.
134 # Because arm64 doesn't save LR in the leaf, it gets an extra 8 bytes.
135 # ppc64 doesn't save LR in the leaf, but CallSize is 32, so it gets 24 bytes.
136 # Because AMD64 uses frame pointer, it has 8 fewer bytes.
137 main 96 nosplit call f; f 0 nosplit
138 main 100 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
139 main 104 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le arm64
140 main 108 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
141 main 112 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64 arm64
142 main 116 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64
143 main 120 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64 arm64
144 main 124 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64 386
145 main 128 nosplit call f; f 0 nosplit; REJECT
146 main 132 nosplit call f; f 0 nosplit; REJECT
147 main 136 nosplit call f; f 0 nosplit; REJECT
149 # Calling a splitting function from a nosplit function requires
150 # having room for the saved caller PC of the call but also the
151 # saved caller PC for the call to morestack.
152 # Architectures differ in the same way as before.
153 main 96 nosplit call f; f 0 call f
154 main 100 nosplit call f; f 0 call f; REJECT ppc64 ppc64le
155 main 104 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 arm64
156 main 108 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
157 main 112 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 arm64
158 main 116 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
159 main 120 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 386 arm64
160 main 124 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 386
161 main 128 nosplit call f; f 0 call f; REJECT
162 main 132 nosplit call f; f 0 call f; REJECT
163 main 136 nosplit call f; f 0 call f; REJECT
165 # Indirect calls are assumed to be splitting functions.
166 main 96 nosplit callind
167 main 100 nosplit callind; REJECT ppc64 ppc64le
168 main 104 nosplit callind; REJECT ppc64 ppc64le amd64 arm64
169 main 108 nosplit callind; REJECT ppc64 ppc64le amd64
170 main 112 nosplit callind; REJECT ppc64 ppc64le amd64 arm64
171 main 116 nosplit callind; REJECT ppc64 ppc64le amd64
172 main 120 nosplit callind; REJECT ppc64 ppc64le amd64 386 arm64
173 main 124 nosplit callind; REJECT ppc64 ppc64le amd64 386
174 main 128 nosplit callind; REJECT
175 main 132 nosplit callind; REJECT
176 main 136 nosplit callind; REJECT
189 commentRE = regexp.MustCompile(`(?m)^#.*`)
190 rejectRE = regexp.MustCompile(`(?s)\A(.+?)((\n|; *)REJECT(.*))?\z`)
191 lineRE = regexp.MustCompile(`(\w+) (\d+)( nosplit)?(.*)`)
192 callRE = regexp.MustCompile(`\bcall (\w+)\b`)
193 callindRE = regexp.MustCompile(`\bcallind\b`)
197 goarch := os.Getenv("GOARCH")
199 goarch = runtime.GOARCH
202 version, err := exec.Command("go", "tool", "compile", "-V").Output()
205 fmt.Printf("running go tool compile -V: %v\n", err)
208 if s := string(version); goarch == "amd64" && strings.Contains(s, "X:") && !strings.Contains(s, "framepointer") {
209 // Skip this test if framepointer is NOT enabled on AMD64
213 dir, err := ioutil.TempDir("", "go-test-nosplit")
216 fmt.Printf("creating temp dir: %v\n", err)
219 defer os.RemoveAll(dir)
220 os.Setenv("GOPATH", filepath.Join(dir, "_gopath"))
222 if err := ioutil.WriteFile(filepath.Join(dir, "go.mod"), []byte("module go-test-nosplit\n"), 0666); err != nil {
226 tests = strings.Replace(tests, "\t", " ", -1)
227 tests = commentRE.ReplaceAllString(tests, "")
234 i := strings.Index(tests, "\nmain ")
236 stanza, tests = tests, ""
238 stanza, tests = tests[:i], tests[i+1:]
241 m := rejectRE.FindStringSubmatch(stanza)
244 fmt.Printf("invalid stanza:\n\t%s\n", indent(stanza))
247 lines := strings.TrimSpace(m[1])
250 if strings.TrimSpace(m[4]) == "" {
253 for _, rej := range strings.Fields(m[4]) {
260 if lines == "" && !reject {
264 var gobuf bytes.Buffer
265 fmt.Fprintf(&gobuf, "package main\n")
270 case "mips", "mipsle":
271 fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
272 case "mips64", "mips64le":
274 fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
275 case "ppc64", "ppc64le":
277 fmt.Fprintf(&buf, "#define REGISTER (CTR)\n")
279 fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
282 fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
285 fmt.Fprintf(&buf, "#define REGISTER AX\n")
288 fmt.Fprintf(&buf, "#define REGISTER R10\n")
290 fmt.Fprintf(&buf, "#define REGISTER AX\n")
293 for _, line := range strings.Split(lines, "\n") {
294 line = strings.TrimSpace(line)
298 for i, subline := range strings.Split(line, ";") {
299 subline = strings.TrimSpace(subline)
303 m := lineRE.FindStringSubmatch(subline)
306 fmt.Printf("invalid function line: %s\n", subline)
310 size, _ := strconv.Atoi(m[2])
312 // The limit was originally 128 but is now 752 (880-128).
313 // Instead of rewriting the test cases above, adjust
314 // the first stack frame to use up the extra bytes.
316 size += (880 - 128) - 128
317 // Noopt builds have a larger stackguard.
318 // See ../src/cmd/dist/buildruntime.go:stackGuardMultiplier
319 // This increase is included in objabi.StackGuard
320 for _, s := range strings.Split(os.Getenv("GO_GCFLAGS"), " ") {
327 if size%ptrSize == 4 {
338 body = callRE.ReplaceAllString(body, "CALL ·$1(SB);")
339 body = callindRE.ReplaceAllString(body, "CALL REGISTER;")
341 fmt.Fprintf(&gobuf, "func %s()\n", name)
342 fmt.Fprintf(&buf, "TEXT ·%s(SB)%s,$%d-0\n\t%s\n\tRET\n\n", name, nosplit, size, body)
346 if err := ioutil.WriteFile(filepath.Join(dir, "asm.s"), buf.Bytes(), 0666); err != nil {
349 if err := ioutil.WriteFile(filepath.Join(dir, "main.go"), gobuf.Bytes(), 0666); err != nil {
353 cmd := exec.Command("go", "build")
355 output, err := cmd.CombinedOutput()
360 fmt.Printf("accepted incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
366 fmt.Printf("rejected incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
367 fmt.Printf("\n\tlinker output:\n\t%s\n", indent(string(output)))
372 if !bugged && (nok == 0 || nfail == 0) {
374 fmt.Printf("not enough test cases run\n")
378 func indent(s string) string {
379 return strings.Replace(s, "\n", "\n\t", -1)