Source file src/simd/archsimd/_gen/simdgen/sve/emit.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  	"slices"
    12  	"strings"
    13  
    14  	"simd/archsimd/_gen/simdgen/types"
    15  	"simd/archsimd/_gen/unify"
    16  )
    17  
    18  var baseTypeRegexps = map[string]*regexp.Regexp{
    19  	"int":   regexp.MustCompile("int"),
    20  	"uint":  regexp.MustCompile("uint"),
    21  	"float": regexp.MustCompile("float"),
    22  }
    23  
    24  // asComment wraps text into // comment lines of at most width columns.
    25  func asComment(text string, width int) string {
    26  	text = strings.TrimSpace(text)
    27  	text = strings.ReplaceAll(text, "&", "&")
    28  	text = strings.ReplaceAll(text, "\n", " ")
    29  	words := strings.Fields(text)
    30  	var lines []string
    31  	line := ""
    32  	for _, w := range words {
    33  		if line != "" {
    34  			line += " "
    35  		}
    36  		line += w
    37  		if len(line) >= width {
    38  			lines = append(lines, "// "+line)
    39  			line = ""
    40  		}
    41  	}
    42  	if line != "" {
    43  		lines = append(lines, "// "+line)
    44  	}
    45  	return strings.Join(lines, "\n")
    46  }
    47  
    48  // mixedWidthLogged dedupes the mixed-element-width warning by mnemonic, so a
    49  // conversion family with many encodings logs once per generate run.
    50  var mixedWidthLogged = map[string]bool{}
    51  
    52  // encode renders an operand as a types.Operand. Z-vectors and predicates are
    53  // scalable (a base type and per-operand element width, no fixed bits/lanes);
    54  // mem, immediate and special operands are opaque (class and position only).
    55  func (op *Operand) encode() types.Operand {
    56  	out := types.Operand{
    57  		Class:  op.Class,
    58  		AsmPos: op.AsmPos,
    59  	}
    60  	if op.BaseType != "" {
    61  		if re, ok := baseTypeRegexps[op.BaseType]; ok {
    62  			out.EncodeBase = re
    63  		} else {
    64  			out.EncodeBase = regexp.MustCompile(op.BaseType)
    65  		}
    66  	}
    67  	switch {
    68  	case op.Bits > 0:
    69  		// A fixed-width SIMD&FP scalar (OperandVFP): a real bit width and lanes.
    70  		out.EncodeBits = &types.VectorSize{NRaw: op.Bits}
    71  		if op.Lanes > 0 {
    72  			out.Lanes = new(op.Lanes)
    73  		}
    74  	case op.Class == "vreg" || op.Class == "mask":
    75  		// SVE vectors and predicates are scalable: no fixed total bit width.
    76  		// The literal "scalable" both marks that and, because it conflicts with
    77  		// any numeric bits, keeps these operands from unifying with the
    78  		// fixed-width (NEON/AVX) types that share types.yaml.
    79  		out.EncodeBits = &types.VectorSize{Scalable: true}
    80  	}
    81  	if op.ElemBits > 0 {
    82  		out.ElemBits = new(op.ElemBits)
    83  	}
    84  	if op.Predication != "" {
    85  		// "M" (merging) or "Z" (zeroing) for a governing predicate. Some SVE
    86  		// instructions support only one; this records which.
    87  		out.Predication = new(op.Predication)
    88  	}
    89  	if op.governing {
    90  		// This operand is a governing predicate.
    91  		out.Governing = new(true)
    92  	}
    93  	if op.isList {
    94  		// This register came from a single-register list ("{ <Zt>.<T> }"), a
    95  		// distinct assembler encoding from a bare register.
    96  		out.ListNumber = new(0)
    97  	}
    98  	if op.regName != "" {
    99  		// The assembly template's register symbol, e.g. "Zdn", "Zn", "Pg".
   100  		out.RegName = new(op.regName)
   101  	}
   102  	// The symbol this operand has in each predicated encoding, indexed to
   103  	// match the def's inVariant. The symbols can differ from the unpredicated
   104  	// ones to predicated ones:
   105  	// ADD <Zd>, <Zn>, <Zm> unpredicated
   106  	// ADD <Zdn>, <Pg>/M, <Zdn>, <Zm> predicated
   107  	//
   108  	// [groupPredicationForms] folds the two into one def.
   109  	// simdgen needs these symbols to recognize resultInArg0.
   110  	out.PredRegName = new(op.predRegName)
   111  	return out
   112  }
   113  
   114  // pickRegNames returns operand idx's symbol in each predicated encoding, in
   115  // variant order. The encodings passed [sameOperandShape], so idx addresses the
   116  // matching operand in every one of them.
   117  func pickRegNames(variants []predVariant, idx int, sel func(predVariant) []string) []string {
   118  	if len(variants) == 0 {
   119  		return nil
   120  	}
   121  	out := make([]string, len(variants))
   122  	for i, pv := range variants {
   123  		names := sel(pv)
   124  		if idx >= len(names) {
   125  			panic(fmt.Sprintf("operand %d has no counterpart in predicated encoding %d", idx, i))
   126  		}
   127  		out[i] = names[idx]
   128  	}
   129  	return out
   130  }
   131  
   132  // emitOne emits a single instruction def from a fully-instantiated operand list:
   133  // the destination is the output, every other operand (including a governing
   134  // predicate) is a literal input.
   135  //
   136  // An SVE predicate is a mandatory input, not an optional AVX-512-style K-mask, so
   137  // it goes in `in`; inVariant is emitted empty just to satisfy the types.yaml schema.
   138  func (inst *Instruction) emitOne(asm string, ops []Operand, widthAgnostic bool) *unify.Value {
   139  	var db unify.DefBuilder
   140  	db.Add("asm", unify.NewValue(unify.NewStringExact(asm)))
   141  	db.Add("goarch", unify.NewValue(unify.NewStringExact("arm64")))
   142  	// The operation's feature level is the floor across its encodings: an
   143  	// operation whose predicated sibling is baseline SVE is available on SVE
   144  	// even when its unpredicated carrier needs SVE2 — the carrier is then a
   145  	// feature-gated upgrade, recorded as unpredCpuFeature for the rules.
   146  	feature := inst.cpuFeature()
   147  	unpred := ""
   148  	for _, pv := range inst.predVariants {
   149  		if pv.cpuFeature == "SVE" && feature == "SVE2" {
   150  			unpred = feature
   151  			feature = pv.cpuFeature
   152  		}
   153  	}
   154  	db.Add("cpuFeature", unify.NewValue(unify.NewStringExact(feature)))
   155  	if unpred != "" {
   156  		db.Add("unpredCPUFeature", unify.NewValue(unify.NewStringExact(unpred)))
   157  	}
   158  	if doc := inst.documentation(); doc != "" {
   159  		db.Add("details", unify.NewValue(unify.NewStringExact(asComment(doc, 80))))
   160  	}
   161  	if widthAgnostic {
   162  		db.Add("widthAgnostic", unify.NewValue(unify.NewStringExact("true")))
   163  	}
   164  
   165  	// One def can describe several encodings of one operation, grouped by
   166  	// [groupPredicationForms] or [groupPredicatedOnly], so each operand also
   167  	// carries the symbol it has in each predicated encoding. The symbols are
   168  	// matched up in template order, so they must be attached before the sort
   169  	// below reorders the inputs.
   170  	var in, out []types.Operand
   171  	var outIdx, inIdx int
   172  	for _, op := range ops {
   173  		switch {
   174  		case op.governing:
   175  			// The governing predicate is the operand the paired encodings differ in, so
   176  			// it is not one of the symbols they are matched up by.
   177  			in = append(in, op.encode())
   178  		case op.role == "destination":
   179  			op.predRegName = pickRegNames(inst.predVariants, outIdx, func(pv predVariant) []string { return pv.outRegNames })
   180  			outIdx++
   181  			out = append(out, op.encode())
   182  		default:
   183  			op.predRegName = pickRegNames(inst.predVariants, inIdx, func(pv predVariant) []string { return pv.inRegNames })
   184  			inIdx++
   185  			in = append(in, op.encode())
   186  		}
   187  	}
   188  	slices.SortStableFunc(in, types.Operand.Compare)
   189  
   190  	db.Add("in", unify.Encode(in))
   191  	var inVar []types.Operand
   192  	for _, pv := range inst.predVariants {
   193  		// The governing predicate of the paired predicated encoding.
   194  		inVar = append(inVar, types.Operand{
   195  			Class:       "mask",
   196  			Bits:        types.VectorSize{Scalable: true},
   197  			Predication: new(pv.quals),
   198  			AsmPos:      pv.predAsmPos,
   199  		})
   200  	}
   201  	db.Add("inVariant", unify.Encode(inVar))
   202  	db.Add("out", unify.Encode(out))
   203  	return unify.NewValue(db.Build())
   204  }
   205  
   206  // emitAll emits the unify defs for this instruction — the concrete variants of
   207  // the source template. See classify (used by both emitAll and analyze) for the
   208  // full disposition.
   209  func (inst *Instruction) emitAll() []*unify.Value {
   210  	// emitAll doesn't check the anomalies, that would be done by
   211  	// a full-corpus test in analyze_test.go.
   212  	defs, _, _ := inst.classify()
   213  	return defs
   214  }
   215  
   216  // lookup returns the element width for the given size key in a table.
   217  func lookup(rows []arngRow, size string) (int, bool) {
   218  	for _, r := range rows {
   219  		if r.size == size {
   220  			return r.bits, true
   221  		}
   222  	}
   223  	return 0, false
   224  }
   225  
   226  // emitVariants emits one def per (integer signedness × arrangement row ×
   227  // predication). Each operand's element width comes from its own arrangement
   228  // symbol's table, keyed by the shared size field, so uniform and non-uniform
   229  // (widening/narrowing) forms are handled the same way; operands with no
   230  // arrangement stay unsized. Each operand's base type is resolved per operand
   231  // (laneIsFloat) — floating-point lanes are always "float", integer lanes take
   232  // the signedness of the current variant — so this naturally extends to
   233  // conversions, whose lanes will differ.
   234  func (inst *Instruction) emitVariants(template []Operand) []*unify.Value {
   235  	asm := inst.goOpPrefix() + inst.mnemonic()
   236  
   237  	links := arngLinks(template)
   238  	tables := map[string][]arngRow{}
   239  	for _, l := range links {
   240  		tables[l] = inst.resolveArrangementTable(l)
   241  	}
   242  
   243  	// Rows to iterate: the primary (destination-first) symbol's size keys, or a
   244  	// single pass when there is no variable arrangement.
   245  	var sizes []string
   246  	if len(links) > 0 {
   247  		for _, r := range tables[links[0]] {
   248  			sizes = append(sizes, r.size)
   249  		}
   250  	} else {
   251  		sizes = []string{""}
   252  	}
   253  
   254  	signs := inst.integerSignedness(template)
   255  
   256  	// Governing-predicate qualifier(s) for this template: /M, /Z, both (a /<ZM>
   257  	// encoding), or a single no-op pass when there is no governing predicate.
   258  	preds := predicationVariants(template)
   259  
   260  	// A bitwise operation with no variable arrangement is width-agnostic: the
   261  	// encoding is written .D, but any element view of it computes the same
   262  	// bits, and its predicated sibling is a per-<T> encoding. Emit a def per
   263  	// element width so every Go type gets the API, marked so that simdgen
   264  	// collapses the unpredicated machine op back to the single .D instruction.
   265  	widths := []int{0}
   266  	widthAgnostic := len(links) == 0 && inst.bitwise()
   267  	if widthAgnostic {
   268  		widths = []int{8, 16, 32, 64}
   269  	}
   270  
   271  	var defs []*unify.Value
   272  	for _, sign := range signs {
   273  		for _, size := range sizes {
   274  			ops := make([]Operand, len(template))
   275  			copy(ops, template)
   276  			skip := false
   277  			for i := range ops {
   278  				eb := ops[i].fixedElem
   279  				if ops[i].fixedBits > 0 {
   280  					// SIMD&FP scalar with a fixed width letter (<Dd> = 64), the
   281  					// same for every arrangement row.
   282  					eb = ops[i].fixedBits
   283  				} else if l := ops[i].arngLink; l != "" {
   284  					b, ok := lookup(tables[l], size)
   285  					if !ok {
   286  						// This operand's symbol has no element for this size
   287  						// (e.g. a RESERVED row on one side of a widening op).
   288  						skip = true
   289  						break
   290  					}
   291  					eb = b
   292  				}
   293  				base := sign
   294  				if inst.laneIsFloat(&ops[i]) {
   295  					base = "float"
   296  					if eb > 0 && eb < 16 {
   297  						// No half/quarter-word floating-point Go types.
   298  						skip = true
   299  						break
   300  					}
   301  				}
   302  				ops[i].instantiate(base, eb)
   303  			}
   304  			if skip {
   305  				continue
   306  			}
   307  			for _, pred := range preds {
   308  				variant := make([]Operand, len(ops))
   309  				copy(variant, ops)
   310  				elem := 0
   311  				mixedWidths := false
   312  				for i := range variant {
   313  					if variant[i].Class == "vreg" && variant[i].ElemBits > 0 {
   314  						if elem == 0 {
   315  							elem = variant[i].ElemBits
   316  						} else if variant[i].ElemBits != elem {
   317  							mixedWidths = true
   318  						}
   319  					}
   320  				}
   321  				for i := range variant {
   322  					if variant[i].Class != "mask" {
   323  						continue
   324  					}
   325  					if variant[i].governing {
   326  						variant[i].Predication = pred
   327  					}
   328  					if variant[i].ElemBits == 0 {
   329  						// This predicate doesn't come with an arrangement (which is usual).
   330  						// Get it from its peer data operand.
   331  						if mixedWidths && !mixedWidthLogged[inst.mnemonic()] {
   332  							mixedWidthLogged[inst.mnemonic()] = true
   333  							log.Printf("sve: %s: operands have mixed element widths; predicate width provisionally %d — derive esize from the pseudocode before generating an API from this def",
   334  								inst.mnemonic(), elem)
   335  						}
   336  						variant[i].ElemBits = elem
   337  					}
   338  				}
   339  				for _, w := range widths {
   340  					v := variant
   341  					if w > 0 {
   342  						v = make([]Operand, len(variant))
   343  						copy(v, variant)
   344  						for i := range v {
   345  							if v[i].Class == "vreg" || v[i].Class == "mask" {
   346  								v[i].ElemBits = w
   347  							}
   348  						}
   349  					}
   350  					defs = append(defs, inst.emitOne(asm, v, widthAgnostic))
   351  				}
   352  			}
   353  		}
   354  	}
   355  	return defs
   356  }
   357  

View as plain text