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.
26 # These are test cases for the linker analysis that detects chains of
27 # nosplit functions that would cause a stack overflow.
29 # Lines beginning with # are comments.
31 # Each test case describes a sequence of functions, one per line.
32 # Each function definition is the function name, then the frame size,
33 # then optionally the keyword 'nosplit', then the body of the function.
34 # The body is assembly code, with some shorthands.
35 # The shorthand 'call x' stands for CALL x(SB).
36 # The shorthand 'callind' stands for 'CALL R0', where R0 is a register.
37 # Each test case must define a function named main, and it must be first.
38 # That is, a line beginning "main " indicates the start of a new test case.
39 # Within a stanza, ; can be used instead of \n to separate lines.
41 # After the function definition, the test case ends with an optional
42 # REJECT line, specifying the architectures on which the case should
43 # be rejected. "REJECT" without any architectures means reject on all architectures.
44 # The linker should accept the test case on systems not explicitly rejected.
46 # 64-bit systems do not attempt to execute test cases with frame sizes
47 # that are only 32-bit aligned.
49 # Ordinary function should work
52 # Large frame marked nosplit is always wrong.
56 # Calling a large frame is okay.
60 # But not if the frame is nosplit.
68 # Recursive nosplit runs out of space.
69 main 0 nosplit call main
72 # Chains of ordinary functions okay.
77 # Chains of nosplit must fit in the stack limit, 128 bytes.
100 f5 16 nosplit call f6
101 f6 16 nosplit call f7
102 f7 16 nosplit call f8
103 f8 16 nosplit call end
107 # Test cases near the 128-byte limit.
109 # Ordinary stack split frame is always okay.
118 # A nosplit leaf can use the whole 128-CallSize bytes available on entry.
119 # (CallSize is 32 on ppc64)
121 main 100 nosplit; REJECT ppc64 ppc64le
122 main 104 nosplit; REJECT ppc64 ppc64le
123 main 108 nosplit; REJECT ppc64 ppc64le
124 main 112 nosplit; REJECT ppc64 ppc64le
125 main 116 nosplit; REJECT ppc64 ppc64le
126 main 120 nosplit; REJECT ppc64 ppc64le
127 main 124 nosplit; REJECT ppc64 ppc64le
128 main 128 nosplit; REJECT
129 main 132 nosplit; REJECT
130 main 136 nosplit; REJECT
132 # Calling a nosplit function from a nosplit function requires
133 # having room for the saved caller PC and the called frame.
134 # Because ARM doesn't save LR in the leaf, it gets an extra 4 bytes.
135 # Because arm64 doesn't save LR in the leaf, it gets an extra 8 bytes.
136 # ppc64 doesn't save LR in the leaf, but CallSize is 32, so it gets 24 fewer bytes than amd64.
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
140 main 108 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
141 main 112 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
142 main 116 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
143 main 120 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64
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 # RISC 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
156 main 108 nosplit call f; f 0 call f; REJECT ppc64 ppc64le
157 main 112 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
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
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
169 main 108 nosplit callind; REJECT ppc64 ppc64le
170 main 112 nosplit callind; REJECT ppc64 ppc64le amd64
171 main 116 nosplit callind; REJECT ppc64 ppc64le amd64
172 main 120 nosplit callind; REJECT ppc64 ppc64le amd64 386
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 strings.Contains(string(version), "framepointer") {
209 // Skip this test if GOEXPERIMENT=framepointer
213 dir, err := ioutil.TempDir("", "go-test-nosplit")
216 fmt.Printf("creating temp dir: %v\n", err)
219 defer os.RemoveAll(dir)
221 tests = strings.Replace(tests, "\t", " ", -1)
222 tests = commentRE.ReplaceAllString(tests, "")
229 i := strings.Index(tests, "\nmain ")
231 stanza, tests = tests, ""
233 stanza, tests = tests[:i], tests[i+1:]
236 m := rejectRE.FindStringSubmatch(stanza)
239 fmt.Printf("invalid stanza:\n\t%s\n", indent(stanza))
242 lines := strings.TrimSpace(m[1])
245 if strings.TrimSpace(m[4]) == "" {
248 for _, rej := range strings.Fields(m[4]) {
255 if lines == "" && !reject {
259 var gobuf bytes.Buffer
260 fmt.Fprintf(&gobuf, "package main\n")
265 case "mips64", "mips64le":
267 fmt.Fprintf(&buf, "#define CALL JAL\n#define REGISTER (R0)\n")
268 case "ppc64", "ppc64le":
270 fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (CTR)\n")
272 fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n")
275 fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n")
278 fmt.Fprintf(&buf, "#define REGISTER AX\n")
280 fmt.Fprintf(&buf, "#define REGISTER AX\n")
283 for _, line := range strings.Split(lines, "\n") {
284 line = strings.TrimSpace(line)
288 for i, subline := range strings.Split(line, ";") {
289 subline = strings.TrimSpace(subline)
293 m := lineRE.FindStringSubmatch(subline)
296 fmt.Printf("invalid function line: %s\n", subline)
300 size, _ := strconv.Atoi(m[2])
302 // The limit was originally 128 but is now 592.
303 // Instead of rewriting the test cases above, adjust
304 // the first stack frame to use up the extra bytes.
306 size += (obj.StackGuard - 128) - 128
307 // Noopt builds have a larger stackguard.
308 // See ../src/cmd/dist/buildruntime.go:stackGuardMultiplier
309 // This increase is included in obj.StackGuard
310 for _, s := range strings.Split(os.Getenv("GO_GCFLAGS"), " ") {
312 size += obj.StackGuard
317 if size%ptrSize == 4 || goarch == "arm64" && size != 0 && (size+8)%16 != 0 {
328 body = callRE.ReplaceAllString(body, "CALL ·$1(SB);")
329 body = callindRE.ReplaceAllString(body, "CALL REGISTER;")
331 fmt.Fprintf(&gobuf, "func %s()\n", name)
332 fmt.Fprintf(&buf, "TEXT ·%s(SB)%s,$%d-0\n\t%s\n\tRET\n\n", name, nosplit, size, body)
336 if err := ioutil.WriteFile(filepath.Join(dir, "asm.s"), buf.Bytes(), 0666); err != nil {
339 if err := ioutil.WriteFile(filepath.Join(dir, "main.go"), gobuf.Bytes(), 0666); err != nil {
343 cmd := exec.Command("go", "build")
345 output, err := cmd.CombinedOutput()
350 fmt.Printf("accepted incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
356 fmt.Printf("rejected incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
357 fmt.Printf("\n\tlinker output:\n\t%s\n", indent(string(output)))
362 if !bugged && (nok == 0 || nfail == 0) {
364 fmt.Printf("not enough test cases run\n")
368 func indent(s string) string {
369 return strings.Replace(s, "\n", "\n\t", -1)