| // Copyright 2020 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package ppc64 | |
| import ( | |
| "bytes" | |
| "fmt" | |
| "internal/buildcfg" | |
| "internal/testenv" | |
| "math" | |
| "os" | |
| "path/filepath" | |
| "regexp" | |
| "strconv" | |
| "strings" | |
| "testing" | |
| "internal/abi" | |
| "cmd/internal/obj" | |
| "cmd/internal/objabi" | |
| ) | |
| var platformEnvs = [][]string{ | |
| {"GOOS=aix", "GOARCH=ppc64"}, | |
| {"GOOS=linux", "GOARCH=ppc64"}, | |
| {"GOOS=linux", "GOARCH=ppc64le"}, | |
| } | |
| const invalidPCAlignSrc = ` | |
| TEXT test(SB),0,$0-0 | |
| ADD $2, R3 | |
| PCALIGN $128 | |
| RET | |
| ` | |
| const validPCAlignSrc = ` | |
| TEXT test(SB),0,$0-0 | |
| ADD $2, R3 | |
| PCALIGN $16 | |
| MOVD $8, R16 | |
| ADD $8, R4 | |
| PCALIGN $32 | |
| ADD $8, R3 | |
| PCALIGN $8 | |
| ADD $4, R8 | |
| RET | |
| ` | |
| const x64pgm = ` | |
| TEXT test(SB),0,$0-0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| PNOP | |
| ` | |
| const x32pgm = ` | |
| TEXT test(SB),0,$0-0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| PNOP | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| ` | |
| const x16pgm = ` | |
| TEXT test(SB),0,$0-0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| PNOP | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| ` | |
| const x0pgm = ` | |
| TEXT test(SB),0,$0-0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| PNOP | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| ` | |
| const x64pgmA64 = ` | |
| TEXT test(SB),0,$0-0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| PNOP | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| PNOP | |
| ` | |
| const x64pgmA32 = ` | |
| TEXT test(SB),0,$0-0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| PNOP | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| OR R0, R0 | |
| PNOP | |
| ` | |
| // Test that nops are inserted when crossing 64B boundaries, and | |
| // alignment is adjusted to avoid crossing. | |
| func TestPfxAlign(t *testing.T) { | |
| testenv.MustHaveGoBuild(t) | |
| dir := t.TempDir() | |
| pgms := []struct { | |
| text []byte | |
| align string | |
| hasNop bool | |
| }{ | |
| {[]byte(x0pgm), "align=0x0", false}, // No alignment or nop adjustments needed | |
| {[]byte(x16pgm), "align=0x20", false}, // Increased alignment needed | |
| {[]byte(x32pgm), "align=0x40", false}, // Worst case alignment needed | |
| {[]byte(x64pgm), "align=0x0", true}, // 0 aligned is default (16B) alignment | |
| {[]byte(x64pgmA64), "align=0x40", true}, // extra alignment + nop | |
| {[]byte(x64pgmA32), "align=0x20", true}, // extra alignment + nop | |
| } | |
| for _, pgm := range pgms { | |
| tmpfile := filepath.Join(dir, "x.s") | |
| err := os.WriteFile(tmpfile, pgm.text, 0644) | |
| if err != nil { | |
| t.Fatalf("can't write output: %v\n", err) | |
| } | |
| cmd := testenv.Command(t, testenv.GoToolPath(t), "tool", "asm", "-S", "-o", filepath.Join(dir, "test.o"), tmpfile) | |
| cmd.Env = append(os.Environ(), "GOOS=linux", "GOARCH=ppc64le") | |
| out, err := cmd.CombinedOutput() | |
| if err != nil { | |
| t.Errorf("Failed to compile %v: %v\n", pgm, err) | |
| } | |
| if !strings.Contains(string(out), pgm.align) { | |
| t.Errorf("Fatal, misaligned text with prefixed instructions:\n%s", out) | |
| } | |
| hasNop := strings.Contains(string(out), "00 00 00 60") | |
| if hasNop != pgm.hasNop { | |
| t.Errorf("Fatal, prefixed instruction is missing nop padding:\n%s", out) | |
| } | |
| } | |
| } | |
| // TestLarge generates a very large file to verify that large | |
| // program builds successfully, and branches which exceed the | |
| // range of BC are rewritten to reach. | |
| func TestLarge(t *testing.T) { | |
| if testing.Short() { | |
| t.Skip("Skip in short mode") | |
| } | |
| testenv.MustHaveGoBuild(t) | |
| dir := t.TempDir() | |
| // A few interesting test cases for long conditional branch fixups | |
| tests := []struct { | |
| jmpinsn string | |
| backpattern []string | |
| fwdpattern []string | |
| }{ | |
| // Test the interesting cases of conditional branch rewrites for too-far targets. Simple conditional | |
| // branches can be made to reach with one JMP insertion, compound conditionals require two. | |
| // | |
| // beq <-> bne conversion (insert one jump) | |
| {"BEQ", | |
| []string{``, | |
| `0x20030 131120\s\(.*\)\tBC\t\$4,\sCR0EQ,\s131128`, | |
| `0x20034 131124\s\(.*\)\tJMP\t0`}, | |
| []string{``, | |
| `0x0000 00000\s\(.*\)\tBC\t\$4,\sCR0EQ,\s8`, | |
| `0x0004 00004\s\(.*\)\tJMP\t131128`}, | |
| }, | |
| {"BNE", | |
| []string{``, | |
| `0x20030 131120\s\(.*\)\tBC\t\$12,\sCR0EQ,\s131128`, | |
| `0x20034 131124\s\(.*\)\tJMP\t0`}, | |
| []string{``, | |
| `0x0000 00000\s\(.*\)\tBC\t\$12,\sCR0EQ,\s8`, | |
| `0x0004 00004\s\(.*\)\tJMP\t131128`}}, | |
| // bdnz (BC 16,0,tgt) <-> bdz (BC 18,0,+4) conversion (insert one jump) | |
| {"BC 16,0,", | |
| []string{``, | |
| `0x20030 131120\s\(.*\)\tBC\t\$18,\sCR0LT,\s131128`, | |
| `0x20034 131124\s\(.*\)\tJMP\t0`}, | |
| []string{``, | |
| `0x0000 00000\s\(.*\)\tBC\t\$18,\sCR0LT,\s8`, | |
| `0x0004 00004\s\(.*\)\tJMP\t131128`}}, | |
| {"BC 18,0,", | |
| []string{``, | |
| `0x20030 131120\s\(.*\)\tBC\t\$16,\sCR0LT,\s131128`, | |
| `0x20034 131124\s\(.*\)\tJMP\t0`}, | |
| []string{``, | |
| `0x0000 00000\s\(.*\)\tBC\t\$16,\sCR0LT,\s8`, | |
| `0x0004 00004\s\(.*\)\tJMP\t131128`}}, | |
| // bdnzt (BC 8,0,tgt) <-> bdnzt (BC 8,0,+4) conversion (insert two jumps) | |
| {"BC 8,0,", | |
| []string{``, | |
| `0x20034 131124\s\(.*\)\tBC\t\$8,\sCR0LT,\s131132`, | |
| `0x20038 131128\s\(.*\)\tJMP\t131136`, | |
| `0x2003c 131132\s\(.*\)\tJMP\t0\n`}, | |
| []string{``, | |
| `0x0000 00000\s\(.*\)\tBC\t\$8,\sCR0LT,\s8`, | |
| `0x0004 00004\s\(.*\)\tJMP\t12`, | |
| `0x0008 00008\s\(.*\)\tJMP\t131136\n`}}, | |
| } | |
| for _, test := range tests { | |
| // generate a very large function | |
| buf := bytes.NewBuffer(make([]byte, 0, 7000000)) | |
| gen(buf, test.jmpinsn) | |
| tmpfile := filepath.Join(dir, "x.s") | |
| err := os.WriteFile(tmpfile, buf.Bytes(), 0644) | |
| if err != nil { | |
| t.Fatalf("can't write output: %v\n", err) | |
| } | |
| // Test on all supported ppc64 platforms | |
| for _, platenv := range platformEnvs { | |
| cmd := testenv.Command(t, testenv.GoToolPath(t), "tool", "asm", "-S", "-o", filepath.Join(dir, "test.o"), tmpfile) | |
| cmd.Env = append(os.Environ(), platenv...) | |
| out, err := cmd.CombinedOutput() | |
| if err != nil { | |
| t.Errorf("Assemble failed (%v): %v, output: %s", platenv, err, out) | |
| } | |
| matched, err := regexp.MatchString(strings.Join(test.fwdpattern, "\n\t*"), string(out)) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if !matched { | |
| t.Errorf("Failed to detect long forward BC fixup in (%v):%s\n", platenv, out) | |
| } | |
| matched, err = regexp.MatchString(strings.Join(test.backpattern, "\n\t*"), string(out)) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if !matched { | |
| t.Errorf("Failed to detect long backward BC fixup in (%v):%s\n", platenv, out) | |
| } | |
| } | |
| } | |
| } | |
| // gen generates a very large program with a very long forward and backwards conditional branch. | |
| func gen(buf *bytes.Buffer, jmpinsn string) { | |
| fmt.Fprintln(buf, "TEXT f(SB),0,$0-0") | |
| fmt.Fprintln(buf, "label_start:") | |
| fmt.Fprintln(buf, jmpinsn, "label_end") | |
| for i := 0; i < (1<<15 + 10); i++ { | |
| fmt.Fprintln(buf, "MOVD R0, R1") | |
| } | |
| fmt.Fprintln(buf, jmpinsn, "label_start") | |
| fmt.Fprintln(buf, "label_end:") | |
| fmt.Fprintln(buf, "MOVD R0, R1") | |
| fmt.Fprintln(buf, "RET") | |
| } | |
| // TestPCalign generates two asm files containing the | |
| // PCALIGN directive, to verify correct values are and | |
| // accepted, and incorrect values are flagged in error. | |
| func TestPCalign(t *testing.T) { | |
| var pattern8 = `0x...8\s.*ADD\s..,\sR8` | |
| var pattern16 = `0x...[80]\s.*MOVD\s..,\sR16` | |
| var pattern32 = `0x...0\s.*ADD\s..,\sR3` | |
| testenv.MustHaveGoBuild(t) | |
| dir := t.TempDir() | |
| // generate a test with valid uses of PCALIGN | |
| tmpfile := filepath.Join(dir, "x.s") | |
| err := os.WriteFile(tmpfile, []byte(validPCAlignSrc), 0644) | |
| if err != nil { | |
| t.Fatalf("can't write output: %v\n", err) | |
| } | |
| // build generated file without errors and assemble it | |
| cmd := testenv.Command(t, testenv.GoToolPath(t), "tool", "asm", "-o", filepath.Join(dir, "x.o"), "-S", tmpfile) | |
| cmd.Env = append(os.Environ(), "GOARCH=ppc64le", "GOOS=linux") | |
| out, err := cmd.CombinedOutput() | |
| if err != nil { | |
| t.Errorf("Build failed: %v, output: %s", err, out) | |
| } | |
| matched, err := regexp.MatchString(pattern8, string(out)) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if !matched { | |
| t.Errorf("The 8 byte alignment is not correct: %t, output:%s\n", matched, out) | |
| } | |
| matched, err = regexp.MatchString(pattern16, string(out)) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if !matched { | |
| t.Errorf("The 16 byte alignment is not correct: %t, output:%s\n", matched, out) | |
| } | |
| matched, err = regexp.MatchString(pattern32, string(out)) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if !matched { | |
| t.Errorf("The 32 byte alignment is not correct: %t, output:%s\n", matched, out) | |
| } | |
| // generate a test with invalid use of PCALIGN | |
| tmpfile = filepath.Join(dir, "xi.s") | |
| err = os.WriteFile(tmpfile, []byte(invalidPCAlignSrc), 0644) | |
| if err != nil { | |
| t.Fatalf("can't write output: %v\n", err) | |
| } | |
| // build test with errors and check for messages | |
| cmd = testenv.Command(t, testenv.GoToolPath(t), "tool", "asm", "-o", filepath.Join(dir, "xi.o"), "-S", tmpfile) | |
| cmd.Env = append(os.Environ(), "GOARCH=ppc64le", "GOOS=linux") | |
| out, err = cmd.CombinedOutput() | |
| if !strings.Contains(string(out), "Unexpected alignment") { | |
| t.Errorf("Invalid alignment not detected for PCALIGN\n") | |
| } | |
| } | |
| // Verify register constants are correctly aligned. Much of the ppc64 assembler assumes masking out significant | |
| // bits will produce a valid register number: | |
| // REG_Rx & 31 == x | |
| // REG_Fx & 31 == x | |
| // REG_Vx & 31 == x | |
| // REG_VSx & 63 == x | |
| // REG_SPRx & 1023 == x | |
| // REG_CRx & 7 == x | |
| // | |
| // VR and FPR disjointly overlap VSR, interpreting as VSR registers should produce the correctly overlapped VSR. | |
| // REG_FPx & 63 == x | |
| // REG_Vx & 63 == x + 32 | |
| func TestRegValueAlignment(t *testing.T) { | |
| tstFunc := func(rstart, rend, msk, rout int) { | |
| for i := rstart; i <= rend; i++ { | |
| if i&msk != rout { | |
| t.Errorf("%v is not aligned to 0x%X (expected %d, got %d)\n", rconv(i), msk, rout, rstart&msk) | |
| } | |
| rout++ | |
| } | |
| } | |
| var testType = []struct { | |
| rstart int | |
| rend int | |
| msk int | |
| rout int | |
| }{ | |
| {REG_VS0, REG_VS63, 63, 0}, | |
| {REG_R0, REG_R31, 31, 0}, | |
| {REG_F0, REG_F31, 31, 0}, | |
| {REG_V0, REG_V31, 31, 0}, | |
| {REG_V0, REG_V31, 63, 32}, | |
| {REG_F0, REG_F31, 63, 0}, | |
| {REG_SPR0, REG_SPR0 + 1023, 1023, 0}, | |
| {REG_CR0, REG_CR7, 7, 0}, | |
| {REG_CR0LT, REG_CR7SO, 31, 0}, | |
| } | |
| for _, t := range testType { | |
| tstFunc(t.rstart, t.rend, t.msk, t.rout) | |
| } | |
| } | |
| // Verify interesting obj.Addr arguments are classified correctly. | |
| func TestAddrClassifier(t *testing.T) { | |
| type cmplx struct { | |
| pic int | |
| pic_dyn int | |
| dyn int | |
| nonpic int | |
| } | |
| tsts := [...]struct { | |
| arg obj.Addr | |
| output any | |
| }{ | |
| // Supported register type args | |
| {obj.Addr{Type: obj.TYPE_REG, Reg: REG_R1}, C_REG}, | |
| {obj.Addr{Type: obj.TYPE_REG, Reg: REG_R2}, C_REGP}, | |
| {obj.Addr{Type: obj.TYPE_REG, Reg: REG_F1}, C_FREG}, | |
| {obj.Addr{Type: obj.TYPE_REG, Reg: REG_F2}, C_FREGP}, | |
| {obj.Addr{Type: obj.TYPE_REG, Reg: REG_V2}, C_VREG}, | |
| {obj.Addr{Type: obj.TYPE_REG, Reg: REG_VS1}, C_VSREG}, | |
| {obj.Addr{Type: obj.TYPE_REG, Reg: REG_VS2}, C_VSREGP}, | |
| {obj.Addr{Type: obj.TYPE_REG, Reg: REG_CR}, C_CREG}, | |
| {obj.Addr{Type: obj.TYPE_REG, Reg: REG_CR1}, C_CREG}, | |
| {obj.Addr{Type: obj.TYPE_REG, Reg: REG_CR1SO}, C_CRBIT}, | |
| {obj.Addr{Type: obj.TYPE_REG, Reg: REG_SPR0}, C_SPR}, | |
| {obj.Addr{Type: obj.TYPE_REG, Reg: REG_SPR0 + 8}, C_LR}, | |
| {obj.Addr{Type: obj.TYPE_REG, Reg: REG_SPR0 + 9}, C_CTR}, | |
| {obj.Addr{Type: obj.TYPE_REG, Reg: REG_FPSCR}, C_FPSCR}, | |
| {obj.Addr{Type: obj.TYPE_REG, Reg: REG_A1}, C_AREG}, | |
| // Memory type arguments. | |
| {obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_GOTREF}, C_ADDR}, | |
| {obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_TOCREF}, C_ADDR}, | |
| {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}}, | |
| {obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: &obj.LSym{Type: objabi.SDATA}}, C_ADDR}, | |
| {obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_AUTO}, C_SOREG}, | |
| {obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_AUTO, Offset: BIG}, C_LOREG}, | |
| {obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_AUTO, Offset: -BIG - 1}, C_LOREG}, | |
| {obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_PARAM}, C_SOREG}, | |
| {obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_PARAM, Offset: BIG}, C_LOREG}, | |
| {obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_PARAM, Offset: -BIG - 33}, C_LOREG}, // 33 is FixedFrameSize-1 | |
| {obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_NONE}, C_ZOREG}, | |
| {obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_NONE, Index: REG_R4}, C_XOREG}, | |
| {obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_NONE, Offset: 1}, C_SOREG}, | |
| {obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_NONE, Offset: BIG}, C_LOREG}, | |
| {obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_NONE, Offset: -BIG - 33}, C_LOREG}, | |
| // Misc (golang initializes -0.0 to 0.0, hence the obfuscation below) | |
| {obj.Addr{Type: obj.TYPE_TEXTSIZE}, C_TEXTSIZE}, | |
| {obj.Addr{Type: obj.TYPE_FCONST, Val: 0.0}, C_ZCON}, | |
| {obj.Addr{Type: obj.TYPE_FCONST, Val: math.Float64frombits(0x8000000000000000)}, C_S16CON}, | |
| // Address type arguments | |
| {obj.Addr{Type: obj.TYPE_ADDR, Reg: REG_R0, Name: obj.NAME_NONE, Offset: 1}, C_SACON}, | |
| {obj.Addr{Type: obj.TYPE_ADDR, Reg: REG_R0, Name: obj.NAME_NONE, Offset: BIG}, C_LACON}, | |
| {obj.Addr{Type: obj.TYPE_ADDR, Reg: REG_R0, Name: obj.NAME_NONE, Offset: -BIG - 1}, C_LACON}, | |
| {obj.Addr{Type: obj.TYPE_ADDR, Reg: REG_R0, Name: obj.NAME_NONE, Offset: 1 << 32}, C_DACON}, | |
| {obj.Addr{Type: obj.TYPE_ADDR, Name: obj.NAME_EXTERN, Sym: &obj.LSym{Type: objabi.SDATA}}, C_LACON}, | |
| {obj.Addr{Type: obj.TYPE_ADDR, Name: obj.NAME_STATIC, Sym: &obj.LSym{Type: objabi.SDATA}}, C_LACON}, | |
| {obj.Addr{Type: obj.TYPE_ADDR, Reg: REG_R0, Name: obj.NAME_AUTO, Offset: 1}, C_SACON}, | |
| {obj.Addr{Type: obj.TYPE_ADDR, Reg: REG_R0, Name: obj.NAME_AUTO, Offset: BIG}, C_LACON}, | |
| {obj.Addr{Type: obj.TYPE_ADDR, Reg: REG_R0, Name: obj.NAME_AUTO, Offset: -BIG - 1}, C_LACON}, | |
| {obj.Addr{Type: obj.TYPE_ADDR, Reg: REG_R0, Name: obj.NAME_PARAM, Offset: 1}, C_SACON}, | |
| {obj.Addr{Type: obj.TYPE_ADDR, Reg: REG_R0, Name: obj.NAME_PARAM, Offset: BIG}, C_LACON}, | |
| {obj.Addr{Type: obj.TYPE_ADDR, Reg: REG_R0, Name: obj.NAME_PARAM, Offset: -BIG - 33}, C_LACON}, // 33 is FixedFrameSize-1 | |
| // Constant type arguments | |
| {obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 0}, C_ZCON}, | |
| {obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 1}, C_U1CON}, | |
| {obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 2}, C_U2CON}, | |
| {obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 4}, C_U3CON}, | |
| {obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 8}, C_U4CON}, | |
| {obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 16}, C_U5CON}, | |
| {obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 32}, C_U8CON}, | |
| {obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 1 << 14}, C_U15CON}, | |
| {obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 1 << 15}, C_U16CON}, | |
| {obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 1 + 1<<16}, C_U31CON}, | |
| {obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 1 << 31}, C_U32CON}, | |
| {obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 1 << 32}, C_S34CON}, | |
| {obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 1 << 33}, C_64CON}, | |
| {obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: -1}, C_S16CON}, | |
| {obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: -0x10001}, C_S32CON}, | |
| {obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 0x10001}, C_U31CON}, | |
| {obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: -(1 << 33)}, C_S34CON}, | |
| {obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: -(1 << 34)}, C_64CON}, | |
| // Branch like arguments | |
| {obj.Addr{Type: obj.TYPE_BRANCH, Sym: &obj.LSym{Type: objabi.SDATA}}, cmplx{C_BRA, C_BRAPIC, C_BRAPIC, C_BRA}}, | |
| {obj.Addr{Type: obj.TYPE_BRANCH}, C_BRA}, | |
| } | |
| pic_ctxt9 := ctxt9{ctxt: &obj.Link{Flag_shared: true, Arch: &Linkppc64}, autosize: 0} | |
| pic_dyn_ctxt9 := ctxt9{ctxt: &obj.Link{Flag_shared: true, Flag_dynlink: true, Arch: &Linkppc64}, autosize: 0} | |
| dyn_ctxt9 := ctxt9{ctxt: &obj.Link{Flag_dynlink: true, Arch: &Linkppc64}, autosize: 0} | |
| nonpic_ctxt9 := ctxt9{ctxt: &obj.Link{Arch: &Linkppc64}, autosize: 0} | |
| ctxts := [...]*ctxt9{&pic_ctxt9, &pic_dyn_ctxt9, &dyn_ctxt9, &nonpic_ctxt9} | |
| name := [...]string{"pic", "pic_dyn", "dyn", "nonpic"} | |
| for _, tst := range tsts { | |
| var expect []int | |
| switch tst.output.(type) { | |
| case cmplx: | |
| v := tst.output.(cmplx) | |
| expect = []int{v.pic, v.pic_dyn, v.dyn, v.nonpic} | |
| case int: | |
| expect = []int{tst.output.(int), tst.output.(int), tst.output.(int), tst.output.(int)} | |
| } | |
| for i := range ctxts { | |
| if output := ctxts[i].aclass(&tst.arg); output != expect[i] { | |
| t.Errorf("%s.aclass(%v) = %v, expected %v\n", name[i], tst.arg, DRconv(output), DRconv(expect[i])) | |
| } | |
| } | |
| } | |
| } | |
| // The optab size should remain constant when reinitializing the PPC64 assembler backend. | |
| func TestOptabReinit(t *testing.T) { | |
| buildcfg.GOOS = "linux" | |
| buildcfg.GOARCH = "ppc64le" | |
| buildcfg.GOPPC64 = 8 | |
| buildop(nil) | |
| optabLen := len(optab) | |
| buildcfg.GOPPC64 = 9 | |
| buildop(nil) | |
| reinitOptabLen := len(optab) | |
| if reinitOptabLen != optabLen { | |
| t.Errorf("rerunning buildop changes optab size from %d to %d", optabLen, reinitOptabLen) | |
| } | |
| } | |
| // A tail call is lowered to "MOVD Rx, CTR; BR (CTR)". runtime.asyncPreempt | |
| // does not preserve CTR, and its resume sequence leaves CTR holding the resume | |
| // PC, so a goroutine preempted anywhere between the load of CTR and the branch | |
| // would resume by branching to the wrong place (for a preemption at the branch | |
| // itself, to that very instruction, spinning there forever). Check that the | |
| // whole sequence is marked as an unsafe point. See go.dev/issue/78576. | |
| const tailCallSrc = ` | |
| // 4 = NOSPLIT, 512 = NOFRAME (see textflag.h). | |
| TEXT ·leafNoFrame(SB),4|512,$0-0 | |
| MOVD $0, R3 | |
| RET (R3) | |
| TEXT ·leafFrame(SB),4,$8-0 | |
| MOVD $0, R3 | |
| RET (R3) | |
| TEXT ·nonLeaf(SB),4,$8-0 | |
| CALL ·leafNoFrame(SB) | |
| MOVD $0, R3 | |
| RET (R3) | |
| // A tail call that is not the last instruction in the function, | |
| // to check that the unsafe point ends at the branch and does not | |
| // swallow the rest of the function. | |
| TEXT ·leafNoFrameCond(SB),4|512,$0-0 | |
| CMP R3, $0 | |
| BEQ skip | |
| RET (R3) | |
| skip: | |
| MOVD $0, R3 | |
| RET | |
| ` | |
| func TestTailCallUnsafePoint(t *testing.T) { | |
| testenv.MustHaveGoBuild(t) | |
| dir := t.TempDir() | |
| tmpfile := filepath.Join(dir, "x.s") | |
| if err := os.WriteFile(tmpfile, []byte(tailCallSrc), 0644); err != nil { | |
| t.Fatalf("can't write output: %v\n", err) | |
| } | |
| for _, goarch := range []string{"ppc64", "ppc64le"} { | |
| cmd := testenv.Command(t, testenv.GoToolPath(t), "tool", "asm", "-o", filepath.Join(dir, "x.o"), "-S", tmpfile) | |
| cmd.Env = append(os.Environ(), "GOARCH="+goarch, "GOOS=linux") | |
| out, err := cmd.CombinedOutput() | |
| if err != nil { | |
| t.Fatalf("GOARCH=%s: assembly failed: %v, output:\n%s", goarch, err, out) | |
| } | |
| // Walk the -S output tracking the current PCDATA_UnsafePoint value. | |
| // Every branch through CTR must be an unsafe point, and the unsafe | |
| // point must end at the branch: the next instruction, if any, must be | |
| // a safe point again, and so must the end of the function. | |
| branches := 0 | |
| sym := "" | |
| unsafePoint := int64(abi.UnsafePointSafe) | |
| atBranch := false | |
| endFunc := func() { | |
| if sym != "" && unsafePoint != abi.UnsafePointSafe { | |
| t.Errorf("GOARCH=%s: %s: unsafe point %d at end of function, want %d", | |
| goarch, sym, unsafePoint, abi.UnsafePointSafe) | |
| } | |
| } | |
| for _, line := range strings.Split(string(out), "\n") { | |
| f := strings.Fields(line) | |
| // Lines look like: | |
| // sym STEXT ... | |
| // <tab>0x0014 00020 (x.s:14)<tab>PCDATA<tab>$0,<tab>$-2 | |
| // Other lines (hex dumps, relocations) have no (file:line) field. | |
| switch { | |
| case len(f) >= 2 && f[1] == "STEXT": | |
| endFunc() | |
| sym, unsafePoint, atBranch = f[0], abi.UnsafePointSafe, false | |
| case len(f) < 4 || !strings.HasPrefix(f[2], "("): | |
| // Not an instruction. | |
| case len(f) >= 6 && f[3] == "PCDATA" && f[4] == fmt.Sprintf("$%d,", abi.PCDATA_UnsafePoint): | |
| v, err := strconv.ParseInt(strings.TrimPrefix(f[5], "$"), 10, 64) | |
| if err != nil { | |
| t.Fatalf("GOARCH=%s: can't parse %q: %v", goarch, line, err) | |
| } | |
| unsafePoint = v | |
| case f[3] == "PCDATA" || f[3] == "FUNCDATA" || f[3] == "TEXT": | |
| // Not a real instruction. | |
| case len(f) >= 5 && f[3] == "JMP" && f[4] == "CTR": | |
| branches++ | |
| atBranch = true | |
| if unsafePoint != abi.UnsafePointUnsafe { | |
| t.Errorf("GOARCH=%s: %s\n\tbranch through CTR has unsafe point %d, want %d", | |
| goarch, strings.TrimSpace(line), unsafePoint, abi.UnsafePointUnsafe) | |
| } | |
| case atBranch: | |
| atBranch = false | |
| if unsafePoint != abi.UnsafePointSafe { | |
| t.Errorf("GOARCH=%s: %s\n\tinstruction after branch through CTR has unsafe point %d, want %d", | |
| goarch, strings.TrimSpace(line), unsafePoint, abi.UnsafePointSafe) | |
| } | |
| } | |
| } | |
| endFunc() | |
| if want := strings.Count(tailCallSrc, "RET\t(R3)"); branches != want { | |
| t.Errorf("GOARCH=%s: found %d branches through CTR, want %d; output:\n%s", goarch, branches, want, out) | |
| } | |
| } | |
| } |