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