rsc.io/go@v0.0.0-20150416155037-e040fd465409/test/nosplit.go (about) 1 // +build !nacl 2 // run 3 4 // 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 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 var 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 49 main 0 50 51 # Large frame marked nosplit is always wrong. 52 main 10000 nosplit 53 REJECT 54 55 # Calling a large frame is okay. 56 main 0 call big 57 big 10000 58 59 # But not if the frame is nosplit. 60 main 0 call big 61 big 10000 nosplit 62 REJECT 63 64 # Recursion is okay. 65 main 0 call main 66 67 # Recursive nosplit runs out of space. 68 main 0 nosplit call main 69 REJECT 70 71 # Chains of ordinary functions okay. 72 main 0 call f1 73 f1 80 call f2 74 f2 80 75 76 # Chains of nosplit must fit in the stack limit, 128 bytes. 77 main 0 call f1 78 f1 80 nosplit call f2 79 f2 80 nosplit 80 REJECT 81 82 # Larger chains. 83 main 0 call f1 84 f1 16 call f2 85 f2 16 call f3 86 f3 16 call f4 87 f4 16 call f5 88 f5 16 call f6 89 f6 16 call f7 90 f7 16 call f8 91 f8 16 call end 92 end 1000 93 94 main 0 call f1 95 f1 16 nosplit call f2 96 f2 16 nosplit call f3 97 f3 16 nosplit call f4 98 f4 16 nosplit call f5 99 f5 16 nosplit call f6 100 f6 16 nosplit call f7 101 f7 16 nosplit call f8 102 f8 16 nosplit call end 103 end 1000 104 REJECT 105 106 # Test cases near the 128-byte limit. 107 108 # Ordinary stack split frame is always okay. 109 main 112 110 main 116 111 main 120 112 main 124 113 main 128 114 main 132 115 main 136 116 117 # A nosplit leaf can use the whole 128-CallSize bytes available on entry. 118 main 112 nosplit 119 main 116 nosplit 120 main 120 nosplit 121 main 124 nosplit 122 main 128 nosplit; REJECT 123 main 132 nosplit; REJECT 124 main 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. 129 # Because ppc64 doesn't save LR in the leaf, it gets an extra 8 bytes. 130 main 112 nosplit call f; f 0 nosplit 131 main 116 nosplit call f; f 0 nosplit 132 main 120 nosplit call f; f 0 nosplit; REJECT amd64 133 main 124 nosplit call f; f 0 nosplit; REJECT amd64 386 134 main 128 nosplit call f; f 0 nosplit; REJECT 135 main 132 nosplit call f; f 0 nosplit; REJECT 136 main 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 140 # saved caller PC for the call to morestack. 141 # Again the ARM and ppc64 work in less space. 142 main 104 nosplit call f; f 0 call f 143 main 108 nosplit call f; f 0 call f 144 main 112 nosplit call f; f 0 call f; REJECT amd64 145 main 116 nosplit call f; f 0 call f; REJECT amd64 146 main 120 nosplit call f; f 0 call f; REJECT amd64 386 147 main 124 nosplit call f; f 0 call f; REJECT amd64 386 148 main 128 nosplit call f; f 0 call f; REJECT 149 main 132 nosplit call f; f 0 call f; REJECT 150 main 136 nosplit call f; f 0 call f; REJECT 151 152 # Indirect calls are assumed to be splitting functions. 153 main 104 nosplit callind 154 main 108 nosplit callind 155 main 112 nosplit callind; REJECT amd64 156 main 116 nosplit callind; REJECT amd64 157 main 120 nosplit callind; REJECT amd64 386 158 main 124 nosplit callind; REJECT amd64 386 159 main 128 nosplit callind; REJECT 160 main 132 nosplit callind; REJECT 161 main 136 nosplit callind; REJECT 162 163 # Issue 7623 164 main 0 call f; f 112 165 main 0 call f; f 116 166 main 0 call f; f 120 167 main 0 call f; f 124 168 main 0 call f; f 128 169 main 0 call f; f 132 170 main 0 call f; f 136 171 ` 172 173 var ( 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 181 func main() { 182 goarch := os.Getenv("GOARCH") 183 if goarch == "" { 184 goarch = runtime.GOARCH 185 } 186 187 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 207 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) 214 215 tests = strings.Replace(tests, "\t", " ", -1) 216 tests = commentRE.ReplaceAllString(tests, "") 217 218 nok := 0 219 nfail := 0 220 TestCases: 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 253 var gobuf bytes.Buffer 254 fmt.Fprintf(&gobuf, "package main\n") 255 256 var buf bytes.Buffer 257 ptrSize := 4 258 switch goarch { 259 case "ppc64", "ppc64le": 260 ptrSize = 8 261 fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (CTR)\n#define RET RETURN\n") 262 case "arm": 263 fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n") 264 case "arm64": 265 ptrSize = 8 266 fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n") 267 case "amd64": 268 ptrSize = 8 269 fmt.Fprintf(&buf, "#define REGISTER AX\n") 270 default: 271 fmt.Fprintf(&buf, "#define REGISTER AX\n") 272 } 273 274 for _, line := range strings.Split(lines, "\n") { 275 line = strings.TrimSpace(line) 276 if line == "" { 277 continue 278 } 279 for i, subline := range strings.Split(line, ";") { 280 subline = strings.TrimSpace(subline) 281 if subline == "" { 282 continue 283 } 284 m := lineRE.FindStringSubmatch(subline) 285 if m == nil { 286 bug() 287 fmt.Printf("invalid function line: %s\n", subline) 288 continue TestCases 289 } 290 name := m[1] 291 size, _ := strconv.Atoi(m[2]) 292 293 // The limit was originally 128 but is now 512. 294 // Instead of rewriting the test cases above, adjust 295 // the first stack frame to use up the extra 32 bytes. 296 if i == 0 { 297 size += 512 - 128 298 } 299 300 if size%ptrSize == 4 { 301 continue TestCases 302 } 303 nosplit := m[3] 304 body := m[4] 305 306 if nosplit != "" { 307 nosplit = ",7" 308 } else { 309 nosplit = ",0" 310 } 311 body = callRE.ReplaceAllString(body, "CALL ·$1(SB);") 312 body = callindRE.ReplaceAllString(body, "CALL REGISTER;") 313 314 fmt.Fprintf(&gobuf, "func %s()\n", name) 315 fmt.Fprintf(&buf, "TEXT ·%s(SB)%s,$%d-0\n\t%s\n\tRET\n\n", name, nosplit, size, body) 316 } 317 } 318 319 if err := ioutil.WriteFile(filepath.Join(dir, "asm.s"), buf.Bytes(), 0666); err != nil { 320 log.Fatal(err) 321 } 322 if err := ioutil.WriteFile(filepath.Join(dir, "main.go"), gobuf.Bytes(), 0666); err != nil { 323 log.Fatal(err) 324 } 325 326 cmd := exec.Command("go", "build") 327 cmd.Dir = dir 328 output, err := cmd.CombinedOutput() 329 if err == nil { 330 nok++ 331 if reject { 332 bug() 333 fmt.Printf("accepted incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza))) 334 } 335 } else { 336 nfail++ 337 if !reject { 338 bug() 339 fmt.Printf("rejected incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza))) 340 fmt.Printf("\n\tlinker output:\n\t%s\n", indent(string(output))) 341 } 342 } 343 } 344 345 if !bugged && (nok == 0 || nfail == 0) { 346 bug() 347 fmt.Printf("not enough test cases run\n") 348 } 349 } 350 351 func indent(s string) string { 352 return strings.Replace(s, "\n", "\n\t", -1) 353 } 354 355 var bugged = false 356 357 func bug() { 358 if !bugged { 359 bugged = true 360 fmt.Printf("BUG\n") 361 } 362 }