Source file src/simd/archsimd/_gen/simdgen/sve/instruction_test.go

     1  // Copyright 2026 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 sve
     6  
     7  import (
     8  	"encoding/xml"
     9  	"reflect"
    10  	"strings"
    11  	"testing"
    12  
    13  	"simd/archsimd/_gen/unify"
    14  
    15  	"golang.org/x/arch/arm64/instgen/xmlspec"
    16  )
    17  
    18  // sizeTable is the XML explanation table shared by ADD/FADD/etc. that maps the
    19  // <T> arrangement symbol to the element specifiers B/H/S/D.
    20  const sizeTable = `
    21    <explanations>
    22      <explanation>
    23        <symbol link="t">&lt;T&gt;</symbol>
    24        <definition>
    25          <table><tgroup><tbody>
    26            <row><entry class="symbol">B</entry></row>
    27            <row><entry class="symbol">H</entry></row>
    28            <row><entry class="symbol">S</entry></row>
    29            <row><entry class="symbol">D</entry></row>
    30          </tbody></tgroup></table>
    31        </definition>
    32      </explanation>
    33    </explanations>`
    34  
    35  // addUnpred is ADD (vectors, unpredicated): ADD <Zd>.<T>, <Zn>.<T>, <Zm>.<T>.
    36  const addUnpred = `<instructionsection id="add_z_zz" title="ADD (vectors, unpredicated)" type="instruction">
    37    <docvars>
    38      <docvar key="instr-class" value="sve"/>
    39      <docvar key="mnemonic" value="ADD"/>
    40    </docvars>
    41    <desc><authored><para>Add active elements of the second source to the first.</para></authored></desc>
    42    <classes><iclass><encoding name="add_z_zz">
    43      <asmtemplate><text>ADD  </text><a link="zd">&lt;Zd&gt;</a><text>.</text><a link="t">&lt;T&gt;</a><text>, </text><a link="zn">&lt;Zn&gt;</a><text>.</text><a link="t">&lt;T&gt;</a><text>, </text><a link="zm">&lt;Zm&gt;</a><text>.</text><a link="t">&lt;T&gt;</a></asmtemplate>
    44    </encoding></iclass></classes>` + sizeTable + `</instructionsection>`
    45  
    46  // addPred is ADD (vectors, predicated): ADD <Zdn>.<T>, <Pg>/M, <Zdn>.<T>, <Zm>.<T>.
    47  // It must be skipped by the current draft (governing predicate).
    48  const addPred = `<instructionsection id="add_z_p_zz" title="ADD (vectors, predicated)" type="instruction">
    49    <docvars>
    50      <docvar key="instr-class" value="sve"/>
    51      <docvar key="mnemonic" value="ADD"/>
    52    </docvars>
    53    <classes><iclass><encoding name="add_z_p_zz">
    54      <asmtemplate><text>ADD  </text><a link="zdn">&lt;Zdn&gt;</a><text>.</text><a link="t">&lt;T&gt;</a><text>, </text><a link="pg">&lt;Pg&gt;</a><text>/M, </text><a link="zdn">&lt;Zdn&gt;</a><text>.</text><a link="t">&lt;T&gt;</a><text>, </text><a link="zm">&lt;Zm&gt;</a><text>.</text><a link="t">&lt;T&gt;</a></asmtemplate>
    55    </encoding></iclass></classes>` + sizeTable + `</instructionsection>`
    56  
    57  // faddUnpred is FADD (vectors, unpredicated): FADD <Zd>.<T>, <Zn>.<T>, <Zm>.<T>.
    58  // Its size table only lists H/S/D.
    59  const faddUnpred = `<instructionsection id="fadd_z_zz" title="FADD (vectors, unpredicated)" type="instruction">
    60    <desc><brief><para>Floating-point add (unpredicated)</para></brief></desc>
    61    <docvars>
    62      <docvar key="instr-class" value="sve"/>
    63      <docvar key="mnemonic" value="FADD"/>
    64    </docvars>
    65    <classes><iclass><encoding name="fadd_z_zz">
    66      <asmtemplate><text>FADD  </text><a link="zd">&lt;Zd&gt;</a><text>.</text><a link="t">&lt;T&gt;</a><text>, </text><a link="zn">&lt;Zn&gt;</a><text>.</text><a link="t">&lt;T&gt;</a><text>, </text><a link="zm">&lt;Zm&gt;</a><text>.</text><a link="t">&lt;T&gt;</a></asmtemplate>
    67    </encoding></iclass></classes>
    68    <explanations>
    69      <explanation>
    70        <symbol link="t">&lt;T&gt;</symbol>
    71        <definition><table><tgroup><tbody>
    72          <row><entry class="symbol">H</entry></row>
    73          <row><entry class="symbol">S</entry></row>
    74          <row><entry class="symbol">D</entry></row>
    75        </tbody></tgroup></table></definition>
    76      </explanation>
    77    </explanations>
    78  </instructionsection>`
    79  
    80  func parse(t *testing.T, x string) *Instruction {
    81  	t.Helper()
    82  	var ip xmlspec.InstructionParsed
    83  	if err := xml.Unmarshal([]byte(x), &ip); err != nil {
    84  		t.Fatalf("unmarshal: %v", err)
    85  	}
    86  	return &Instruction{Instruction: ip.Instruction}
    87  }
    88  
    89  // briefInst builds a minimal SVE instruction with the given mnemonic and brief
    90  // description, for testing signedness classification.
    91  func briefInst(t *testing.T, mnemonic, brief string) *Instruction {
    92  	t.Helper()
    93  	return parse(t, `<instructionsection id="x" title="x" type="instruction">
    94  	  <desc><brief><para>`+brief+`</para></brief></desc>
    95  	  <classes><iclass>
    96  	    <docvars><docvar key="instr-class" value="sve"/><docvar key="mnemonic" value="`+mnemonic+`"/></docvars>
    97  	  </iclass></classes>
    98  	</instructionsection>`)
    99  }
   100  
   101  func TestSignedness(t *testing.T) {
   102  	cases := []struct{ mn, brief, want string }{
   103  		// Agnostic: no "signed"/"unsigned" in the brief.
   104  		{"ADD", "Add (predicated)", ""},
   105  		{"MUL", "Multiply (unpredicated)", ""},
   106  		{"EOR", "Bitwise exclusive-OR (predicated)", ""},
   107  		// A signed/unsigned *immediate* is about the immediate, not the lane.
   108  		{"DUP", "Move signed integer immediate to vector elements", ""},
   109  		// "sign bits" is not the word "signed".
   110  		{"CLS", "Count leading sign bits (predicated)", ""},
   111  		// Signedness-specific from the brief.
   112  		{"SMAX", "Signed maximum (predicated)", "int"},
   113  		{"UMAX", "Unsigned maximum (predicated)", "uint"},
   114  		{"SQDMULH", "Signed saturating doubling multiply high (unpredicated)", "int"},
   115  		{"SCVTF", "Signed integer convert to floating-point (predicated)", "int"},
   116  		// Shift family and FLOGB name signedness differently -> explicit.
   117  		{"ASR", "Arithmetic shift right (predicated)", "int"},
   118  		{"LSR", "Logical shift right (predicated)", "uint"},
   119  		{"FLOGB", "Floating-point base 2 logarithm as integer (predicated)", "int"},
   120  	}
   121  	for _, c := range cases {
   122  		if got := briefInst(t, c.mn, c.brief).signedness(); got != c.want {
   123  			t.Errorf("%s (%q): signedness=%q, want %q", c.mn, c.brief, got, c.want)
   124  		}
   125  	}
   126  }
   127  
   128  func TestIsFloatBrief(t *testing.T) {
   129  	cases := []struct {
   130  		brief string
   131  		want  bool
   132  	}{
   133  		{"Floating-point add (predicated)", true},
   134  		{"Double-precision convert to single-precision, rounding to odd", true}, // FCVTX
   135  		{"Half-precision multiply-add to single-precision", true},               // FMLALT
   136  		{"8-bit floating-point convert to BFloat16", true},                      // BF1CVT
   137  		{"Add (predicated)", false},                                             // ADD
   138  		{"Multiply (unpredicated)", false},                                      // MUL
   139  		{"Scalar index of first true predicate element (predicated)", false},    // FIRSTP: F-prefixed but integer
   140  		{"Count leading sign bits (predicated)", false},                         // CLS
   141  	}
   142  	for _, c := range cases {
   143  		if got := isFloatBrief(c.brief); got != c.want {
   144  			t.Errorf("isFloatBrief(%q) = %v, want %v", c.brief, got, c.want)
   145  		}
   146  	}
   147  }
   148  
   149  func TestMnemonicAndClass(t *testing.T) {
   150  	inst := parse(t, addUnpred)
   151  	if got := inst.mnemonic(); got != "ADD" {
   152  		t.Errorf("mnemonic = %q, want ADD", got)
   153  	}
   154  	if !inst.isSVE() {
   155  		t.Errorf("isSVE = false, want true")
   156  	}
   157  	if got := inst.cpuFeature(); got != "SVE" {
   158  		t.Errorf("cpuFeature = %q, want SVE", got)
   159  	}
   160  }
   161  
   162  func bitsOf(rows []arngRow) []int {
   163  	var b []int
   164  	for _, r := range rows {
   165  		b = append(b, r.bits)
   166  	}
   167  	return b
   168  }
   169  
   170  func TestArrangements(t *testing.T) {
   171  	if got := bitsOf(parse(t, addUnpred).resolveArrangementTable("t")); !reflect.DeepEqual(got, []int{8, 16, 32, 64}) {
   172  		t.Errorf("ADD <T> domain = %v, want [8 16 32 64]", got)
   173  	}
   174  	if got := bitsOf(parse(t, faddUnpred).resolveArrangementTable("t")); !reflect.DeepEqual(got, []int{16, 32, 64}) {
   175  		t.Errorf("FADD <T> domain = %v, want [16 32 64]", got)
   176  	}
   177  }
   178  
   179  func TestOperands(t *testing.T) {
   180  	ops := parse(t, addUnpred).operands()
   181  	var got []string
   182  	for _, op := range ops {
   183  		got = append(got, op.Type.String()+":"+op.role)
   184  	}
   185  	want := []string{"ZReg:destination", "ZReg:op0", "ZReg:op1"}
   186  	if !reflect.DeepEqual(got, want) {
   187  		t.Errorf("operands = %v, want %v", got, want)
   188  	}
   189  }
   190  
   191  func TestEmitAllUnpredicated(t *testing.T) {
   192  	// ADD: int|uint × {8,16,32,64} = 8 defs.
   193  	defs := parse(t, addUnpred).emitAll()
   194  	if len(defs) != 8 {
   195  		t.Fatalf("ADD emitAll = %d defs, want 8", len(defs))
   196  	}
   197  	s := defs[0].String()
   198  	for _, want := range []string{"ZADD", "arm64", "SVE", "elemBits"} {
   199  		if !strings.Contains(s, want) {
   200  			t.Errorf("emitted def missing %q:\n%s", want, s)
   201  		}
   202  	}
   203  
   204  	// FADD: float × {16,32,64} = 3 defs.
   205  	if got := len(parse(t, faddUnpred).emitAll()); got != 3 {
   206  		t.Errorf("FADD emitAll = %d defs, want 3", got)
   207  	}
   208  }
   209  
   210  func TestOperandsPredicated(t *testing.T) {
   211  	// ADD <Zdn>.<T>, <Pg>/M, <Zdn>.<T>, <Zm>.<T>: the first <Zdn> is the
   212  	// destination, the governing predicate becomes a mask, and the repeated
   213  	// <Zdn> is the in-place source input.
   214  	ops := parse(t, addPred).operands()
   215  	var got []string
   216  	for _, op := range ops {
   217  		got = append(got, op.Class+":"+op.role)
   218  	}
   219  	want := []string{"vreg:destination", "mask:mask", "vreg:op0", "vreg:op1"}
   220  	if !reflect.DeepEqual(got, want) {
   221  		t.Errorf("predicated operands = %v, want %v", got, want)
   222  	}
   223  	if !ops[0].resultInArg0() {
   224  		t.Errorf("expected <Zdn> destination to be result-in-arg0")
   225  	}
   226  }
   227  
   228  // maskPredications returns the predication qualifier of every mask operand in
   229  // the def's inputs.
   230  func maskPredications(t *testing.T, d *unify.Value) []string {
   231  	t.Helper()
   232  	var op struct {
   233  		In []struct {
   234  			Class       string
   235  			Predication *string
   236  		} `unify:"in"`
   237  	}
   238  	if err := d.Decode(&op); err != nil {
   239  		t.Fatal(err)
   240  	}
   241  	var got []string
   242  	for _, in := range op.In {
   243  		if in.Class == "mask" && in.Predication != nil {
   244  			got = append(got, *in.Predication)
   245  		}
   246  	}
   247  	return got
   248  }
   249  
   250  func TestEmitAllPredicated(t *testing.T) {
   251  	// The governing-predicate ADD enumerates the same int|uint × {8,16,32,64}
   252  	// arrangements, and each def carries the predicate as a mandatory merging
   253  	// (/M) mask *input* (not an inVariant).
   254  	defs := parse(t, addPred).emitAll()
   255  	if len(defs) != 8 {
   256  		t.Fatalf("predicated ADD emitAll = %d defs, want 8", len(defs))
   257  	}
   258  	for _, d := range defs {
   259  		if got := maskPredications(t, d); !reflect.DeepEqual(got, []string{"M"}) {
   260  			t.Errorf("want one mask input with predication M, got %v", got)
   261  		}
   262  	}
   263  }
   264  
   265  // fabsMZ mimics FABS: one iclass with two encodings, /M (merging) and /Z
   266  // (zeroing), so both predication variants must be emitted.
   267  const fabsMZ = `<instructionsection id="fabs_z_p_z" title="FABS -- A64" type="instruction">
   268    <desc><brief><para>Floating-point absolute value (predicated)</para></brief></desc>
   269    <classes><iclass>
   270      <docvars><docvar key="instr-class" value="sve"/><docvar key="mnemonic" value="FABS"/></docvars>
   271      <encoding name="fabs_z_p_z_m">
   272        <asmtemplate><text>FABS  </text><a link="zd">&lt;Zd&gt;</a><text>.</text><a link="t">&lt;T&gt;</a><text>, </text><a link="pg">&lt;Pg&gt;</a><text>/M, </text><a link="zn">&lt;Zn&gt;</a><text>.</text><a link="t">&lt;T&gt;</a></asmtemplate>
   273      </encoding>
   274      <encoding name="fabs_z_p_z_z">
   275        <asmtemplate><text>FABS  </text><a link="zd">&lt;Zd&gt;</a><text>.</text><a link="t">&lt;T&gt;</a><text>, </text><a link="pg">&lt;Pg&gt;</a><text>/Z, </text><a link="zn">&lt;Zn&gt;</a><text>.</text><a link="t">&lt;T&gt;</a></asmtemplate>
   276      </encoding>
   277    </iclass></classes>
   278    <explanations><explanation>
   279      <symbol link="t">&lt;T&gt;</symbol>
   280      <definition><table><tgroup><tbody>
   281        <row><entry class="bitfield">01</entry><entry class="symbol">H</entry></row>
   282        <row><entry class="bitfield">10</entry><entry class="symbol">S</entry></row>
   283        <row><entry class="bitfield">11</entry><entry class="symbol">D</entry></row>
   284      </tbody></tgroup></table></definition>
   285    </explanation></explanations>
   286  </instructionsection>`
   287  
   288  func TestPredicationMergingAndZeroing(t *testing.T) {
   289  	// FABS has two encodings in one iclass (/M and /Z); both forms are emitted.
   290  	// float × {16,32,64} × {M,Z} = 6 defs.
   291  	defs := parse(t, fabsMZ).emitAll()
   292  	if len(defs) != 6 {
   293  		t.Fatalf("FABS emitAll = %d defs, want 6", len(defs))
   294  	}
   295  	seen := map[string]int{}
   296  	for _, d := range defs {
   297  		for _, p := range maskPredications(t, d) {
   298  			seen[p]++
   299  		}
   300  	}
   301  	if seen["M"] != 3 || seen["Z"] != 3 {
   302  		t.Errorf("want 3 M and 3 Z variants, got %v", seen)
   303  	}
   304  }
   305  
   306  // movprfxZM mimics MOVPRFX <Zd>.<T>, <Pg>/<ZM>, <Zn>.<T>: a single encoding whose
   307  // predication bit selects merging or zeroing, so both are emitted.
   308  const movprfxZM = `<instructionsection id="movprfx_z_p_z" title="MOVPRFX -- A64" type="instruction">
   309    <desc><brief><para>Move prefix (predicated)</para></brief></desc>
   310    <classes><iclass>
   311      <docvars><docvar key="instr-class" value="sve"/><docvar key="mnemonic" value="MOVPRFX"/></docvars>
   312      <encoding name="movprfx_z_p_z_">
   313        <asmtemplate><text>MOVPRFX  </text><a link="zd">&lt;Zd&gt;</a><text>.</text><a link="t">&lt;T&gt;</a><text>, </text><a link="pg">&lt;Pg&gt;</a><text>/</text><a link="zm">&lt;ZM&gt;</a><text>, </text><a link="zn">&lt;Zn&gt;</a><text>.</text><a link="t">&lt;T&gt;</a></asmtemplate>
   314      </encoding>
   315    </iclass></classes>
   316    <explanations><explanation>
   317      <symbol link="t">&lt;T&gt;</symbol>
   318      <definition><table><tgroup><tbody>
   319        <row><entry class="bitfield">00</entry><entry class="symbol">B</entry></row>
   320        <row><entry class="bitfield">01</entry><entry class="symbol">H</entry></row>
   321        <row><entry class="bitfield">10</entry><entry class="symbol">S</entry></row>
   322        <row><entry class="bitfield">11</entry><entry class="symbol">D</entry></row>
   323      </tbody></tgroup></table></definition>
   324    </explanation></explanations>
   325  </instructionsection>`
   326  
   327  func TestPredicationZM(t *testing.T) {
   328  	// MOVPRFX is agnostic (a move), so: int|uint × {B,H,S,D} × {M,Z} = 16 defs.
   329  	defs := parse(t, movprfxZM).emitAll()
   330  	if len(defs) != 16 {
   331  		t.Fatalf("MOVPRFX emitAll = %d defs, want 16", len(defs))
   332  	}
   333  	seen := map[string]int{}
   334  	for _, d := range defs {
   335  		for _, p := range maskPredications(t, d) {
   336  			seen[p]++
   337  		}
   338  	}
   339  	if seen["M"] != 8 || seen["Z"] != 8 {
   340  		t.Errorf("want 8 M and 8 Z variants, got %v", seen)
   341  	}
   342  }
   343  
   344  // sunpkhi mimics SUNPKHI <Zd>.<T>, <Zn>.<Tb>: the destination is one element
   345  // size wider than the source, and both select on the same "size" field. The two
   346  // symbols use bitfield-keyed tables so they line up by size.
   347  const sunpkhi = `<instructionsection id="sunpkhi_z_z" title="SUNPKHI -- A64" type="instruction">
   348    <docvars>
   349      <docvar key="instr-class" value="sve"/>
   350      <docvar key="mnemonic" value="SUNPKHI"/>
   351    </docvars>
   352    <classes><iclass><encoding name="sunpkhi_z_z">
   353      <asmtemplate><text>SUNPKHI  </text><a link="zd">&lt;Zd&gt;</a><text>.</text><a link="t">&lt;T&gt;</a><text>, </text><a link="zn">&lt;Zn&gt;</a><text>.</text><a link="tb">&lt;Tb&gt;</a></asmtemplate>
   354    </encoding></iclass></classes>
   355    <explanations>
   356      <explanation><symbol link="t">&lt;T&gt;</symbol><definition><table><tgroup><tbody>
   357        <row><entry class="bitfield">01</entry><entry class="symbol">H</entry></row>
   358        <row><entry class="bitfield">10</entry><entry class="symbol">S</entry></row>
   359        <row><entry class="bitfield">11</entry><entry class="symbol">D</entry></row>
   360      </tbody></tgroup></table></definition></explanation>
   361      <explanation><symbol link="tb">&lt;Tb&gt;</symbol><definition><table><tgroup><tbody>
   362        <row><entry class="bitfield">01</entry><entry class="symbol">B</entry></row>
   363        <row><entry class="bitfield">10</entry><entry class="symbol">H</entry></row>
   364        <row><entry class="bitfield">11</entry><entry class="symbol">S</entry></row>
   365      </tbody></tgroup></table></definition></explanation>
   366    </explanations>
   367  </instructionsection>`
   368  
   369  func TestNonUniformArrangement(t *testing.T) {
   370  	// int|uint × {H/B, S/H, D/S} = 6 defs, each with the destination one size
   371  	// wider than the source.
   372  	inst := parse(t, sunpkhi)
   373  	defs := inst.emitAll()
   374  	if len(defs) != 6 {
   375  		t.Fatalf("SUNPKHI emitAll = %d defs, want 6", len(defs))
   376  	}
   377  	// In every def the (single) out elemBits must be double the (single) in.
   378  	sawWiden := false
   379  	for _, d := range defs {
   380  		var op struct {
   381  			In  []struct{ ElemBits int } `unify:"in"`
   382  			Out []struct{ ElemBits int } `unify:"out"`
   383  		}
   384  		if err := d.Decode(&op); err != nil {
   385  			t.Fatalf("decode: %v", err)
   386  		}
   387  		if len(op.In) != 1 || len(op.Out) != 1 {
   388  			t.Fatalf("want 1 in + 1 out, got in=%d out=%d", len(op.In), len(op.Out))
   389  		}
   390  		if op.Out[0].ElemBits != 2*op.In[0].ElemBits {
   391  			t.Errorf("out elemBits %d, want 2×in elemBits %d", op.Out[0].ElemBits, op.In[0].ElemBits)
   392  		}
   393  		if op.Out[0].ElemBits == 16 && op.In[0].ElemBits == 8 {
   394  			sawWiden = true
   395  		}
   396  	}
   397  	if !sawWiden {
   398  		t.Errorf("expected an H<-B widening variant")
   399  	}
   400  }
   401  
   402  // saddv mimics SADDV <Dd>, <Pg>, <Zn>.<T>: a horizontal reduction whose scalar
   403  // result <Dd> is a (special, opaque) destination, not an input.
   404  const saddv = `<instructionsection id="saddv_r_p_z" title="SADDV -- A64" type="instruction">
   405    <desc><brief><para>Signed add reduction to scalar</para></brief></desc>
   406    <classes><iclass>
   407      <docvars><docvar key="instr-class" value="sve"/><docvar key="mnemonic" value="SADDV"/></docvars>
   408      <encoding name="saddv_r_p_z_">
   409        <asmtemplate><text>SADDV  </text><a link="dd">&lt;Dd&gt;</a><text>, </text><a link="pg">&lt;Pg&gt;</a><text>, </text><a link="zn">&lt;Zn&gt;</a><text>.</text><a link="t">&lt;T&gt;</a></asmtemplate>
   410      </encoding>
   411    </iclass></classes>
   412    <explanations><explanation>
   413      <symbol link="t">&lt;T&gt;</symbol>
   414      <definition><table><tgroup><tbody>
   415        <row><entry class="bitfield">00</entry><entry class="symbol">B</entry></row>
   416        <row><entry class="bitfield">01</entry><entry class="symbol">H</entry></row>
   417        <row><entry class="bitfield">10</entry><entry class="symbol">S</entry></row>
   418      </tbody></tgroup></table></definition>
   419    </explanation></explanations>
   420  </instructionsection>`
   421  
   422  func TestReductionOutput(t *testing.T) {
   423  	ops := parse(t, saddv).operands()
   424  	var got []string
   425  	for _, op := range ops {
   426  		got = append(got, op.Class+":"+op.role)
   427  	}
   428  	// The scalar result <Dd> is a SIMD&FP register destination, not an input.
   429  	want := []string{"vreg:destination", "mask:mask", "vreg:op0"}
   430  	if !reflect.DeepEqual(got, want) {
   431  		t.Errorf("SADDV operands = %v, want %v", got, want)
   432  	}
   433  	// <Dd> is a fixed 64-bit SIMD&FP scalar output, not the scalable source.
   434  	for _, d := range parse(t, saddv).emitAll() {
   435  		var op struct {
   436  			Out []struct {
   437  				Class string
   438  				Bits  string
   439  				Lanes string
   440  			} `unify:"out"`
   441  		}
   442  		if err := d.Decode(&op); err != nil {
   443  			t.Fatal(err)
   444  		}
   445  		if len(op.Out) != 1 || op.Out[0].Class != "vreg" || op.Out[0].Bits != "64" || op.Out[0].Lanes != "1" {
   446  			t.Errorf("SADDV out = %+v, want one vreg bits=64 lanes=1", op.Out)
   447  		}
   448  	}
   449  }
   450  
   451  // st1b mimics ST1B { <Zt>.<T> }, <Pg>, [<Xn|SP>{, #<imm>, MUL VL}]: a store
   452  // whose single-register list is the data source and whose memory operand (last)
   453  // is the destination.
   454  const st1b = `<instructionsection id="st1b_z_p_bi" title="ST1B -- A64" type="instruction">
   455    <desc><brief><para>Contiguous store bytes from vector (immediate index)</para></brief></desc>
   456    <classes><iclass>
   457      <docvars><docvar key="instr-class" value="sve"/><docvar key="mnemonic" value="ST1B"/></docvars>
   458      <encoding name="st1b_z_p_bi_">
   459        <asmtemplate><text>ST1B  { </text><a link="zt">&lt;Zt&gt;</a><text>.</text><a link="t">&lt;T&gt;</a><text> }, </text><a link="pg">&lt;Pg&gt;</a><text>, [</text><a link="xn">&lt;Xn|SP&gt;</a><text>{, #</text><a link="imm">&lt;imm&gt;</a><text>, MUL VL}]</text></asmtemplate>
   460      </encoding>
   461    </iclass></classes>
   462    <explanations><explanation>
   463      <symbol link="t">&lt;T&gt;</symbol>
   464      <definition><table><tgroup><tbody>
   465        <row><entry class="bitfield">00</entry><entry class="symbol">B</entry></row>
   466        <row><entry class="bitfield">01</entry><entry class="symbol">H</entry></row>
   467        <row><entry class="bitfield">10</entry><entry class="symbol">S</entry></row>
   468        <row><entry class="bitfield">11</entry><entry class="symbol">D</entry></row>
   469      </tbody></tgroup></table></definition>
   470    </explanation></explanations>
   471  </instructionsection>`
   472  
   473  func TestStoreReglist(t *testing.T) {
   474  	ops := parse(t, st1b).operands()
   475  	var got []string
   476  	for _, op := range ops {
   477  		got = append(got, op.Class+":"+op.role)
   478  	}
   479  	// The single-register list unwraps to a vreg (the data source); the memory
   480  	// operand is the store destination. Order follows the source template. The
   481  	// predicate is <Pg>: a governing predicate (role "mask"), even though a
   482  	// store writes no /Z or /M qualifier.
   483  	want := []string{"vreg:op0", "mask:mask", "mem:destination"}
   484  	if !reflect.DeepEqual(got, want) {
   485  		t.Errorf("ST1B operands = %v, want %v", got, want)
   486  	}
   487  	for _, d := range parse(t, st1b).emitAll() {
   488  		var op struct {
   489  			In []struct {
   490  				Class      string
   491  				ListNumber *string
   492  			} `unify:"in"`
   493  			Out []struct{ Class string } `unify:"out"`
   494  		}
   495  		if err := d.Decode(&op); err != nil {
   496  			t.Fatal(err)
   497  		}
   498  		if len(op.Out) != 1 || op.Out[0].Class != "mem" {
   499  			t.Errorf("ST1B out = %+v, want one mem", op.Out)
   500  		}
   501  		// The data vreg is a register list (distinct assembler encoding); the
   502  		// mask is not.
   503  		for _, in := range op.In {
   504  			isList := in.ListNumber != nil
   505  			if want := in.Class == "vreg"; isList != want {
   506  				t.Errorf("in %q listNumber present = %v, want %v", in.Class, isList, want)
   507  			}
   508  		}
   509  	}
   510  }
   511  
   512  func TestMemoryOperandClassified(t *testing.T) {
   513  	// A bracketed operand is classified as a single "mem" class (not mistaken
   514  	// for a scalable vector); no addressing-mode sub-classification.
   515  	ops := operands("LD1B  <Zt>.<T>, <Pg>/Z, [<Xn|SP>, #<imm>, MUL VL]")
   516  	if !hasClass(ops, "mem") {
   517  		t.Fatalf("expected a mem operand, got %v", ops)
   518  	}
   519  	for _, op := range ops {
   520  		if op.Class == "vreg" && strings.Contains(op.regName, "Xn") {
   521  			t.Errorf("memory address misclassified as vreg: %+v", op)
   522  		}
   523  	}
   524  }
   525  

View as plain text