]> Cypherpunks.ru repositories - gostls13.git/blob - test/nosplit.go
test: check for build constraints only upto the first blank line
[gostls13.git] / test / nosplit.go
1 // +build !nacl
2 // run
3
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.
7
8 package main
9
10 import (
11         "bytes"
12         "fmt"
13         "io/ioutil"
14         "log"
15         "os"
16         "os/exec"
17         "path/filepath"
18         "regexp"
19         "runtime"
20         "strconv"
21         "strings"
22 )
23
24 var tests = `
25 # These are test cases for the linker analysis that detects chains of
26 # nosplit functions that would cause a stack overflow.
27 #
28 # Lines beginning with # are comments.
29 #
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.
39 #
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.
44 #
45 # 64-bit systems do not attempt to execute test cases with frame sizes
46 # that are only 32-bit aligned.
47
48 # Ordinary function should work
49 main 0
50
51 # Large frame marked nosplit is always wrong.
52 main 10000 nosplit
53 REJECT
54
55 # Calling a large frame is okay.
56 main 0 call big
57 big 10000
58
59 # But not if the frame is nosplit.
60 main 0 call big
61 big 10000 nosplit
62 REJECT
63
64 # Recursion is okay.
65 main 0 call main
66
67 # Recursive nosplit runs out of space.
68 main 0 nosplit call main
69 REJECT
70
71 # Chains of ordinary functions okay.
72 main 0 call f1
73 f1 80 call f2
74 f2 80
75
76 # Chains of nosplit must fit in the stack limit, 128 bytes.
77 main 0 call f1
78 f1 80 nosplit call f2
79 f2 80 nosplit
80 REJECT
81
82 # Larger chains.
83 main 0 call f1
84 f1 16 call f2
85 f2 16 call f3
86 f3 16 call f4
87 f4 16 call f5
88 f5 16 call f6
89 f6 16 call f7
90 f7 16 call f8
91 f8 16 call end
92 end 1000
93
94 main 0 call f1
95 f1 16 nosplit call f2
96 f2 16 nosplit call f3
97 f3 16 nosplit call f4
98 f4 16 nosplit call f5
99 f5 16 nosplit call f6
100 f6 16 nosplit call f7
101 f7 16 nosplit call f8
102 f8 16 nosplit call end
103 end 1000
104 REJECT
105
106 # Test cases near the 128-byte limit.
107
108 # Ordinary stack split frame is always okay.
109 main 112
110 main 116
111 main 120
112 main 124
113 main 128
114 main 132
115 main 136
116
117 # A nosplit leaf can use the whole 128-CallSize bytes available on entry.
118 main 112 nosplit
119 main 116 nosplit
120 main 120 nosplit
121 main 124 nosplit
122 main 128 nosplit; REJECT
123 main 132 nosplit; REJECT
124 main 136 nosplit; REJECT
125
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
137
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
151
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
162
163 # Issue 7623
164 main 0 call f; f 112
165 main 0 call f; f 116
166 main 0 call f; f 120
167 main 0 call f; f 124
168 main 0 call f; f 128
169 main 0 call f; f 132
170 main 0 call f; f 136
171 `
172
173 var (
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`)
179 )
180
181 func main() {
182         goarch := os.Getenv("GOARCH")
183         if goarch == "" {
184                 goarch = runtime.GOARCH
185         }
186
187         thechar := ""
188         if gochar, err := exec.Command("go", "env", "GOCHAR").Output(); err != nil {
189                 bug()
190                 fmt.Printf("running go env GOCHAR: %v\n", err)
191                 return
192         } else {
193                 thechar = strings.TrimSpace(string(gochar))
194         }
195
196         version, err := exec.Command("go", "tool", thechar+"g", "-V").Output()
197         if err != nil {
198                 bug()
199                 fmt.Printf("running go tool %sg -V: %v\n", thechar, err)
200                 return
201         }
202         if strings.Contains(string(version), "framepointer") {
203                 // Skip this test if GOEXPERIMENT=framepointer
204                 return
205         }
206
207         dir, err := ioutil.TempDir("", "go-test-nosplit")
208         if err != nil {
209                 bug()
210                 fmt.Printf("creating temp dir: %v\n", err)
211                 return
212         }
213         defer os.RemoveAll(dir)
214
215         tests = strings.Replace(tests, "\t", " ", -1)
216         tests = commentRE.ReplaceAllString(tests, "")
217
218         nok := 0
219         nfail := 0
220 TestCases:
221         for len(tests) > 0 {
222                 var stanza string
223                 i := strings.Index(tests, "\nmain ")
224                 if i < 0 {
225                         stanza, tests = tests, ""
226                 } else {
227                         stanza, tests = tests[:i], tests[i+1:]
228                 }
229
230                 m := rejectRE.FindStringSubmatch(stanza)
231                 if m == nil {
232                         bug()
233                         fmt.Printf("invalid stanza:\n\t%s\n", indent(stanza))
234                         continue
235                 }
236                 lines := strings.TrimSpace(m[1])
237                 reject := false
238                 if m[2] != "" {
239                         if strings.TrimSpace(m[4]) == "" {
240                                 reject = true
241                         } else {
242                                 for _, rej := range strings.Fields(m[4]) {
243                                         if rej == goarch {
244                                                 reject = true
245                                         }
246                                 }
247                         }
248                 }
249                 if lines == "" && !reject {
250                         continue
251                 }
252
253                 var gobuf bytes.Buffer
254                 fmt.Fprintf(&gobuf, "package main\n")
255
256                 var buf bytes.Buffer
257                 ptrSize := 4
258                 switch goarch {
259                 case "ppc64", "ppc64le":
260                         ptrSize = 8
261                         fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (CTR)\n#define RET RETURN\n")
262                 case "arm":
263                         fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n")
264                 case "amd64":
265                         ptrSize = 8
266                         fmt.Fprintf(&buf, "#define REGISTER AX\n")
267                 default:
268                         fmt.Fprintf(&buf, "#define REGISTER AX\n")
269                 }
270
271                 for _, line := range strings.Split(lines, "\n") {
272                         line = strings.TrimSpace(line)
273                         if line == "" {
274                                 continue
275                         }
276                         for i, subline := range strings.Split(line, ";") {
277                                 subline = strings.TrimSpace(subline)
278                                 if subline == "" {
279                                         continue
280                                 }
281                                 m := lineRE.FindStringSubmatch(subline)
282                                 if m == nil {
283                                         bug()
284                                         fmt.Printf("invalid function line: %s\n", subline)
285                                         continue TestCases
286                                 }
287                                 name := m[1]
288                                 size, _ := strconv.Atoi(m[2])
289
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.
293                                 if i == 0 {
294                                         size += 512 - 128
295                                 }
296
297                                 if size%ptrSize == 4 {
298                                         continue TestCases
299                                 }
300                                 nosplit := m[3]
301                                 body := m[4]
302
303                                 if nosplit != "" {
304                                         nosplit = ",7"
305                                 } else {
306                                         nosplit = ",0"
307                                 }
308                                 body = callRE.ReplaceAllString(body, "CALL ·$1(SB);")
309                                 body = callindRE.ReplaceAllString(body, "CALL REGISTER;")
310
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)
313                         }
314                 }
315
316                 if err := ioutil.WriteFile(filepath.Join(dir, "asm.s"), buf.Bytes(), 0666); err != nil {
317                         log.Fatal(err)
318                 }
319                 if err := ioutil.WriteFile(filepath.Join(dir, "main.go"), gobuf.Bytes(), 0666); err != nil {
320                         log.Fatal(err)
321                 }
322
323                 cmd := exec.Command("go", "build")
324                 cmd.Dir = dir
325                 output, err := cmd.CombinedOutput()
326                 if err == nil {
327                         nok++
328                         if reject {
329                                 bug()
330                                 fmt.Printf("accepted incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
331                         }
332                 } else {
333                         nfail++
334                         if !reject {
335                                 bug()
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)))
338                         }
339                 }
340         }
341
342         if !bugged && (nok == 0 || nfail == 0) {
343                 bug()
344                 fmt.Printf("not enough test cases run\n")
345         }
346 }
347
348 func indent(s string) string {
349         return strings.Replace(s, "\n", "\n\t", -1)
350 }
351
352 var bugged = false
353
354 func bug() {
355         if !bugged {
356                 bugged = true
357                 fmt.Printf("BUG\n")
358         }
359 }