blob: 8864137eb195ef44e126d363a15d22b74e7b28a8 [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
Russ Cox0f4132c2015-05-21 13:28:13 -0400187 version, err := exec.Command("go", "tool", "compile", "-V").Output()
Austin Clements3c0fee12015-01-14 11:09:50 -0500188 if err != nil {
189 bug()
Josh Bleecher Snyder8b186df2015-05-27 12:33:43 -0700190 fmt.Printf("running go tool compile -V: %v\n", err)
Austin Clements3c0fee12015-01-14 11:09:50 -0500191 return
192 }
193 if strings.Contains(string(version), "framepointer") {
194 // Skip this test if GOEXPERIMENT=framepointer
195 return
196 }
197
Russ Cox5e8c9222014-04-16 22:08:00 -0400198 dir, err := ioutil.TempDir("", "go-test-nosplit")
199 if err != nil {
200 bug()
201 fmt.Printf("creating temp dir: %v\n", err)
202 return
203 }
204 defer os.RemoveAll(dir)
Russ Cox5e8c9222014-04-16 22:08:00 -0400205
206 tests = strings.Replace(tests, "\t", " ", -1)
207 tests = commentRE.ReplaceAllString(tests, "")
208
209 nok := 0
210 nfail := 0
211TestCases:
212 for len(tests) > 0 {
213 var stanza string
214 i := strings.Index(tests, "\nmain ")
215 if i < 0 {
216 stanza, tests = tests, ""
217 } else {
218 stanza, tests = tests[:i], tests[i+1:]
219 }
220
221 m := rejectRE.FindStringSubmatch(stanza)
222 if m == nil {
223 bug()
224 fmt.Printf("invalid stanza:\n\t%s\n", indent(stanza))
225 continue
226 }
227 lines := strings.TrimSpace(m[1])
228 reject := false
229 if m[2] != "" {
230 if strings.TrimSpace(m[4]) == "" {
231 reject = true
232 } else {
233 for _, rej := range strings.Fields(m[4]) {
234 if rej == goarch {
235 reject = true
236 }
237 }
238 }
239 }
240 if lines == "" && !reject {
241 continue
242 }
243
Russ Cox653fb6d2014-09-16 17:39:55 -0400244 var gobuf bytes.Buffer
245 fmt.Fprintf(&gobuf, "package main\n")
246
Russ Cox5e8c9222014-04-16 22:08:00 -0400247 var buf bytes.Buffer
Shenghou Ma80e76e22014-08-14 13:59:58 -0400248 ptrSize := 4
249 switch goarch {
Russ Cox09d92b62014-12-05 19:13:20 -0500250 case "ppc64", "ppc64le":
Shenghou Ma80e76e22014-08-14 13:59:58 -0400251 ptrSize = 8
Russ Coxed68c7d2014-08-14 15:29:37 -0400252 fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (CTR)\n#define RET RETURN\n")
Shenghou Ma80e76e22014-08-14 13:59:58 -0400253 case "arm":
Russ Cox5e8c9222014-04-16 22:08:00 -0400254 fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n")
Aram Hăvărneanuddf6d802015-03-08 14:27:51 +0100255 case "arm64":
256 ptrSize = 8
257 fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n")
Shenghou Ma80e76e22014-08-14 13:59:58 -0400258 case "amd64":
259 ptrSize = 8
260 fmt.Fprintf(&buf, "#define REGISTER AX\n")
261 default:
Russ Cox5e8c9222014-04-16 22:08:00 -0400262 fmt.Fprintf(&buf, "#define REGISTER AX\n")
263 }
264
265 for _, line := range strings.Split(lines, "\n") {
266 line = strings.TrimSpace(line)
267 if line == "" {
268 continue
269 }
Russ Coxfe910062014-08-27 14:08:26 -0400270 for i, subline := range strings.Split(line, ";") {
Russ Cox5e8c9222014-04-16 22:08:00 -0400271 subline = strings.TrimSpace(subline)
272 if subline == "" {
273 continue
274 }
275 m := lineRE.FindStringSubmatch(subline)
276 if m == nil {
277 bug()
278 fmt.Printf("invalid function line: %s\n", subline)
279 continue TestCases
280 }
281 name := m[1]
282 size, _ := strconv.Atoi(m[2])
Russ Coxfe910062014-08-27 14:08:26 -0400283
Russ Coxdb406242014-12-05 19:50:09 -0500284 // The limit was originally 128 but is now 512.
Russ Coxfe910062014-08-27 14:08:26 -0400285 // Instead of rewriting the test cases above, adjust
Keith Randall6f42b612015-05-01 09:36:18 -0700286 // the first stack frame to use up the extra bytes.
Russ Coxfe910062014-08-27 14:08:26 -0400287 if i == 0 {
Russ Coxdb406242014-12-05 19:50:09 -0500288 size += 512 - 128
Keith Randall6f42b612015-05-01 09:36:18 -0700289 // Noopt builds have a larger stackguard.
290 // See ../cmd/dist/buildruntime.go:stackGuardMultiplier
291 for _, s := range strings.Split(os.Getenv("GO_GCFLAGS"), " ") {
292 if s == "-N" {
293 size += 640
294 }
295 }
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}