github.com/flyinox/gosm@v0.0.0-20171117061539-16768cb62077/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  	// Frame pointer is on by default now.
   202  	// golang.org/issue/18317.
   203  	return
   204  
   205  	version, err := exec.Command("go", "tool", "compile", "-V").Output()
   206  	if err != nil {
   207  		bug()
   208  		fmt.Printf("running go tool compile -V: %v\n", err)
   209  		return
   210  	}
   211  	if strings.Contains(string(version), "framepointer") {
   212  		// Skip this test if GOEXPERIMENT=framepointer
   213  		return
   214  	}
   215  
   216  	dir, err := ioutil.TempDir("", "go-test-nosplit")
   217  	if err != nil {
   218  		bug()
   219  		fmt.Printf("creating temp dir: %v\n", err)
   220  		return
   221  	}
   222  	defer os.RemoveAll(dir)
   223  
   224  	tests = strings.Replace(tests, "\t", " ", -1)
   225  	tests = commentRE.ReplaceAllString(tests, "")
   226  
   227  	nok := 0
   228  	nfail := 0
   229  TestCases:
   230  	for len(tests) > 0 {
   231  		var stanza string
   232  		i := strings.Index(tests, "\nmain ")
   233  		if i < 0 {
   234  			stanza, tests = tests, ""
   235  		} else {
   236  			stanza, tests = tests[:i], tests[i+1:]
   237  		}
   238  
   239  		m := rejectRE.FindStringSubmatch(stanza)
   240  		if m == nil {
   241  			bug()
   242  			fmt.Printf("invalid stanza:\n\t%s\n", indent(stanza))
   243  			continue
   244  		}
   245  		lines := strings.TrimSpace(m[1])
   246  		reject := false
   247  		if m[2] != "" {
   248  			if strings.TrimSpace(m[4]) == "" {
   249  				reject = true
   250  			} else {
   251  				for _, rej := range strings.Fields(m[4]) {
   252  					if rej == goarch {
   253  						reject = true
   254  					}
   255  				}
   256  			}
   257  		}
   258  		if lines == "" && !reject {
   259  			continue
   260  		}
   261  
   262  		var gobuf bytes.Buffer
   263  		fmt.Fprintf(&gobuf, "package main\n")
   264  
   265  		var buf bytes.Buffer
   266  		ptrSize := 4
   267  		switch goarch {
   268  		case "mips", "mipsle":
   269  			fmt.Fprintf(&buf, "#define CALL JAL\n#define REGISTER (R0)\n")
   270  		case "mips64", "mips64le":
   271  			ptrSize = 8
   272  			fmt.Fprintf(&buf, "#define CALL JAL\n#define REGISTER (R0)\n")
   273  		case "ppc64", "ppc64le":
   274  			ptrSize = 8
   275  			fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (CTR)\n")
   276  		case "arm":
   277  			fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n")
   278  		case "arm64":
   279  			ptrSize = 8
   280  			fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n")
   281  		case "amd64":
   282  			ptrSize = 8
   283  			fmt.Fprintf(&buf, "#define REGISTER AX\n")
   284  		case "s390x":
   285  			ptrSize = 8
   286  			fmt.Fprintf(&buf, "#define REGISTER R10\n")
   287  		default:
   288  			fmt.Fprintf(&buf, "#define REGISTER AX\n")
   289  		}
   290  
   291  		for _, line := range strings.Split(lines, "\n") {
   292  			line = strings.TrimSpace(line)
   293  			if line == "" {
   294  				continue
   295  			}
   296  			for i, subline := range strings.Split(line, ";") {
   297  				subline = strings.TrimSpace(subline)
   298  				if subline == "" {
   299  					continue
   300  				}
   301  				m := lineRE.FindStringSubmatch(subline)
   302  				if m == nil {
   303  					bug()
   304  					fmt.Printf("invalid function line: %s\n", subline)
   305  					continue TestCases
   306  				}
   307  				name := m[1]
   308  				size, _ := strconv.Atoi(m[2])
   309  
   310  				// The limit was originally 128 but is now 592.
   311  				// Instead of rewriting the test cases above, adjust
   312  				// the first stack frame to use up the extra bytes.
   313  				if i == 0 {
   314  					size += (880 - 128) - 128
   315  					// Noopt builds have a larger stackguard.
   316  					// See ../src/cmd/dist/buildruntime.go:stackGuardMultiplier
   317  					// This increase is included in objabi.StackGuard
   318  					for _, s := range strings.Split(os.Getenv("GO_GCFLAGS"), " ") {
   319  						if s == "-N" {
   320  							size += 880
   321  						}
   322  					}
   323  				}
   324  
   325  				if size%ptrSize == 4 || goarch == "arm64" && size != 0 && (size+8)%16 != 0 {
   326  					continue TestCases
   327  				}
   328  				nosplit := m[3]
   329  				body := m[4]
   330  
   331  				if nosplit != "" {
   332  					nosplit = ",7"
   333  				} else {
   334  					nosplit = ",0"
   335  				}
   336  				body = callRE.ReplaceAllString(body, "CALL ·$1(SB);")
   337  				body = callindRE.ReplaceAllString(body, "CALL REGISTER;")
   338  
   339  				fmt.Fprintf(&gobuf, "func %s()\n", name)
   340  				fmt.Fprintf(&buf, "TEXT ·%s(SB)%s,$%d-0\n\t%s\n\tRET\n\n", name, nosplit, size, body)
   341  			}
   342  		}
   343  
   344  		if err := ioutil.WriteFile(filepath.Join(dir, "asm.s"), buf.Bytes(), 0666); err != nil {
   345  			log.Fatal(err)
   346  		}
   347  		if err := ioutil.WriteFile(filepath.Join(dir, "main.go"), gobuf.Bytes(), 0666); err != nil {
   348  			log.Fatal(err)
   349  		}
   350  
   351  		cmd := exec.Command("go", "build")
   352  		cmd.Dir = dir
   353  		output, err := cmd.CombinedOutput()
   354  		if err == nil {
   355  			nok++
   356  			if reject {
   357  				bug()
   358  				fmt.Printf("accepted incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
   359  			}
   360  		} else {
   361  			nfail++
   362  			if !reject {
   363  				bug()
   364  				fmt.Printf("rejected incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
   365  				fmt.Printf("\n\tlinker output:\n\t%s\n", indent(string(output)))
   366  			}
   367  		}
   368  	}
   369  
   370  	if !bugged && (nok == 0 || nfail == 0) {
   371  		bug()
   372  		fmt.Printf("not enough test cases run\n")
   373  	}
   374  }
   375  
   376  func indent(s string) string {
   377  	return strings.Replace(s, "\n", "\n\t", -1)
   378  }
   379  
   380  var bugged = false
   381  
   382  func bug() {
   383  	if !bugged {
   384  		bugged = true
   385  		fmt.Printf("BUG\n")
   386  	}
   387  }