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