Source file
test/nosplit.go
Documentation: test
1
2
3
4
5
6
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 start 10000 nosplit
55 REJECT
56
57 # Calling a large frame is okay.
58 start 0 call big
59 big 10000
60
61 # But not if the frame is nosplit.
62 start 0 call big
63 big 10000 nosplit
64 REJECT
65
66 # Recursion is okay.
67 start 0 call start
68
69 # Recursive nosplit runs out of space.
70 start 0 nosplit call start
71 REJECT
72
73 # Chains of ordinary functions okay.
74 start 0 call f1
75 f1 80 call f2
76 f2 80
77
78 # Chains of nosplit must fit in the stack limit, 128 bytes.
79 start 0 call f1
80 f1 80 nosplit call f2
81 f2 80 nosplit
82 REJECT
83
84 # Larger chains.
85 start 0 call f1
86 f1 16 call f2
87 f2 16 call f3
88 f3 16 call f4
89 f4 16 call f5
90 f5 16 call f6
91 f6 16 call f7
92 f7 16 call f8
93 f8 16 call end
94 end 1000
95
96 start 0 call f1
97 f1 16 nosplit call f2
98 f2 16 nosplit call f3
99 f3 16 nosplit call f4
100 f4 16 nosplit call f5
101 f5 16 nosplit call f6
102 f6 16 nosplit call f7
103 f7 16 nosplit call f8
104 f8 16 nosplit call end
105 end 1000
106 REJECT
107
108 # Test cases near the 128-byte limit.
109
110 # Ordinary stack split frame is always okay.
111 start 112
112 start 116
113 start 120
114 start 124
115 start 128
116 start 132
117 start 136
118
119 # A nosplit leaf can use the whole 128-CallSize bytes available on entry.
120 # (CallSize is 32 on ppc64, 8 on amd64 for frame pointer.)
121 start 96 nosplit
122 start 100 nosplit; REJECT ppc64 ppc64le
123 start 104 nosplit; REJECT ppc64 ppc64le arm64
124 start 108 nosplit; REJECT ppc64 ppc64le
125 start 112 nosplit; REJECT ppc64 ppc64le arm64
126 start 116 nosplit; REJECT ppc64 ppc64le
127 start 120 nosplit; REJECT ppc64 ppc64le amd64 arm64
128 start 124 nosplit; REJECT ppc64 ppc64le amd64
129 start 128 nosplit; REJECT
130 start 132 nosplit; REJECT
131 start 136 nosplit; REJECT
132
133 # Calling a nosplit function from a nosplit function requires
134 # having room for the saved caller PC and the called frame.
135 # Because ARM doesn't save LR in the leaf, it gets an extra 4 bytes.
136 # Because arm64 doesn't save LR in the leaf, it gets an extra 8 bytes.
137 # ppc64 doesn't save LR in the leaf, but CallSize is 32, so it gets 24 bytes.
138 # Because AMD64 uses frame pointer, it has 8 fewer bytes.
139 start 96 nosplit call f; f 0 nosplit
140 start 100 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
141 start 104 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le arm64
142 start 108 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
143 start 112 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64 arm64
144 start 116 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64
145 start 120 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64 arm64
146 start 124 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64 386
147 start 128 nosplit call f; f 0 nosplit; REJECT
148 start 132 nosplit call f; f 0 nosplit; REJECT
149 start 136 nosplit call f; f 0 nosplit; REJECT
150
151 # Calling a splitting function from a nosplit function requires
152 # having room for the saved caller PC of the call but also the
153 # saved caller PC for the call to morestack.
154 # Architectures differ in the same way as before.
155 start 96 nosplit call f; f 0 call f
156 start 100 nosplit call f; f 0 call f; REJECT ppc64 ppc64le
157 start 104 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 arm64
158 start 108 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
159 start 112 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 arm64
160 start 116 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
161 start 120 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 386 arm64
162 start 124 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 386
163 start 128 nosplit call f; f 0 call f; REJECT
164 start 132 nosplit call f; f 0 call f; REJECT
165 start 136 nosplit call f; f 0 call f; REJECT
166
167 # Indirect calls are assumed to be splitting functions.
168 start 96 nosplit callind
169 start 100 nosplit callind; REJECT ppc64 ppc64le
170 start 104 nosplit callind; REJECT ppc64 ppc64le amd64 arm64
171 start 108 nosplit callind; REJECT ppc64 ppc64le amd64
172 start 112 nosplit callind; REJECT ppc64 ppc64le amd64 arm64
173 start 116 nosplit callind; REJECT ppc64 ppc64le amd64
174 start 120 nosplit callind; REJECT ppc64 ppc64le amd64 386 arm64
175 start 124 nosplit callind; REJECT ppc64 ppc64le amd64 386
176 start 128 nosplit callind; REJECT
177 start 132 nosplit callind; REJECT
178 start 136 nosplit callind; REJECT
179
180 # Issue 7623
181 start 0 call f; f 112
182 start 0 call f; f 116
183 start 0 call f; f 120
184 start 0 call f; f 124
185 start 0 call f; f 128
186 start 0 call f; f 132
187 start 0 call f; f 136
188 `
189
190 var (
191 commentRE = regexp.MustCompile(`(?m)^#.*`)
192 rejectRE = regexp.MustCompile(`(?s)\A(.+?)((\n|; *)REJECT(.*))?\z`)
193 lineRE = regexp.MustCompile(`(\w+) (\d+)( nosplit)?(.*)`)
194 callRE = regexp.MustCompile(`\bcall (\w+)\b`)
195 callindRE = regexp.MustCompile(`\bcallind\b`)
196 )
197
198 func main() {
199 goarch := os.Getenv("GOARCH")
200 if goarch == "" {
201 goarch = runtime.GOARCH
202 }
203
204 dir, err := ioutil.TempDir("", "go-test-nosplit")
205 if err != nil {
206 bug()
207 fmt.Printf("creating temp dir: %v\n", err)
208 return
209 }
210 defer os.RemoveAll(dir)
211 os.Setenv("GOPATH", filepath.Join(dir, "_gopath"))
212
213 if err := ioutil.WriteFile(filepath.Join(dir, "go.mod"), []byte("module go-test-nosplit\n"), 0666); err != nil {
214 log.Panic(err)
215 }
216
217 tests = strings.Replace(tests, "\t", " ", -1)
218 tests = commentRE.ReplaceAllString(tests, "")
219
220 nok := 0
221 nfail := 0
222 TestCases:
223 for len(tests) > 0 {
224 var stanza string
225 i := strings.Index(tests, "\nstart ")
226 if i < 0 {
227 stanza, tests = tests, ""
228 } else {
229 stanza, tests = tests[:i], tests[i+1:]
230 }
231
232 m := rejectRE.FindStringSubmatch(stanza)
233 if m == nil {
234 bug()
235 fmt.Printf("invalid stanza:\n\t%s\n", indent(stanza))
236 continue
237 }
238 lines := strings.TrimSpace(m[1])
239 reject := false
240 if m[2] != "" {
241 if strings.TrimSpace(m[4]) == "" {
242 reject = true
243 } else {
244 for _, rej := range strings.Fields(m[4]) {
245 if rej == goarch {
246 reject = true
247 }
248 }
249 }
250 }
251 if lines == "" && !reject {
252 continue
253 }
254
255 var gobuf bytes.Buffer
256 fmt.Fprintf(&gobuf, "package main\n")
257
258 var buf bytes.Buffer
259 ptrSize := 4
260 switch goarch {
261 case "mips", "mipsle":
262 fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
263 case "mips64", "mips64le":
264 ptrSize = 8
265 fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
266 case "ppc64", "ppc64le":
267 ptrSize = 8
268 fmt.Fprintf(&buf, "#define REGISTER (CTR)\n")
269 case "arm":
270 fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
271 case "arm64":
272 ptrSize = 8
273 fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
274 case "amd64":
275 ptrSize = 8
276 fmt.Fprintf(&buf, "#define REGISTER AX\n")
277 case "riscv64":
278 ptrSize = 8
279 fmt.Fprintf(&buf, "#define REGISTER A0\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
288
289
290
291 fmt.Fprintf(&gobuf, "func main0()\n")
292 fmt.Fprintf(&gobuf, "func main() { main0() }\n")
293 fmt.Fprintf(&buf, "TEXT ·main0(SB),0,$0-0\n\tCALL ·start(SB)\n")
294
295 for _, line := range strings.Split(lines, "\n") {
296 line = strings.TrimSpace(line)
297 if line == "" {
298 continue
299 }
300 for i, subline := range strings.Split(line, ";") {
301 subline = strings.TrimSpace(subline)
302 if subline == "" {
303 continue
304 }
305 m := lineRE.FindStringSubmatch(subline)
306 if m == nil {
307 bug()
308 fmt.Printf("invalid function line: %s\n", subline)
309 continue TestCases
310 }
311 name := m[1]
312 size, _ := strconv.Atoi(m[2])
313
314
315
316
317 if i == 0 {
318 size += (928 - 128) - 128
319
320
321
322 for _, s := range strings.Split(os.Getenv("GO_GCFLAGS"), " ") {
323 if s == "-N" {
324 size += 928
325 }
326 }
327 }
328
329 if size%ptrSize == 4 {
330 continue TestCases
331 }
332 nosplit := m[3]
333 body := m[4]
334
335 if nosplit != "" {
336 nosplit = ",7"
337 } else {
338 nosplit = ",0"
339 }
340 body = callRE.ReplaceAllString(body, "CALL ·$1(SB);")
341 body = callindRE.ReplaceAllString(body, "CALL REGISTER;")
342
343 fmt.Fprintf(&gobuf, "func %s()\n", name)
344 fmt.Fprintf(&buf, "TEXT ·%s(SB)%s,$%d-0\n\t%s\n\tRET\n\n", name, nosplit, size, body)
345 }
346 }
347
348 if debug {
349 fmt.Printf("===\n%s\n", strings.TrimSpace(stanza))
350 fmt.Printf("-- main.go --\n%s", gobuf.String())
351 fmt.Printf("-- asm.s --\n%s", buf.String())
352 }
353
354 if err := ioutil.WriteFile(filepath.Join(dir, "asm.s"), buf.Bytes(), 0666); err != nil {
355 log.Fatal(err)
356 }
357 if err := ioutil.WriteFile(filepath.Join(dir, "main.go"), gobuf.Bytes(), 0666); err != nil {
358 log.Fatal(err)
359 }
360
361 cmd := exec.Command("go", "build")
362 cmd.Dir = dir
363 output, err := cmd.CombinedOutput()
364 if err == nil {
365 nok++
366 if reject {
367 bug()
368 fmt.Printf("accepted incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
369 }
370 } else {
371 nfail++
372 if !reject {
373 bug()
374 fmt.Printf("rejected incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
375 fmt.Printf("\n\tlinker output:\n\t%s\n", indent(string(output)))
376 }
377 }
378 }
379
380 if !bugged && (nok == 0 || nfail == 0) {
381 bug()
382 fmt.Printf("not enough test cases run\n")
383 }
384 }
385
386 func indent(s string) string {
387 return strings.Replace(s, "\n", "\n\t", -1)
388 }
389
390 var bugged = false
391
392 func bug() {
393 if !bugged {
394 bugged = true
395 fmt.Printf("BUG\n")
396 }
397 }
398
View as plain text