Source file src/cmd/internal/obj/ppc64/asm_test.go

     1  // Copyright 2020 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package ppc64
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"internal/buildcfg"
    11  	"internal/testenv"
    12  	"math"
    13  	"os"
    14  	"path/filepath"
    15  	"regexp"
    16  	"strconv"
    17  	"strings"
    18  	"testing"
    19  
    20  	"internal/abi"
    21  
    22  	"cmd/internal/obj"
    23  	"cmd/internal/objabi"
    24  )
    25  
    26  var platformEnvs = [][]string{
    27  	{"GOOS=aix", "GOARCH=ppc64"},
    28  	{"GOOS=linux", "GOARCH=ppc64"},
    29  	{"GOOS=linux", "GOARCH=ppc64le"},
    30  }
    31  
    32  const invalidPCAlignSrc = `
    33  TEXT test(SB),0,$0-0
    34  ADD $2, R3
    35  PCALIGN $128
    36  RET
    37  `
    38  
    39  const validPCAlignSrc = `
    40  TEXT test(SB),0,$0-0
    41  ADD $2, R3
    42  PCALIGN $16
    43  MOVD $8, R16
    44  ADD $8, R4
    45  PCALIGN $32
    46  ADD $8, R3
    47  PCALIGN $8
    48  ADD $4, R8
    49  RET
    50  `
    51  
    52  const x64pgm = `
    53  TEXT test(SB),0,$0-0
    54  OR R0, R0
    55  OR R0, R0
    56  OR R0, R0
    57  OR R0, R0
    58  OR R0, R0
    59  OR R0, R0
    60  OR R0, R0
    61  OR R0, R0
    62  OR R0, R0
    63  OR R0, R0
    64  OR R0, R0
    65  OR R0, R0
    66  OR R0, R0
    67  OR R0, R0
    68  OR R0, R0
    69  PNOP
    70  `
    71  const x32pgm = `
    72  TEXT test(SB),0,$0-0
    73  OR R0, R0
    74  OR R0, R0
    75  OR R0, R0
    76  OR R0, R0
    77  OR R0, R0
    78  OR R0, R0
    79  OR R0, R0
    80  PNOP
    81  OR R0, R0
    82  OR R0, R0
    83  OR R0, R0
    84  OR R0, R0
    85  OR R0, R0
    86  OR R0, R0
    87  OR R0, R0
    88  OR R0, R0
    89  `
    90  
    91  const x16pgm = `
    92  TEXT test(SB),0,$0-0
    93  OR R0, R0
    94  OR R0, R0
    95  OR R0, R0
    96  PNOP
    97  OR R0, R0
    98  OR R0, R0
    99  OR R0, R0
   100  OR R0, R0
   101  OR R0, R0
   102  OR R0, R0
   103  OR R0, R0
   104  OR R0, R0
   105  OR R0, R0
   106  OR R0, R0
   107  OR R0, R0
   108  OR R0, R0
   109  `
   110  
   111  const x0pgm = `
   112  TEXT test(SB),0,$0-0
   113  OR R0, R0
   114  OR R0, R0
   115  OR R0, R0
   116  OR R0, R0
   117  PNOP
   118  OR R0, R0
   119  OR R0, R0
   120  OR R0, R0
   121  OR R0, R0
   122  OR R0, R0
   123  OR R0, R0
   124  OR R0, R0
   125  OR R0, R0
   126  OR R0, R0
   127  OR R0, R0
   128  OR R0, R0
   129  `
   130  const x64pgmA64 = `
   131  TEXT test(SB),0,$0-0
   132  OR R0, R0
   133  OR R0, R0
   134  OR R0, R0
   135  OR R0, R0
   136  OR R0, R0
   137  OR R0, R0
   138  OR R0, R0
   139  PNOP
   140  OR R0, R0
   141  OR R0, R0
   142  OR R0, R0
   143  OR R0, R0
   144  OR R0, R0
   145  OR R0, R0
   146  PNOP
   147  `
   148  
   149  const x64pgmA32 = `
   150  TEXT test(SB),0,$0-0
   151  OR R0, R0
   152  OR R0, R0
   153  OR R0, R0
   154  PNOP
   155  OR R0, R0
   156  OR R0, R0
   157  OR R0, R0
   158  OR R0, R0
   159  OR R0, R0
   160  OR R0, R0
   161  OR R0, R0
   162  OR R0, R0
   163  OR R0, R0
   164  OR R0, R0
   165  PNOP
   166  `
   167  
   168  // Test that nops are inserted when crossing 64B boundaries, and
   169  // alignment is adjusted to avoid crossing.
   170  func TestPfxAlign(t *testing.T) {
   171  	testenv.MustHaveGoBuild(t)
   172  
   173  	dir := t.TempDir()
   174  
   175  	pgms := []struct {
   176  		text   []byte
   177  		align  string
   178  		hasNop bool
   179  	}{
   180  		{[]byte(x0pgm), "align=0x0", false},     // No alignment or nop adjustments needed
   181  		{[]byte(x16pgm), "align=0x20", false},   // Increased alignment needed
   182  		{[]byte(x32pgm), "align=0x40", false},   // Worst case alignment needed
   183  		{[]byte(x64pgm), "align=0x0", true},     // 0 aligned is default (16B) alignment
   184  		{[]byte(x64pgmA64), "align=0x40", true}, // extra alignment + nop
   185  		{[]byte(x64pgmA32), "align=0x20", true}, // extra alignment + nop
   186  	}
   187  
   188  	for _, pgm := range pgms {
   189  		tmpfile := filepath.Join(dir, "x.s")
   190  		err := os.WriteFile(tmpfile, pgm.text, 0644)
   191  		if err != nil {
   192  			t.Fatalf("can't write output: %v\n", err)
   193  		}
   194  		cmd := testenv.Command(t, testenv.GoToolPath(t), "tool", "asm", "-S", "-o", filepath.Join(dir, "test.o"), tmpfile)
   195  		cmd.Env = append(os.Environ(), "GOOS=linux", "GOARCH=ppc64le")
   196  		out, err := cmd.CombinedOutput()
   197  		if err != nil {
   198  			t.Errorf("Failed to compile %v: %v\n", pgm, err)
   199  		}
   200  		if !strings.Contains(string(out), pgm.align) {
   201  			t.Errorf("Fatal, misaligned text with prefixed instructions:\n%s", out)
   202  		}
   203  		hasNop := strings.Contains(string(out), "00 00 00 60")
   204  		if hasNop != pgm.hasNop {
   205  			t.Errorf("Fatal, prefixed instruction is missing nop padding:\n%s", out)
   206  		}
   207  	}
   208  }
   209  
   210  // TestLarge generates a very large file to verify that large
   211  // program builds successfully, and branches which exceed the
   212  // range of BC are rewritten to reach.
   213  func TestLarge(t *testing.T) {
   214  	if testing.Short() {
   215  		t.Skip("Skip in short mode")
   216  	}
   217  	testenv.MustHaveGoBuild(t)
   218  
   219  	dir := t.TempDir()
   220  
   221  	// A few interesting test cases for long conditional branch fixups
   222  	tests := []struct {
   223  		jmpinsn     string
   224  		backpattern []string
   225  		fwdpattern  []string
   226  	}{
   227  		// Test the interesting cases of conditional branch rewrites for too-far targets. Simple conditional
   228  		// branches can be made to reach with one JMP insertion, compound conditionals require two.
   229  		//
   230  		// beq <-> bne conversion (insert one jump)
   231  		{"BEQ",
   232  			[]string{``,
   233  				`0x20030 131120\s\(.*\)\tBC\t\$4,\sCR0EQ,\s131128`,
   234  				`0x20034 131124\s\(.*\)\tJMP\t0`},
   235  			[]string{``,
   236  				`0x0000 00000\s\(.*\)\tBC\t\$4,\sCR0EQ,\s8`,
   237  				`0x0004 00004\s\(.*\)\tJMP\t131128`},
   238  		},
   239  		{"BNE",
   240  			[]string{``,
   241  				`0x20030 131120\s\(.*\)\tBC\t\$12,\sCR0EQ,\s131128`,
   242  				`0x20034 131124\s\(.*\)\tJMP\t0`},
   243  			[]string{``,
   244  				`0x0000 00000\s\(.*\)\tBC\t\$12,\sCR0EQ,\s8`,
   245  				`0x0004 00004\s\(.*\)\tJMP\t131128`}},
   246  		// bdnz (BC 16,0,tgt) <-> bdz (BC 18,0,+4) conversion (insert one jump)
   247  		{"BC 16,0,",
   248  			[]string{``,
   249  				`0x20030 131120\s\(.*\)\tBC\t\$18,\sCR0LT,\s131128`,
   250  				`0x20034 131124\s\(.*\)\tJMP\t0`},
   251  			[]string{``,
   252  				`0x0000 00000\s\(.*\)\tBC\t\$18,\sCR0LT,\s8`,
   253  				`0x0004 00004\s\(.*\)\tJMP\t131128`}},
   254  		{"BC 18,0,",
   255  			[]string{``,
   256  				`0x20030 131120\s\(.*\)\tBC\t\$16,\sCR0LT,\s131128`,
   257  				`0x20034 131124\s\(.*\)\tJMP\t0`},
   258  			[]string{``,
   259  				`0x0000 00000\s\(.*\)\tBC\t\$16,\sCR0LT,\s8`,
   260  				`0x0004 00004\s\(.*\)\tJMP\t131128`}},
   261  		// bdnzt (BC 8,0,tgt) <-> bdnzt (BC 8,0,+4) conversion (insert two jumps)
   262  		{"BC 8,0,",
   263  			[]string{``,
   264  				`0x20034 131124\s\(.*\)\tBC\t\$8,\sCR0LT,\s131132`,
   265  				`0x20038 131128\s\(.*\)\tJMP\t131136`,
   266  				`0x2003c 131132\s\(.*\)\tJMP\t0\n`},
   267  			[]string{``,
   268  				`0x0000 00000\s\(.*\)\tBC\t\$8,\sCR0LT,\s8`,
   269  				`0x0004 00004\s\(.*\)\tJMP\t12`,
   270  				`0x0008 00008\s\(.*\)\tJMP\t131136\n`}},
   271  	}
   272  
   273  	for _, test := range tests {
   274  		// generate a very large function
   275  		buf := bytes.NewBuffer(make([]byte, 0, 7000000))
   276  		gen(buf, test.jmpinsn)
   277  
   278  		tmpfile := filepath.Join(dir, "x.s")
   279  		err := os.WriteFile(tmpfile, buf.Bytes(), 0644)
   280  		if err != nil {
   281  			t.Fatalf("can't write output: %v\n", err)
   282  		}
   283  
   284  		// Test on all supported ppc64 platforms
   285  		for _, platenv := range platformEnvs {
   286  			cmd := testenv.Command(t, testenv.GoToolPath(t), "tool", "asm", "-S", "-o", filepath.Join(dir, "test.o"), tmpfile)
   287  			cmd.Env = append(os.Environ(), platenv...)
   288  			out, err := cmd.CombinedOutput()
   289  			if err != nil {
   290  				t.Errorf("Assemble failed (%v): %v, output: %s", platenv, err, out)
   291  			}
   292  			matched, err := regexp.MatchString(strings.Join(test.fwdpattern, "\n\t*"), string(out))
   293  			if err != nil {
   294  				t.Fatal(err)
   295  			}
   296  			if !matched {
   297  				t.Errorf("Failed to detect long forward BC fixup in (%v):%s\n", platenv, out)
   298  			}
   299  			matched, err = regexp.MatchString(strings.Join(test.backpattern, "\n\t*"), string(out))
   300  			if err != nil {
   301  				t.Fatal(err)
   302  			}
   303  			if !matched {
   304  				t.Errorf("Failed to detect long backward BC fixup in (%v):%s\n", platenv, out)
   305  			}
   306  		}
   307  	}
   308  }
   309  
   310  // gen generates a very large program with a very long forward and backwards conditional branch.
   311  func gen(buf *bytes.Buffer, jmpinsn string) {
   312  	fmt.Fprintln(buf, "TEXT f(SB),0,$0-0")
   313  	fmt.Fprintln(buf, "label_start:")
   314  	fmt.Fprintln(buf, jmpinsn, "label_end")
   315  	for i := 0; i < (1<<15 + 10); i++ {
   316  		fmt.Fprintln(buf, "MOVD R0, R1")
   317  	}
   318  	fmt.Fprintln(buf, jmpinsn, "label_start")
   319  	fmt.Fprintln(buf, "label_end:")
   320  	fmt.Fprintln(buf, "MOVD R0, R1")
   321  	fmt.Fprintln(buf, "RET")
   322  }
   323  
   324  // TestPCalign generates two asm files containing the
   325  // PCALIGN directive, to verify correct values are and
   326  // accepted, and incorrect values are flagged in error.
   327  func TestPCalign(t *testing.T) {
   328  	var pattern8 = `0x...8\s.*ADD\s..,\sR8`
   329  	var pattern16 = `0x...[80]\s.*MOVD\s..,\sR16`
   330  	var pattern32 = `0x...0\s.*ADD\s..,\sR3`
   331  
   332  	testenv.MustHaveGoBuild(t)
   333  
   334  	dir := t.TempDir()
   335  
   336  	// generate a test with valid uses of PCALIGN
   337  
   338  	tmpfile := filepath.Join(dir, "x.s")
   339  	err := os.WriteFile(tmpfile, []byte(validPCAlignSrc), 0644)
   340  	if err != nil {
   341  		t.Fatalf("can't write output: %v\n", err)
   342  	}
   343  
   344  	// build generated file without errors and assemble it
   345  	cmd := testenv.Command(t, testenv.GoToolPath(t), "tool", "asm", "-o", filepath.Join(dir, "x.o"), "-S", tmpfile)
   346  	cmd.Env = append(os.Environ(), "GOARCH=ppc64le", "GOOS=linux")
   347  	out, err := cmd.CombinedOutput()
   348  	if err != nil {
   349  		t.Errorf("Build failed: %v, output: %s", err, out)
   350  	}
   351  
   352  	matched, err := regexp.MatchString(pattern8, string(out))
   353  	if err != nil {
   354  		t.Fatal(err)
   355  	}
   356  	if !matched {
   357  		t.Errorf("The 8 byte alignment is not correct: %t, output:%s\n", matched, out)
   358  	}
   359  
   360  	matched, err = regexp.MatchString(pattern16, string(out))
   361  	if err != nil {
   362  		t.Fatal(err)
   363  	}
   364  	if !matched {
   365  		t.Errorf("The 16 byte alignment is not correct: %t, output:%s\n", matched, out)
   366  	}
   367  
   368  	matched, err = regexp.MatchString(pattern32, string(out))
   369  	if err != nil {
   370  		t.Fatal(err)
   371  	}
   372  	if !matched {
   373  		t.Errorf("The 32 byte alignment is not correct: %t, output:%s\n", matched, out)
   374  	}
   375  
   376  	// generate a test with invalid use of PCALIGN
   377  
   378  	tmpfile = filepath.Join(dir, "xi.s")
   379  	err = os.WriteFile(tmpfile, []byte(invalidPCAlignSrc), 0644)
   380  	if err != nil {
   381  		t.Fatalf("can't write output: %v\n", err)
   382  	}
   383  
   384  	// build test with errors and check for messages
   385  	cmd = testenv.Command(t, testenv.GoToolPath(t), "tool", "asm", "-o", filepath.Join(dir, "xi.o"), "-S", tmpfile)
   386  	cmd.Env = append(os.Environ(), "GOARCH=ppc64le", "GOOS=linux")
   387  	out, err = cmd.CombinedOutput()
   388  	if !strings.Contains(string(out), "Unexpected alignment") {
   389  		t.Errorf("Invalid alignment not detected for PCALIGN\n")
   390  	}
   391  }
   392  
   393  // Verify register constants are correctly aligned. Much of the ppc64 assembler assumes masking out significant
   394  // bits will produce a valid register number:
   395  // REG_Rx & 31 == x
   396  // REG_Fx & 31 == x
   397  // REG_Vx & 31 == x
   398  // REG_VSx & 63 == x
   399  // REG_SPRx & 1023 == x
   400  // REG_CRx & 7 == x
   401  //
   402  // VR and FPR disjointly overlap VSR, interpreting as VSR registers should produce the correctly overlapped VSR.
   403  // REG_FPx & 63 == x
   404  // REG_Vx & 63 == x + 32
   405  func TestRegValueAlignment(t *testing.T) {
   406  	tstFunc := func(rstart, rend, msk, rout int) {
   407  		for i := rstart; i <= rend; i++ {
   408  			if i&msk != rout {
   409  				t.Errorf("%v is not aligned to 0x%X (expected %d, got %d)\n", rconv(i), msk, rout, rstart&msk)
   410  			}
   411  			rout++
   412  		}
   413  	}
   414  	var testType = []struct {
   415  		rstart int
   416  		rend   int
   417  		msk    int
   418  		rout   int
   419  	}{
   420  		{REG_VS0, REG_VS63, 63, 0},
   421  		{REG_R0, REG_R31, 31, 0},
   422  		{REG_F0, REG_F31, 31, 0},
   423  		{REG_V0, REG_V31, 31, 0},
   424  		{REG_V0, REG_V31, 63, 32},
   425  		{REG_F0, REG_F31, 63, 0},
   426  		{REG_SPR0, REG_SPR0 + 1023, 1023, 0},
   427  		{REG_CR0, REG_CR7, 7, 0},
   428  		{REG_CR0LT, REG_CR7SO, 31, 0},
   429  	}
   430  	for _, t := range testType {
   431  		tstFunc(t.rstart, t.rend, t.msk, t.rout)
   432  	}
   433  }
   434  
   435  // Verify interesting obj.Addr arguments are classified correctly.
   436  func TestAddrClassifier(t *testing.T) {
   437  	type cmplx struct {
   438  		pic     int
   439  		pic_dyn int
   440  		dyn     int
   441  		nonpic  int
   442  	}
   443  	tsts := [...]struct {
   444  		arg    obj.Addr
   445  		output any
   446  	}{
   447  		// Supported register type args
   448  		{obj.Addr{Type: obj.TYPE_REG, Reg: REG_R1}, C_REG},
   449  		{obj.Addr{Type: obj.TYPE_REG, Reg: REG_R2}, C_REGP},
   450  		{obj.Addr{Type: obj.TYPE_REG, Reg: REG_F1}, C_FREG},
   451  		{obj.Addr{Type: obj.TYPE_REG, Reg: REG_F2}, C_FREGP},
   452  		{obj.Addr{Type: obj.TYPE_REG, Reg: REG_V2}, C_VREG},
   453  		{obj.Addr{Type: obj.TYPE_REG, Reg: REG_VS1}, C_VSREG},
   454  		{obj.Addr{Type: obj.TYPE_REG, Reg: REG_VS2}, C_VSREGP},
   455  		{obj.Addr{Type: obj.TYPE_REG, Reg: REG_CR}, C_CREG},
   456  		{obj.Addr{Type: obj.TYPE_REG, Reg: REG_CR1}, C_CREG},
   457  		{obj.Addr{Type: obj.TYPE_REG, Reg: REG_CR1SO}, C_CRBIT},
   458  		{obj.Addr{Type: obj.TYPE_REG, Reg: REG_SPR0}, C_SPR},
   459  		{obj.Addr{Type: obj.TYPE_REG, Reg: REG_SPR0 + 8}, C_LR},
   460  		{obj.Addr{Type: obj.TYPE_REG, Reg: REG_SPR0 + 9}, C_CTR},
   461  		{obj.Addr{Type: obj.TYPE_REG, Reg: REG_FPSCR}, C_FPSCR},
   462  		{obj.Addr{Type: obj.TYPE_REG, Reg: REG_A1}, C_AREG},
   463  
   464  		// Memory type arguments.
   465  		{obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_GOTREF}, C_ADDR},
   466  		{obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_TOCREF}, C_ADDR},
   467  		{obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: &obj.LSym{Type: objabi.STLSBSS}}, cmplx{C_TLS_IE, C_TLS_IE, C_TLS_LE, C_TLS_LE}},
   468  		{obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: &obj.LSym{Type: objabi.SDATA}}, C_ADDR},
   469  		{obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_AUTO}, C_SOREG},
   470  		{obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_AUTO, Offset: BIG}, C_LOREG},
   471  		{obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_AUTO, Offset: -BIG - 1}, C_LOREG},
   472  		{obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_PARAM}, C_SOREG},
   473  		{obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_PARAM, Offset: BIG}, C_LOREG},
   474  		{obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_PARAM, Offset: -BIG - 33}, C_LOREG}, // 33 is FixedFrameSize-1
   475  		{obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_NONE}, C_ZOREG},
   476  		{obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_NONE, Index: REG_R4}, C_XOREG},
   477  		{obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_NONE, Offset: 1}, C_SOREG},
   478  		{obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_NONE, Offset: BIG}, C_LOREG},
   479  		{obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_NONE, Offset: -BIG - 33}, C_LOREG},
   480  
   481  		// Misc (golang initializes -0.0 to 0.0, hence the obfuscation below)
   482  		{obj.Addr{Type: obj.TYPE_TEXTSIZE}, C_TEXTSIZE},
   483  		{obj.Addr{Type: obj.TYPE_FCONST, Val: 0.0}, C_ZCON},
   484  		{obj.Addr{Type: obj.TYPE_FCONST, Val: math.Float64frombits(0x8000000000000000)}, C_S16CON},
   485  
   486  		// Address type arguments
   487  		{obj.Addr{Type: obj.TYPE_ADDR, Reg: REG_R0, Name: obj.NAME_NONE, Offset: 1}, C_SACON},
   488  		{obj.Addr{Type: obj.TYPE_ADDR, Reg: REG_R0, Name: obj.NAME_NONE, Offset: BIG}, C_LACON},
   489  		{obj.Addr{Type: obj.TYPE_ADDR, Reg: REG_R0, Name: obj.NAME_NONE, Offset: -BIG - 1}, C_LACON},
   490  		{obj.Addr{Type: obj.TYPE_ADDR, Reg: REG_R0, Name: obj.NAME_NONE, Offset: 1 << 32}, C_DACON},
   491  		{obj.Addr{Type: obj.TYPE_ADDR, Name: obj.NAME_EXTERN, Sym: &obj.LSym{Type: objabi.SDATA}}, C_LACON},
   492  		{obj.Addr{Type: obj.TYPE_ADDR, Name: obj.NAME_STATIC, Sym: &obj.LSym{Type: objabi.SDATA}}, C_LACON},
   493  		{obj.Addr{Type: obj.TYPE_ADDR, Reg: REG_R0, Name: obj.NAME_AUTO, Offset: 1}, C_SACON},
   494  		{obj.Addr{Type: obj.TYPE_ADDR, Reg: REG_R0, Name: obj.NAME_AUTO, Offset: BIG}, C_LACON},
   495  		{obj.Addr{Type: obj.TYPE_ADDR, Reg: REG_R0, Name: obj.NAME_AUTO, Offset: -BIG - 1}, C_LACON},
   496  		{obj.Addr{Type: obj.TYPE_ADDR, Reg: REG_R0, Name: obj.NAME_PARAM, Offset: 1}, C_SACON},
   497  		{obj.Addr{Type: obj.TYPE_ADDR, Reg: REG_R0, Name: obj.NAME_PARAM, Offset: BIG}, C_LACON},
   498  		{obj.Addr{Type: obj.TYPE_ADDR, Reg: REG_R0, Name: obj.NAME_PARAM, Offset: -BIG - 33}, C_LACON}, // 33 is FixedFrameSize-1
   499  
   500  		// Constant type arguments
   501  		{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 0}, C_ZCON},
   502  		{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 1}, C_U1CON},
   503  		{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 2}, C_U2CON},
   504  		{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 4}, C_U3CON},
   505  		{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 8}, C_U4CON},
   506  		{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 16}, C_U5CON},
   507  		{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 32}, C_U8CON},
   508  		{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 1 << 14}, C_U15CON},
   509  		{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 1 << 15}, C_U16CON},
   510  		{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 1 + 1<<16}, C_U31CON},
   511  		{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 1 << 31}, C_U32CON},
   512  		{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 1 << 32}, C_S34CON},
   513  		{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 1 << 33}, C_64CON},
   514  		{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: -1}, C_S16CON},
   515  		{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: -0x10001}, C_S32CON},
   516  		{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 0x10001}, C_U31CON},
   517  		{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: -(1 << 33)}, C_S34CON},
   518  		{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: -(1 << 34)}, C_64CON},
   519  
   520  		// Branch like arguments
   521  		{obj.Addr{Type: obj.TYPE_BRANCH, Sym: &obj.LSym{Type: objabi.SDATA}}, cmplx{C_BRA, C_BRAPIC, C_BRAPIC, C_BRA}},
   522  		{obj.Addr{Type: obj.TYPE_BRANCH}, C_BRA},
   523  	}
   524  
   525  	pic_ctxt9 := ctxt9{ctxt: &obj.Link{Flag_shared: true, Arch: &Linkppc64}, autosize: 0}
   526  	pic_dyn_ctxt9 := ctxt9{ctxt: &obj.Link{Flag_shared: true, Flag_dynlink: true, Arch: &Linkppc64}, autosize: 0}
   527  	dyn_ctxt9 := ctxt9{ctxt: &obj.Link{Flag_dynlink: true, Arch: &Linkppc64}, autosize: 0}
   528  	nonpic_ctxt9 := ctxt9{ctxt: &obj.Link{Arch: &Linkppc64}, autosize: 0}
   529  	ctxts := [...]*ctxt9{&pic_ctxt9, &pic_dyn_ctxt9, &dyn_ctxt9, &nonpic_ctxt9}
   530  	name := [...]string{"pic", "pic_dyn", "dyn", "nonpic"}
   531  	for _, tst := range tsts {
   532  		var expect []int
   533  		switch tst.output.(type) {
   534  		case cmplx:
   535  			v := tst.output.(cmplx)
   536  			expect = []int{v.pic, v.pic_dyn, v.dyn, v.nonpic}
   537  		case int:
   538  			expect = []int{tst.output.(int), tst.output.(int), tst.output.(int), tst.output.(int)}
   539  		}
   540  		for i := range ctxts {
   541  			if output := ctxts[i].aclass(&tst.arg); output != expect[i] {
   542  				t.Errorf("%s.aclass(%v) = %v, expected %v\n", name[i], tst.arg, DRconv(output), DRconv(expect[i]))
   543  			}
   544  		}
   545  	}
   546  }
   547  
   548  // The optab size should remain constant when reinitializing the PPC64 assembler backend.
   549  func TestOptabReinit(t *testing.T) {
   550  	buildcfg.GOOS = "linux"
   551  	buildcfg.GOARCH = "ppc64le"
   552  	buildcfg.GOPPC64 = 8
   553  	buildop(nil)
   554  	optabLen := len(optab)
   555  	buildcfg.GOPPC64 = 9
   556  	buildop(nil)
   557  	reinitOptabLen := len(optab)
   558  	if reinitOptabLen != optabLen {
   559  		t.Errorf("rerunning buildop changes optab size from %d to %d", optabLen, reinitOptabLen)
   560  	}
   561  }
   562  
   563  // A tail call is lowered to "MOVD Rx, CTR; BR (CTR)". runtime.asyncPreempt
   564  // does not preserve CTR, and its resume sequence leaves CTR holding the resume
   565  // PC, so a goroutine preempted anywhere between the load of CTR and the branch
   566  // would resume by branching to the wrong place (for a preemption at the branch
   567  // itself, to that very instruction, spinning there forever). Check that the
   568  // whole sequence is marked as an unsafe point. See go.dev/issue/78576.
   569  const tailCallSrc = `
   570  // 4 = NOSPLIT, 512 = NOFRAME (see textflag.h).
   571  TEXT ·leafNoFrame(SB),4|512,$0-0
   572  	MOVD	$0, R3
   573  	RET	(R3)
   574  
   575  TEXT ·leafFrame(SB),4,$8-0
   576  	MOVD	$0, R3
   577  	RET	(R3)
   578  
   579  TEXT ·nonLeaf(SB),4,$8-0
   580  	CALL	·leafNoFrame(SB)
   581  	MOVD	$0, R3
   582  	RET	(R3)
   583  
   584  // A tail call that is not the last instruction in the function,
   585  // to check that the unsafe point ends at the branch and does not
   586  // swallow the rest of the function.
   587  TEXT ·leafNoFrameCond(SB),4|512,$0-0
   588  	CMP	R3, $0
   589  	BEQ	skip
   590  	RET	(R3)
   591  skip:
   592  	MOVD	$0, R3
   593  	RET
   594  `
   595  
   596  func TestTailCallUnsafePoint(t *testing.T) {
   597  	testenv.MustHaveGoBuild(t)
   598  
   599  	dir := t.TempDir()
   600  	tmpfile := filepath.Join(dir, "x.s")
   601  	if err := os.WriteFile(tmpfile, []byte(tailCallSrc), 0644); err != nil {
   602  		t.Fatalf("can't write output: %v\n", err)
   603  	}
   604  
   605  	for _, goarch := range []string{"ppc64", "ppc64le"} {
   606  		cmd := testenv.Command(t, testenv.GoToolPath(t), "tool", "asm", "-o", filepath.Join(dir, "x.o"), "-S", tmpfile)
   607  		cmd.Env = append(os.Environ(), "GOARCH="+goarch, "GOOS=linux")
   608  		out, err := cmd.CombinedOutput()
   609  		if err != nil {
   610  			t.Fatalf("GOARCH=%s: assembly failed: %v, output:\n%s", goarch, err, out)
   611  		}
   612  
   613  		// Walk the -S output tracking the current PCDATA_UnsafePoint value.
   614  		// Every branch through CTR must be an unsafe point, and the unsafe
   615  		// point must end at the branch: the next instruction, if any, must be
   616  		// a safe point again, and so must the end of the function.
   617  		branches := 0
   618  		sym := ""
   619  		unsafePoint := int64(abi.UnsafePointSafe)
   620  		atBranch := false
   621  		endFunc := func() {
   622  			if sym != "" && unsafePoint != abi.UnsafePointSafe {
   623  				t.Errorf("GOARCH=%s: %s: unsafe point %d at end of function, want %d",
   624  					goarch, sym, unsafePoint, abi.UnsafePointSafe)
   625  			}
   626  		}
   627  		for _, line := range strings.Split(string(out), "\n") {
   628  			f := strings.Fields(line)
   629  			// Lines look like:
   630  			//	sym STEXT ...
   631  			//	<tab>0x0014 00020 (x.s:14)<tab>PCDATA<tab>$0,<tab>$-2
   632  			// Other lines (hex dumps, relocations) have no (file:line) field.
   633  			switch {
   634  			case len(f) >= 2 && f[1] == "STEXT":
   635  				endFunc()
   636  				sym, unsafePoint, atBranch = f[0], abi.UnsafePointSafe, false
   637  			case len(f) < 4 || !strings.HasPrefix(f[2], "("):
   638  				// Not an instruction.
   639  			case len(f) >= 6 && f[3] == "PCDATA" && f[4] == fmt.Sprintf("$%d,", abi.PCDATA_UnsafePoint):
   640  				v, err := strconv.ParseInt(strings.TrimPrefix(f[5], "$"), 10, 64)
   641  				if err != nil {
   642  					t.Fatalf("GOARCH=%s: can't parse %q: %v", goarch, line, err)
   643  				}
   644  				unsafePoint = v
   645  			case f[3] == "PCDATA" || f[3] == "FUNCDATA" || f[3] == "TEXT":
   646  				// Not a real instruction.
   647  			case len(f) >= 5 && f[3] == "JMP" && f[4] == "CTR":
   648  				branches++
   649  				atBranch = true
   650  				if unsafePoint != abi.UnsafePointUnsafe {
   651  					t.Errorf("GOARCH=%s: %s\n\tbranch through CTR has unsafe point %d, want %d",
   652  						goarch, strings.TrimSpace(line), unsafePoint, abi.UnsafePointUnsafe)
   653  				}
   654  			case atBranch:
   655  				atBranch = false
   656  				if unsafePoint != abi.UnsafePointSafe {
   657  					t.Errorf("GOARCH=%s: %s\n\tinstruction after branch through CTR has unsafe point %d, want %d",
   658  						goarch, strings.TrimSpace(line), unsafePoint, abi.UnsafePointSafe)
   659  				}
   660  			}
   661  		}
   662  		endFunc()
   663  		if want := strings.Count(tailCallSrc, "RET\t(R3)"); branches != want {
   664  			t.Errorf("GOARCH=%s: found %d branches through CTR, want %d; output:\n%s", goarch, branches, want, out)
   665  		}
   666  	}
   667  }
   668  

View as plain text