Source file src/simd/archsimd/_gen/simdgen/sve/operands.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  	"fmt"
     9  	"regexp"
    10  	"strings"
    11  
    12  	"golang.org/x/arch/arm64/instgen/xmlspec"
    13  )
    14  
    15  // arngValueRe matches an arrangement symbol's displayed value: the vector forms
    16  // <T>, <Ta>, <Tb>, and the <V> size specifier of a SIMD&FP scalar (<V><d>). Its
    17  // <a> link identifies the size table that gives this operand's element widths
    18  // (see Instruction.resolveArrangementTable).
    19  var arngValueRe = regexp.MustCompile(`^<(T[a-z]*|V)>$`)
    20  
    21  // fixedArngRe matches a hardcoded element specifier, e.g. the ".D" in <Zm>.D.
    22  var fixedArngRe = regexp.MustCompile(`\.([BHSD])\b`)
    23  
    24  // simdFPRe matches a SIMD&FP scalar register: a fixed-width form (<Dd>, <Sn>,
    25  // <Hd>, <Bd>, <Qd>) or an element-sized form (<V><d>, <V><n>). These hold a
    26  // single value (a reduction result, or a DUP source), not a scalable vector.
    27  var simdFPRe = regexp.MustCompile(`^(<[BHSDQ][a-z]>|<V><[a-z]>)$`)
    28  
    29  // OperandType classifies an SVE instruction operand.
    30  type OperandType int
    31  
    32  const (
    33  	// OperandZReg is a scalable vector register (Z), e.g. <Zd>.<T>, <Zn>.<T>.
    34  	// It has no fixed total bit width: the width is the implementation-defined
    35  	// vector length. Only its element type and element width are known.
    36  	OperandZReg OperandType = iota
    37  	// OperandPReg is a scalable predicate register (P), e.g. <Pg>/M, <Pd>.<T>.
    38  	// A predicate is modeled as a Go mask value.
    39  	OperandPReg
    40  	// OperandGReg is a general-purpose scalar register (W/X/R).
    41  	OperandGReg
    42  	// OperandVFP is a SIMD&FP scalar register (<Dd>, <V><d>, ...): a single
    43  	// fixed-width value, such as a horizontal reduction's result (SADDV <Dd>) or
    44  	// a DUP scalar source. Unlike a Z register it is not scalable.
    45  	OperandVFP
    46  	// OperandImm is an immediate.
    47  	OperandImm
    48  	// OperandMem is a memory operand, e.g. [<Xn|SP>{, #<imm>, MUL VL}] or a
    49  	// gather/scatter address like [<Xn|SP>, <Zm>.D, SXTW]. simdgen does not yet
    50  	// distinguish the memory addressing modes; they are all one "mem" class.
    51  	OperandMem
    52  	// OperandList is a register list, e.g. { <Zt>.B } or { <Zt1>.D-<Zt2>.D }.
    53  	// TODO: register lists are not modeled yet; instructions carrying one are
    54  	// skipped (see classify).
    55  	OperandList
    56  	// OperandSpecial is a recognized but not-yet-detailed operand: an indexed
    57  	// register (<Zm>.<T>[<index>]), a register with an optional modifier
    58  	// ({, <pattern>}), or a special token (<prfop>, <vl>, <pattern>, <const>,
    59  	// <mod>, and NEON-style <Vd>/<Dd> reduction results).
    60  	OperandSpecial
    61  	// OperandUnknown is a token the classifier could not place at all; an anomaly.
    62  	OperandUnknown
    63  )
    64  
    65  func (t OperandType) String() string {
    66  	switch t {
    67  	case OperandZReg:
    68  		return "ZReg"
    69  	case OperandPReg:
    70  		return "PReg"
    71  	case OperandGReg:
    72  		return "GReg"
    73  	case OperandVFP:
    74  		return "VFP"
    75  	case OperandImm:
    76  		return "Imm"
    77  	case OperandMem:
    78  		return "Mem"
    79  	case OperandList:
    80  		return "List"
    81  	case OperandSpecial:
    82  		return "Special"
    83  	default:
    84  		return "Unknown"
    85  	}
    86  }
    87  
    88  // Operand is an SVE instruction operand instantiated for a concrete element size.
    89  type Operand struct {
    90  	Type     OperandType
    91  	Class    string // "vreg", "mask", "greg", "immediate", "mem", "reglist", "special"
    92  	BaseType string // "int", "uint", "float" (for vreg/mask/greg)
    93  	ElemBits int    // element width in bits (8/16/32/64); 0 if unsized
    94  	// Bits and Lanes are set for a fixed-width scalar register — a general-purpose
    95  	// greg (<Xd>) or a SIMD&FP vreg (<Dd>): the total register width and lane
    96  	// count (always 1). A scalable Z-vector leaves them 0 and is marked
    97  	// "scalable" in the emitted def instead.
    98  	Bits  int
    99  	Lanes int
   100  
   101  	// Predication is "M" (merging) or "Z" (zeroing) for governing predicates,
   102  	// otherwise "".
   103  	Predication string
   104  	// AsmPos is the position in the assembly syntax (0 for the destination
   105  	// register, 1+ for inputs). It mirrors the source template order and is the
   106  	// field simdgen uses to order operands.
   107  	AsmPos int
   108  	// Raw is the source operand token, retained for deferred (mem/list/special)
   109  	// and unknown operands so diagnostics can name what was skipped.
   110  	Raw string
   111  
   112  	// role is the operand's internal role: "destination", "op0"/"op1"/..., or
   113  	// "mask" (a governing predicate). It drives out/in/inVariant partitioning at
   114  	// emit time but is NOT emitted (simdgen orders operands by AsmPos, so a role
   115  	// field in the YAML would be redundant).
   116  	role string
   117  	// arngLink is the <a> link of this operand's arrangement symbol (<T>/<Ta>/
   118  	// <Tb>), used to resolve its per-operand element widths. Empty if the
   119  	// operand has a fixed or no arrangement.
   120  	arngLink string
   121  	// fixedElem is a hardcoded element width (from e.g. ".D"), or 0.
   122  	fixedElem int
   123  	// fixedBits is the fixed total width of a SIMD&FP scalar named by a size
   124  	// letter (<Dd> -> 64, <Sd> -> 32, ...), or 0 for an element-sized <V><d>.
   125  	fixedBits int
   126  	// isList reports that this register came from a single-register list
   127  	// ("{ <Zt>.<T> }"). It is a distinct assembler encoding from a bare register,
   128  	// so it is preserved (emitted as listNumber) even though the register is
   129  	// otherwise handled like any vreg.
   130  	isList bool
   131  	// regName is the inner register symbol, e.g. "Zdn", "Zm", "Pg".
   132  	regName string
   133  }
   134  
   135  // resultInArg0 reports whether this destination register is also read, i.e. it
   136  // is written in place (an ARM <Zdn>/<Zda>-style operand).
   137  func (op *Operand) resultInArg0() bool {
   138  	return op.role == "destination" && isInPlaceReg(op.regName)
   139  }
   140  
   141  // aElem is a single <a> symbol from an assembly template: its displayed value
   142  // and its link. The link, not the value, is the stable key used to resolve a
   143  // symbol's definition (see Instruction.findExplanation).
   144  //
   145  // For example, in the template "ADD <Zdn>.<T>, ..." the operand "<Zdn>.<T>"
   146  // contributes two <a> elements:
   147  //
   148  //	{value: "<Zdn>", link: "Zdn"}   // the register symbol
   149  //	{value: "<T>",   link: "T__3"}  // the arrangement symbol
   150  type aElem struct {
   151  	value string
   152  	link  string
   153  }
   154  
   155  // rawTok is one operand's raw text plus the <a> symbols it contains, before
   156  // classification. The <a> links let us resolve each operand's arrangement.
   157  //
   158  // For "ADD <Zdn>.<T>, <Pg>/M, <Zdn>.<T>, <Zm>.<T>", the third operand tokenizes
   159  // to:
   160  //
   161  //	rawTok{
   162  //	    text:   "<Zdn>.<T>",
   163  //	    asmPos: 2,                 // 0 = destination, 1+ = following operands
   164  //	    aElems: [{"<Zdn>","Zdn"}, {"<T>","T__3"}],
   165  //	}
   166  type rawTok struct {
   167  	text   string
   168  	asmPos int
   169  	aElems []aElem
   170  }
   171  
   172  // tok is a rawTok after classification, before it is instantiated for
   173  // a concrete element size. Examples of the interesting fields:
   174  //
   175  //	"<Zdn>.<T>"          -> {operandType: OperandZReg,  isDestination: true,
   176  //	                         regName: "Zdn", arngLink: "T__3"}
   177  //	"<Zm>.<T>"           -> {operandType: OperandZReg,  isDestination: false,
   178  //	                         regName: "Zm",  arngLink: "T__3"}
   179  //	"<Pg>/M"             -> {operandType: OperandPReg,  predication: "M",
   180  //	                         regName: "Pg"}   // governing predicate ("Z"/"MZ" too)
   181  //	"<Zt>.D"             -> {operandType: OperandZReg,  fixedElem: 64}
   182  //	                         // hardcoded arrangement, so no arngLink
   183  //	"#<imm>"             -> {operandType: OperandImm}
   184  //	"[<Xn|SP>{, #<imm>}]"-> {operandType: OperandMem}
   185  //	"<Zm>.<T>[<index>]"  -> {operandType: OperandSpecial} // indexed, not modeled
   186  type tok struct {
   187  	// text is the raw operand token, e.g. "<Zdn>.<T>".
   188  	text string
   189  	// asmPos is the position in the assembly syntax (0 = destination, 1+ = the
   190  	// following operands), mirroring the template order.
   191  	asmPos int
   192  	// operandType is the classification (OperandZReg, OperandPReg, OperandMem,
   193  	// OperandSpecial, ...).
   194  	operandType OperandType
   195  	// isDestination is true when the register is written (an ARM 'd'-role symbol
   196  	// such as <Zd>, <Zdn>, <Pd>).
   197  	isDestination bool
   198  	// predication is "M" (merging), "Z" (zeroing), or "MZ" (a <Pg>/<ZM> encoding
   199  	// selecting either) for a governing predicate; "" otherwise.
   200  	predication string
   201  	// regName is the inner register symbol, e.g. "Zdn", "Zm", "Pg".
   202  	regName string
   203  	// arngLink is the <a> link of this operand's variable arrangement symbol
   204  	// (<T>/<Ta>/<Tb>, or <V> for a SIMD&FP scalar), used to resolve its element
   205  	// widths; "" if the arrangement is fixed or absent.
   206  	arngLink string
   207  	// fixedElem is a hardcoded element width in bits from a literal ".B"/".H"/
   208  	// ".S"/".D" (8/16/32/64), or 0.
   209  	fixedElem int
   210  	// fixedBits is the fixed total width of a SIMD&FP scalar named by a size
   211  	// letter (<Dd> -> 64, <Sd> -> 32, ...), or 0 for an element-sized <V><d>.
   212  	fixedBits int
   213  	// isList reports that this register came from a single-register list
   214  	isList bool
   215  }
   216  
   217  // operandsFromTextA parses operands from an assembly template's <text>/<a>
   218  // sequence, preserving each operand's arrangement-symbol link.
   219  func operandsFromTextA(textA []xmlspec.TextA) []Operand {
   220  	return buildOperandList(classifyToks(tokenizeTextA(textA)))
   221  }
   222  
   223  // operands parses operands from a flattened template string. It cannot recover
   224  // <a> links, so arrangement symbols resolve to empty links; it is used for
   225  // classification-only paths and tests. The real loader path uses
   226  // operandsFromTextA.
   227  func operands(asmTemplate string) []Operand {
   228  	return buildOperandList(classifyToks(tokenizeString(asmTemplate)))
   229  }
   230  
   231  // tokenizeTextA splits a <text>/<a> sequence into operand tokens on top-level
   232  // commas, stripping the leading mnemonic and recording each <a> symbol.
   233  func tokenizeTextA(textA []xmlspec.TextA) []rawTok {
   234  	var toks []rawTok
   235  	cur := rawTok{}
   236  	depth := 0
   237  	started := false // have we passed the mnemonic word?
   238  	flush := func() {
   239  		cur.text = strings.TrimSpace(cur.text)
   240  		if cur.text != "" || len(cur.aElems) > 0 {
   241  			cur.asmPos = len(toks)
   242  			toks = append(toks, cur)
   243  		}
   244  		cur = rawTok{}
   245  	}
   246  	for _, ta := range textA {
   247  		if ta.Link != "" {
   248  			cur.text += ta.Value
   249  			cur.aElems = append(cur.aElems, aElem{strings.TrimSpace(ta.Value), ta.Link})
   250  			started = true
   251  			continue
   252  		}
   253  		s := ta.Value
   254  		if !started {
   255  			// Strip the mnemonic: keep everything after the first space.
   256  			if i := strings.IndexByte(s, ' '); i >= 0 {
   257  				s = s[i:]
   258  			} else {
   259  				s = ""
   260  			}
   261  			started = true
   262  		}
   263  		for _, r := range s {
   264  			switch r {
   265  			case '[', '{':
   266  				depth++
   267  			case ']', '}':
   268  				depth--
   269  			case ',':
   270  				if depth == 0 {
   271  					flush()
   272  					continue
   273  				}
   274  			}
   275  			cur.text += string(r)
   276  		}
   277  	}
   278  	flush()
   279  	return toks
   280  }
   281  
   282  // tokenizeString splits a flattened template string into operand tokens. It has
   283  // no <a> link information.
   284  func tokenizeString(template string) []rawTok {
   285  	template = stripMnemonic(template)
   286  	var toks []rawTok
   287  	depth := 0
   288  	var cur strings.Builder
   289  	flush := func() {
   290  		if s := strings.TrimSpace(cur.String()); s != "" {
   291  			toks = append(toks, rawTok{text: s, asmPos: len(toks)})
   292  		}
   293  		cur.Reset()
   294  	}
   295  	for _, r := range template {
   296  		switch r {
   297  		case '[', '{':
   298  			depth++
   299  		case ']', '}':
   300  			depth--
   301  		case ',':
   302  			if depth == 0 {
   303  				flush()
   304  				continue
   305  			}
   306  		}
   307  		cur.WriteRune(r)
   308  	}
   309  	flush()
   310  	return toks
   311  }
   312  
   313  // stripMnemonic removes the leading mnemonic from an assembly template. A
   314  // template with no space is a mnemonic-only (nullary) instruction.
   315  func stripMnemonic(template string) string {
   316  	if _, after, ok := strings.Cut(strings.TrimSpace(template), " "); ok {
   317  		return strings.TrimSpace(after)
   318  	}
   319  	return ""
   320  }
   321  
   322  // classifyToks classifies each raw token and attaches its arrangement source.
   323  func classifyToks(toks []rawTok) []tok {
   324  	parsed := make([]tok, 0, len(toks))
   325  	for _, t := range toks {
   326  		p := classifyText(t.text, t.asmPos)
   327  		// Per-operand arrangement: could be a variable arrangement symbol (<T>/<Ta>/<Tb>)
   328  		// or a fixed element, or none, e.g. for a greg.
   329  		for _, a := range t.aElems {
   330  			if arngValueRe.MatchString(a.value) {
   331  				p.arngLink = a.link
   332  			}
   333  		}
   334  		if p.arngLink == "" {
   335  			if m := fixedArngRe.FindStringSubmatch(t.text); m != nil {
   336  				p.fixedElem = elemLetterBits(m[1])
   337  			}
   338  		}
   339  		parsed = append(parsed, p)
   340  	}
   341  	return parsed
   342  }
   343  
   344  // classifyText determines an operand's type, destination-ness, predication and
   345  // register symbol from its text.
   346  //
   347  // A register token counts as "clean" only if it has no index or optional
   348  // modifier ('[' or '{'). Indexed/modified registers and other angle-bracket
   349  // tokens (<prfop>, <vl>, <mod>, <Vd>, ...) are OperandSpecial; anything else is
   350  // OperandUnknown.
   351  func classifyText(text string, asmPos int) tok {
   352  	p := tok{text: text, asmPos: asmPos}
   353  	// A single-register list ("{ <Zt>.<T> }") is treated as its inner register
   354  	// (but flagged, as it is a distinct assembler encoding); multi-register lists
   355  	// remain OperandList (deferred).
   356  	reg := text
   357  	if inner, ok := singleRegList(text); ok {
   358  		reg = inner
   359  		p.isList = true
   360  	}
   361  	clean := !strings.ContainsAny(reg, "[{")
   362  	switch {
   363  	case strings.HasPrefix(reg, "["):
   364  		p.operandType = OperandMem
   365  	case strings.HasPrefix(reg, "{"):
   366  		p.operandType = OperandList
   367  	case strings.HasPrefix(reg, "#"), strings.HasPrefix(reg, "<const>"):
   368  		p.operandType = OperandImm
   369  	case simdFPRe.MatchString(reg):
   370  		// A SIMD&FP scalar register: a reduction result <Dd>/<V><d> or a DUP
   371  		// source <V><n>. Its width is fixed by the size letter, or element-sized
   372  		// for the <V> form (resolved via its <a> link like <T>).
   373  		p.operandType = OperandVFP
   374  		p.regName = regSymbol(reg)
   375  		p.isDestination = isDestinationReg(p.regName) || strings.Contains(reg, "<d>")
   376  		p.fixedBits = simdFPLetterBits(reg)
   377  	case clean && strings.HasPrefix(reg, "<Z"):
   378  		p.operandType = OperandZReg
   379  		p.regName = regSymbol(reg)
   380  		p.isDestination = isDestinationReg(p.regName)
   381  	case clean && strings.HasPrefix(reg, "<P"):
   382  		p.operandType = OperandPReg
   383  		p.regName = regSymbol(reg)
   384  		p.isDestination = isDestinationReg(p.regName)
   385  		switch {
   386  		case strings.Contains(reg, "/<ZM>"):
   387  			// A single encoding (MOVPRFX) whose bit selects merging or zeroing.
   388  			p.predication = "MZ"
   389  		case strings.HasSuffix(reg, "/M"):
   390  			p.predication = "M"
   391  		case strings.HasSuffix(reg, "/Z"):
   392  			p.predication = "Z"
   393  		}
   394  	case clean && (strings.HasPrefix(reg, "<W") || strings.HasPrefix(reg, "<X") || strings.HasPrefix(reg, "<R")):
   395  		p.operandType = OperandGReg
   396  		p.regName = regSymbol(reg)
   397  		p.isDestination = isDestinationReg(p.regName)
   398  		p.fixedBits = gregLetterBits(reg)
   399  	case strings.HasPrefix(reg, "<"):
   400  		p.operandType = OperandSpecial
   401  		// A special operand can still be a destination, e.g. an indexed
   402  		// destination <Zd>.<T>[<index>].
   403  		p.regName = regSymbol(reg)
   404  		p.isDestination = isDestinationReg(p.regName) || strings.Contains(reg, "<d>")
   405  	default:
   406  		p.operandType = OperandUnknown
   407  	}
   408  	return p
   409  }
   410  
   411  // singleRegList reports whether text is a single-register list like
   412  // "{ <Zt>.<T> }" and, if so, returns its inner register token. Multi-register
   413  // lists (a comma-separated set or a "-" range) return false and stay opaque.
   414  func singleRegList(text string) (string, bool) {
   415  	if !strings.HasPrefix(text, "{") || !strings.HasSuffix(text, "}") {
   416  		return "", false
   417  	}
   418  	inner := strings.TrimSpace(text[1 : len(text)-1])
   419  	if strings.ContainsAny(inner, ",-") { // multiple registers or a range
   420  		return "", false
   421  	}
   422  	return inner, true
   423  }
   424  
   425  // simdFPLetterBits returns the fixed width of a size-lettered SIMD&FP scalar
   426  // register (<Bd>=8, <Hd>=16, <Sd>=32, <Dd>=64, <Qd>=128), or 0 for the
   427  // element-sized <V><d> form (whose width comes from its <V> arrangement link).
   428  func simdFPLetterBits(text string) int {
   429  	if len(text) < 2 {
   430  		return 0
   431  	}
   432  	switch text[1] {
   433  	case 'B':
   434  		return 8
   435  	case 'H':
   436  		return 16
   437  	case 'S':
   438  		return 32
   439  	case 'D':
   440  		return 64
   441  	case 'Q':
   442  		return 128
   443  	}
   444  	return 0
   445  }
   446  
   447  // gregLetterBits returns the width of a general-purpose scalar register from its
   448  // size letter (<Wd>=32, <Xd>=64), or 0 when the width is not fixed by the name
   449  // (e.g. the width-variable <R> form).
   450  func gregLetterBits(text string) int {
   451  	if len(text) < 2 {
   452  		return 0
   453  	}
   454  	switch text[1] {
   455  	case 'W':
   456  		return 32
   457  	case 'X':
   458  		return 64
   459  	}
   460  	return 0
   461  }
   462  
   463  // regSymbol extracts the inner register symbol from a token, e.g. "<Zdn>.<T>" ->
   464  // "Zdn", "<Pg>/M" -> "Pg".
   465  func regSymbol(text string) string {
   466  	if i := strings.IndexByte(text, '<'); i >= 0 {
   467  		text = text[i+1:]
   468  	}
   469  	if i := strings.IndexByte(text, '>'); i >= 0 {
   470  		text = text[:i]
   471  	}
   472  	return text
   473  }
   474  
   475  // isDestinationReg reports whether a register symbol names a destination
   476  // register. The destination role letter 'd' appears either right after the
   477  // class letter (Zd, Zda, Zdn), or as the trailing role letter (Pd, Wd, Xd, PNd).
   478  func isDestinationReg(name string) bool {
   479  	if len(name) < 2 {
   480  		return false
   481  	}
   482  	return name[1] == 'd' || name[len(name)-1] == 'd'
   483  }
   484  
   485  // isInPlaceReg reports whether a destination register symbol is also a source
   486  // (read-modify-write), such as <Zdn> or <Zda>. A bare <Zd> is a pure output.
   487  func isInPlaceReg(name string) bool {
   488  	return len(name) >= 3 && name[1] == 'd'
   489  }
   490  
   491  // buildOperandList lowers tokens into Operands ordered as outputs then
   492  // inputs, assigning roles and handling read-modify-write destinations.
   493  //
   494  // Unlike an AMD64 AVX-512 K-mask, an SVE governing predicate is NOT optional:
   495  // there is no K0-style "no predicate" encoding, so it is a mandatory literal
   496  // input (class "mask", role "mask"), not an inVariant. See the discussion in
   497  // emitOne.
   498  func buildOperandList(parsed []tok) []Operand {
   499  	var outs, ins []Operand
   500  	inputCount := 0
   501  	destAssigned := false
   502  
   503  	// place assigns op's role — the (single) destination if isDestination,
   504  	// otherwise the next numbered input "opN" (a repeated destination symbol is
   505  	// the in-place source) — and files it under outs or ins.
   506  	place := func(op Operand, isDestination bool) {
   507  		if isDestination && !destAssigned {
   508  			op.role = "destination"
   509  			destAssigned = true
   510  			outs = append(outs, op)
   511  			return
   512  		}
   513  		op.role = inputRole(inputCount)
   514  		inputCount++
   515  		ins = append(ins, op)
   516  	}
   517  
   518  	deferredClass := map[OperandType]string{
   519  		OperandMem:     "mem",
   520  		OperandList:    "reglist",
   521  		OperandSpecial: "special",
   522  		OperandUnknown: "unknown",
   523  	}
   524  
   525  	for _, p := range parsed {
   526  		// We don't model the details of these types yet, so just naively record them and continue.
   527  		// TODO: we might need at least the details of OperandMem soon.
   528  		if class, ok := deferredClass[p.operandType]; ok {
   529  			place(Operand{
   530  				Type: p.operandType, Class: class, Raw: p.text,
   531  				AsmPos: p.asmPos, regName: p.regName,
   532  			}, p.isDestination)
   533  			continue
   534  		}
   535  		switch p.operandType {
   536  		case OperandPReg:
   537  			if p.regName == "Pg" || p.predication != "" {
   538  				// Governing predicate: the operand named <Pg> ("g" for governing), a
   539  				// mandatory mask input (role "mask", not a numbered opN). Most carry a
   540  				// /Z or /M qualifier (predicated data-processing ops), but some do not
   541  				// — e.g. the store ST1B {<Zt>.B}, <Pg>, [...] governs with a plain
   542  				// <Pg> — so key on the register name, not the qualifier. Source
   543  				// predicates <Pn>/<Pm> and the destination <Pd> are ordinary operands,
   544  				// filed by place() below.
   545  				ins = append(ins, Operand{
   546  					Type: OperandPReg, Class: "mask", role: "mask",
   547  					Predication: p.predication, AsmPos: p.asmPos,
   548  					arngLink: p.arngLink, fixedElem: p.fixedElem, regName: p.regName,
   549  				})
   550  				continue
   551  			}
   552  			place(Operand{
   553  				Type: OperandPReg, Class: "mask", AsmPos: p.asmPos,
   554  				arngLink: p.arngLink, fixedElem: p.fixedElem, isList: p.isList, regName: p.regName,
   555  			}, p.isDestination)
   556  		case OperandImm:
   557  			place(Operand{Type: OperandImm, Class: "immediate", AsmPos: p.asmPos}, false)
   558  		default: // OperandZReg, OperandGReg, OperandVFP
   559  			class := "vreg"
   560  			if p.operandType == OperandGReg {
   561  				// A general-purpose scalar register.
   562  				class = "greg"
   563  			}
   564  			// A SIMD&FP scalar (OperandVFP) stays "vreg": it lives in the FP/SIMD
   565  			// register bank, not the GP bank — just with a fixed width and lanes:1
   566  			// rather than a scalable length.
   567  			place(Operand{
   568  				Type: p.operandType, Class: class, AsmPos: p.asmPos,
   569  				arngLink: p.arngLink, fixedElem: p.fixedElem, fixedBits: p.fixedBits,
   570  				isList: p.isList, regName: p.regName,
   571  			}, p.isDestination)
   572  		}
   573  	}
   574  	return append(outs, ins...)
   575  }
   576  
   577  // inputRole names an input operand: "op0", "op1", ...
   578  func inputRole(index int) string {
   579  	return fmt.Sprintf("op%d", index)
   580  }
   581  
   582  // instantiate stamps a base type and element width into a typed operand. mem,
   583  // immediate, reglist and special operands are opaque and left unchanged.
   584  func (op *Operand) instantiate(baseType string, elemBits int) {
   585  	switch op.Type {
   586  	case OperandZReg:
   587  		// A scalable Z vector: only base type and element width; the total width
   588  		// is the (unknown) vector length.
   589  		op.BaseType = baseType
   590  		op.ElemBits = elemBits
   591  	case OperandGReg, OperandVFP:
   592  		// A scalar register — general-purpose (<Xd>) or SIMD&FP (<Dd>) — holds a
   593  		// single fixed-width value, so it has a concrete total width and lanes=1.
   594  		op.BaseType = baseType
   595  		op.ElemBits = elemBits
   596  		op.Bits = elemBits
   597  		op.Lanes = 1
   598  	case OperandPReg:
   599  		// Predicates are integer masks; their element width tracks the governed
   600  		// vector's element width.
   601  		op.BaseType = "int"
   602  		op.ElemBits = elemBits
   603  	}
   604  }
   605  

View as plain text