1// Copyright 2015 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 5package x86_test 6 7import ( 8 "bufio" 9 "bytes" 10 "fmt" 11 "internal/testenv" 12 "os" 13 "path/filepath" 14 "regexp" 15 "strconv" 16 "strings" 17 "testing" 18) 19 20const testdata = ` 21MOVQ AX, AX -> MOVQ AX, AX 22 23LEAQ name(SB), AX -> MOVQ name@GOT(SB), AX 24LEAQ name+10(SB), AX -> MOVQ name@GOT(SB), AX; LEAQ 10(AX), AX 25MOVQ $name(SB), AX -> MOVQ name@GOT(SB), AX 26MOVQ $name+10(SB), AX -> MOVQ name@GOT(SB), AX; LEAQ 10(AX), AX 27 28MOVQ name(SB), AX -> NOP; MOVQ name@GOT(SB), R15; MOVQ (R15), AX 29MOVQ name+10(SB), AX -> NOP; MOVQ name@GOT(SB), R15; MOVQ 10(R15), AX 30 31CMPQ name(SB), $0 -> NOP; MOVQ name@GOT(SB), R15; CMPQ (R15), $0 32 33MOVQ $1, name(SB) -> NOP; MOVQ name@GOT(SB), R15; MOVQ $1, (R15) 34MOVQ $1, name+10(SB) -> NOP; MOVQ name@GOT(SB), R15; MOVQ $1, 10(R15) 35` 36 37type ParsedTestData struct { 38 input string 39 marks []int 40 marker_to_input map[int][]string 41 marker_to_expected map[int][]string 42 marker_to_output map[int][]string 43} 44 45const marker_start = 1234 46 47func parseTestData(t *testing.T) *ParsedTestData { 48 r := &ParsedTestData{} 49 scanner := bufio.NewScanner(strings.NewReader(testdata)) 50 r.marker_to_input = make(map[int][]string) 51 r.marker_to_expected = make(map[int][]string) 52 marker := marker_start 53 input_insns := []string{} 54 for scanner.Scan() { 55 line := scanner.Text() 56 if len(strings.TrimSpace(line)) == 0 { 57 continue 58 } 59 parts := strings.Split(line, "->") 60 if len(parts) != 2 { 61 t.Fatalf("malformed line %v", line) 62 } 63 r.marks = append(r.marks, marker) 64 marker_insn := fmt.Sprintf("MOVQ $%d, AX", marker) 65 input_insns = append(input_insns, marker_insn) 66 for _, input_insn := range strings.Split(parts[0], ";") { 67 input_insns = append(input_insns, input_insn) 68 r.marker_to_input[marker] = append(r.marker_to_input[marker], normalize(input_insn)) 69 } 70 for _, expected_insn := range strings.Split(parts[1], ";") { 71 r.marker_to_expected[marker] = append(r.marker_to_expected[marker], normalize(expected_insn)) 72 } 73 marker++ 74 } 75 r.input = "TEXT ·foo(SB),$0\n" + strings.Join(input_insns, "\n") + "\n" 76 return r 77} 78 79var spaces_re *regexp.Regexp = regexp.MustCompile(`\s+`) 80 81func normalize(s string) string { 82 return spaces_re.ReplaceAllLiteralString(strings.TrimSpace(s), " ") 83} 84 85func asmOutput(t *testing.T, s string) []byte { 86 tmpdir, err := os.MkdirTemp("", "progedittest") 87 if err != nil { 88 t.Fatal(err) 89 } 90 defer os.RemoveAll(tmpdir) 91 tmpfile, err := os.Create(filepath.Join(tmpdir, "input.s")) 92 if err != nil { 93 t.Fatal(err) 94 } 95 defer tmpfile.Close() 96 _, err = tmpfile.WriteString(s) 97 if err != nil { 98 t.Fatal(err) 99 } 100 cmd := testenv.Command(t, 101 testenv.GoToolPath(t), "tool", "asm", "-S", "-dynlink", 102 "-o", filepath.Join(tmpdir, "output.6"), tmpfile.Name()) 103 104 cmd.Env = append(os.Environ(), 105 "GOARCH=amd64", "GOOS=linux", "GOPATH="+filepath.Join(tmpdir, "_gopath")) 106 asmout, err := cmd.CombinedOutput() 107 if err != nil { 108 t.Fatalf("error %s output %s", err, asmout) 109 } 110 return asmout 111} 112 113func parseOutput(t *testing.T, td *ParsedTestData, asmout []byte) { 114 scanner := bufio.NewScanner(bytes.NewReader(asmout)) 115 marker := regexp.MustCompile(`MOVQ \$([0-9]+), AX`) 116 mark := -1 117 td.marker_to_output = make(map[int][]string) 118 for scanner.Scan() { 119 line := scanner.Text() 120 if line[0] != '\t' { 121 continue 122 } 123 parts := strings.SplitN(line, "\t", 3) 124 if len(parts) != 3 { 125 continue 126 } 127 n := normalize(parts[2]) 128 mark_matches := marker.FindStringSubmatch(n) 129 if mark_matches != nil { 130 mark, _ = strconv.Atoi(mark_matches[1]) 131 if _, ok := td.marker_to_input[mark]; !ok { 132 t.Fatalf("unexpected marker %d", mark) 133 } 134 } else if mark != -1 { 135 td.marker_to_output[mark] = append(td.marker_to_output[mark], n) 136 } 137 } 138} 139 140func TestDynlink(t *testing.T) { 141 testenv.MustHaveGoBuild(t) 142 143 if os.Getenv("GOHOSTARCH") != "" { 144 // TODO: make this work? It was failing due to the 145 // GOARCH= filtering above and skipping is easiest for 146 // now. 147 t.Skip("skipping when GOHOSTARCH is set") 148 } 149 150 testdata := parseTestData(t) 151 asmout := asmOutput(t, testdata.input) 152 parseOutput(t, testdata, asmout) 153 for _, m := range testdata.marks { 154 i := strings.Join(testdata.marker_to_input[m], "; ") 155 o := strings.Join(testdata.marker_to_output[m], "; ") 156 e := strings.Join(testdata.marker_to_expected[m], "; ") 157 if o != e { 158 if o == i { 159 t.Errorf("%s was unchanged; should have become %s", i, e) 160 } else { 161 t.Errorf("%s became %s; should have become %s", i, o, e) 162 } 163 } else if i != e { 164 t.Logf("%s correctly became %s", i, o) 165 } 166 } 167} 168