blob: a639150511c2fa1e8776d4fa687669880cb795c6 [file] [log] [blame]
Russ Cox5e8c9222014-04-16 22:08:00 -04001// run
2
Russ Cox0c2a7272014-05-20 12:10:19 -04003// +build !nacl
4
Russ Cox5e8c9222014-04-16 22:08:00 -04005// 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
9package main
10
11import (
12 "bytes"
13 "fmt"
14 "io/ioutil"
Russ Cox653fb6d2014-09-16 17:39:55 -040015 "log"
Russ Cox5e8c9222014-04-16 22:08:00 -040016 "os"
17 "os/exec"
18 "path/filepath"
19 "regexp"
20 "runtime"
21 "strconv"
22 "strings"
23)
24
25var tests = `
26# These are test cases for the linker analysis that detects chains of
27# nosplit functions that would cause a stack overflow.
28#
29# Lines beginning with # are comments.
30#
31# Each test case describes a sequence of functions, one per line.
32# Each function definition is the function name, then the frame size,
33# then optionally the keyword 'nosplit', then the body of the function.
34# The body is assembly code, with some shorthands.
35# The shorthand 'call x' stands for CALL x(SB).
36# The shorthand 'callind' stands for 'CALL R0', where R0 is a register.
37# Each test case must define a function named main, and it must be first.
38# That is, a line beginning "main " indicates the start of a new test case.
39# Within a stanza, ; can be used instead of \n to separate lines.
40#
41# After the function definition, the test case ends with an optional
42# REJECT line, specifying the architectures on which the case should
43# be rejected. "REJECT" without any architectures means reject on all architectures.
44# The linker should accept the test case on systems not explicitly rejected.
45#
46# 64-bit systems do not attempt to execute test cases with frame sizes
47# that are only 32-bit aligned.
48
49# Ordinary function should work
50main 0
51
52# Large frame marked nosplit is always wrong.
53main 10000 nosplit
54REJECT
55
56# Calling a large frame is okay.
57main 0 call big
58big 10000
59
60# But not if the frame is nosplit.
61main 0 call big
62big 10000 nosplit
63REJECT
64
65# Recursion is okay.
66main 0 call main
67
68# Recursive nosplit runs out of space.
69main 0 nosplit call main
70REJECT
71
72# Chains of ordinary functions okay.
73main 0 call f1
74f1 80 call f2
75f2 80
76
77# Chains of nosplit must fit in the stack limit, 128 bytes.
78main 0 call f1
79f1 80 nosplit call f2
80f2 80 nosplit
81REJECT
82
83# Larger chains.
84main 0 call f1
85f1 16 call f2
86f2 16 call f3
87f3 16 call f4
88f4 16 call f5
89f5 16 call f6
90f6 16 call f7
91f7 16 call f8
92f8 16 call end
93end 1000
94
95main 0 call f1
96f1 16 nosplit call f2
97f2 16 nosplit call f3
98f3 16 nosplit call f4
99f4 16 nosplit call f5
100f5 16 nosplit call f6
101f6 16 nosplit call f7
102f7 16 nosplit call f8
103f8 16 nosplit call end
104end 1000
105REJECT
106
107# Test cases near the 128-byte limit.
108
109# Ordinary stack split frame is always okay.
110main 112
111main 116
112main 120
113main 124
114main 128
115main 132
116main 136
117
118# A nosplit leaf can use the whole 128-CallSize bytes available on entry.
119main 112 nosplit
120main 116 nosplit
121main 120 nosplit
122main 124 nosplit
123main 128 nosplit; REJECT
124main 132 nosplit; REJECT
125main 136 nosplit; REJECT
126
127# Calling a nosplit function from a nosplit function requires
128# having room for the saved caller PC and the called frame.
129# Because ARM doesn't save LR in the leaf, it gets an extra 4 bytes.
Russ Cox09d92b62014-12-05 19:13:20 -0500130# Because ppc64 doesn't save LR in the leaf, it gets an extra 8 bytes.
Russ Cox5e8c9222014-04-16 22:08:00 -0400131main 112 nosplit call f; f 0 nosplit
Russ Coxed68c7d2014-08-14 15:29:37 -0400132main 116 nosplit call f; f 0 nosplit
Russ Cox5e8c9222014-04-16 22:08:00 -0400133main 120 nosplit call f; f 0 nosplit; REJECT amd64
134main 124 nosplit call f; f 0 nosplit; REJECT amd64 386
135main 128 nosplit call f; f 0 nosplit; REJECT
136main 132 nosplit call f; f 0 nosplit; REJECT
137main 136 nosplit call f; f 0 nosplit; REJECT
138
139# Calling a splitting function from a nosplit function requires
140# having room for the saved caller PC of the call but also the
Russ Coxed68c7d2014-08-14 15:29:37 -0400141# saved caller PC for the call to morestack.
Russ Cox09d92b62014-12-05 19:13:20 -0500142# Again the ARM and ppc64 work in less space.
Russ Cox5e8c9222014-04-16 22:08:00 -0400143main 104 nosplit call f; f 0 call f
144main 108 nosplit call f; f 0 call f
145main 112 nosplit call f; f 0 call f; REJECT amd64
146main 116 nosplit call f; f 0 call f; REJECT amd64
147main 120 nosplit call f; f 0 call f; REJECT amd64 386
148main 124 nosplit call f; f 0 call f; REJECT amd64 386
149main 128 nosplit call f; f 0 call f; REJECT
150main 132 nosplit call f; f 0 call f; REJECT
151main 136 nosplit call f; f 0 call f; REJECT
152
153# Indirect calls are assumed to be splitting functions.
154main 104 nosplit callind
155main 108 nosplit callind
156main 112 nosplit callind; REJECT amd64
157main 116 nosplit callind; REJECT amd64
158main 120 nosplit callind; REJECT amd64 386
159main 124 nosplit callind; REJECT amd64 386
160main 128 nosplit callind; REJECT
161main 132 nosplit callind; REJECT
162main 136 nosplit callind; REJECT
163
164# Issue 7623
165main 0 call f; f 112
166main 0 call f; f 116
167main 0 call f; f 120
168main 0 call f; f 124
169main 0 call f; f 128
170main 0 call f; f 132
171main 0 call f; f 136
172`
173
174var (
175 commentRE = regexp.MustCompile(`(?m)^#.*`)
176 rejectRE = regexp.MustCompile(`(?s)\A(.+?)((\n|; *)REJECT(.*))?\z`)
177 lineRE = regexp.MustCompile(`(\w+) (\d+)( nosplit)?(.*)`)
178 callRE = regexp.MustCompile(`\bcall (\w+)\b`)
179 callindRE = regexp.MustCompile(`\bcallind\b`)
180)
181
182func main() {
183 goarch := os.Getenv("GOARCH")
184 if goarch == "" {
185 goarch = runtime.GOARCH
186 }
187
Austin Clements3c0fee12015-01-14 11:09:50 -0500188 thechar := ""
189 if gochar, err := exec.Command("go", "env", "GOCHAR").Output(); err != nil {
190 bug()
191 fmt.Printf("running go env GOCHAR: %v\n", err)
192 return
193 } else {
194 thechar = strings.TrimSpace(string(gochar))
195 }
196
197 version, err := exec.Command("go", "tool", thechar+"g", "-V").Output()
198 if err != nil {
199 bug()
200 fmt.Printf("running go tool %sg -V: %v\n", thechar, err)
201 return
202 }
203 if strings.Contains(string(version), "framepointer") {
204 // Skip this test if GOEXPERIMENT=framepointer
205 return
206 }
207
Russ Cox5e8c9222014-04-16 22:08:00 -0400208 dir, err := ioutil.TempDir("", "go-test-nosplit")
209 if err != nil {
210 bug()
211 fmt.Printf("creating temp dir: %v\n", err)
212 return
213 }
214 defer os.RemoveAll(dir)
Russ Cox5e8c9222014-04-16 22:08:00 -0400215
216 tests = strings.Replace(tests, "\t", " ", -1)
217 tests = commentRE.ReplaceAllString(tests, "")
218
219 nok := 0
220 nfail := 0
221TestCases:
222 for len(tests) > 0 {
223 var stanza string
224 i := strings.Index(tests, "\nmain ")
225 if i < 0 {
226 stanza, tests = tests, ""
227 } else {
228 stanza, tests = tests[:i], tests[i+1:]
229 }
230
231 m := rejectRE.FindStringSubmatch(stanza)
232 if m == nil {
233 bug()
234 fmt.Printf("invalid stanza:\n\t%s\n", indent(stanza))
235 continue
236 }
237 lines := strings.TrimSpace(m[1])
238 reject := false
239 if m[2] != "" {
240 if strings.TrimSpace(m[4]) == "" {
241 reject = true
242 } else {
243 for _, rej := range strings.Fields(m[4]) {
244 if rej == goarch {
245 reject = true
246 }
247 }
248 }
249 }
250 if lines == "" && !reject {
251 continue
252 }
253
Russ Cox653fb6d2014-09-16 17:39:55 -0400254 var gobuf bytes.Buffer
255 fmt.Fprintf(&gobuf, "package main\n")
256
Russ Cox5e8c9222014-04-16 22:08:00 -0400257 var buf bytes.Buffer
Shenghou Ma80e76e22014-08-14 13:59:58 -0400258 ptrSize := 4
259 switch goarch {
Russ Cox09d92b62014-12-05 19:13:20 -0500260 case "ppc64", "ppc64le":
Shenghou Ma80e76e22014-08-14 13:59:58 -0400261 ptrSize = 8
Russ Coxed68c7d2014-08-14 15:29:37 -0400262 fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (CTR)\n#define RET RETURN\n")
Shenghou Ma80e76e22014-08-14 13:59:58 -0400263 case "arm":
Russ Cox5e8c9222014-04-16 22:08:00 -0400264 fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n")
Shenghou Ma80e76e22014-08-14 13:59:58 -0400265 case "amd64":
266 ptrSize = 8
267 fmt.Fprintf(&buf, "#define REGISTER AX\n")
268 default:
Russ Cox5e8c9222014-04-16 22:08:00 -0400269 fmt.Fprintf(&buf, "#define REGISTER AX\n")
270 }
271
272 for _, line := range strings.Split(lines, "\n") {
273 line = strings.TrimSpace(line)
274 if line == "" {
275 continue
276 }
Russ Coxfe910062014-08-27 14:08:26 -0400277 for i, subline := range strings.Split(line, ";") {
Russ Cox5e8c9222014-04-16 22:08:00 -0400278 subline = strings.TrimSpace(subline)
279 if subline == "" {
280 continue
281 }
282 m := lineRE.FindStringSubmatch(subline)
283 if m == nil {
284 bug()
285 fmt.Printf("invalid function line: %s\n", subline)
286 continue TestCases
287 }
288 name := m[1]
289 size, _ := strconv.Atoi(m[2])
Russ Coxfe910062014-08-27 14:08:26 -0400290
Russ Coxdb406242014-12-05 19:50:09 -0500291 // The limit was originally 128 but is now 512.
Russ Coxfe910062014-08-27 14:08:26 -0400292 // Instead of rewriting the test cases above, adjust
293 // the first stack frame to use up the extra 32 bytes.
294 if i == 0 {
Russ Coxdb406242014-12-05 19:50:09 -0500295 size += 512 - 128
Russ Coxfe910062014-08-27 14:08:26 -0400296 }
297
Shenghou Ma80e76e22014-08-14 13:59:58 -0400298 if size%ptrSize == 4 {
Russ Cox5e8c9222014-04-16 22:08:00 -0400299 continue TestCases
300 }
301 nosplit := m[3]
302 body := m[4]
303
304 if nosplit != "" {
305 nosplit = ",7"
306 } else {
307 nosplit = ",0"
308 }
309 body = callRE.ReplaceAllString(body, "CALL ·$1(SB);")
310 body = callindRE.ReplaceAllString(body, "CALL REGISTER;")
311
Russ Cox653fb6d2014-09-16 17:39:55 -0400312 fmt.Fprintf(&gobuf, "func %s()\n", name)
Russ Cox5e8c9222014-04-16 22:08:00 -0400313 fmt.Fprintf(&buf, "TEXT ·%s(SB)%s,$%d-0\n\t%s\n\tRET\n\n", name, nosplit, size, body)
314 }
315 }
316
Russ Cox653fb6d2014-09-16 17:39:55 -0400317 if err := ioutil.WriteFile(filepath.Join(dir, "asm.s"), buf.Bytes(), 0666); err != nil {
318 log.Fatal(err)
319 }
320 if err := ioutil.WriteFile(filepath.Join(dir, "main.go"), gobuf.Bytes(), 0666); err != nil {
321 log.Fatal(err)
322 }
Russ Cox5e8c9222014-04-16 22:08:00 -0400323
Russ Cox5e8c9222014-04-16 22:08:00 -0400324 cmd := exec.Command("go", "build")
325 cmd.Dir = dir
326 output, err := cmd.CombinedOutput()
327 if err == nil {
328 nok++
329 if reject {
330 bug()
331 fmt.Printf("accepted incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
332 }
333 } else {
334 nfail++
335 if !reject {
336 bug()
337 fmt.Printf("rejected incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
338 fmt.Printf("\n\tlinker output:\n\t%s\n", indent(string(output)))
339 }
340 }
341 }
342
343 if !bugged && (nok == 0 || nfail == 0) {
344 bug()
345 fmt.Printf("not enough test cases run\n")
346 }
347}
348
349func indent(s string) string {
350 return strings.Replace(s, "\n", "\n\t", -1)
351}
352
353var bugged = false
354
355func bug() {
356 if !bugged {
357 bugged = true
358 fmt.Printf("BUG\n")
359 }
360}