]> Cypherpunks.ru repositories - gostls13.git/blob - test/nosplit.go
cmd/compile/internal/inline: score call sites exposed by inlines
[gostls13.git] / test / nosplit.go
1 // run
2
3 //go:build !nacl && !js && !aix && !wasip1 && !gcflags_noopt && gc
4
5 // Copyright 2014 The Go Authors. All rights reserved.
6 // Use of this source code is governed by a BSD-style
7 // license that can be found in the LICENSE file.
8
9 package main
10
11 import (
12         "bytes"
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 const debug = false
26
27 var tests = `
28 # These are test cases for the linker analysis that detects chains of
29 # nosplit functions that would cause a stack overflow.
30 #
31 # Lines beginning with # are comments.
32 #
33 # Each test case describes a sequence of functions, one per line.
34 # Each function definition is the function name, then the frame size,
35 # then optionally the keyword 'nosplit', then the body of the function.
36 # The body is assembly code, with some shorthands.
37 # The shorthand 'call x' stands for CALL x(SB).
38 # The shorthand 'callind' stands for 'CALL R0', where R0 is a register.
39 # Each test case must define a function named start, and it must be first.
40 # That is, a line beginning "start " indicates the start of a new test case.
41 # Within a stanza, ; can be used instead of \n to separate lines.
42 #
43 # After the function definition, the test case ends with an optional
44 # REJECT line, specifying the architectures on which the case should
45 # be rejected. "REJECT" without any architectures means reject on all architectures.
46 # The linker should accept the test case on systems not explicitly rejected.
47 #
48 # 64-bit systems do not attempt to execute test cases with frame sizes
49 # that are only 32-bit aligned.
50
51 # Ordinary function should work
52 start 0
53
54 # Large frame marked nosplit is always wrong.
55 # Frame is so large it overflows cmd/link's int16.
56 start 100000 nosplit
57 REJECT
58
59 # Calling a large frame is okay.
60 start 0 call big
61 big 10000
62
63 # But not if the frame is nosplit.
64 start 0 call big
65 big 10000 nosplit
66 REJECT
67
68 # Recursion is okay.
69 start 0 call start
70
71 # Recursive nosplit runs out of space.
72 start 0 nosplit call start
73 REJECT
74
75 # Non-trivial recursion runs out of space.
76 start 0 call f1
77 f1 0 nosplit call f2
78 f2 0 nosplit call f1
79 REJECT
80 # Same but cycle starts below nosplit entry.
81 start 0 call f1
82 f1 0 nosplit call f2
83 f2 0 nosplit call f3
84 f3 0 nosplit call f2
85 REJECT
86
87 # Chains of ordinary functions okay.
88 start 0 call f1
89 f1 80 call f2
90 f2 80
91
92 # Chains of nosplit must fit in the stack limit, 128 bytes.
93 start 0 call f1
94 f1 80 nosplit call f2
95 f2 80 nosplit
96 REJECT
97
98 # Larger chains.
99 start 0 call f1
100 f1 16 call f2
101 f2 16 call f3
102 f3 16 call f4
103 f4 16 call f5
104 f5 16 call f6
105 f6 16 call f7
106 f7 16 call f8
107 f8 16 call end
108 end 1000
109
110 start 0 call f1
111 f1 16 nosplit call f2
112 f2 16 nosplit call f3
113 f3 16 nosplit call f4
114 f4 16 nosplit call f5
115 f5 16 nosplit call f6
116 f6 16 nosplit call f7
117 f7 16 nosplit call f8
118 f8 16 nosplit call end
119 end 1000
120 REJECT
121
122 # Two paths both go over the stack limit.
123 start 0 call f1
124 f1 80 nosplit call f2 call f3
125 f2 40 nosplit call f4
126 f3 96 nosplit
127 f4 40 nosplit
128 REJECT
129
130 # Test cases near the 128-byte limit.
131
132 # Ordinary stack split frame is always okay.
133 start 112
134 start 116
135 start 120
136 start 124
137 start 128
138 start 132
139 start 136
140
141 # A nosplit leaf can use the whole 128-CallSize bytes available on entry.
142 # (CallSize is 32 on ppc64, 8 on amd64 for frame pointer.)
143 start 96 nosplit
144 start 100 nosplit; REJECT ppc64 ppc64le
145 start 104 nosplit; REJECT ppc64 ppc64le arm64
146 start 108 nosplit; REJECT ppc64 ppc64le
147 start 112 nosplit; REJECT ppc64 ppc64le arm64
148 start 116 nosplit; REJECT ppc64 ppc64le
149 start 120 nosplit; REJECT ppc64 ppc64le amd64 arm64
150 start 124 nosplit; REJECT ppc64 ppc64le amd64
151 start 128 nosplit; REJECT
152 start 132 nosplit; REJECT
153 start 136 nosplit; REJECT
154
155 # Calling a nosplit function from a nosplit function requires
156 # having room for the saved caller PC and the called frame.
157 # Because ARM doesn't save LR in the leaf, it gets an extra 4 bytes.
158 # Because arm64 doesn't save LR in the leaf, it gets an extra 8 bytes.
159 # ppc64 doesn't save LR in the leaf, but CallSize is 32, so it gets 24 bytes.
160 # Because AMD64 uses frame pointer, it has 8 fewer bytes.
161 start 96 nosplit call f; f 0 nosplit
162 start 100 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
163 start 104 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le arm64
164 start 108 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
165 start 112 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64 arm64
166 start 116 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64
167 start 120 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64 arm64
168 start 124 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64 386
169 start 128 nosplit call f; f 0 nosplit; REJECT
170 start 132 nosplit call f; f 0 nosplit; REJECT
171 start 136 nosplit call f; f 0 nosplit; REJECT
172
173 # Calling a splitting function from a nosplit function requires
174 # having room for the saved caller PC of the call but also the
175 # saved caller PC for the call to morestack.
176 # Architectures differ in the same way as before.
177 start 96 nosplit call f; f 0 call f
178 start 100 nosplit call f; f 0 call f; REJECT ppc64 ppc64le
179 start 104 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 arm64
180 start 108 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
181 start 112 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 arm64
182 start 116 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
183 start 120 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 386 arm64
184 start 124 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 386
185 start 128 nosplit call f; f 0 call f; REJECT
186 start 132 nosplit call f; f 0 call f; REJECT
187 start 136 nosplit call f; f 0 call f; REJECT
188
189 # Indirect calls are assumed to be splitting functions.
190 start 96 nosplit callind
191 start 100 nosplit callind; REJECT ppc64 ppc64le
192 start 104 nosplit callind; REJECT ppc64 ppc64le amd64 arm64
193 start 108 nosplit callind; REJECT ppc64 ppc64le amd64
194 start 112 nosplit callind; REJECT ppc64 ppc64le amd64 arm64
195 start 116 nosplit callind; REJECT ppc64 ppc64le amd64
196 start 120 nosplit callind; REJECT ppc64 ppc64le amd64 386 arm64
197 start 124 nosplit callind; REJECT ppc64 ppc64le amd64 386
198 start 128 nosplit callind; REJECT
199 start 132 nosplit callind; REJECT
200 start 136 nosplit callind; REJECT
201
202 # Issue 7623
203 start 0 call f; f 112
204 start 0 call f; f 116
205 start 0 call f; f 120
206 start 0 call f; f 124
207 start 0 call f; f 128
208 start 0 call f; f 132
209 start 0 call f; f 136
210 `
211
212 var (
213         commentRE = regexp.MustCompile(`(?m)^#.*`)
214         rejectRE  = regexp.MustCompile(`(?s)\A(.+?)((\n|; *)REJECT(.*))?\z`)
215         lineRE    = regexp.MustCompile(`(\w+) (\d+)( nosplit)?(.*)`)
216         callRE    = regexp.MustCompile(`\bcall (\w+)\b`)
217         callindRE = regexp.MustCompile(`\bcallind\b`)
218 )
219
220 func main() {
221         goarch := os.Getenv("GOARCH")
222         if goarch == "" {
223                 goarch = runtime.GOARCH
224         }
225
226         dir, err := ioutil.TempDir("", "go-test-nosplit")
227         if err != nil {
228                 bug()
229                 fmt.Printf("creating temp dir: %v\n", err)
230                 return
231         }
232         defer os.RemoveAll(dir)
233         os.Setenv("GOPATH", filepath.Join(dir, "_gopath"))
234
235         if err := ioutil.WriteFile(filepath.Join(dir, "go.mod"), []byte("module go-test-nosplit\n"), 0666); err != nil {
236                 log.Panic(err)
237         }
238
239         tests = strings.Replace(tests, "\t", " ", -1)
240         tests = commentRE.ReplaceAllString(tests, "")
241
242         nok := 0
243         nfail := 0
244 TestCases:
245         for len(tests) > 0 {
246                 var stanza string
247                 i := strings.Index(tests, "\nstart ")
248                 if i < 0 {
249                         stanza, tests = tests, ""
250                 } else {
251                         stanza, tests = tests[:i], tests[i+1:]
252                 }
253
254                 m := rejectRE.FindStringSubmatch(stanza)
255                 if m == nil {
256                         bug()
257                         fmt.Printf("invalid stanza:\n\t%s\n", indent(stanza))
258                         continue
259                 }
260                 lines := strings.TrimSpace(m[1])
261                 reject := false
262                 if m[2] != "" {
263                         if strings.TrimSpace(m[4]) == "" {
264                                 reject = true
265                         } else {
266                                 for _, rej := range strings.Fields(m[4]) {
267                                         if rej == goarch {
268                                                 reject = true
269                                         }
270                                 }
271                         }
272                 }
273                 if lines == "" && !reject {
274                         continue
275                 }
276
277                 var gobuf bytes.Buffer
278                 fmt.Fprintf(&gobuf, "package main\n")
279
280                 var buf bytes.Buffer
281                 ptrSize := 4
282                 switch goarch {
283                 case "mips", "mipsle":
284                         fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
285                 case "mips64", "mips64le":
286                         ptrSize = 8
287                         fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
288                 case "loong64":
289                         ptrSize = 8
290                         fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
291                 case "ppc64", "ppc64le":
292                         ptrSize = 8
293                         fmt.Fprintf(&buf, "#define REGISTER (CTR)\n")
294                 case "arm":
295                         fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
296                 case "arm64":
297                         ptrSize = 8
298                         fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
299                 case "amd64":
300                         ptrSize = 8
301                         fmt.Fprintf(&buf, "#define REGISTER AX\n")
302                 case "riscv64":
303                         ptrSize = 8
304                         fmt.Fprintf(&buf, "#define REGISTER A0\n")
305                 case "s390x":
306                         ptrSize = 8
307                         fmt.Fprintf(&buf, "#define REGISTER R10\n")
308                 default:
309                         fmt.Fprintf(&buf, "#define REGISTER AX\n")
310                 }
311
312                 // Since all of the functions we're generating are
313                 // ABI0, first enter ABI0 via a splittable function
314                 // and then go to the chain we're testing. This way we
315                 // don't have to account for ABI wrappers in the chain.
316                 fmt.Fprintf(&gobuf, "func main0()\n")
317                 fmt.Fprintf(&gobuf, "func main() { main0() }\n")
318                 fmt.Fprintf(&buf, "TEXT ·main0(SB),0,$0-0\n\tCALL ·start(SB)\n")
319
320                 adjusted := false
321                 for _, line := range strings.Split(lines, "\n") {
322                         line = strings.TrimSpace(line)
323                         if line == "" {
324                                 continue
325                         }
326                         for _, subline := range strings.Split(line, ";") {
327                                 subline = strings.TrimSpace(subline)
328                                 if subline == "" {
329                                         continue
330                                 }
331                                 m := lineRE.FindStringSubmatch(subline)
332                                 if m == nil {
333                                         bug()
334                                         fmt.Printf("invalid function line: %s\n", subline)
335                                         continue TestCases
336                                 }
337                                 name := m[1]
338                                 size, _ := strconv.Atoi(m[2])
339
340                                 if size%ptrSize == 4 {
341                                         continue TestCases
342                                 }
343                                 nosplit := m[3]
344                                 body := m[4]
345
346                                 // The limit was originally 128 but is now 800.
347                                 // Instead of rewriting the test cases above, adjust
348                                 // the first nosplit frame to use up the extra bytes.
349                                 // This isn't exactly right because we could have
350                                 // nosplit -> split -> nosplit, but it's good enough.
351                                 if !adjusted && nosplit != "" {
352                                         const stackNosplitBase = 800 // internal/abi.StackNosplitBase
353                                         adjusted = true
354                                         size += stackNosplitBase - 128
355                                 }
356
357                                 if nosplit != "" {
358                                         nosplit = ",7"
359                                 } else {
360                                         nosplit = ",0"
361                                 }
362                                 body = callRE.ReplaceAllString(body, "CALL ·$1(SB);")
363                                 body = callindRE.ReplaceAllString(body, "CALL REGISTER;")
364
365                                 fmt.Fprintf(&gobuf, "func %s()\n", name)
366                                 fmt.Fprintf(&buf, "TEXT ·%s(SB)%s,$%d-0\n\t%s\n\tRET\n\n", name, nosplit, size, body)
367                         }
368                 }
369
370                 if debug {
371                         fmt.Printf("===\n%s\n", strings.TrimSpace(stanza))
372                         fmt.Printf("-- main.go --\n%s", gobuf.String())
373                         fmt.Printf("-- asm.s --\n%s", buf.String())
374                 }
375
376                 if err := ioutil.WriteFile(filepath.Join(dir, "asm.s"), buf.Bytes(), 0666); err != nil {
377                         log.Fatal(err)
378                 }
379                 if err := ioutil.WriteFile(filepath.Join(dir, "main.go"), gobuf.Bytes(), 0666); err != nil {
380                         log.Fatal(err)
381                 }
382
383                 cmd := exec.Command("go", "build")
384                 cmd.Dir = dir
385                 output, err := cmd.CombinedOutput()
386                 if err == nil {
387                         nok++
388                         if reject {
389                                 bug()
390                                 fmt.Printf("accepted incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
391                         }
392                 } else {
393                         nfail++
394                         if !reject {
395                                 bug()
396                                 fmt.Printf("rejected incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
397                                 fmt.Printf("\n\tlinker output:\n\t%s\n", indent(string(output)))
398                         }
399                 }
400         }
401
402         if !bugged && (nok == 0 || nfail == 0) {
403                 bug()
404                 fmt.Printf("not enough test cases run\n")
405         }
406 }
407
408 func indent(s string) string {
409         return strings.Replace(s, "\n", "\n\t", -1)
410 }
411
412 var bugged = false
413
414 func bug() {
415         if !bugged {
416                 bugged = true
417                 fmt.Printf("BUG\n")
418         }
419 }