Source file test/nosplit.go

     1  // +build !nacl,!js,!aix,!gcflags_noopt,gc
     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  const debug = false
    25  
    26  var tests = `
    27  # These are test cases for the linker analysis that detects chains of
    28  # nosplit functions that would cause a stack overflow.
    29  #
    30  # Lines beginning with # are comments.
    31  #
    32  # Each test case describes a sequence of functions, one per line.
    33  # Each function definition is the function name, then the frame size,
    34  # then optionally the keyword 'nosplit', then the body of the function.
    35  # The body is assembly code, with some shorthands.
    36  # The shorthand 'call x' stands for CALL x(SB).
    37  # The shorthand 'callind' stands for 'CALL R0', where R0 is a register.
    38  # Each test case must define a function named start, and it must be first.
    39  # That is, a line beginning "start " indicates the start of a new test case.
    40  # Within a stanza, ; can be used instead of \n to separate lines.
    41  #
    42  # After the function definition, the test case ends with an optional
    43  # REJECT line, specifying the architectures on which the case should
    44  # be rejected. "REJECT" without any architectures means reject on all architectures.
    45  # The linker should accept the test case on systems not explicitly rejected.
    46  #
    47  # 64-bit systems do not attempt to execute test cases with frame sizes
    48  # that are only 32-bit aligned.
    49  
    50  # Ordinary function should work
    51  start 0
    52  
    53  # Large frame marked nosplit is always wrong.
    54  start 10000 nosplit
    55  REJECT
    56  
    57  # Calling a large frame is okay.
    58  start 0 call big
    59  big 10000
    60  
    61  # But not if the frame is nosplit.
    62  start 0 call big
    63  big 10000 nosplit
    64  REJECT
    65  
    66  # Recursion is okay.
    67  start 0 call start
    68  
    69  # Recursive nosplit runs out of space.
    70  start 0 nosplit call start
    71  REJECT
    72  
    73  # Chains of ordinary functions okay.
    74  start 0 call f1
    75  f1 80 call f2
    76  f2 80
    77  
    78  # Chains of nosplit must fit in the stack limit, 128 bytes.
    79  start 0 call f1
    80  f1 80 nosplit call f2
    81  f2 80 nosplit
    82  REJECT
    83  
    84  # Larger chains.
    85  start 0 call f1
    86  f1 16 call f2
    87  f2 16 call f3
    88  f3 16 call f4
    89  f4 16 call f5
    90  f5 16 call f6
    91  f6 16 call f7
    92  f7 16 call f8
    93  f8 16 call end
    94  end 1000
    95  
    96  start 0 call f1
    97  f1 16 nosplit call f2
    98  f2 16 nosplit call f3
    99  f3 16 nosplit call f4
   100  f4 16 nosplit call f5
   101  f5 16 nosplit call f6
   102  f6 16 nosplit call f7
   103  f7 16 nosplit call f8
   104  f8 16 nosplit call end
   105  end 1000
   106  REJECT
   107  
   108  # Test cases near the 128-byte limit.
   109  
   110  # Ordinary stack split frame is always okay.
   111  start 112
   112  start 116
   113  start 120
   114  start 124
   115  start 128
   116  start 132
   117  start 136
   118  
   119  # A nosplit leaf can use the whole 128-CallSize bytes available on entry.
   120  # (CallSize is 32 on ppc64, 8 on amd64 for frame pointer.)
   121  start 96 nosplit
   122  start 100 nosplit; REJECT ppc64 ppc64le
   123  start 104 nosplit; REJECT ppc64 ppc64le arm64
   124  start 108 nosplit; REJECT ppc64 ppc64le
   125  start 112 nosplit; REJECT ppc64 ppc64le arm64
   126  start 116 nosplit; REJECT ppc64 ppc64le
   127  start 120 nosplit; REJECT ppc64 ppc64le amd64 arm64
   128  start 124 nosplit; REJECT ppc64 ppc64le amd64
   129  start 128 nosplit; REJECT
   130  start 132 nosplit; REJECT
   131  start 136 nosplit; REJECT
   132  
   133  # Calling a nosplit function from a nosplit function requires
   134  # having room for the saved caller PC and the called frame.
   135  # Because ARM doesn't save LR in the leaf, it gets an extra 4 bytes.
   136  # Because arm64 doesn't save LR in the leaf, it gets an extra 8 bytes.
   137  # ppc64 doesn't save LR in the leaf, but CallSize is 32, so it gets 24 bytes.
   138  # Because AMD64 uses frame pointer, it has 8 fewer bytes.
   139  start 96 nosplit call f; f 0 nosplit
   140  start 100 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
   141  start 104 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le arm64
   142  start 108 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
   143  start 112 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64 arm64
   144  start 116 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64
   145  start 120 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64 arm64
   146  start 124 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64 386
   147  start 128 nosplit call f; f 0 nosplit; REJECT
   148  start 132 nosplit call f; f 0 nosplit; REJECT
   149  start 136 nosplit call f; f 0 nosplit; REJECT
   150  
   151  # Calling a splitting function from a nosplit function requires
   152  # having room for the saved caller PC of the call but also the
   153  # saved caller PC for the call to morestack.
   154  # Architectures differ in the same way as before.
   155  start 96 nosplit call f; f 0 call f
   156  start 100 nosplit call f; f 0 call f; REJECT ppc64 ppc64le
   157  start 104 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 arm64
   158  start 108 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
   159  start 112 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 arm64
   160  start 116 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
   161  start 120 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 386 arm64
   162  start 124 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 386
   163  start 128 nosplit call f; f 0 call f; REJECT
   164  start 132 nosplit call f; f 0 call f; REJECT
   165  start 136 nosplit call f; f 0 call f; REJECT
   166  
   167  # Indirect calls are assumed to be splitting functions.
   168  start 96 nosplit callind
   169  start 100 nosplit callind; REJECT ppc64 ppc64le
   170  start 104 nosplit callind; REJECT ppc64 ppc64le amd64 arm64
   171  start 108 nosplit callind; REJECT ppc64 ppc64le amd64
   172  start 112 nosplit callind; REJECT ppc64 ppc64le amd64 arm64
   173  start 116 nosplit callind; REJECT ppc64 ppc64le amd64
   174  start 120 nosplit callind; REJECT ppc64 ppc64le amd64 386 arm64
   175  start 124 nosplit callind; REJECT ppc64 ppc64le amd64 386
   176  start 128 nosplit callind; REJECT
   177  start 132 nosplit callind; REJECT
   178  start 136 nosplit callind; REJECT
   179  
   180  # Issue 7623
   181  start 0 call f; f 112
   182  start 0 call f; f 116
   183  start 0 call f; f 120
   184  start 0 call f; f 124
   185  start 0 call f; f 128
   186  start 0 call f; f 132
   187  start 0 call f; f 136
   188  `
   189  
   190  var (
   191  	commentRE = regexp.MustCompile(`(?m)^#.*`)
   192  	rejectRE  = regexp.MustCompile(`(?s)\A(.+?)((\n|; *)REJECT(.*))?\z`)
   193  	lineRE    = regexp.MustCompile(`(\w+) (\d+)( nosplit)?(.*)`)
   194  	callRE    = regexp.MustCompile(`\bcall (\w+)\b`)
   195  	callindRE = regexp.MustCompile(`\bcallind\b`)
   196  )
   197  
   198  func main() {
   199  	goarch := os.Getenv("GOARCH")
   200  	if goarch == "" {
   201  		goarch = runtime.GOARCH
   202  	}
   203  
   204  	dir, err := ioutil.TempDir("", "go-test-nosplit")
   205  	if err != nil {
   206  		bug()
   207  		fmt.Printf("creating temp dir: %v\n", err)
   208  		return
   209  	}
   210  	defer os.RemoveAll(dir)
   211  	os.Setenv("GOPATH", filepath.Join(dir, "_gopath"))
   212  
   213  	if err := ioutil.WriteFile(filepath.Join(dir, "go.mod"), []byte("module go-test-nosplit\n"), 0666); err != nil {
   214  		log.Panic(err)
   215  	}
   216  
   217  	tests = strings.Replace(tests, "\t", " ", -1)
   218  	tests = commentRE.ReplaceAllString(tests, "")
   219  
   220  	nok := 0
   221  	nfail := 0
   222  TestCases:
   223  	for len(tests) > 0 {
   224  		var stanza string
   225  		i := strings.Index(tests, "\nstart ")
   226  		if i < 0 {
   227  			stanza, tests = tests, ""
   228  		} else {
   229  			stanza, tests = tests[:i], tests[i+1:]
   230  		}
   231  
   232  		m := rejectRE.FindStringSubmatch(stanza)
   233  		if m == nil {
   234  			bug()
   235  			fmt.Printf("invalid stanza:\n\t%s\n", indent(stanza))
   236  			continue
   237  		}
   238  		lines := strings.TrimSpace(m[1])
   239  		reject := false
   240  		if m[2] != "" {
   241  			if strings.TrimSpace(m[4]) == "" {
   242  				reject = true
   243  			} else {
   244  				for _, rej := range strings.Fields(m[4]) {
   245  					if rej == goarch {
   246  						reject = true
   247  					}
   248  				}
   249  			}
   250  		}
   251  		if lines == "" && !reject {
   252  			continue
   253  		}
   254  
   255  		var gobuf bytes.Buffer
   256  		fmt.Fprintf(&gobuf, "package main\n")
   257  
   258  		var buf bytes.Buffer
   259  		ptrSize := 4
   260  		switch goarch {
   261  		case "mips", "mipsle":
   262  			fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
   263  		case "mips64", "mips64le":
   264  			ptrSize = 8
   265  			fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
   266  		case "ppc64", "ppc64le":
   267  			ptrSize = 8
   268  			fmt.Fprintf(&buf, "#define REGISTER (CTR)\n")
   269  		case "arm":
   270  			fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
   271  		case "arm64":
   272  			ptrSize = 8
   273  			fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
   274  		case "amd64":
   275  			ptrSize = 8
   276  			fmt.Fprintf(&buf, "#define REGISTER AX\n")
   277  		case "riscv64":
   278  			ptrSize = 8
   279  			fmt.Fprintf(&buf, "#define REGISTER A0\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  		// Since all of the functions we're generating are
   288  		// ABI0, first enter ABI0 via a splittable function
   289  		// and then go to the chain we're testing. This way we
   290  		// don't have to account for ABI wrappers in the chain.
   291  		fmt.Fprintf(&gobuf, "func main0()\n")
   292  		fmt.Fprintf(&gobuf, "func main() { main0() }\n")
   293  		fmt.Fprintf(&buf, "TEXT ·main0(SB),0,$0-0\n\tCALL ·start(SB)\n")
   294  
   295  		for _, line := range strings.Split(lines, "\n") {
   296  			line = strings.TrimSpace(line)
   297  			if line == "" {
   298  				continue
   299  			}
   300  			for i, subline := range strings.Split(line, ";") {
   301  				subline = strings.TrimSpace(subline)
   302  				if subline == "" {
   303  					continue
   304  				}
   305  				m := lineRE.FindStringSubmatch(subline)
   306  				if m == nil {
   307  					bug()
   308  					fmt.Printf("invalid function line: %s\n", subline)
   309  					continue TestCases
   310  				}
   311  				name := m[1]
   312  				size, _ := strconv.Atoi(m[2])
   313  
   314  				// The limit was originally 128 but is now 800 (928-128).
   315  				// Instead of rewriting the test cases above, adjust
   316  				// the first stack frame to use up the extra bytes.
   317  				if i == 0 {
   318  					size += (928 - 128) - 128
   319  					// Noopt builds have a larger stackguard.
   320  					// See ../src/cmd/dist/buildruntime.go:stackGuardMultiplier
   321  					// This increase is included in objabi.StackGuard
   322  					for _, s := range strings.Split(os.Getenv("GO_GCFLAGS"), " ") {
   323  						if s == "-N" {
   324  							size += 928
   325  						}
   326  					}
   327  				}
   328  
   329  				if size%ptrSize == 4 {
   330  					continue TestCases
   331  				}
   332  				nosplit := m[3]
   333  				body := m[4]
   334  
   335  				if nosplit != "" {
   336  					nosplit = ",7"
   337  				} else {
   338  					nosplit = ",0"
   339  				}
   340  				body = callRE.ReplaceAllString(body, "CALL ·$1(SB);")
   341  				body = callindRE.ReplaceAllString(body, "CALL REGISTER;")
   342  
   343  				fmt.Fprintf(&gobuf, "func %s()\n", name)
   344  				fmt.Fprintf(&buf, "TEXT ·%s(SB)%s,$%d-0\n\t%s\n\tRET\n\n", name, nosplit, size, body)
   345  			}
   346  		}
   347  
   348  		if debug {
   349  			fmt.Printf("===\n%s\n", strings.TrimSpace(stanza))
   350  			fmt.Printf("-- main.go --\n%s", gobuf.String())
   351  			fmt.Printf("-- asm.s --\n%s", buf.String())
   352  		}
   353  
   354  		if err := ioutil.WriteFile(filepath.Join(dir, "asm.s"), buf.Bytes(), 0666); err != nil {
   355  			log.Fatal(err)
   356  		}
   357  		if err := ioutil.WriteFile(filepath.Join(dir, "main.go"), gobuf.Bytes(), 0666); err != nil {
   358  			log.Fatal(err)
   359  		}
   360  
   361  		cmd := exec.Command("go", "build")
   362  		cmd.Dir = dir
   363  		output, err := cmd.CombinedOutput()
   364  		if err == nil {
   365  			nok++
   366  			if reject {
   367  				bug()
   368  				fmt.Printf("accepted incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
   369  			}
   370  		} else {
   371  			nfail++
   372  			if !reject {
   373  				bug()
   374  				fmt.Printf("rejected incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
   375  				fmt.Printf("\n\tlinker output:\n\t%s\n", indent(string(output)))
   376  			}
   377  		}
   378  	}
   379  
   380  	if !bugged && (nok == 0 || nfail == 0) {
   381  		bug()
   382  		fmt.Printf("not enough test cases run\n")
   383  	}
   384  }
   385  
   386  func indent(s string) string {
   387  	return strings.Replace(s, "\n", "\n\t", -1)
   388  }
   389  
   390  var bugged = false
   391  
   392  func bug() {
   393  	if !bugged {
   394  		bugged = true
   395  		fmt.Printf("BUG\n")
   396  	}
   397  }
   398  

View as plain text