github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/obj/x86/obj6_test.go (about)

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