Source file src/simd/archsimd/_gen/simdgen/sve/instruction.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 loads ARM64 SVE / SVE2 instruction definitions from the ARM A64
     6  // ISA XML files and emits them as simdgen unify values.
     7  // TODO: merge with the arm64 package, the approach taken here should take over
     8  // the NEON loader.
     9  // TODO: merge with x/arch/arm64/instgen?
    10  //
    11  // SVE registers are "scalable": their total bit width is the hardware
    12  // implementation-defined vector length rather than a fixed 128/256/512 bits. So
    13  // emitted vector operands carry only a base type and an element width, without a
    14  // fixed bits/lanes count.
    15  //
    16  // Arrangement is per-operand. An SVE instruction template such as
    17  //
    18  //	ADD  <Zdn>.<T>, <Pg>/M, <Zdn>.<T>, <Zm>.<T>
    19  //
    20  // stands for a family of concrete instructions, one per value of the <T>
    21  // arrangement symbol. simdgen enumerates them by resolving each operand's
    22  // arrangement symbol from the section's explanations. Different symbols can be
    23  // encoded in the same instruction field but interpreted differently, the
    24  // loader also takes care of this.
    25  //
    26  // It emits register, mask, immediate, memory and special operands.
    27  // Memory and special operands are opaque at this moment.
    28  // Register-list operands are not modeled yet, except for single-register lists,
    29  // so instructions carrying one are skipped (TODO); see classify.
    30  //
    31  // TODO: Peepholes might need the structure of memory operands, implement it?
    32  // TODO: special operands are like registers with indexing, prefetch ops, etc.
    33  // They seem too specialized that we might want to manually implment them instead
    34  // of via simdgen, but we can revisit this.
    35  package sve
    36  
    37  import (
    38  	"fmt"
    39  	"regexp"
    40  	"strings"
    41  
    42  	"golang.org/x/arch/arm64/instgen/xmlspec"
    43  )
    44  
    45  // signedImmRe matches an [Instruction.brief] that describes a signed/unsigned *immediate*
    46  // (e.g. DUP/CPY "Move signed integer immediate ..."). There the signedness is a
    47  // property of the immediate encoding, not of the vector lane, so such ops are
    48  // signedness-agnostic.
    49  var signedImmRe = regexp.MustCompile(`(un)?signed(\s+\w+)?\s+immediate`)
    50  
    51  // reZReg and rePReg detect a Z (scalable vector) or P (predicate) register in an
    52  // assembly template, used to choose the Go opcode prefix (see goOpPrefix). The
    53  // [^/] guard excludes the /<ZM> predication qualifier, which is not a Z register.
    54  // Copied from x/arch/arm64/instgen/xmlspec.
    55  var (
    56  	reZReg = regexp.MustCompile(`(^|[^/])<Z[A-Za-z1-9]+>`)
    57  	rePReg = regexp.MustCompile(`<P[A-Za-z1-9]+>`)
    58  )
    59  
    60  // Instruction is a *logical* SVE instruction, one per iclass.
    61  type Instruction struct {
    62  	xmlspec.Instruction
    63  	// iclass is the specific class this logical instruction represents.
    64  	// A raw xmlspec.Instruction can hold several iclasses with distinct mnemonics.
    65  	// If nil, the first iclass is used.
    66  	iclass        *xmlspec.Iclass
    67  	mnemonicCache string
    68  	// predVariants is set on the unpredicated instruction of a
    69  	// predicated/unpredicated pair (see [groupPredicationForms]), one entry per
    70  	// predicated machine op the pair implies. It is nil for an instruction that
    71  	// comes in one form only.
    72  	predVariants []predVariant
    73  }
    74  
    75  // predVariant is one predicated encoding of an operation, as seen from its
    76  // unpredicated sibling: the governing-predicate qualifiers it offers ("M", "Z",
    77  // or "MZ" for an encoding written <Pg>/<ZM>, which supports either) and its
    78  // register symbols, in the same order as the sibling's own results and
    79  // non-predicate inputs.
    80  //
    81  // One encoding can imply several machine ops — one per qualifier — but they
    82  // share these symbols, because they are the same encoding. A second entry would
    83  // mean a genuinely separate predicated encoding, which no paired operation in
    84  // the ISA has today; the list exists so that such an encoding could be
    85  // described with its own symbols rather than collapsed onto the first one's.
    86  type predVariant struct {
    87  	quals       string
    88  	outRegNames []string
    89  	inRegNames  []string
    90  	// predAsmPos is the assembly position of the encoding's governing
    91  	// predicate: 1 on every encoding grouped today, but recorded rather than
    92  	// assumed — PTEST, with no destination, governs from position 0.
    93  	predAsmPos int
    94  }
    95  
    96  // ic returns the iclass this logical instruction represents, defaulting to the
    97  // first iclass of the section.
    98  func (inst *Instruction) ic() *xmlspec.Iclass {
    99  	if inst.iclass != nil {
   100  		return inst.iclass
   101  	}
   102  	if len(inst.Classes.Iclass) > 0 {
   103  		return &inst.Classes.Iclass[0]
   104  	}
   105  	return nil
   106  }
   107  
   108  // extractDocVar returns the value of the named docvar, searching from most to
   109  // least specific: this iclass, its encodings, then the section top level.
   110  func (inst *Instruction) extractDocVar(key string) string {
   111  	if ic := inst.ic(); ic != nil {
   112  		for _, dv := range ic.DocVars {
   113  			if dv.Key == key {
   114  				return dv.Value
   115  			}
   116  		}
   117  		for _, enc := range ic.Encodings {
   118  			for _, dv := range enc.DocVars {
   119  				if dv.Key == key {
   120  					return dv.Value
   121  				}
   122  			}
   123  		}
   124  	}
   125  	for _, dv := range inst.DocVars {
   126  		if dv.Key == key {
   127  			return dv.Value
   128  		}
   129  	}
   130  	return ""
   131  }
   132  
   133  // mnemonic returns the instruction mnemonic, e.g. "ADD", "FADD", "SQADD".
   134  func (inst *Instruction) mnemonic() string {
   135  	if inst.mnemonicCache != "" {
   136  		return inst.mnemonicCache
   137  	}
   138  	m := inst.extractDocVar("mnemonic")
   139  	if inst.isAlias() {
   140  		m = inst.extractDocVar("alias_mnemonic")
   141  	}
   142  	inst.mnemonicCache = m
   143  	return m
   144  }
   145  
   146  // isAlias reports whether this XML entry describes an alias of another
   147  // instruction.
   148  func (inst *Instruction) isAlias() bool {
   149  	return inst.Type == "alias"
   150  }
   151  
   152  // instrClass returns the instruction class docvar, e.g. "sve" or "sve2".
   153  func (inst *Instruction) instrClass() string {
   154  	return inst.extractDocVar("instr-class")
   155  }
   156  
   157  // isSVE reports whether this is an SVE or SVE2 instruction.
   158  func (inst *Instruction) isSVE() bool {
   159  	switch inst.instrClass() {
   160  	case "sve", "sve2":
   161  		return true
   162  	}
   163  	return false
   164  }
   165  
   166  // cpuFeature returns the simdgen cpuFeature string for this instruction.
   167  func (inst *Instruction) cpuFeature() string {
   168  	switch inst.instrClass() {
   169  	case "sve2":
   170  		return "SVE2"
   171  	default:
   172  		return "SVE"
   173  	}
   174  }
   175  
   176  // goOpPrefix returns the Go opcode prefix: "Z" if the instruction uses a
   177  // scalable vector register, else "P" if it uses a predicate register, else "".
   178  // So the Go opcode is goOpPrefix()+mnemonic, e.g. ZADD but PPTRUE. Matches
   179  // x/arch/arm64/instgen/xmlspec.goOpcodePrefix.
   180  func (inst *Instruction) goOpPrefix() string {
   181  	ic := inst.ic()
   182  	if ic == nil {
   183  		return ""
   184  	}
   185  	hasZ, hasP := false, false
   186  	for _, enc := range ic.Encodings {
   187  		s := asmTemplateToString(enc.AsmTemplate)
   188  		hasZ = hasZ || reZReg.MatchString(s)
   189  		hasP = hasP || rePReg.MatchString(s)
   190  	}
   191  	switch {
   192  	case hasZ:
   193  		return "Z"
   194  	case hasP:
   195  		return "P"
   196  	default:
   197  		return ""
   198  	}
   199  }
   200  
   201  // laneIsFloat reports whether the given operand's vector lane holds
   202  // floating-point values.
   203  //
   204  // The int<->float conversions have different lane types on input and output, and the
   205  // operand's role selects which side this is:
   206  //
   207  //   - int->float (SCVTF/SCVTFLT, UCVTF/UCVTFLT): destination float, source int.
   208  //   - float->int (FCVTZS/FCVTZU and narrowing, FLOGB): destination int, source
   209  //     float.
   210  //
   211  // Every other instruction is uniform, i.e. all lanes the same type.
   212  func (inst *Instruction) laneIsFloat(op *Operand) bool {
   213  	switch op.Class {
   214  	case "vreg", "greg":
   215  		// has a lane
   216  	default:
   217  		// mask lanes are always integer; mem/immediate/special have no lane.
   218  		return false
   219  	}
   220  	dst := op.role == "destination"
   221  	switch inst.mnemonic() {
   222  	case "SCVTF", "SCVTFLT", "UCVTF", "UCVTFLT": // integer -> floating point
   223  		return dst
   224  	case "FCVTZS", "FCVTZSN", "FCVTZU", "FCVTZUN", "FLOGB": // floating point -> integer
   225  		return !dst
   226  	}
   227  	return isFloatBrief(inst.brief())
   228  }
   229  
   230  // bitwise reports whether this instruction is a bitwise operation, which the
   231  // spec's brief description spells with a "Bitwise " prefix (mirroring the NEON
   232  // loader's test). A bitwise vector encoding is written .D but is element-width
   233  // agnostic: any lane view of it is valid.
   234  func (inst *Instruction) bitwise() bool {
   235  	return strings.HasPrefix(inst.brief(), "Bitwise ")
   236  }
   237  
   238  // isFloatBrief reports whether a brief description names a floating-point type.
   239  // SVE spells these as "floating-point", "bfloat", or an "X-precision" (half /
   240  // single / double / 8-bit) qualifier.
   241  func isFloatBrief(brief string) bool {
   242  	b := strings.ToLower(brief)
   243  	return strings.Contains(b, "floating-point") ||
   244  		strings.Contains(b, "bfloat") ||
   245  		strings.Contains(b, "precision")
   246  }
   247  
   248  // signedness reports whether an integer instruction interprets its lanes as
   249  // signed, unsigned, or agnostic, so the loader emits only the signedness the
   250  // hardware actually implements, not spurious values. Many low-half/bitwise
   251  // ops, e.g. ADD, SUB, MUL, EOR, etc., are genuinely agnostic.
   252  // others are signedness-specific, e.g. SMAX vs UMAX, SDIV vs UDIV,
   253  // the int<->float converts, etc.
   254  //
   255  // The signal is the instruction's brief description, which names the signedness
   256  // for the specific ops ("Signed maximum", "Unsigned divide", "Signed integer
   257  // convert ...") and omits it for the agnostic ones.
   258  //
   259  // Two adjustments: a brief describing a signed/unsigned *immediate*
   260  // (DUP/CPY) is about the immediate, not the lane, so it stays agnostic; and the
   261  // shift-right family and FLOGB name their signedness differently (arithmetic vs
   262  // logical shift; "logarithm as integer") and are handled explicitly.
   263  func (inst *Instruction) signedness() string {
   264  	switch inst.mnemonic() {
   265  	case "ASR", "ASRD", "ASRR", "FLOGB": // arithmetic (sign-propagating) / signed exponent
   266  		return "int"
   267  	case "LSR", "LSRR": // logical (zero-filling) shift right
   268  		return "uint"
   269  	}
   270  	b := strings.ToLower(inst.brief())
   271  	if signedImmRe.MatchString(b) {
   272  		return ""
   273  	}
   274  	switch {
   275  	case strings.Contains(b, "unsigned"):
   276  		return "uint"
   277  	case strings.Contains(b, "signed"): // "unsigned" already handled, so this is the word "signed"
   278  		return "int"
   279  	}
   280  	return ""
   281  }
   282  
   283  // integerSignedness returns the signed/unsigned base variants to enumerate for
   284  // the instruction's integer lanes: the single value fixed by signedness for a
   285  // signedness-specific op, both {"int","uint"} for an agnostic op with an integer
   286  // lane (simdgen narrows later via the Go op definitions), or a single no-op pass
   287  // when there are no integer lanes.
   288  func (inst *Instruction) integerSignedness(ops []Operand) []string {
   289  	switch inst.signedness() {
   290  	case "int":
   291  		return []string{"int"}
   292  	case "uint":
   293  		return []string{"uint"}
   294  	}
   295  	for i := range ops {
   296  		if c := ops[i].Class; (c == "vreg" || c == "greg") && !inst.laneIsFloat(&ops[i]) {
   297  			return []string{"int", "uint"}
   298  		}
   299  	}
   300  	return []string{""}
   301  }
   302  
   303  // brief returns the instruction's short human-readable description, e.g. "Signed
   304  // maximum (predicated)".
   305  func (inst *Instruction) brief() string {
   306  	if len(inst.Desc.Brief.Para) > 0 {
   307  		return strings.TrimSpace(inst.Desc.Brief.Para[0].Text)
   308  	}
   309  	return ""
   310  }
   311  
   312  // findExplanation returns the explanation whose symbol is encoded with the
   313  // given link, or nil.
   314  func (inst *Instruction) findExplanation(link string) *xmlspec.Explanation {
   315  	for i := range inst.Explanations.Explanations {
   316  		if inst.Explanations.Explanations[i].Symbol.Link == link {
   317  			return &inst.Explanations.Explanations[i]
   318  		}
   319  	}
   320  	return nil
   321  }
   322  
   323  // symbolIsGoverning reports whether this instruction's explanation for
   324  // register symbol name (e.g. "Pg") describes it as the governing predicate —
   325  // the spec writes "the governing scalable predicate register" for exactly the
   326  // symbols with that role. found reports whether any explanation names the
   327  // symbol at all. This is the authoritative classification; [buildOperandList]
   328  // cross-checks it against the syntactic <Pg>/qualifier signal.
   329  func (inst *Instruction) symbolIsGoverning(name string) (governing, found bool) {
   330  	want := "<" + name + ">"
   331  	for i := range inst.Explanations.Explanations {
   332  		e := &inst.Explanations.Explanations[i]
   333  		if strings.TrimSpace(e.Symbol.Value) != want {
   334  			continue
   335  		}
   336  		found = true
   337  		if strings.Contains(strings.ToLower(e.Account.Intro), "governing") {
   338  			return true, true
   339  		}
   340  	}
   341  	return false, found
   342  }
   343  
   344  // arngRow is one row of an arrangement size table: the encoding value of the
   345  // size field and the resulting element width in bits.
   346  type arngRow struct {
   347  	size string // the size bitfield value, e.g. "01"; the shared key across symbols
   348  	bits int    // element width for this size (8/16/32/64)
   349  }
   350  
   351  // resolveArrangementTable returns the (size -> element width) rows for the
   352  // arrangement symbol encoded with the given link, read from its definition
   353  // table in encoding order. RESERVED and header rows (no valid element letter)
   354  // are dropped.
   355  //
   356  // Crucially, the size key is the shared encoding field, so different symbols
   357  // (<T> and <Tb>) that select on the same field line up by size. That is what
   358  // lets non-uniform (widening/narrowing) instructions like SUNPKHI give each
   359  // operand its own element width for the same encoded instruction.
   360  func (inst *Instruction) resolveArrangementTable(link string) []arngRow {
   361  	exp := inst.findExplanation(link)
   362  	if exp == nil {
   363  		return nil
   364  	}
   365  	var rows []arngRow
   366  	for i, row := range exp.Definition.Table.TGroup.TBody.Row {
   367  		var size string
   368  		bits := 0
   369  		for _, entry := range row.Entries {
   370  			switch entry.Class {
   371  			case "bitfield":
   372  				size = strings.TrimSpace(entry.Value)
   373  			case "symbol":
   374  				bits = elemLetterBits(strings.TrimSpace(entry.Value))
   375  			}
   376  		}
   377  		if bits == 0 {
   378  			continue // header or RESERVED row
   379  		}
   380  		if size == "" {
   381  			size = fmt.Sprintf("#%d", i) // single-column table: key by position
   382  		}
   383  		rows = append(rows, arngRow{size: size, bits: bits})
   384  	}
   385  	return rows
   386  }
   387  
   388  // arngLinks returns the distinct arrangement-symbol links used by ops, with the
   389  // destination's link first (it is the primary size driver), preserving order.
   390  func arngLinks(ops []Operand) []string {
   391  	seen := map[string]bool{}
   392  	var links []string
   393  	add := func(l string) {
   394  		if l != "" && !seen[l] {
   395  			seen[l] = true
   396  			links = append(links, l)
   397  		}
   398  	}
   399  	for _, op := range ops {
   400  		if op.role == "destination" {
   401  			add(op.arngLink)
   402  		}
   403  	}
   404  	for _, op := range ops {
   405  		add(op.arngLink)
   406  	}
   407  	return links
   408  }
   409  
   410  // elemLetterBits maps an SVE element specifier letter to its bit width.
   411  func elemLetterBits(letter string) int {
   412  	switch letter {
   413  	case "B":
   414  		return 8
   415  	case "H":
   416  		return 16
   417  	case "S":
   418  		return 32
   419  	case "D":
   420  		return 64
   421  	default:
   422  		return 0
   423  	}
   424  }
   425  
   426  // elemLetter is the inverse of elemLetterBits: it maps a bit width to its SVE
   427  // element specifier letter (used as the arrangement in emitted defs).
   428  func elemLetter(bits int) string {
   429  	switch bits {
   430  	case 8:
   431  		return "B"
   432  	case 16:
   433  		return "H"
   434  	case 32:
   435  		return "S"
   436  	case 64:
   437  		return "D"
   438  	default:
   439  		return ""
   440  	}
   441  }
   442  
   443  // allEncodingOperands returns the operand list of every distinct encoding of this iclass.
   444  func (inst *Instruction) allEncodingOperands() [][]Operand {
   445  	ic := inst.ic()
   446  	if ic == nil {
   447  		return nil
   448  	}
   449  	seen := map[string]bool{}
   450  	var out [][]Operand
   451  	for _, enc := range ic.Encodings {
   452  		s := asmTemplateToString(enc.AsmTemplate)
   453  		if s == "" || seen[s] {
   454  			continue
   455  		}
   456  		seen[s] = true
   457  		ops := func() []Operand {
   458  			// A classification panic names only the operand; add which
   459  			// instruction and template it came from.
   460  			defer func() {
   461  				if r := recover(); r != nil {
   462  					panic(fmt.Sprintf("%v\n  in %q template %q", r, inst.Title, s))
   463  				}
   464  			}()
   465  			return operandsFromTextA(enc.AsmTemplate.TextA, inst.symbolIsGoverning)
   466  		}()
   467  		if len(ops) > 0 {
   468  			inst.fixMemoryDirection(ops)
   469  			out = append(out, ops)
   470  		}
   471  	}
   472  	return out
   473  }
   474  
   475  // fixMemoryDirection re-roles a load/store's data direction, which the operand
   476  // order does not reveal on its own. A store's destination is its memory operand
   477  // (unusually, at the end of the syntax, e.g. ST1B {<Zt>.<T>}, <Pg>, [<Xn|SP>]);
   478  // a load's destination is the transferred vector register (the memory is then a
   479  // source). Load/store is read from the brief description.
   480  func (inst *Instruction) fixMemoryDirection(ops []Operand) {
   481  	b := strings.ToLower(inst.brief())
   482  	store := strings.Contains(b, "store")
   483  	load := strings.Contains(b, "load")
   484  	if !store && !load {
   485  		return
   486  	}
   487  	for i := range ops {
   488  		switch {
   489  		case store && ops[i].Class == "mem":
   490  			ops[i].role = "destination"
   491  		case load && ops[i].Class == "vreg":
   492  			ops[i].role = "destination"
   493  		}
   494  	}
   495  }
   496  
   497  // operands parses the operands of this instruction's first encoding form. Most
   498  // instructions have exactly one; use templates for the complete set.
   499  func (inst *Instruction) operands() []Operand {
   500  	if ops := inst.allEncodingOperands(); len(ops) > 0 {
   501  		return ops[0]
   502  	}
   503  	return nil
   504  }
   505  
   506  // hasClass reports whether any operand has the given class.
   507  func hasClass(ops []Operand, class string) bool {
   508  	for _, op := range ops {
   509  		if op.Class == class {
   510  			return true
   511  		}
   512  	}
   513  	return false
   514  }
   515  
   516  // predicationVariants returns the governing-predicate qualifiers to emit for a
   517  // template: the predicate operand's own qualifier ("M" or "Z"), both when a
   518  // single encoding written "<Pg>/<ZM>" (MOVPRFX) selects merging or zeroing via a
   519  // bit, or a single no-op pass when the template has no governing predicate.
   520  func predicationVariants(ops []Operand) []string {
   521  	for i := range ops {
   522  		if ops[i].governing {
   523  			if ops[i].Predication == "MZ" {
   524  				return []string{"M", "Z"}
   525  			}
   526  			return []string{ops[i].Predication}
   527  		}
   528  	}
   529  	return []string{""}
   530  }
   531  
   532  // predicationForm reports whether this encoding is the predicated or the
   533  // unpredicated form of an operation, as "predicated" / "unpredicated".
   534  //
   535  // It reads the encoding rather than the title: an encoding that takes a
   536  // governing predicate is the predicated one. SVE does also spell this out in
   537  // the title of an operation that has both forms ("ADD (vectors, predicated)"
   538  // and "ADD (vectors, unpredicated)"), and [predicationGroupKey] uses that to pair
   539  // them, but an operation that only comes predicated says nothing in its title —
   540  // both of ABS's encodings are titled plain "ABS".
   541  func (inst *Instruction) predicationForm() string {
   542  	for _, ops := range inst.allEncodingOperands() {
   543  		for i := range ops {
   544  			if ops[i].governing {
   545  				return "predicated"
   546  			}
   547  		}
   548  	}
   549  	return "unpredicated"
   550  }
   551  
   552  // predicationGroupKey returns the key that groups the encodings of one
   553  // operation: the title with any predicated/unpredicated qualifier removed, e.g.
   554  // both "ADD (vectors, predicated)" and "ADD (vectors, unpredicated)" yield "add
   555  // (vectors)", and both of ABS's encodings yield "abs".
   556  //
   557  // Encodings that are not variations on one another keep distinct titles — "ADD
   558  // (immediate)", "ADD (extended register)" — so they land in groups of their own,
   559  // which groupPredicationForms then leaves alone.
   560  func (inst *Instruction) predicationGroupKey() string {
   561  	t := strings.ToLower(inst.Title)
   562  	t = strings.ReplaceAll(t, "unpredicated", "")
   563  	t = strings.ReplaceAll(t, "predicated", "")
   564  	// Tidy the separator the qualifier left behind: "(vectors, )" -> "(vectors)".
   565  	t = strings.ReplaceAll(t, ", )", ")")
   566  	t = strings.ReplaceAll(t, "( ", "(")
   567  	return strings.Join(strings.Fields(t), " ")
   568  }
   569  
   570  // documentation returns a one-line description of the instruction.
   571  func (inst *Instruction) documentation() string {
   572  	if len(inst.Desc.Authored.Paragraphs) > 0 {
   573  		return inst.Desc.Authored.Paragraphs[0].Text
   574  	}
   575  	return inst.Title
   576  }
   577  
   578  // asmTemplateToString flattens an AsmTemplate to its text.
   579  func asmTemplateToString(t xmlspec.AsmTemplate) string {
   580  	var b strings.Builder
   581  	for _, ta := range t.TextA {
   582  		b.WriteString(ta.Value)
   583  	}
   584  	return b.String()
   585  }
   586  

View as plain text