Source file test/nosplit.go

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

View as plain text