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  }