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.
122 main 128 nosplit; REJECT
123 main 132 nosplit; REJECT
124 main 136 nosplit; REJECT
126 # Calling a nosplit function from a nosplit function requires
127 # having room for the saved caller PC and the called frame.
128 # Because ARM doesn't save LR in the leaf, it gets an extra 4 bytes.
129 # Because ppc64 doesn't save LR in the leaf, it gets an extra 8 bytes.
130 main 112 nosplit call f; f 0 nosplit
131 main 116 nosplit call f; f 0 nosplit
132 main 120 nosplit call f; f 0 nosplit; REJECT amd64
133 main 124 nosplit call f; f 0 nosplit; REJECT amd64 386
134 main 128 nosplit call f; f 0 nosplit; REJECT
135 main 132 nosplit call f; f 0 nosplit; REJECT
136 main 136 nosplit call f; f 0 nosplit; REJECT
138 # Calling a splitting function from a nosplit function requires
139 # having room for the saved caller PC of the call but also the
140 # saved caller PC for the call to morestack.
141 # Again the ARM and ppc64 work in less space.
142 main 104 nosplit call f; f 0 call f
143 main 108 nosplit call f; f 0 call f
144 main 112 nosplit call f; f 0 call f; REJECT amd64
145 main 116 nosplit call f; f 0 call f; REJECT amd64
146 main 120 nosplit call f; f 0 call f; REJECT amd64 386
147 main 124 nosplit call f; f 0 call f; REJECT amd64 386
148 main 128 nosplit call f; f 0 call f; REJECT
149 main 132 nosplit call f; f 0 call f; REJECT
150 main 136 nosplit call f; f 0 call f; REJECT
152 # Indirect calls are assumed to be splitting functions.
153 main 104 nosplit callind
154 main 108 nosplit callind
155 main 112 nosplit callind; REJECT amd64
156 main 116 nosplit callind; REJECT amd64
157 main 120 nosplit callind; REJECT amd64 386
158 main 124 nosplit callind; REJECT amd64 386
159 main 128 nosplit callind; REJECT
160 main 132 nosplit callind; REJECT
161 main 136 nosplit callind; REJECT
174 commentRE = regexp.MustCompile(`(?m)^#.*`)
175 rejectRE = regexp.MustCompile(`(?s)\A(.+?)((\n|; *)REJECT(.*))?\z`)
176 lineRE = regexp.MustCompile(`(\w+) (\d+)( nosplit)?(.*)`)
177 callRE = regexp.MustCompile(`\bcall (\w+)\b`)
178 callindRE = regexp.MustCompile(`\bcallind\b`)
182 goarch := os.Getenv("GOARCH")
184 goarch = runtime.GOARCH
188 if gochar, err := exec.Command("go", "env", "GOCHAR").Output(); err != nil {
190 fmt.Printf("running go env GOCHAR: %v\n", err)
193 thechar = strings.TrimSpace(string(gochar))
196 version, err := exec.Command("go", "tool", thechar+"g", "-V").Output()
199 fmt.Printf("running go tool %sg -V: %v\n", thechar, err)
202 if strings.Contains(string(version), "framepointer") {
203 // Skip this test if GOEXPERIMENT=framepointer
207 dir, err := ioutil.TempDir("", "go-test-nosplit")
210 fmt.Printf("creating temp dir: %v\n", err)
213 defer os.RemoveAll(dir)
215 tests = strings.Replace(tests, "\t", " ", -1)
216 tests = commentRE.ReplaceAllString(tests, "")
223 i := strings.Index(tests, "\nmain ")
225 stanza, tests = tests, ""
227 stanza, tests = tests[:i], tests[i+1:]
230 m := rejectRE.FindStringSubmatch(stanza)
233 fmt.Printf("invalid stanza:\n\t%s\n", indent(stanza))
236 lines := strings.TrimSpace(m[1])
239 if strings.TrimSpace(m[4]) == "" {
242 for _, rej := range strings.Fields(m[4]) {
249 if lines == "" && !reject {
253 var gobuf bytes.Buffer
254 fmt.Fprintf(&gobuf, "package main\n")
259 case "ppc64", "ppc64le":
261 fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (CTR)\n#define RET RETURN\n")
263 fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n")
266 fmt.Fprintf(&buf, "#define REGISTER AX\n")
268 fmt.Fprintf(&buf, "#define REGISTER AX\n")
271 for _, line := range strings.Split(lines, "\n") {
272 line = strings.TrimSpace(line)
276 for i, subline := range strings.Split(line, ";") {
277 subline = strings.TrimSpace(subline)
281 m := lineRE.FindStringSubmatch(subline)
284 fmt.Printf("invalid function line: %s\n", subline)
288 size, _ := strconv.Atoi(m[2])
290 // The limit was originally 128 but is now 512.
291 // Instead of rewriting the test cases above, adjust
292 // the first stack frame to use up the extra 32 bytes.
297 if size%ptrSize == 4 {
308 body = callRE.ReplaceAllString(body, "CALL ·$1(SB);")
309 body = callindRE.ReplaceAllString(body, "CALL REGISTER;")
311 fmt.Fprintf(&gobuf, "func %s()\n", name)
312 fmt.Fprintf(&buf, "TEXT ·%s(SB)%s,$%d-0\n\t%s\n\tRET\n\n", name, nosplit, size, body)
316 if err := ioutil.WriteFile(filepath.Join(dir, "asm.s"), buf.Bytes(), 0666); err != nil {
319 if err := ioutil.WriteFile(filepath.Join(dir, "main.go"), gobuf.Bytes(), 0666); err != nil {
323 cmd := exec.Command("go", "build")
325 output, err := cmd.CombinedOutput()
330 fmt.Printf("accepted incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
336 fmt.Printf("rejected incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
337 fmt.Printf("\n\tlinker output:\n\t%s\n", indent(string(output)))
342 if !bugged && (nok == 0 || nfail == 0) {
344 fmt.Printf("not enough test cases run\n")
348 func indent(s string) string {
349 return strings.Replace(s, "\n", "\n\t", -1)