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

View as plain text