github.com/brownsys/tracing-framework-go@v0.0.0-20161210174012-0542a62412fe/go/darwin_amd64/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 "mips64", "mips64le":
   265  			ptrSize = 8
   266  			fmt.Fprintf(&buf, "#define CALL JAL\n#define REGISTER (R0)\n")
   267  		case "ppc64", "ppc64le":
   268  			ptrSize = 8
   269  			fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (CTR)\n")
   270  		case "arm":
   271  			fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n")
   272  		case "arm64":
   273  			ptrSize = 8
   274  			fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n")
   275  		case "amd64":
   276  			ptrSize = 8
   277  			fmt.Fprintf(&buf, "#define REGISTER AX\n")
   278  		case "s390x":
   279  			ptrSize = 8
   280  			fmt.Fprintf(&buf, "#define REGISTER R10\n")
   281  		default:
   282  			fmt.Fprintf(&buf, "#define REGISTER AX\n")
   283  		}
   284  
   285  		for _, line := range strings.Split(lines, "\n") {
   286  			line = strings.TrimSpace(line)
   287  			if line == "" {
   288  				continue
   289  			}
   290  			for i, subline := range strings.Split(line, ";") {
   291  				subline = strings.TrimSpace(subline)
   292  				if subline == "" {
   293  					continue
   294  				}
   295  				m := lineRE.FindStringSubmatch(subline)
   296  				if m == nil {
   297  					bug()
   298  					fmt.Printf("invalid function line: %s\n", subline)
   299  					continue TestCases
   300  				}
   301  				name := m[1]
   302  				size, _ := strconv.Atoi(m[2])
   303  
   304  				// The limit was originally 128 but is now 592.
   305  				// Instead of rewriting the test cases above, adjust
   306  				// the first stack frame to use up the extra bytes.
   307  				if i == 0 {
   308  					size += (720 - 128) - 128
   309  					// Noopt builds have a larger stackguard.
   310  					// See ../src/cmd/dist/buildruntime.go:stackGuardMultiplier
   311  					// This increase is included in obj.StackGuard
   312  					for _, s := range strings.Split(os.Getenv("GO_GCFLAGS"), " ") {
   313  						if s == "-N" {
   314  							size += 720
   315  						}
   316  					}
   317  				}
   318  
   319  				if size%ptrSize == 4 || goarch == "arm64" && size != 0 && (size+8)%16 != 0 {
   320  					continue TestCases
   321  				}
   322  				nosplit := m[3]
   323  				body := m[4]
   324  
   325  				if nosplit != "" {
   326  					nosplit = ",7"
   327  				} else {
   328  					nosplit = ",0"
   329  				}
   330  				body = callRE.ReplaceAllString(body, "CALL ·$1(SB);")
   331  				body = callindRE.ReplaceAllString(body, "CALL REGISTER;")
   332  
   333  				fmt.Fprintf(&gobuf, "func %s()\n", name)
   334  				fmt.Fprintf(&buf, "TEXT ·%s(SB)%s,$%d-0\n\t%s\n\tRET\n\n", name, nosplit, size, body)
   335  			}
   336  		}
   337  
   338  		if err := ioutil.WriteFile(filepath.Join(dir, "asm.s"), buf.Bytes(), 0666); err != nil {
   339  			log.Fatal(err)
   340  		}
   341  		if err := ioutil.WriteFile(filepath.Join(dir, "main.go"), gobuf.Bytes(), 0666); err != nil {
   342  			log.Fatal(err)
   343  		}
   344  
   345  		cmd := exec.Command("go", "build")
   346  		cmd.Dir = dir
   347  		output, err := cmd.CombinedOutput()
   348  		if err == nil {
   349  			nok++
   350  			if reject {
   351  				bug()
   352  				fmt.Printf("accepted incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
   353  			}
   354  		} else {
   355  			nfail++
   356  			if !reject {
   357  				bug()
   358  				fmt.Printf("rejected incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
   359  				fmt.Printf("\n\tlinker output:\n\t%s\n", indent(string(output)))
   360  			}
   361  		}
   362  	}
   363  
   364  	if !bugged && (nok == 0 || nfail == 0) {
   365  		bug()
   366  		fmt.Printf("not enough test cases run\n")
   367  	}
   368  }
   369  
   370  func indent(s string) string {
   371  	return strings.Replace(s, "\n", "\n\t", -1)
   372  }
   373  
   374  var bugged = false
   375  
   376  func bug() {
   377  	if !bugged {
   378  		bugged = true
   379  		fmt.Printf("BUG\n")
   380  	}
   381  }