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  }
    69  
    70  // ic returns the iclass this logical instruction represents, defaulting to the
    71  // first iclass of the section.
    72  func (inst *Instruction) ic() *xmlspec.Iclass {
    73  	if inst.iclass != nil {
    74  		return inst.iclass
    75  	}
    76  	if len(inst.Classes.Iclass) > 0 {
    77  		return &inst.Classes.Iclass[0]
    78  	}
    79  	return nil
    80  }
    81  
    82  // extractDocVar returns the value of the named docvar, searching from most to
    83  // least specific: this iclass, its encodings, then the section top level.
    84  func (inst *Instruction) extractDocVar(key string) string {
    85  	if ic := inst.ic(); ic != nil {
    86  		for _, dv := range ic.DocVars {
    87  			if dv.Key == key {
    88  				return dv.Value
    89  			}
    90  		}
    91  		for _, enc := range ic.Encodings {
    92  			for _, dv := range enc.DocVars {
    93  				if dv.Key == key {
    94  					return dv.Value
    95  				}
    96  			}
    97  		}
    98  	}
    99  	for _, dv := range inst.DocVars {
   100  		if dv.Key == key {
   101  			return dv.Value
   102  		}
   103  	}
   104  	return ""
   105  }
   106  
   107  // mnemonic returns the instruction mnemonic, e.g. "ADD", "FADD", "SQADD".
   108  func (inst *Instruction) mnemonic() string {
   109  	if inst.mnemonicCache != "" {
   110  		return inst.mnemonicCache
   111  	}
   112  	m := inst.extractDocVar("mnemonic")
   113  	if inst.isAlias() {
   114  		m = inst.extractDocVar("alias_mnemonic")
   115  	}
   116  	inst.mnemonicCache = m
   117  	return m
   118  }
   119  
   120  // isAlias reports whether this XML entry describes an alias of another
   121  // instruction.
   122  func (inst *Instruction) isAlias() bool {
   123  	return inst.Type == "alias"
   124  }
   125  
   126  // instrClass returns the instruction class docvar, e.g. "sve" or "sve2".
   127  func (inst *Instruction) instrClass() string {
   128  	return inst.extractDocVar("instr-class")
   129  }
   130  
   131  // isSVE reports whether this is an SVE or SVE2 instruction.
   132  func (inst *Instruction) isSVE() bool {
   133  	switch inst.instrClass() {
   134  	case "sve", "sve2":
   135  		return true
   136  	}
   137  	return false
   138  }
   139  
   140  // cpuFeature returns the simdgen cpuFeature string for this instruction.
   141  func (inst *Instruction) cpuFeature() string {
   142  	switch inst.instrClass() {
   143  	case "sve2":
   144  		return "SVE2"
   145  	default:
   146  		return "SVE"
   147  	}
   148  }
   149  
   150  // goOpPrefix returns the Go opcode prefix: "Z" if the instruction uses a
   151  // scalable vector register, else "P" if it uses a predicate register, else "".
   152  // So the Go opcode is goOpPrefix()+mnemonic, e.g. ZADD but PPTRUE. Matches
   153  // x/arch/arm64/instgen/xmlspec.goOpcodePrefix.
   154  func (inst *Instruction) goOpPrefix() string {
   155  	ic := inst.ic()
   156  	if ic == nil {
   157  		return ""
   158  	}
   159  	hasZ, hasP := false, false
   160  	for _, enc := range ic.Encodings {
   161  		s := asmTemplateToString(enc.AsmTemplate)
   162  		hasZ = hasZ || reZReg.MatchString(s)
   163  		hasP = hasP || rePReg.MatchString(s)
   164  	}
   165  	switch {
   166  	case hasZ:
   167  		return "Z"
   168  	case hasP:
   169  		return "P"
   170  	default:
   171  		return ""
   172  	}
   173  }
   174  
   175  // laneIsFloat reports whether the given operand's vector lane holds
   176  // floating-point values.
   177  //
   178  // The int<->float conversions have different lane types on input and output, and the
   179  // operand's role selects which side this is:
   180  //
   181  //   - int->float (SCVTF/SCVTFLT, UCVTF/UCVTFLT): destination float, source int.
   182  //   - float->int (FCVTZS/FCVTZU and narrowing, FLOGB): destination int, source
   183  //     float.
   184  //
   185  // Every other instruction is uniform, i.e. all lanes the same type.
   186  func (inst *Instruction) laneIsFloat(op *Operand) bool {
   187  	switch op.Class {
   188  	case "vreg", "greg":
   189  		// has a lane
   190  	default:
   191  		// mask lanes are always integer; mem/immediate/special have no lane.
   192  		return false
   193  	}
   194  	dst := op.role == "destination"
   195  	switch inst.mnemonic() {
   196  	case "SCVTF", "SCVTFLT", "UCVTF", "UCVTFLT": // integer -> floating point
   197  		return dst
   198  	case "FCVTZS", "FCVTZSN", "FCVTZU", "FCVTZUN", "FLOGB": // floating point -> integer
   199  		return !dst
   200  	}
   201  	return isFloatBrief(inst.brief())
   202  }
   203  
   204  // isFloatBrief reports whether a brief description names a floating-point type.
   205  // SVE spells these as "floating-point", "bfloat", or an "X-precision" (half /
   206  // single / double / 8-bit) qualifier.
   207  func isFloatBrief(brief string) bool {
   208  	b := strings.ToLower(brief)
   209  	return strings.Contains(b, "floating-point") ||
   210  		strings.Contains(b, "bfloat") ||
   211  		strings.Contains(b, "precision")
   212  }
   213  
   214  // signedness reports whether an integer instruction interprets its lanes as
   215  // signed, unsigned, or agnostic, so the loader emits only the signedness the
   216  // hardware actually implements, not spurious values. Many low-half/bitwise
   217  // ops, e.g. ADD, SUB, MUL, EOR, etc., are genuinely agnostic.
   218  // others are signedness-specific, e.g. SMAX vs UMAX, SDIV vs UDIV,
   219  // the int<->float converts, etc.
   220  //
   221  // The signal is the instruction's brief description, which names the signedness
   222  // for the specific ops ("Signed maximum", "Unsigned divide", "Signed integer
   223  // convert ...") and omits it for the agnostic ones.
   224  //
   225  // Two adjustments: a brief describing a signed/unsigned *immediate*
   226  // (DUP/CPY) is about the immediate, not the lane, so it stays agnostic; and the
   227  // shift-right family and FLOGB name their signedness differently (arithmetic vs
   228  // logical shift; "logarithm as integer") and are handled explicitly.
   229  func (inst *Instruction) signedness() string {
   230  	switch inst.mnemonic() {
   231  	case "ASR", "ASRD", "ASRR", "FLOGB": // arithmetic (sign-propagating) / signed exponent
   232  		return "int"
   233  	case "LSR", "LSRR": // logical (zero-filling) shift right
   234  		return "uint"
   235  	}
   236  	b := strings.ToLower(inst.brief())
   237  	if signedImmRe.MatchString(b) {
   238  		return ""
   239  	}
   240  	switch {
   241  	case strings.Contains(b, "unsigned"):
   242  		return "uint"
   243  	case strings.Contains(b, "signed"): // "unsigned" already handled, so this is the word "signed"
   244  		return "int"
   245  	}
   246  	return ""
   247  }
   248  
   249  // integerSignedness returns the signed/unsigned base variants to enumerate for
   250  // the instruction's integer lanes: the single value fixed by signedness for a
   251  // signedness-specific op, both {"int","uint"} for an agnostic op with an integer
   252  // lane (simdgen narrows later via the Go op definitions), or a single no-op pass
   253  // when there are no integer lanes.
   254  func (inst *Instruction) integerSignedness(ops []Operand) []string {
   255  	switch inst.signedness() {
   256  	case "int":
   257  		return []string{"int"}
   258  	case "uint":
   259  		return []string{"uint"}
   260  	}
   261  	for i := range ops {
   262  		if c := ops[i].Class; (c == "vreg" || c == "greg") && !inst.laneIsFloat(&ops[i]) {
   263  			return []string{"int", "uint"}
   264  		}
   265  	}
   266  	return []string{""}
   267  }
   268  
   269  // brief returns the instruction's short human-readable description, e.g. "Signed
   270  // maximum (predicated)".
   271  func (inst *Instruction) brief() string {
   272  	if len(inst.Desc.Brief.Para) > 0 {
   273  		return strings.TrimSpace(inst.Desc.Brief.Para[0].Text)
   274  	}
   275  	return ""
   276  }
   277  
   278  // findExplanation returns the explanation whose symbol is encoded with the
   279  // given link, or nil.
   280  func (inst *Instruction) findExplanation(link string) *xmlspec.Explanation {
   281  	for i := range inst.Explanations.Explanations {
   282  		if inst.Explanations.Explanations[i].Symbol.Link == link {
   283  			return &inst.Explanations.Explanations[i]
   284  		}
   285  	}
   286  	return nil
   287  }
   288  
   289  // arngRow is one row of an arrangement size table: the encoding value of the
   290  // size field and the resulting element width in bits.
   291  type arngRow struct {
   292  	size string // the size bitfield value, e.g. "01"; the shared key across symbols
   293  	bits int    // element width for this size (8/16/32/64)
   294  }
   295  
   296  // resolveArrangementTable returns the (size -> element width) rows for the
   297  // arrangement symbol encoded with the given link, read from its definition
   298  // table in encoding order. RESERVED and header rows (no valid element letter)
   299  // are dropped.
   300  //
   301  // Crucially, the size key is the shared encoding field, so different symbols
   302  // (<T> and <Tb>) that select on the same field line up by size. That is what
   303  // lets non-uniform (widening/narrowing) instructions like SUNPKHI give each
   304  // operand its own element width for the same encoded instruction.
   305  func (inst *Instruction) resolveArrangementTable(link string) []arngRow {
   306  	exp := inst.findExplanation(link)
   307  	if exp == nil {
   308  		return nil
   309  	}
   310  	var rows []arngRow
   311  	for i, row := range exp.Definition.Table.TGroup.TBody.Row {
   312  		var size string
   313  		bits := 0
   314  		for _, entry := range row.Entries {
   315  			switch entry.Class {
   316  			case "bitfield":
   317  				size = strings.TrimSpace(entry.Value)
   318  			case "symbol":
   319  				bits = elemLetterBits(strings.TrimSpace(entry.Value))
   320  			}
   321  		}
   322  		if bits == 0 {
   323  			continue // header or RESERVED row
   324  		}
   325  		if size == "" {
   326  			size = fmt.Sprintf("#%d", i) // single-column table: key by position
   327  		}
   328  		rows = append(rows, arngRow{size: size, bits: bits})
   329  	}
   330  	return rows
   331  }
   332  
   333  // arngLinks returns the distinct arrangement-symbol links used by ops, with the
   334  // destination's link first (it is the primary size driver), preserving order.
   335  func arngLinks(ops []Operand) []string {
   336  	seen := map[string]bool{}
   337  	var links []string
   338  	add := func(l string) {
   339  		if l != "" && !seen[l] {
   340  			seen[l] = true
   341  			links = append(links, l)
   342  		}
   343  	}
   344  	for _, op := range ops {
   345  		if op.role == "destination" {
   346  			add(op.arngLink)
   347  		}
   348  	}
   349  	for _, op := range ops {
   350  		add(op.arngLink)
   351  	}
   352  	return links
   353  }
   354  
   355  // elemLetterBits maps an SVE element specifier letter to its bit width.
   356  func elemLetterBits(letter string) int {
   357  	switch letter {
   358  	case "B":
   359  		return 8
   360  	case "H":
   361  		return 16
   362  	case "S":
   363  		return 32
   364  	case "D":
   365  		return 64
   366  	default:
   367  		return 0
   368  	}
   369  }
   370  
   371  // elemLetter is the inverse of elemLetterBits: it maps a bit width to its SVE
   372  // element specifier letter (used as the arrangement in emitted defs).
   373  func elemLetter(bits int) string {
   374  	switch bits {
   375  	case 8:
   376  		return "B"
   377  	case 16:
   378  		return "H"
   379  	case 32:
   380  		return "S"
   381  	case 64:
   382  		return "D"
   383  	default:
   384  		return ""
   385  	}
   386  }
   387  
   388  // allEncodingOperands returns the operand list of every distinct encoding of this iclass.
   389  func (inst *Instruction) allEncodingOperands() [][]Operand {
   390  	ic := inst.ic()
   391  	if ic == nil {
   392  		return nil
   393  	}
   394  	seen := map[string]bool{}
   395  	var out [][]Operand
   396  	for _, enc := range ic.Encodings {
   397  		s := asmTemplateToString(enc.AsmTemplate)
   398  		if s == "" || seen[s] {
   399  			continue
   400  		}
   401  		seen[s] = true
   402  		if ops := operandsFromTextA(enc.AsmTemplate.TextA); len(ops) > 0 {
   403  			inst.fixMemoryDirection(ops)
   404  			out = append(out, ops)
   405  		}
   406  	}
   407  	return out
   408  }
   409  
   410  // fixMemoryDirection re-roles a load/store's data direction, which the operand
   411  // order does not reveal on its own. A store's destination is its memory operand
   412  // (unusually, at the end of the syntax, e.g. ST1B {<Zt>.<T>}, <Pg>, [<Xn|SP>]);
   413  // a load's destination is the transferred vector register (the memory is then a
   414  // source). Load/store is read from the brief description.
   415  func (inst *Instruction) fixMemoryDirection(ops []Operand) {
   416  	b := strings.ToLower(inst.brief())
   417  	store := strings.Contains(b, "store")
   418  	load := strings.Contains(b, "load")
   419  	if !store && !load {
   420  		return
   421  	}
   422  	for i := range ops {
   423  		switch {
   424  		case store && ops[i].Class == "mem":
   425  			ops[i].role = "destination"
   426  		case load && ops[i].Class == "vreg":
   427  			ops[i].role = "destination"
   428  		}
   429  	}
   430  }
   431  
   432  // operands parses the operands of this instruction's first encoding form. Most
   433  // instructions have exactly one; use templates for the complete set.
   434  func (inst *Instruction) operands() []Operand {
   435  	if ops := inst.allEncodingOperands(); len(ops) > 0 {
   436  		return ops[0]
   437  	}
   438  	return nil
   439  }
   440  
   441  // hasClass reports whether any operand has the given class.
   442  func hasClass(ops []Operand, class string) bool {
   443  	for _, op := range ops {
   444  		if op.Class == class {
   445  			return true
   446  		}
   447  	}
   448  	return false
   449  }
   450  
   451  // predicationVariants returns the governing-predicate qualifiers to emit for a
   452  // template: the predicate operand's own qualifier ("M" or "Z"), both when a
   453  // single encoding written "<Pg>/<ZM>" (MOVPRFX) selects merging or zeroing via a
   454  // bit, or a single no-op pass when the template has no governing predicate.
   455  func predicationVariants(ops []Operand) []string {
   456  	for i := range ops {
   457  		if ops[i].Class == "mask" && ops[i].role == "mask" {
   458  			if ops[i].Predication == "MZ" {
   459  				return []string{"M", "Z"}
   460  			}
   461  			return []string{ops[i].Predication}
   462  		}
   463  	}
   464  	return []string{""}
   465  }
   466  
   467  // documentation returns a one-line description of the instruction.
   468  func (inst *Instruction) documentation() string {
   469  	if len(inst.Desc.Authored.Paragraphs) > 0 {
   470  		return inst.Desc.Authored.Paragraphs[0].Text
   471  	}
   472  	return inst.Title
   473  }
   474  
   475  // asmTemplateToString flattens an AsmTemplate to its text.
   476  func asmTemplateToString(t xmlspec.AsmTemplate) string {
   477  	var b strings.Builder
   478  	for _, ta := range t.TextA {
   479  		b.WriteString(ta.Value)
   480  	}
   481  	return b.String()
   482  }
   483  

View as plain text