mirror of
https://github.com/golang/go
synced 2024-11-18 00:24:48 -07:00
5e8c922625
The new code is adapted from the Go 1.2 nosplit code, but it does not have the bug reported in issue 7623: g% go run nosplit.go g% go1.2 run nosplit.go BUG rejected incorrectly: main 0 call f; f 120 linker output: # _/tmp/go-test-nosplit021064539 main.main: nosplit stack overflow 120 guaranteed after split check in main.main 112 on entry to main.f -8 after main.f uses 120 g% Fixes #6931. Fixes #7623. LGTM=iant R=golang-codereviews, iant, ality CC=golang-codereviews, r https://golang.org/cl/88190043
314 lines
7.5 KiB
Go
314 lines
7.5 KiB
Go
// run
|
|
|
|
// Copyright 2014 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"regexp"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
var tests = `
|
|
# These are test cases for the linker analysis that detects chains of
|
|
# nosplit functions that would cause a stack overflow.
|
|
#
|
|
# Lines beginning with # are comments.
|
|
#
|
|
# Each test case describes a sequence of functions, one per line.
|
|
# Each function definition is the function name, then the frame size,
|
|
# then optionally the keyword 'nosplit', then the body of the function.
|
|
# The body is assembly code, with some shorthands.
|
|
# The shorthand 'call x' stands for CALL x(SB).
|
|
# The shorthand 'callind' stands for 'CALL R0', where R0 is a register.
|
|
# Each test case must define a function named main, and it must be first.
|
|
# That is, a line beginning "main " indicates the start of a new test case.
|
|
# Within a stanza, ; can be used instead of \n to separate lines.
|
|
#
|
|
# After the function definition, the test case ends with an optional
|
|
# REJECT line, specifying the architectures on which the case should
|
|
# be rejected. "REJECT" without any architectures means reject on all architectures.
|
|
# The linker should accept the test case on systems not explicitly rejected.
|
|
#
|
|
# 64-bit systems do not attempt to execute test cases with frame sizes
|
|
# that are only 32-bit aligned.
|
|
|
|
# Ordinary function should work
|
|
main 0
|
|
|
|
# Large frame marked nosplit is always wrong.
|
|
main 10000 nosplit
|
|
REJECT
|
|
|
|
# Calling a large frame is okay.
|
|
main 0 call big
|
|
big 10000
|
|
|
|
# But not if the frame is nosplit.
|
|
main 0 call big
|
|
big 10000 nosplit
|
|
REJECT
|
|
|
|
# Recursion is okay.
|
|
main 0 call main
|
|
|
|
# Recursive nosplit runs out of space.
|
|
main 0 nosplit call main
|
|
REJECT
|
|
|
|
# Chains of ordinary functions okay.
|
|
main 0 call f1
|
|
f1 80 call f2
|
|
f2 80
|
|
|
|
# Chains of nosplit must fit in the stack limit, 128 bytes.
|
|
main 0 call f1
|
|
f1 80 nosplit call f2
|
|
f2 80 nosplit
|
|
REJECT
|
|
|
|
# Larger chains.
|
|
main 0 call f1
|
|
f1 16 call f2
|
|
f2 16 call f3
|
|
f3 16 call f4
|
|
f4 16 call f5
|
|
f5 16 call f6
|
|
f6 16 call f7
|
|
f7 16 call f8
|
|
f8 16 call end
|
|
end 1000
|
|
|
|
main 0 call f1
|
|
f1 16 nosplit call f2
|
|
f2 16 nosplit call f3
|
|
f3 16 nosplit call f4
|
|
f4 16 nosplit call f5
|
|
f5 16 nosplit call f6
|
|
f6 16 nosplit call f7
|
|
f7 16 nosplit call f8
|
|
f8 16 nosplit call end
|
|
end 1000
|
|
REJECT
|
|
|
|
# Test cases near the 128-byte limit.
|
|
|
|
# Ordinary stack split frame is always okay.
|
|
main 112
|
|
main 116
|
|
main 120
|
|
main 124
|
|
main 128
|
|
main 132
|
|
main 136
|
|
|
|
# A nosplit leaf can use the whole 128-CallSize bytes available on entry.
|
|
main 112 nosplit
|
|
main 116 nosplit
|
|
main 120 nosplit
|
|
main 124 nosplit
|
|
main 128 nosplit; REJECT
|
|
main 132 nosplit; REJECT
|
|
main 136 nosplit; REJECT
|
|
|
|
# Calling a nosplit function from a nosplit function requires
|
|
# having room for the saved caller PC and the called frame.
|
|
# Because ARM doesn't save LR in the leaf, it gets an extra 4 bytes.
|
|
main 112 nosplit call f; f 0 nosplit
|
|
main 116 nosplit call f; f 0 nosplit; REJECT amd64
|
|
main 120 nosplit call f; f 0 nosplit; REJECT amd64
|
|
main 124 nosplit call f; f 0 nosplit; REJECT amd64 386
|
|
main 128 nosplit call f; f 0 nosplit; REJECT
|
|
main 132 nosplit call f; f 0 nosplit; REJECT
|
|
main 136 nosplit call f; f 0 nosplit; REJECT
|
|
|
|
# Calling a splitting function from a nosplit function requires
|
|
# having room for the saved caller PC of the call but also the
|
|
# saved caller PC for the call to morestack. Again the ARM works
|
|
# in less space.
|
|
main 104 nosplit call f; f 0 call f
|
|
main 108 nosplit call f; f 0 call f
|
|
main 112 nosplit call f; f 0 call f; REJECT amd64
|
|
main 116 nosplit call f; f 0 call f; REJECT amd64
|
|
main 120 nosplit call f; f 0 call f; REJECT amd64 386
|
|
main 124 nosplit call f; f 0 call f; REJECT amd64 386
|
|
main 128 nosplit call f; f 0 call f; REJECT
|
|
main 132 nosplit call f; f 0 call f; REJECT
|
|
main 136 nosplit call f; f 0 call f; REJECT
|
|
|
|
# Indirect calls are assumed to be splitting functions.
|
|
main 104 nosplit callind
|
|
main 108 nosplit callind
|
|
main 112 nosplit callind; REJECT amd64
|
|
main 116 nosplit callind; REJECT amd64
|
|
main 120 nosplit callind; REJECT amd64 386
|
|
main 124 nosplit callind; REJECT amd64 386
|
|
main 128 nosplit callind; REJECT
|
|
main 132 nosplit callind; REJECT
|
|
main 136 nosplit callind; REJECT
|
|
|
|
# Issue 7623
|
|
main 0 call f; f 112
|
|
main 0 call f; f 116
|
|
main 0 call f; f 120
|
|
main 0 call f; f 124
|
|
main 0 call f; f 128
|
|
main 0 call f; f 132
|
|
main 0 call f; f 136
|
|
`
|
|
|
|
var (
|
|
commentRE = regexp.MustCompile(`(?m)^#.*`)
|
|
rejectRE = regexp.MustCompile(`(?s)\A(.+?)((\n|; *)REJECT(.*))?\z`)
|
|
lineRE = regexp.MustCompile(`(\w+) (\d+)( nosplit)?(.*)`)
|
|
callRE = regexp.MustCompile(`\bcall (\w+)\b`)
|
|
callindRE = regexp.MustCompile(`\bcallind\b`)
|
|
)
|
|
|
|
func main() {
|
|
goarch := os.Getenv("GOARCH")
|
|
if goarch == "" {
|
|
goarch = runtime.GOARCH
|
|
}
|
|
|
|
dir, err := ioutil.TempDir("", "go-test-nosplit")
|
|
if err != nil {
|
|
bug()
|
|
fmt.Printf("creating temp dir: %v\n", err)
|
|
return
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
ioutil.WriteFile(filepath.Join(dir, "main.go"), []byte("package main\nfunc main()\n"), 0666)
|
|
|
|
tests = strings.Replace(tests, "\t", " ", -1)
|
|
tests = commentRE.ReplaceAllString(tests, "")
|
|
|
|
nok := 0
|
|
nfail := 0
|
|
TestCases:
|
|
for len(tests) > 0 {
|
|
var stanza string
|
|
i := strings.Index(tests, "\nmain ")
|
|
if i < 0 {
|
|
stanza, tests = tests, ""
|
|
} else {
|
|
stanza, tests = tests[:i], tests[i+1:]
|
|
}
|
|
|
|
m := rejectRE.FindStringSubmatch(stanza)
|
|
if m == nil {
|
|
bug()
|
|
fmt.Printf("invalid stanza:\n\t%s\n", indent(stanza))
|
|
continue
|
|
}
|
|
lines := strings.TrimSpace(m[1])
|
|
reject := false
|
|
if m[2] != "" {
|
|
if strings.TrimSpace(m[4]) == "" {
|
|
reject = true
|
|
} else {
|
|
for _, rej := range strings.Fields(m[4]) {
|
|
if rej == goarch {
|
|
reject = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if lines == "" && !reject {
|
|
continue
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
if goarch == "arm" {
|
|
fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n")
|
|
} else {
|
|
fmt.Fprintf(&buf, "#define REGISTER AX\n")
|
|
}
|
|
|
|
for _, line := range strings.Split(lines, "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if line == "" {
|
|
continue
|
|
}
|
|
for _, subline := range strings.Split(line, ";") {
|
|
subline = strings.TrimSpace(subline)
|
|
if subline == "" {
|
|
continue
|
|
}
|
|
m := lineRE.FindStringSubmatch(subline)
|
|
if m == nil {
|
|
bug()
|
|
fmt.Printf("invalid function line: %s\n", subline)
|
|
continue TestCases
|
|
}
|
|
name := m[1]
|
|
size, _ := strconv.Atoi(m[2])
|
|
if goarch == "amd64" && size%8 == 4 {
|
|
continue TestCases
|
|
}
|
|
nosplit := m[3]
|
|
body := m[4]
|
|
|
|
if nosplit != "" {
|
|
nosplit = ",7"
|
|
} else {
|
|
nosplit = ",0"
|
|
}
|
|
body = callRE.ReplaceAllString(body, "CALL ·$1(SB);")
|
|
body = callindRE.ReplaceAllString(body, "CALL REGISTER;")
|
|
|
|
fmt.Fprintf(&buf, "TEXT ·%s(SB)%s,$%d-0\n\t%s\n\tRET\n\n", name, nosplit, size, body)
|
|
}
|
|
}
|
|
|
|
ioutil.WriteFile(filepath.Join(dir, "asm.s"), buf.Bytes(), 0666)
|
|
|
|
|
|
cmd := exec.Command("go", "build")
|
|
cmd.Dir = dir
|
|
output, err := cmd.CombinedOutput()
|
|
if err == nil {
|
|
nok++
|
|
if reject {
|
|
bug()
|
|
fmt.Printf("accepted incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
|
|
}
|
|
} else {
|
|
nfail++
|
|
if !reject {
|
|
bug()
|
|
fmt.Printf("rejected incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
|
|
fmt.Printf("\n\tlinker output:\n\t%s\n", indent(string(output)))
|
|
}
|
|
}
|
|
}
|
|
|
|
if !bugged && (nok == 0 || nfail == 0) {
|
|
bug()
|
|
fmt.Printf("not enough test cases run\n")
|
|
}
|
|
}
|
|
|
|
func indent(s string) string {
|
|
return strings.Replace(s, "\n", "\n\t", -1)
|
|
}
|
|
|
|
var bugged = false
|
|
|
|
func bug() {
|
|
if !bugged {
|
|
bugged = true
|
|
fmt.Printf("BUG\n")
|
|
}
|
|
}
|