github.com/huandu/go@v0.0.0-20151114150818-04e615e41150/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  	version, err := exec.Command("go", "tool", "compile", "-V").Output()
   188  	if err != nil {
   189  		bug()
   190  		fmt.Printf("running go tool compile -V: %v\n", err)
   191  		return
   192  	}
   193  	if strings.Contains(string(version), "framepointer") {
   194  		// Skip this test if GOEXPERIMENT=framepointer
   195  		return
   196  	}
   197  
   198  	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)
   205  
   206  	tests = strings.Replace(tests, "\t", " ", -1)
   207  	tests = commentRE.ReplaceAllString(tests, "")
   208  
   209  	nok := 0
   210  	nfail := 0
   211  TestCases:
   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  
   244  		var gobuf bytes.Buffer
   245  		fmt.Fprintf(&gobuf, "package main\n")
   246  
   247  		var buf bytes.Buffer
   248  		ptrSize := 4
   249  		switch goarch {
   250  		case "ppc64", "ppc64le":
   251  			ptrSize = 8
   252  			fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (CTR)\n")
   253  		case "arm":
   254  			fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n")
   255  		case "arm64":
   256  			ptrSize = 8
   257  			fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n")
   258  		case "amd64":
   259  			ptrSize = 8
   260  			fmt.Fprintf(&buf, "#define REGISTER AX\n")
   261  		default:
   262  			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  			}
   270  			for i, subline := range strings.Split(line, ";") {
   271  				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])
   283  
   284  				// The limit was originally 128 but is now 512.
   285  				// Instead of rewriting the test cases above, adjust
   286  				// the first stack frame to use up the extra bytes.
   287  				if i == 0 {
   288  					size += 512 - 128
   289  					// 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  					}
   296  				}
   297  
   298  				if size%ptrSize == 4 || goarch == "arm64" && size != 0 && (size+8)%16 != 0 {
   299  					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  
   312  				fmt.Fprintf(&gobuf, "func %s()\n", name)
   313  				fmt.Fprintf(&buf, "TEXT ·%s(SB)%s,$%d-0\n\t%s\n\tRET\n\n", name, nosplit, size, body)
   314  			}
   315  		}
   316  
   317  		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  		}
   323  
   324  		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  
   349  func indent(s string) string {
   350  	return strings.Replace(s, "\n", "\n\t", -1)
   351  }
   352  
   353  var bugged = false
   354  
   355  func bug() {
   356  	if !bugged {
   357  		bugged = true
   358  		fmt.Printf("BUG\n")
   359  	}
   360  }