]> 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         "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 # (CallSize is 32 on ppc64)
119 main 96 nosplit
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
130
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
147
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
163
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
176
177 # Issue 7623
178 main 0 call f; f 112
179 main 0 call f; f 116
180 main 0 call f; f 120
181 main 0 call f; f 124
182 main 0 call f; f 128
183 main 0 call f; f 132
184 main 0 call f; f 136
185 `
186
187 var (
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`)
193 )
194
195 func main() {
196         goarch := os.Getenv("GOARCH")
197         if goarch == "" {
198                 goarch = runtime.GOARCH
199         }
200
201         version, err := exec.Command("go", "tool", "compile", "-V").Output()
202         if err != nil {
203                 bug()
204                 fmt.Printf("running go tool compile -V: %v\n", err)
205                 return
206         }
207         if strings.Contains(string(version), "framepointer") {
208                 // Skip this test if GOEXPERIMENT=framepointer
209                 return
210         }
211
212         dir, err := ioutil.TempDir("", "go-test-nosplit")
213         if err != nil {
214                 bug()
215                 fmt.Printf("creating temp dir: %v\n", err)
216                 return
217         }
218         defer os.RemoveAll(dir)
219
220         tests = strings.Replace(tests, "\t", " ", -1)
221         tests = commentRE.ReplaceAllString(tests, "")
222
223         nok := 0
224         nfail := 0
225 TestCases:
226         for len(tests) > 0 {
227                 var stanza string
228                 i := strings.Index(tests, "\nmain ")
229                 if i < 0 {
230                         stanza, tests = tests, ""
231                 } else {
232                         stanza, tests = tests[:i], tests[i+1:]
233                 }
234
235                 m := rejectRE.FindStringSubmatch(stanza)
236                 if m == nil {
237                         bug()
238                         fmt.Printf("invalid stanza:\n\t%s\n", indent(stanza))
239                         continue
240                 }
241                 lines := strings.TrimSpace(m[1])
242                 reject := false
243                 if m[2] != "" {
244                         if strings.TrimSpace(m[4]) == "" {
245                                 reject = true
246                         } else {
247                                 for _, rej := range strings.Fields(m[4]) {
248                                         if rej == goarch {
249                                                 reject = true
250                                         }
251                                 }
252                         }
253                 }
254                 if lines == "" && !reject {
255                         continue
256                 }
257
258                 var gobuf bytes.Buffer
259                 fmt.Fprintf(&gobuf, "package main\n")
260
261                 var buf bytes.Buffer
262                 ptrSize := 4
263                 switch goarch {
264                 case "mips64", "mips64le":
265                         ptrSize = 8
266                         fmt.Fprintf(&buf, "#define CALL JAL\n#define REGISTER (R0)\n")
267                 case "ppc64", "ppc64le":
268                         ptrSize = 8
269                         fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (CTR)\n")
270                 case "arm":
271                         fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n")
272                 case "arm64":
273                         ptrSize = 8
274                         fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n")
275                 case "amd64":
276                         ptrSize = 8
277                         fmt.Fprintf(&buf, "#define REGISTER AX\n")
278                 default:
279                         fmt.Fprintf(&buf, "#define REGISTER AX\n")
280                 }
281
282                 for _, line := range strings.Split(lines, "\n") {
283                         line = strings.TrimSpace(line)
284                         if line == "" {
285                                 continue
286                         }
287                         for i, subline := range strings.Split(line, ";") {
288                                 subline = strings.TrimSpace(subline)
289                                 if subline == "" {
290                                         continue
291                                 }
292                                 m := lineRE.FindStringSubmatch(subline)
293                                 if m == nil {
294                                         bug()
295                                         fmt.Printf("invalid function line: %s\n", subline)
296                                         continue TestCases
297                                 }
298                                 name := m[1]
299                                 size, _ := strconv.Atoi(m[2])
300
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.
304                                 if i == 0 {
305                                         size += (1024 - 128) - 128
306                                         // Noopt builds have a larger stackguard.
307                                         // See ../src/cmd/dist/buildruntime.go:stackGuardMultiplier
308                                         // This increase is included in obj.StackGuard
309                                         for _, s := range strings.Split(os.Getenv("GO_GCFLAGS"), " ") {
310                                                 if s == "-N" {
311                                                         size += 1024
312                                                 }
313                                         }
314                                 }
315
316                                 if size%ptrSize == 4 || goarch == "arm64" && size != 0 && (size+8)%16 != 0 {
317                                         continue TestCases
318                                 }
319                                 nosplit := m[3]
320                                 body := m[4]
321
322                                 if nosplit != "" {
323                                         nosplit = ",7"
324                                 } else {
325                                         nosplit = ",0"
326                                 }
327                                 body = callRE.ReplaceAllString(body, "CALL ·$1(SB);")
328                                 body = callindRE.ReplaceAllString(body, "CALL REGISTER;")
329
330                                 fmt.Fprintf(&gobuf, "func %s()\n", name)
331                                 fmt.Fprintf(&buf, "TEXT ·%s(SB)%s,$%d-0\n\t%s\n\tRET\n\n", name, nosplit, size, body)
332                         }
333                 }
334
335                 if err := ioutil.WriteFile(filepath.Join(dir, "asm.s"), buf.Bytes(), 0666); err != nil {
336                         log.Fatal(err)
337                 }
338                 if err := ioutil.WriteFile(filepath.Join(dir, "main.go"), gobuf.Bytes(), 0666); err != nil {
339                         log.Fatal(err)
340                 }
341
342                 cmd := exec.Command("go", "build")
343                 cmd.Dir = dir
344                 output, err := cmd.CombinedOutput()
345                 if err == nil {
346                         nok++
347                         if reject {
348                                 bug()
349                                 fmt.Printf("accepted incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
350                         }
351                 } else {
352                         nfail++
353                         if !reject {
354                                 bug()
355                                 fmt.Printf("rejected incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
356                                 fmt.Printf("\n\tlinker output:\n\t%s\n", indent(string(output)))
357                         }
358                 }
359         }
360
361         if !bugged && (nok == 0 || nfail == 0) {
362                 bug()
363                 fmt.Printf("not enough test cases run\n")
364         }
365 }
366
367 func indent(s string) string {
368         return strings.Replace(s, "\n", "\n\t", -1)
369 }
370
371 var bugged = false
372
373 func bug() {
374         if !bugged {
375                 bugged = true
376                 fmt.Printf("BUG\n")
377         }
378 }