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)
120 main 100 nosplit; REJECT ppc64 ppc64le
121 main 104 nosplit; REJECT ppc64 ppc64le
122 main 108 nosplit; REJECT ppc64 ppc64le
123 main 112 nosplit; REJECT ppc64 ppc64le
124 main 116 nosplit; REJECT ppc64 ppc64le
125 main 120 nosplit; REJECT ppc64 ppc64le
126 main 124 nosplit; REJECT ppc64 ppc64le
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 fewer bytes than amd64.
136 main 96 nosplit call f; f 0 nosplit
137 main 100 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
138 main 104 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
139 main 108 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
140 main 112 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
141 main 116 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
142 main 120 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64
143 main 124 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64 386
144 main 128 nosplit call f; f 0 nosplit; REJECT
145 main 132 nosplit call f; f 0 nosplit; REJECT
146 main 136 nosplit call f; f 0 nosplit; REJECT
148 # Calling a splitting function from a nosplit function requires
149 # having room for the saved caller PC of the call but also the
150 # saved caller PC for the call to morestack.
151 # RISC architectures differ in the same way as before.
152 main 96 nosplit call f; f 0 call f
153 main 100 nosplit call f; f 0 call f; REJECT ppc64 ppc64le
154 main 104 nosplit call f; f 0 call f; REJECT ppc64 ppc64le
155 main 108 nosplit call f; f 0 call f; REJECT ppc64 ppc64le
156 main 112 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
157 main 116 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
158 main 120 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 386
159 main 124 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 386
160 main 128 nosplit call f; f 0 call f; REJECT
161 main 132 nosplit call f; f 0 call f; REJECT
162 main 136 nosplit call f; f 0 call f; REJECT
164 # Indirect calls are assumed to be splitting functions.
165 main 96 nosplit callind
166 main 100 nosplit callind; REJECT ppc64 ppc64le
167 main 104 nosplit callind; REJECT ppc64 ppc64le
168 main 108 nosplit callind; REJECT ppc64 ppc64le
169 main 112 nosplit callind; REJECT ppc64 ppc64le amd64
170 main 116 nosplit callind; REJECT ppc64 ppc64le amd64
171 main 120 nosplit callind; REJECT ppc64 ppc64le amd64 386
172 main 124 nosplit callind; REJECT ppc64 ppc64le amd64 386
173 main 128 nosplit callind; REJECT
174 main 132 nosplit callind; REJECT
175 main 136 nosplit callind; REJECT
188 commentRE = regexp.MustCompile(`(?m)^#.*`)
189 rejectRE = regexp.MustCompile(`(?s)\A(.+?)((\n|; *)REJECT(.*))?\z`)
190 lineRE = regexp.MustCompile(`(\w+) (\d+)( nosplit)?(.*)`)
191 callRE = regexp.MustCompile(`\bcall (\w+)\b`)
192 callindRE = regexp.MustCompile(`\bcallind\b`)
196 goarch := os.Getenv("GOARCH")
198 goarch = runtime.GOARCH
201 version, err := exec.Command("go", "tool", "compile", "-V").Output()
204 fmt.Printf("running go tool compile -V: %v\n", err)
207 if strings.Contains(string(version), "framepointer") {
208 // Skip this test if GOEXPERIMENT=framepointer
212 dir, err := ioutil.TempDir("", "go-test-nosplit")
215 fmt.Printf("creating temp dir: %v\n", err)
218 defer os.RemoveAll(dir)
220 tests = strings.Replace(tests, "\t", " ", -1)
221 tests = commentRE.ReplaceAllString(tests, "")
228 i := strings.Index(tests, "\nmain ")
230 stanza, tests = tests, ""
232 stanza, tests = tests[:i], tests[i+1:]
235 m := rejectRE.FindStringSubmatch(stanza)
238 fmt.Printf("invalid stanza:\n\t%s\n", indent(stanza))
241 lines := strings.TrimSpace(m[1])
244 if strings.TrimSpace(m[4]) == "" {
247 for _, rej := range strings.Fields(m[4]) {
254 if lines == "" && !reject {
258 var gobuf bytes.Buffer
259 fmt.Fprintf(&gobuf, "package main\n")
264 case "mips64", "mips64le":
266 fmt.Fprintf(&buf, "#define CALL JAL\n#define REGISTER (R0)\n")
267 case "ppc64", "ppc64le":
269 fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (CTR)\n")
271 fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n")
274 fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n")
277 fmt.Fprintf(&buf, "#define REGISTER AX\n")
279 fmt.Fprintf(&buf, "#define REGISTER AX\n")
282 for _, line := range strings.Split(lines, "\n") {
283 line = strings.TrimSpace(line)
287 for i, subline := range strings.Split(line, ";") {
288 subline = strings.TrimSpace(subline)
292 m := lineRE.FindStringSubmatch(subline)
295 fmt.Printf("invalid function line: %s\n", subline)
299 size, _ := strconv.Atoi(m[2])
301 // The limit was originally 128 but is now 592.
302 // Instead of rewriting the test cases above, adjust
303 // the first stack frame to use up the extra bytes.
306 // Noopt builds have a larger stackguard.
307 // See ../cmd/dist/buildruntime.go:stackGuardMultiplier
308 for _, s := range strings.Split(os.Getenv("GO_GCFLAGS"), " ") {
315 if size%ptrSize == 4 || goarch == "arm64" && size != 0 && (size+8)%16 != 0 {
326 body = callRE.ReplaceAllString(body, "CALL ·$1(SB);")
327 body = callindRE.ReplaceAllString(body, "CALL REGISTER;")
329 fmt.Fprintf(&gobuf, "func %s()\n", name)
330 fmt.Fprintf(&buf, "TEXT ·%s(SB)%s,$%d-0\n\t%s\n\tRET\n\n", name, nosplit, size, body)
334 if err := ioutil.WriteFile(filepath.Join(dir, "asm.s"), buf.Bytes(), 0666); err != nil {
337 if err := ioutil.WriteFile(filepath.Join(dir, "main.go"), gobuf.Bytes(), 0666); err != nil {
341 cmd := exec.Command("go", "build")
343 output, err := cmd.CombinedOutput()
348 fmt.Printf("accepted incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
354 fmt.Printf("rejected incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
355 fmt.Printf("\n\tlinker output:\n\t%s\n", indent(string(output)))
360 if !bugged && (nok == 0 || nfail == 0) {
362 fmt.Printf("not enough test cases run\n")
366 func indent(s string) string {
367 return strings.Replace(s, "\n", "\n\t", -1)