blob: 0bd13c1db4150e8d6514531c30054f61b15bad25 [file] [log] [blame]
Russ Cox0c2a7272014-05-20 12:10:19 -04001// +build !nacl
Rahul Chaudhry8581d482015-02-03 10:52:18 -08002// run
Russ Cox0c2a7272014-05-20 12:10:19 -04003
Russ Cox5e8c9222014-04-16 22:08:00 -04004// 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
8package main
9
10import (
11 "bytes"
12 "fmt"
13 "io/ioutil"
Russ Cox653fb6d2014-09-16 17:39:55 -040014 "log"
Russ Cox5e8c9222014-04-16 22:08:00 -040015 "os"
16 "os/exec"
17 "path/filepath"
18 "regexp"
19 "runtime"
20 "strconv"
21 "strings"
22)
23
24var 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
49main 0
50
51# Large frame marked nosplit is always wrong.
52main 10000 nosplit
53REJECT
54
55# Calling a large frame is okay.
56main 0 call big
57big 10000
58
59# But not if the frame is nosplit.
60main 0 call big
61big 10000 nosplit
62REJECT
63
64# Recursion is okay.
65main 0 call main
66
67# Recursive nosplit runs out of space.
68main 0 nosplit call main
69REJECT
70
71# Chains of ordinary functions okay.
72main 0 call f1
73f1 80 call f2
74f2 80
75
76# Chains of nosplit must fit in the stack limit, 128 bytes.
77main 0 call f1
78f1 80 nosplit call f2
79f2 80 nosplit
80REJECT
81
82# Larger chains.
83main 0 call f1
84f1 16 call f2
85f2 16 call f3
86f3 16 call f4
87f4 16 call f5
88f5 16 call f6
89f6 16 call f7
90f7 16 call f8
91f8 16 call end
92end 1000
93
94main 0 call f1
95f1 16 nosplit call f2
96f2 16 nosplit call f3
97f3 16 nosplit call f4
98f4 16 nosplit call f5
99f5 16 nosplit call f6
100f6 16 nosplit call f7
101f7 16 nosplit call f8
102f8 16 nosplit call end
103end 1000
104REJECT
105
106# Test cases near the 128-byte limit.
107
108# Ordinary stack split frame is always okay.
109main 112
110main 116
111main 120
112main 124
113main 128
114main 132
115main 136
116
117# A nosplit leaf can use the whole 128-CallSize bytes available on entry.
118main 112 nosplit
119main 116 nosplit
120main 120 nosplit
121main 124 nosplit
122main 128 nosplit; REJECT
123main 132 nosplit; REJECT
124main 136 nosplit; REJECT
125
126# Calling a nosplit function from a nosplit function requires
127# having room for the saved caller PC and the called frame.
128# Because ARM doesn't save LR in the leaf, it gets an extra 4 bytes.
Russ Cox09d92b62014-12-05 19:13:20 -0500129# Because ppc64 doesn't save LR in the leaf, it gets an extra 8 bytes.
Russ Cox5e8c9222014-04-16 22:08:00 -0400130main 112 nosplit call f; f 0 nosplit
Russ Coxed68c7d2014-08-14 15:29:37 -0400131main 116 nosplit call f; f 0 nosplit
Russ Cox5e8c9222014-04-16 22:08:00 -0400132main 120 nosplit call f; f 0 nosplit; REJECT amd64
133main 124 nosplit call f; f 0 nosplit; REJECT amd64 386
134main 128 nosplit call f; f 0 nosplit; REJECT
135main 132 nosplit call f; f 0 nosplit; REJECT
136main 136 nosplit call f; f 0 nosplit; REJECT
137
138# Calling a splitting function from a nosplit function requires
139# having room for the saved caller PC of the call but also the
Russ Coxed68c7d2014-08-14 15:29:37 -0400140# saved caller PC for the call to morestack.
Russ Cox09d92b62014-12-05 19:13:20 -0500141# Again the ARM and ppc64 work in less space.
Russ Cox5e8c9222014-04-16 22:08:00 -0400142main 104 nosplit call f; f 0 call f
143main 108 nosplit call f; f 0 call f
144main 112 nosplit call f; f 0 call f; REJECT amd64
145main 116 nosplit call f; f 0 call f; REJECT amd64
146main 120 nosplit call f; f 0 call f; REJECT amd64 386
147main 124 nosplit call f; f 0 call f; REJECT amd64 386
148main 128 nosplit call f; f 0 call f; REJECT
149main 132 nosplit call f; f 0 call f; REJECT
150main 136 nosplit call f; f 0 call f; REJECT
151
152# Indirect calls are assumed to be splitting functions.
153main 104 nosplit callind
154main 108 nosplit callind
155main 112 nosplit callind; REJECT amd64
156main 116 nosplit callind; REJECT amd64
157main 120 nosplit callind; REJECT amd64 386
158main 124 nosplit callind; REJECT amd64 386
159main 128 nosplit callind; REJECT
160main 132 nosplit callind; REJECT
161main 136 nosplit callind; REJECT
162
163# Issue 7623
164main 0 call f; f 112
165main 0 call f; f 116
166main 0 call f; f 120
167main 0 call f; f 124
168main 0 call f; f 128
169main 0 call f; f 132
170main 0 call f; f 136
171`
172
173var (
174 commentRE = regexp.MustCompile(`(?m)^#.*`)
175 rejectRE = regexp.MustCompile(`(?s)\A(.+?)((\n|; *)REJECT(.*))?\z`)
176 lineRE = regexp.MustCompile(`(\w+) (\d+)( nosplit)?(.*)`)
177 callRE = regexp.MustCompile(`\bcall (\w+)\b`)
178 callindRE = regexp.MustCompile(`\bcallind\b`)
179)
180
181func main() {
182 goarch := os.Getenv("GOARCH")
183 if goarch == "" {
184 goarch = runtime.GOARCH
185 }
186
Austin Clements3c0fee12015-01-14 11:09:50 -0500187 thechar := ""
188 if gochar, err := exec.Command("go", "env", "GOCHAR").Output(); err != nil {
189 bug()
190 fmt.Printf("running go env GOCHAR: %v\n", err)
191 return
192 } else {
193 thechar = strings.TrimSpace(string(gochar))
194 }
195
196 version, err := exec.Command("go", "tool", thechar+"g", "-V").Output()
197 if err != nil {
198 bug()
199 fmt.Printf("running go tool %sg -V: %v\n", thechar, err)
200 return
201 }
202 if strings.Contains(string(version), "framepointer") {
203 // Skip this test if GOEXPERIMENT=framepointer
204 return
205 }
206
Russ Cox5e8c9222014-04-16 22:08:00 -0400207 dir, err := ioutil.TempDir("", "go-test-nosplit")
208 if err != nil {
209 bug()
210 fmt.Printf("creating temp dir: %v\n", err)
211 return
212 }
213 defer os.RemoveAll(dir)
Russ Cox5e8c9222014-04-16 22:08:00 -0400214
215 tests = strings.Replace(tests, "\t", " ", -1)
216 tests = commentRE.ReplaceAllString(tests, "")
217
218 nok := 0
219 nfail := 0
220TestCases:
221 for len(tests) > 0 {
222 var stanza string
223 i := strings.Index(tests, "\nmain ")
224 if i < 0 {
225 stanza, tests = tests, ""
226 } else {
227 stanza, tests = tests[:i], tests[i+1:]
228 }
229
230 m := rejectRE.FindStringSubmatch(stanza)
231 if m == nil {
232 bug()
233 fmt.Printf("invalid stanza:\n\t%s\n", indent(stanza))
234 continue
235 }
236 lines := strings.TrimSpace(m[1])
237 reject := false
238 if m[2] != "" {
239 if strings.TrimSpace(m[4]) == "" {
240 reject = true
241 } else {
242 for _, rej := range strings.Fields(m[4]) {
243 if rej == goarch {
244 reject = true
245 }
246 }
247 }
248 }
249 if lines == "" && !reject {
250 continue
251 }
252
Russ Cox653fb6d2014-09-16 17:39:55 -0400253 var gobuf bytes.Buffer
254 fmt.Fprintf(&gobuf, "package main\n")
255
Russ Cox5e8c9222014-04-16 22:08:00 -0400256 var buf bytes.Buffer
Shenghou Ma80e76e22014-08-14 13:59:58 -0400257 ptrSize := 4
258 switch goarch {
Russ Cox09d92b62014-12-05 19:13:20 -0500259 case "ppc64", "ppc64le":
Shenghou Ma80e76e22014-08-14 13:59:58 -0400260 ptrSize = 8
Russ Coxed68c7d2014-08-14 15:29:37 -0400261 fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (CTR)\n#define RET RETURN\n")
Shenghou Ma80e76e22014-08-14 13:59:58 -0400262 case "arm":
Russ Cox5e8c9222014-04-16 22:08:00 -0400263 fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n")
Shenghou Ma80e76e22014-08-14 13:59:58 -0400264 case "amd64":
265 ptrSize = 8
266 fmt.Fprintf(&buf, "#define REGISTER AX\n")
267 default:
Russ Cox5e8c9222014-04-16 22:08:00 -0400268 fmt.Fprintf(&buf, "#define REGISTER AX\n")
269 }
270
271 for _, line := range strings.Split(lines, "\n") {
272 line = strings.TrimSpace(line)
273 if line == "" {
274 continue
275 }
Russ Coxfe910062014-08-27 14:08:26 -0400276 for i, subline := range strings.Split(line, ";") {
Russ Cox5e8c9222014-04-16 22:08:00 -0400277 subline = strings.TrimSpace(subline)
278 if subline == "" {
279 continue
280 }
281 m := lineRE.FindStringSubmatch(subline)
282 if m == nil {
283 bug()
284 fmt.Printf("invalid function line: %s\n", subline)
285 continue TestCases
286 }
287 name := m[1]
288 size, _ := strconv.Atoi(m[2])
Russ Coxfe910062014-08-27 14:08:26 -0400289
Russ Coxdb406242014-12-05 19:50:09 -0500290 // The limit was originally 128 but is now 512.
Russ Coxfe910062014-08-27 14:08:26 -0400291 // Instead of rewriting the test cases above, adjust
292 // the first stack frame to use up the extra 32 bytes.
293 if i == 0 {
Russ Coxdb406242014-12-05 19:50:09 -0500294 size += 512 - 128
Russ Coxfe910062014-08-27 14:08:26 -0400295 }
296
Shenghou Ma80e76e22014-08-14 13:59:58 -0400297 if size%ptrSize == 4 {
Russ Cox5e8c9222014-04-16 22:08:00 -0400298 continue TestCases
299 }
300 nosplit := m[3]
301 body := m[4]
302
303 if nosplit != "" {
304 nosplit = ",7"
305 } else {
306 nosplit = ",0"
307 }
308 body = callRE.ReplaceAllString(body, "CALL ·$1(SB);")
309 body = callindRE.ReplaceAllString(body, "CALL REGISTER;")
310
Russ Cox653fb6d2014-09-16 17:39:55 -0400311 fmt.Fprintf(&gobuf, "func %s()\n", name)
Russ Cox5e8c9222014-04-16 22:08:00 -0400312 fmt.Fprintf(&buf, "TEXT ·%s(SB)%s,$%d-0\n\t%s\n\tRET\n\n", name, nosplit, size, body)
313 }
314 }
315
Russ Cox653fb6d2014-09-16 17:39:55 -0400316 if err := ioutil.WriteFile(filepath.Join(dir, "asm.s"), buf.Bytes(), 0666); err != nil {
317 log.Fatal(err)
318 }
319 if err := ioutil.WriteFile(filepath.Join(dir, "main.go"), gobuf.Bytes(), 0666); err != nil {
320 log.Fatal(err)
321 }
Russ Cox5e8c9222014-04-16 22:08:00 -0400322
Russ Cox5e8c9222014-04-16 22:08:00 -0400323 cmd := exec.Command("go", "build")
324 cmd.Dir = dir
325 output, err := cmd.CombinedOutput()
326 if err == nil {
327 nok++
328 if reject {
329 bug()
330 fmt.Printf("accepted incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
331 }
332 } else {
333 nfail++
334 if !reject {
335 bug()
336 fmt.Printf("rejected incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
337 fmt.Printf("\n\tlinker output:\n\t%s\n", indent(string(output)))
338 }
339 }
340 }
341
342 if !bugged && (nok == 0 || nfail == 0) {
343 bug()
344 fmt.Printf("not enough test cases run\n")
345 }
346}
347
348func indent(s string) string {
349 return strings.Replace(s, "\n", "\n\t", -1)
350}
351
352var bugged = false
353
354func bug() {
355 if !bugged {
356 bugged = true
357 fmt.Printf("BUG\n")
358 }
359}