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