github.com/FenixAra/go@v0.0.0-20170127160404-96ea0918e670/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  # (CallSize is 32 on ppc64)
   119  main 96 nosplit
   120  main 100 nosplit; REJECT ppc64 ppc64le
   121  main 104 nosplit; REJECT ppc64 ppc64le
   122  main 108 nosplit; REJECT ppc64 ppc64le
   123  main 112 nosplit; REJECT ppc64 ppc64le
   124  main 116 nosplit; REJECT ppc64 ppc64le
   125  main 120 nosplit; REJECT ppc64 ppc64le
   126  main 124 nosplit; REJECT ppc64 ppc64le
   127  main 128 nosplit; REJECT
   128  main 132 nosplit; REJECT
   129  main 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.
   134  # 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.
   136  main 96 nosplit call f; f 0 nosplit
   137  main 100 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
   138  main 104 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
   139  main 108 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
   140  main 112 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
   141  main 116 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
   142  main 120 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64
   143  main 124 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64 386
   144  main 128 nosplit call f; f 0 nosplit; REJECT
   145  main 132 nosplit call f; f 0 nosplit; REJECT
   146  main 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
   150  # saved caller PC for the call to morestack.
   151  # RISC architectures differ in the same way as before.
   152  main 96 nosplit call f; f 0 call f
   153  main 100 nosplit call f; f 0 call f; REJECT ppc64 ppc64le
   154  main 104 nosplit call f; f 0 call f; REJECT ppc64 ppc64le
   155  main 108 nosplit call f; f 0 call f; REJECT ppc64 ppc64le
   156  main 112 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
   157  main 116 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
   158  main 120 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 386
   159  main 124 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 386
   160  main 128 nosplit call f; f 0 call f; REJECT
   161  main 132 nosplit call f; f 0 call f; REJECT
   162  main 136 nosplit call f; f 0 call f; REJECT
   163  
   164  # Indirect calls are assumed to be splitting functions.
   165  main 96 nosplit callind
   166  main 100 nosplit callind; REJECT ppc64 ppc64le
   167  main 104 nosplit callind; REJECT ppc64 ppc64le
   168  main 108 nosplit callind; REJECT ppc64 ppc64le
   169  main 112 nosplit callind; REJECT ppc64 ppc64le amd64
   170  main 116 nosplit callind; REJECT ppc64 ppc64le amd64
   171  main 120 nosplit callind; REJECT ppc64 ppc64le amd64 386
   172  main 124 nosplit callind; REJECT ppc64 ppc64le amd64 386
   173  main 128 nosplit callind; REJECT
   174  main 132 nosplit callind; REJECT
   175  main 136 nosplit callind; REJECT
   176  
   177  # Issue 7623
   178  main 0 call f; f 112
   179  main 0 call f; f 116
   180  main 0 call f; f 120
   181  main 0 call f; f 124
   182  main 0 call f; f 128
   183  main 0 call f; f 132
   184  main 0 call f; f 136
   185  `
   186  
   187  var (
   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  
   195  func main() {
   196  	goarch := os.Getenv("GOARCH")
   197  	if goarch == "" {
   198  		goarch = runtime.GOARCH
   199  	}
   200  
   201  	version, err := exec.Command("go", "tool", "compile", "-V").Output()
   202  	if err != nil {
   203  		bug()
   204  		fmt.Printf("running go tool compile -V: %v\n", err)
   205  		return
   206  	}
   207  	if strings.Contains(string(version), "framepointer") {
   208  		// Skip this test if GOEXPERIMENT=framepointer
   209  		return
   210  	}
   211  
   212  	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)
   219  
   220  	tests = strings.Replace(tests, "\t", " ", -1)
   221  	tests = commentRE.ReplaceAllString(tests, "")
   222  
   223  	nok := 0
   224  	nfail := 0
   225  TestCases:
   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  
   258  		var gobuf bytes.Buffer
   259  		fmt.Fprintf(&gobuf, "package main\n")
   260  
   261  		var buf bytes.Buffer
   262  		ptrSize := 4
   263  		switch goarch {
   264  		case "mips", "mipsle":
   265  			fmt.Fprintf(&buf, "#define CALL JAL\n#define REGISTER (R0)\n")
   266  		case "mips64", "mips64le":
   267  			ptrSize = 8
   268  			fmt.Fprintf(&buf, "#define CALL JAL\n#define REGISTER (R0)\n")
   269  		case "ppc64", "ppc64le":
   270  			ptrSize = 8
   271  			fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (CTR)\n")
   272  		case "arm":
   273  			fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n")
   274  		case "arm64":
   275  			ptrSize = 8
   276  			fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n")
   277  		case "amd64":
   278  			ptrSize = 8
   279  			fmt.Fprintf(&buf, "#define REGISTER AX\n")
   280  		case "s390x":
   281  			ptrSize = 8
   282  			fmt.Fprintf(&buf, "#define REGISTER R10\n")
   283  		default:
   284  			fmt.Fprintf(&buf, "#define REGISTER AX\n")
   285  		}
   286  
   287  		for _, line := range strings.Split(lines, "\n") {
   288  			line = strings.TrimSpace(line)
   289  			if line == "" {
   290  				continue
   291  			}
   292  			for i, subline := range strings.Split(line, ";") {
   293  				subline = strings.TrimSpace(subline)
   294  				if subline == "" {
   295  					continue
   296  				}
   297  				m := lineRE.FindStringSubmatch(subline)
   298  				if m == nil {
   299  					bug()
   300  					fmt.Printf("invalid function line: %s\n", subline)
   301  					continue TestCases
   302  				}
   303  				name := m[1]
   304  				size, _ := strconv.Atoi(m[2])
   305  
   306  				// The limit was originally 128 but is now 592.
   307  				// Instead of rewriting the test cases above, adjust
   308  				// the first stack frame to use up the extra bytes.
   309  				if i == 0 {
   310  					size += (880 - 128) - 128
   311  					// Noopt builds have a larger stackguard.
   312  					// See ../src/cmd/dist/buildruntime.go:stackGuardMultiplier
   313  					// This increase is included in obj.StackGuard
   314  					for _, s := range strings.Split(os.Getenv("GO_GCFLAGS"), " ") {
   315  						if s == "-N" {
   316  							size += 880
   317  						}
   318  					}
   319  				}
   320  
   321  				if size%ptrSize == 4 || goarch == "arm64" && size != 0 && (size+8)%16 != 0 {
   322  					continue TestCases
   323  				}
   324  				nosplit := m[3]
   325  				body := m[4]
   326  
   327  				if nosplit != "" {
   328  					nosplit = ",7"
   329  				} else {
   330  					nosplit = ",0"
   331  				}
   332  				body = callRE.ReplaceAllString(body, "CALL ·$1(SB);")
   333  				body = callindRE.ReplaceAllString(body, "CALL REGISTER;")
   334  
   335  				fmt.Fprintf(&gobuf, "func %s()\n", name)
   336  				fmt.Fprintf(&buf, "TEXT ·%s(SB)%s,$%d-0\n\t%s\n\tRET\n\n", name, nosplit, size, body)
   337  			}
   338  		}
   339  
   340  		if err := ioutil.WriteFile(filepath.Join(dir, "asm.s"), buf.Bytes(), 0666); err != nil {
   341  			log.Fatal(err)
   342  		}
   343  		if err := ioutil.WriteFile(filepath.Join(dir, "main.go"), gobuf.Bytes(), 0666); err != nil {
   344  			log.Fatal(err)
   345  		}
   346  
   347  		cmd := exec.Command("go", "build")
   348  		cmd.Dir = dir
   349  		output, err := cmd.CombinedOutput()
   350  		if err == nil {
   351  			nok++
   352  			if reject {
   353  				bug()
   354  				fmt.Printf("accepted incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
   355  			}
   356  		} else {
   357  			nfail++
   358  			if !reject {
   359  				bug()
   360  				fmt.Printf("rejected incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
   361  				fmt.Printf("\n\tlinker output:\n\t%s\n", indent(string(output)))
   362  			}
   363  		}
   364  	}
   365  
   366  	if !bugged && (nok == 0 || nfail == 0) {
   367  		bug()
   368  		fmt.Printf("not enough test cases run\n")
   369  	}
   370  }
   371  
   372  func indent(s string) string {
   373  	return strings.Replace(s, "\n", "\n\t", -1)
   374  }
   375  
   376  var bugged = false
   377  
   378  func bug() {
   379  	if !bugged {
   380  		bugged = true
   381  		fmt.Printf("BUG\n")
   382  	}
   383  }