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

View as plain text