]> Cypherpunks.ru repositories - gostls13.git/blob - test/nosplit.go
[dev.ssa] Merge remote-tracking branch 'origin/master' into dev.ssa
[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 "arm64":
265                         ptrSize = 8
266                         fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n")
267                 case "amd64":
268                         ptrSize = 8
269                         fmt.Fprintf(&buf, "#define REGISTER AX\n")
270                 default:
271                         fmt.Fprintf(&buf, "#define REGISTER AX\n")
272                 }
273
274                 for _, line := range strings.Split(lines, "\n") {
275                         line = strings.TrimSpace(line)
276                         if line == "" {
277                                 continue
278                         }
279                         for i, subline := range strings.Split(line, ";") {
280                                 subline = strings.TrimSpace(subline)
281                                 if subline == "" {
282                                         continue
283                                 }
284                                 m := lineRE.FindStringSubmatch(subline)
285                                 if m == nil {
286                                         bug()
287                                         fmt.Printf("invalid function line: %s\n", subline)
288                                         continue TestCases
289                                 }
290                                 name := m[1]
291                                 size, _ := strconv.Atoi(m[2])
292
293                                 // The limit was originally 128 but is now 512.
294                                 // Instead of rewriting the test cases above, adjust
295                                 // the first stack frame to use up the extra bytes.
296                                 if i == 0 {
297                                         size += 512 - 128
298                                         // Noopt builds have a larger stackguard.
299                                         // See ../cmd/dist/buildruntime.go:stackGuardMultiplier
300                                         for _, s := range strings.Split(os.Getenv("GO_GCFLAGS"), " ") {
301                                                 if s == "-N" {
302                                                         size += 640
303                                                 }
304                                         }
305                                 }
306
307                                 if size%ptrSize == 4 {
308                                         continue TestCases
309                                 }
310                                 nosplit := m[3]
311                                 body := m[4]
312
313                                 if nosplit != "" {
314                                         nosplit = ",7"
315                                 } else {
316                                         nosplit = ",0"
317                                 }
318                                 body = callRE.ReplaceAllString(body, "CALL ·$1(SB);")
319                                 body = callindRE.ReplaceAllString(body, "CALL REGISTER;")
320
321                                 fmt.Fprintf(&gobuf, "func %s()\n", name)
322                                 fmt.Fprintf(&buf, "TEXT ·%s(SB)%s,$%d-0\n\t%s\n\tRET\n\n", name, nosplit, size, body)
323                         }
324                 }
325
326                 if err := ioutil.WriteFile(filepath.Join(dir, "asm.s"), buf.Bytes(), 0666); err != nil {
327                         log.Fatal(err)
328                 }
329                 if err := ioutil.WriteFile(filepath.Join(dir, "main.go"), gobuf.Bytes(), 0666); err != nil {
330                         log.Fatal(err)
331                 }
332
333                 cmd := exec.Command("go", "build")
334                 cmd.Dir = dir
335                 output, err := cmd.CombinedOutput()
336                 if err == nil {
337                         nok++
338                         if reject {
339                                 bug()
340                                 fmt.Printf("accepted incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
341                         }
342                 } else {
343                         nfail++
344                         if !reject {
345                                 bug()
346                                 fmt.Printf("rejected incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
347                                 fmt.Printf("\n\tlinker output:\n\t%s\n", indent(string(output)))
348                         }
349                 }
350         }
351
352         if !bugged && (nok == 0 || nfail == 0) {
353                 bug()
354                 fmt.Printf("not enough test cases run\n")
355         }
356 }
357
358 func indent(s string) string {
359         return strings.Replace(s, "\n", "\n\t", -1)
360 }
361
362 var bugged = false
363
364 func bug() {
365         if !bugged {
366                 bugged = true
367                 fmt.Printf("BUG\n")
368         }
369 }