github.com/razvanm/vanadium-go-1.3@v0.0.0-20160721203343-4a65068e5915/test/nosplit.go (about)

     1  // run
     2  
     3  // +build !nacl
     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  var tests = `
    26  # These are test cases for the linker analysis that detects chains of
    27  # nosplit functions that would cause a stack overflow.
    28  #
    29  # Lines beginning with # are comments.
    30  #
    31  # Each test case describes a sequence of functions, one per line.
    32  # Each function definition is the function name, then the frame size,
    33  # then optionally the keyword 'nosplit', then the body of the function.
    34  # The body is assembly code, with some shorthands.
    35  # The shorthand 'call x' stands for CALL x(SB).
    36  # The shorthand 'callind' stands for 'CALL R0', where R0 is a register.
    37  # Each test case must define a function named main, and it must be first.
    38  # That is, a line beginning "main " indicates the start of a new test case.
    39  # Within a stanza, ; can be used instead of \n to separate lines.
    40  #
    41  # After the function definition, the test case ends with an optional
    42  # REJECT line, specifying the architectures on which the case should
    43  # be rejected. "REJECT" without any architectures means reject on all architectures.
    44  # The linker should accept the test case on systems not explicitly rejected.
    45  #
    46  # 64-bit systems do not attempt to execute test cases with frame sizes
    47  # that are only 32-bit aligned.
    48  
    49  # Ordinary function should work
    50  main 0
    51  
    52  # Large frame marked nosplit is always wrong.
    53  main 10000 nosplit
    54  REJECT
    55  
    56  # Calling a large frame is okay.
    57  main 0 call big
    58  big 10000
    59  
    60  # But not if the frame is nosplit.
    61  main 0 call big
    62  big 10000 nosplit
    63  REJECT
    64  
    65  # Recursion is okay.
    66  main 0 call main
    67  
    68  # Recursive nosplit runs out of space.
    69  main 0 nosplit call main
    70  REJECT
    71  
    72  # Chains of ordinary functions okay.
    73  main 0 call f1
    74  f1 80 call f2
    75  f2 80
    76  
    77  # Chains of nosplit must fit in the stack limit, 128 bytes.
    78  main 0 call f1
    79  f1 80 nosplit call f2
    80  f2 80 nosplit
    81  REJECT
    82  
    83  # Larger chains.
    84  main 0 call f1
    85  f1 16 call f2
    86  f2 16 call f3
    87  f3 16 call f4
    88  f4 16 call f5
    89  f5 16 call f6
    90  f6 16 call f7
    91  f7 16 call f8
    92  f8 16 call end
    93  end 1000
    94  
    95  main 0 call f1
    96  f1 16 nosplit call f2
    97  f2 16 nosplit call f3
    98  f3 16 nosplit call f4
    99  f4 16 nosplit call f5
   100  f5 16 nosplit call f6
   101  f6 16 nosplit call f7
   102  f7 16 nosplit call f8
   103  f8 16 nosplit call end
   104  end 1000
   105  REJECT
   106  
   107  # Test cases near the 128-byte limit.
   108  
   109  # Ordinary stack split frame is always okay.
   110  main 112
   111  main 116
   112  main 120
   113  main 124
   114  main 128
   115  main 132
   116  main 136
   117  
   118  # A nosplit leaf can use the whole 128-CallSize bytes available on entry.
   119  main 112 nosplit
   120  main 116 nosplit
   121  main 120 nosplit
   122  main 124 nosplit
   123  main 128 nosplit; REJECT
   124  main 132 nosplit; REJECT
   125  main 136 nosplit; REJECT
   126  
   127  # Calling a nosplit function from a nosplit function requires
   128  # having room for the saved caller PC and the called frame.
   129  # Because ARM doesn't save LR in the leaf, it gets an extra 4 bytes.
   130  main 112 nosplit call f; f 0 nosplit
   131  main 116 nosplit call f; f 0 nosplit; REJECT amd64
   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. Again the ARM works
   141  # 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  	dir, err := ioutil.TempDir("", "go-test-nosplit")
   188  	if err != nil {
   189  		bug()
   190  		fmt.Printf("creating temp dir: %v\n", err)
   191  		return
   192  	}
   193  	defer os.RemoveAll(dir)
   194  
   195  	tests = strings.Replace(tests, "\t", " ", -1)
   196  	tests = commentRE.ReplaceAllString(tests, "")
   197  
   198  	nok := 0
   199  	nfail := 0
   200  TestCases:
   201  	for len(tests) > 0 {
   202  		var stanza string
   203  		i := strings.Index(tests, "\nmain ")
   204  		if i < 0 {
   205  			stanza, tests = tests, ""
   206  		} else {
   207  			stanza, tests = tests[:i], tests[i+1:]
   208  		}
   209  
   210  		m := rejectRE.FindStringSubmatch(stanza)
   211  		if m == nil {
   212  			bug()
   213  			fmt.Printf("invalid stanza:\n\t%s\n", indent(stanza))
   214  			continue
   215  		}
   216  		lines := strings.TrimSpace(m[1])
   217  		reject := false
   218  		if m[2] != "" {
   219  			if strings.TrimSpace(m[4]) == "" {
   220  				reject = true
   221  			} else {
   222  				for _, rej := range strings.Fields(m[4]) {
   223  					if rej == goarch {
   224  						reject = true
   225  					}
   226  				}
   227  			}
   228  		}
   229  		if lines == "" && !reject {
   230  			continue
   231  		}
   232  
   233  		var gobuf bytes.Buffer
   234  		fmt.Fprintf(&gobuf, "package main\n")
   235  
   236  		var buf bytes.Buffer
   237  		if goarch == "arm" {
   238  			fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n")
   239  		} else {
   240  			fmt.Fprintf(&buf, "#define REGISTER AX\n")
   241  		}
   242  
   243  		for _, line := range strings.Split(lines, "\n") {
   244  			line = strings.TrimSpace(line)
   245  			if line == "" {
   246  				continue
   247  			}
   248  			for i, subline := range strings.Split(line, ";") {
   249  				subline = strings.TrimSpace(subline)
   250  				if subline == "" {
   251  					continue
   252  				}
   253  				m := lineRE.FindStringSubmatch(subline)
   254  				if m == nil {
   255  					bug()
   256  					fmt.Printf("invalid function line: %s\n", subline)
   257  					continue TestCases
   258  				}
   259  				name := m[1]
   260  				size, _ := strconv.Atoi(m[2])
   261  
   262  				// The limit was originally 128 but is now 384.
   263  				// Instead of rewriting the test cases above, adjust
   264  				// the first stack frame to use up the extra 32 bytes.
   265  				if i == 0 {
   266  					size += 384 - 128
   267  				}
   268  
   269  				if goarch == "amd64" && size%8 == 4 {
   270  					continue TestCases
   271  				}
   272  				nosplit := m[3]
   273  				body := m[4]
   274  
   275  				if nosplit != "" {
   276  					nosplit = ",7"
   277  				} else {
   278  					nosplit = ",0"
   279  				}
   280  				body = callRE.ReplaceAllString(body, "CALL ·$1(SB);")
   281  				body = callindRE.ReplaceAllString(body, "CALL REGISTER;")
   282  
   283  				fmt.Fprintf(&gobuf, "func %s()\n", name)
   284  				fmt.Fprintf(&buf, "TEXT ·%s(SB)%s,$%d-0\n\t%s\n\tRET\n\n", name, nosplit, size, body)
   285  			}
   286  		}
   287  
   288  		if err := ioutil.WriteFile(filepath.Join(dir, "asm.s"), buf.Bytes(), 0666); err != nil {
   289  			log.Fatal(err)
   290  		}
   291  		if err := ioutil.WriteFile(filepath.Join(dir, "main.go"), gobuf.Bytes(), 0666); err != nil {
   292  			log.Fatal(err)
   293  		}
   294  
   295  		cmd := exec.Command("go", "build")
   296  		cmd.Dir = dir
   297  		output, err := cmd.CombinedOutput()
   298  		if err == nil {
   299  			nok++
   300  			if reject {
   301  				bug()
   302  				fmt.Printf("accepted incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
   303  			}
   304  		} else {
   305  			nfail++
   306  			if !reject {
   307  				bug()
   308  				fmt.Printf("rejected incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
   309  				fmt.Printf("\n\tlinker output:\n\t%s\n", indent(string(output)))
   310  			}
   311  		}
   312  	}
   313  
   314  	if !bugged && (nok == 0 || nfail == 0) {
   315  		bug()
   316  		fmt.Printf("not enough test cases run\n")
   317  	}
   318  }
   319  
   320  func indent(s string) string {
   321  	return strings.Replace(s, "\n", "\n\t", -1)
   322  }
   323  
   324  var bugged = false
   325  
   326  func bug() {
   327  	if !bugged {
   328  		bugged = true
   329  		fmt.Printf("BUG\n")
   330  	}
   331  }