]> Cypherpunks.ru repositories - gostls13.git/blob - test/nosplit.go
[dev.regabi] all: merge master (c9fb4eb) into dev.regabi
[gostls13.git] / test / nosplit.go
1 // +build !nacl,!js,!aix,!gcflags_noopt,gc
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, 8 on amd64 for frame pointer.)
119 main 96 nosplit
120 main 100 nosplit; REJECT ppc64 ppc64le
121 main 104 nosplit; REJECT ppc64 ppc64le arm64
122 main 108 nosplit; REJECT ppc64 ppc64le
123 main 112 nosplit; REJECT ppc64 ppc64le arm64
124 main 116 nosplit; REJECT ppc64 ppc64le
125 main 120 nosplit; REJECT ppc64 ppc64le amd64 arm64
126 main 124 nosplit; REJECT ppc64 ppc64le amd64
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 bytes.
136 # Because AMD64 uses frame pointer, it has 8 fewer bytes.
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 arm64
140 main 108 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
141 main 112 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64 arm64
142 main 116 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64
143 main 120 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64 arm64
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 # 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 amd64 arm64
156 main 108 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
157 main 112 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 arm64
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 arm64
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 amd64 arm64
169 main 108 nosplit callind; REJECT ppc64 ppc64le amd64
170 main 112 nosplit callind; REJECT ppc64 ppc64le amd64 arm64
171 main 116 nosplit callind; REJECT ppc64 ppc64le amd64
172 main 120 nosplit callind; REJECT ppc64 ppc64le amd64 386 arm64
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 s := string(version); goarch == "amd64" && strings.Contains(s, "X:") && !strings.Contains(s, "framepointer") {
209                 // Skip this test if framepointer is NOT enabled on AMD64
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         os.Setenv("GOPATH", filepath.Join(dir, "_gopath"))
221
222         if err := ioutil.WriteFile(filepath.Join(dir, "go.mod"), []byte("module go-test-nosplit\n"), 0666); err != nil {
223                 log.Panic(err)
224         }
225
226         tests = strings.Replace(tests, "\t", " ", -1)
227         tests = commentRE.ReplaceAllString(tests, "")
228
229         nok := 0
230         nfail := 0
231 TestCases:
232         for len(tests) > 0 {
233                 var stanza string
234                 i := strings.Index(tests, "\nmain ")
235                 if i < 0 {
236                         stanza, tests = tests, ""
237                 } else {
238                         stanza, tests = tests[:i], tests[i+1:]
239                 }
240
241                 m := rejectRE.FindStringSubmatch(stanza)
242                 if m == nil {
243                         bug()
244                         fmt.Printf("invalid stanza:\n\t%s\n", indent(stanza))
245                         continue
246                 }
247                 lines := strings.TrimSpace(m[1])
248                 reject := false
249                 if m[2] != "" {
250                         if strings.TrimSpace(m[4]) == "" {
251                                 reject = true
252                         } else {
253                                 for _, rej := range strings.Fields(m[4]) {
254                                         if rej == goarch {
255                                                 reject = true
256                                         }
257                                 }
258                         }
259                 }
260                 if lines == "" && !reject {
261                         continue
262                 }
263
264                 var gobuf bytes.Buffer
265                 fmt.Fprintf(&gobuf, "package main\n")
266
267                 var buf bytes.Buffer
268                 ptrSize := 4
269                 switch goarch {
270                 case "mips", "mipsle":
271                         fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
272                 case "mips64", "mips64le":
273                         ptrSize = 8
274                         fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
275                 case "ppc64", "ppc64le":
276                         ptrSize = 8
277                         fmt.Fprintf(&buf, "#define REGISTER (CTR)\n")
278                 case "arm":
279                         fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
280                 case "arm64":
281                         ptrSize = 8
282                         fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
283                 case "amd64":
284                         ptrSize = 8
285                         fmt.Fprintf(&buf, "#define REGISTER AX\n")
286                 case "riscv64":
287                         ptrSize = 8
288                         fmt.Fprintf(&buf, "#define REGISTER A0\n")
289                 case "s390x":
290                         ptrSize = 8
291                         fmt.Fprintf(&buf, "#define REGISTER R10\n")
292                 default:
293                         fmt.Fprintf(&buf, "#define REGISTER AX\n")
294                 }
295
296                 for _, line := range strings.Split(lines, "\n") {
297                         line = strings.TrimSpace(line)
298                         if line == "" {
299                                 continue
300                         }
301                         for i, subline := range strings.Split(line, ";") {
302                                 subline = strings.TrimSpace(subline)
303                                 if subline == "" {
304                                         continue
305                                 }
306                                 m := lineRE.FindStringSubmatch(subline)
307                                 if m == nil {
308                                         bug()
309                                         fmt.Printf("invalid function line: %s\n", subline)
310                                         continue TestCases
311                                 }
312                                 name := m[1]
313                                 size, _ := strconv.Atoi(m[2])
314
315                                 // The limit was originally 128 but is now 800 (928-128).
316                                 // Instead of rewriting the test cases above, adjust
317                                 // the first stack frame to use up the extra bytes.
318                                 if i == 0 {
319                                         size += (928 - 128) - 128
320                                         // Noopt builds have a larger stackguard.
321                                         // See ../src/cmd/dist/buildruntime.go:stackGuardMultiplier
322                                         // This increase is included in objabi.StackGuard
323                                         for _, s := range strings.Split(os.Getenv("GO_GCFLAGS"), " ") {
324                                                 if s == "-N" {
325                                                         size += 928
326                                                 }
327                                         }
328                                 }
329
330                                 if size%ptrSize == 4 {
331                                         continue TestCases
332                                 }
333                                 nosplit := m[3]
334                                 body := m[4]
335
336                                 if nosplit != "" {
337                                         nosplit = ",7"
338                                 } else {
339                                         nosplit = ",0"
340                                 }
341                                 body = callRE.ReplaceAllString(body, "CALL ·$1(SB);")
342                                 body = callindRE.ReplaceAllString(body, "CALL REGISTER;")
343
344                                 fmt.Fprintf(&gobuf, "func %s()\n", name)
345                                 fmt.Fprintf(&buf, "TEXT ·%s(SB)%s,$%d-0\n\t%s\n\tRET\n\n", name, nosplit, size, body)
346                         }
347                 }
348
349                 if err := ioutil.WriteFile(filepath.Join(dir, "asm.s"), buf.Bytes(), 0666); err != nil {
350                         log.Fatal(err)
351                 }
352                 if err := ioutil.WriteFile(filepath.Join(dir, "main.go"), gobuf.Bytes(), 0666); err != nil {
353                         log.Fatal(err)
354                 }
355
356                 // Turn off ABI0 wrapper generation for now. The problem here is
357                 // that in these test cases main.main is an assembly routine,
358                 // thus calls to it will have to go through an ABI wrapper. The
359                 // ABI wrapper will consume some stack space, which throws off
360                 // the numbers.
361                 workaround := "-gcflags=-abiwrap=0"
362
363                 cmd := exec.Command("go", "build", workaround)
364                 cmd.Dir = dir
365                 output, err := cmd.CombinedOutput()
366                 if err == nil {
367                         nok++
368                         if reject {
369                                 bug()
370                                 fmt.Printf("accepted incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
371                         }
372                 } else {
373                         nfail++
374                         if !reject {
375                                 bug()
376                                 fmt.Printf("rejected incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
377                                 fmt.Printf("\n\tlinker output:\n\t%s\n", indent(string(output)))
378                         }
379                 }
380         }
381
382         if !bugged && (nok == 0 || nfail == 0) {
383                 bug()
384                 fmt.Printf("not enough test cases run\n")
385         }
386 }
387
388 func indent(s string) string {
389         return strings.Replace(s, "\n", "\n\t", -1)
390 }
391
392 var bugged = false
393
394 func bug() {
395         if !bugged {
396                 bugged = true
397                 fmt.Printf("BUG\n")
398         }
399 }