blob: 3c4ae1079dc40e79ab468c7eb6bbc57f53e82d26 [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.
Michael Hudson-Doylec1b6e392015-10-30 12:47:24 +1300118# (CallSize is 32 on ppc64)
119main 96 nosplit
120main 100 nosplit; REJECT ppc64 ppc64le
121main 104 nosplit; REJECT ppc64 ppc64le
122main 108 nosplit; REJECT ppc64 ppc64le
123main 112 nosplit; REJECT ppc64 ppc64le
124main 116 nosplit; REJECT ppc64 ppc64le
125main 120 nosplit; REJECT ppc64 ppc64le
126main 124 nosplit; REJECT ppc64 ppc64le
Russ Cox5e8c9222014-04-16 22:08:00 -0400127main 128 nosplit; REJECT
128main 132 nosplit; REJECT
129main 136 nosplit; REJECT
130
131# Calling a nosplit function from a nosplit function requires
132# having room for the saved caller PC and the called frame.
133# Because ARM doesn't save LR in the leaf, it gets an extra 4 bytes.
Michael Hudson-Doylec1b6e392015-10-30 12:47:24 +1300134# Because arm64 doesn't save LR in the leaf, it gets an extra 8 bytes.
135# ppc64 doesn't save LR in the leaf, but CallSize is 32, so it gets 24 fewer bytes than amd64.
136main 96 nosplit call f; f 0 nosplit
137main 100 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
138main 104 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
139main 108 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
140main 112 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
141main 116 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
142main 120 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64
143main 124 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64 386
Russ Cox5e8c9222014-04-16 22:08:00 -0400144main 128 nosplit call f; f 0 nosplit; REJECT
145main 132 nosplit call f; f 0 nosplit; REJECT
146main 136 nosplit call f; f 0 nosplit; REJECT
147
148# Calling a splitting function from a nosplit function requires
149# having room for the saved caller PC of the call but also the
Russ Coxed68c7d2014-08-14 15:29:37 -0400150# saved caller PC for the call to morestack.
Michael Hudson-Doylec1b6e392015-10-30 12:47:24 +1300151# RISC architectures differ in the same way as before.
152main 96 nosplit call f; f 0 call f
153main 100 nosplit call f; f 0 call f; REJECT ppc64 ppc64le
154main 104 nosplit call f; f 0 call f; REJECT ppc64 ppc64le
155main 108 nosplit call f; f 0 call f; REJECT ppc64 ppc64le
156main 112 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
157main 116 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
158main 120 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 386
159main 124 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 386
Russ Cox5e8c9222014-04-16 22:08:00 -0400160main 128 nosplit call f; f 0 call f; REJECT
161main 132 nosplit call f; f 0 call f; REJECT
162main 136 nosplit call f; f 0 call f; REJECT
163
164# Indirect calls are assumed to be splitting functions.
Michael Hudson-Doylec1b6e392015-10-30 12:47:24 +1300165main 96 nosplit callind
166main 100 nosplit callind; REJECT ppc64 ppc64le
167main 104 nosplit callind; REJECT ppc64 ppc64le
168main 108 nosplit callind; REJECT ppc64 ppc64le
169main 112 nosplit callind; REJECT ppc64 ppc64le amd64
170main 116 nosplit callind; REJECT ppc64 ppc64le amd64
171main 120 nosplit callind; REJECT ppc64 ppc64le amd64 386
172main 124 nosplit callind; REJECT ppc64 ppc64le amd64 386
Russ Cox5e8c9222014-04-16 22:08:00 -0400173main 128 nosplit callind; REJECT
174main 132 nosplit callind; REJECT
175main 136 nosplit callind; REJECT
176
177# Issue 7623
178main 0 call f; f 112
179main 0 call f; f 116
180main 0 call f; f 120
181main 0 call f; f 124
182main 0 call f; f 128
183main 0 call f; f 132
184main 0 call f; f 136
185`
186
187var (
188 commentRE = regexp.MustCompile(`(?m)^#.*`)
189 rejectRE = regexp.MustCompile(`(?s)\A(.+?)((\n|; *)REJECT(.*))?\z`)
190 lineRE = regexp.MustCompile(`(\w+) (\d+)( nosplit)?(.*)`)
191 callRE = regexp.MustCompile(`\bcall (\w+)\b`)
192 callindRE = regexp.MustCompile(`\bcallind\b`)
193)
194
195func main() {
196 goarch := os.Getenv("GOARCH")
197 if goarch == "" {
198 goarch = runtime.GOARCH
199 }
200
Russ Cox0f4132c2015-05-21 13:28:13 -0400201 version, err := exec.Command("go", "tool", "compile", "-V").Output()
Austin Clements3c0fee12015-01-14 11:09:50 -0500202 if err != nil {
203 bug()
Josh Bleecher Snyder8b186df2015-05-27 12:33:43 -0700204 fmt.Printf("running go tool compile -V: %v\n", err)
Austin Clements3c0fee12015-01-14 11:09:50 -0500205 return
206 }
207 if strings.Contains(string(version), "framepointer") {
208 // Skip this test if GOEXPERIMENT=framepointer
209 return
210 }
211
Russ Cox5e8c9222014-04-16 22:08:00 -0400212 dir, err := ioutil.TempDir("", "go-test-nosplit")
213 if err != nil {
214 bug()
215 fmt.Printf("creating temp dir: %v\n", err)
216 return
217 }
218 defer os.RemoveAll(dir)
Russ Cox5e8c9222014-04-16 22:08:00 -0400219
220 tests = strings.Replace(tests, "\t", " ", -1)
221 tests = commentRE.ReplaceAllString(tests, "")
222
223 nok := 0
224 nfail := 0
225TestCases:
226 for len(tests) > 0 {
227 var stanza string
228 i := strings.Index(tests, "\nmain ")
229 if i < 0 {
230 stanza, tests = tests, ""
231 } else {
232 stanza, tests = tests[:i], tests[i+1:]
233 }
234
235 m := rejectRE.FindStringSubmatch(stanza)
236 if m == nil {
237 bug()
238 fmt.Printf("invalid stanza:\n\t%s\n", indent(stanza))
239 continue
240 }
241 lines := strings.TrimSpace(m[1])
242 reject := false
243 if m[2] != "" {
244 if strings.TrimSpace(m[4]) == "" {
245 reject = true
246 } else {
247 for _, rej := range strings.Fields(m[4]) {
248 if rej == goarch {
249 reject = true
250 }
251 }
252 }
253 }
254 if lines == "" && !reject {
255 continue
256 }
257
Russ Cox653fb6d2014-09-16 17:39:55 -0400258 var gobuf bytes.Buffer
259 fmt.Fprintf(&gobuf, "package main\n")
260
Russ Cox5e8c9222014-04-16 22:08:00 -0400261 var buf bytes.Buffer
Shenghou Ma80e76e22014-08-14 13:59:58 -0400262 ptrSize := 4
263 switch goarch {
Yao Zhang15676b52015-09-28 17:30:32 -0400264 case "mips64", "mips64le":
265 ptrSize = 8
266 fmt.Fprintf(&buf, "#define CALL JAL\n#define REGISTER (R0)\n")
Russ Cox09d92b62014-12-05 19:13:20 -0500267 case "ppc64", "ppc64le":
Shenghou Ma80e76e22014-08-14 13:59:58 -0400268 ptrSize = 8
Shenghou Ma21ec72c2015-06-06 15:24:18 -0400269 fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (CTR)\n")
Shenghou Ma80e76e22014-08-14 13:59:58 -0400270 case "arm":
Russ Cox5e8c9222014-04-16 22:08:00 -0400271 fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n")
Aram Hăvărneanuddf6d802015-03-08 14:27:51 +0100272 case "arm64":
273 ptrSize = 8
274 fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n")
Shenghou Ma80e76e22014-08-14 13:59:58 -0400275 case "amd64":
276 ptrSize = 8
277 fmt.Fprintf(&buf, "#define REGISTER AX\n")
278 default:
Russ Cox5e8c9222014-04-16 22:08:00 -0400279 fmt.Fprintf(&buf, "#define REGISTER AX\n")
280 }
281
282 for _, line := range strings.Split(lines, "\n") {
283 line = strings.TrimSpace(line)
284 if line == "" {
285 continue
286 }
Russ Coxfe910062014-08-27 14:08:26 -0400287 for i, subline := range strings.Split(line, ";") {
Russ Cox5e8c9222014-04-16 22:08:00 -0400288 subline = strings.TrimSpace(subline)
289 if subline == "" {
290 continue
291 }
292 m := lineRE.FindStringSubmatch(subline)
293 if m == nil {
294 bug()
295 fmt.Printf("invalid function line: %s\n", subline)
296 continue TestCases
297 }
298 name := m[1]
299 size, _ := strconv.Atoi(m[2])
Russ Coxfe910062014-08-27 14:08:26 -0400300
Michael Hudson-Doylec1b6e392015-10-30 12:47:24 +1300301 // The limit was originally 128 but is now 592.
Russ Coxfe910062014-08-27 14:08:26 -0400302 // Instead of rewriting the test cases above, adjust
Keith Randall6f42b612015-05-01 09:36:18 -0700303 // the first stack frame to use up the extra bytes.
Russ Coxfe910062014-08-27 14:08:26 -0400304 if i == 0 {
Michael Hudson-Doylec1b6e392015-10-30 12:47:24 +1300305 size += 592 - 128
Keith Randall6f42b612015-05-01 09:36:18 -0700306 // Noopt builds have a larger stackguard.
307 // See ../cmd/dist/buildruntime.go:stackGuardMultiplier
308 for _, s := range strings.Split(os.Getenv("GO_GCFLAGS"), " ") {
309 if s == "-N" {
Michael Hudson-Doylec1b6e392015-10-30 12:47:24 +1300310 size += 720
Keith Randall6f42b612015-05-01 09:36:18 -0700311 }
312 }
Russ Coxfe910062014-08-27 14:08:26 -0400313 }
314
Russ Cox034a10d2015-07-29 15:15:03 -0400315 if size%ptrSize == 4 || goarch == "arm64" && size != 0 && (size+8)%16 != 0 {
Russ Cox5e8c9222014-04-16 22:08:00 -0400316 continue TestCases
317 }
318 nosplit := m[3]
319 body := m[4]
320
321 if nosplit != "" {
322 nosplit = ",7"
323 } else {
324 nosplit = ",0"
325 }
326 body = callRE.ReplaceAllString(body, "CALL ·$1(SB);")
327 body = callindRE.ReplaceAllString(body, "CALL REGISTER;")
328
Russ Cox653fb6d2014-09-16 17:39:55 -0400329 fmt.Fprintf(&gobuf, "func %s()\n", name)
Russ Cox5e8c9222014-04-16 22:08:00 -0400330 fmt.Fprintf(&buf, "TEXT ·%s(SB)%s,$%d-0\n\t%s\n\tRET\n\n", name, nosplit, size, body)
331 }
332 }
333
Russ Cox653fb6d2014-09-16 17:39:55 -0400334 if err := ioutil.WriteFile(filepath.Join(dir, "asm.s"), buf.Bytes(), 0666); err != nil {
335 log.Fatal(err)
336 }
337 if err := ioutil.WriteFile(filepath.Join(dir, "main.go"), gobuf.Bytes(), 0666); err != nil {
338 log.Fatal(err)
339 }
Russ Cox5e8c9222014-04-16 22:08:00 -0400340
Russ Cox5e8c9222014-04-16 22:08:00 -0400341 cmd := exec.Command("go", "build")
342 cmd.Dir = dir
343 output, err := cmd.CombinedOutput()
344 if err == nil {
345 nok++
346 if reject {
347 bug()
348 fmt.Printf("accepted incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
349 }
350 } else {
351 nfail++
352 if !reject {
353 bug()
354 fmt.Printf("rejected incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
355 fmt.Printf("\n\tlinker output:\n\t%s\n", indent(string(output)))
356 }
357 }
358 }
359
360 if !bugged && (nok == 0 || nfail == 0) {
361 bug()
362 fmt.Printf("not enough test cases run\n")
363 }
364}
365
366func indent(s string) string {
367 return strings.Replace(s, "\n", "\n\t", -1)
368}
369
370var bugged = false
371
372func bug() {
373 if !bugged {
374 bugged = true
375 fmt.Printf("BUG\n")
376 }
377}