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  	"slices"
    11  	"strings"
    12  
    13  	"simd/archsimd/_gen/unify"
    14  )
    15  
    16  // asComment wraps text into // comment lines of at most width columns.
    17  func asComment(text string, width int) string {
    18  	text = strings.TrimSpace(text)
    19  	text = strings.ReplaceAll(text, "&", "&")
    20  	text = strings.ReplaceAll(text, "\n", " ")
    21  	words := strings.Fields(text)
    22  	var lines []string
    23  	line := ""
    24  	for _, w := range words {
    25  		if line != "" {
    26  			line += " "
    27  		}
    28  		line += w
    29  		if len(line) >= width {
    30  			lines = append(lines, "// "+line)
    31  			line = ""
    32  		}
    33  	}
    34  	if line != "" {
    35  		lines = append(lines, "// "+line)
    36  	}
    37  	return strings.Join(lines, "\n")
    38  }
    39  
    40  // emit renders an operand as a unify value. Z-vectors and predicates are
    41  // scalable (a base type and per-operand element width, no fixed bits/lanes);
    42  // mem, immediate and special operands are opaque (class and position only).
    43  func (op *Operand) emit() *unify.Value {
    44  	var db unify.DefBuilder
    45  	db.Add("class", unify.NewValue(unify.NewStringExact(op.Class)))
    46  	if op.BaseType != "" {
    47  		db.Add("base", unify.NewValue(unify.NewStringExact(op.BaseType)))
    48  	}
    49  	switch {
    50  	case op.Bits > 0:
    51  		// A fixed-width SIMD&FP scalar (OperandVFP): a real bit width and lanes.
    52  		db.Add("bits", unify.NewValue(unify.NewStringExact(fmt.Sprint(op.Bits))))
    53  		if op.Lanes > 0 {
    54  			db.Add("lanes", unify.NewValue(unify.NewStringExact(fmt.Sprint(op.Lanes))))
    55  		}
    56  	case op.Class == "vreg" || op.Class == "mask":
    57  		// SVE vectors and predicates are scalable: no fixed total bit width.
    58  		// The literal "scalable" both marks that and, because it conflicts with
    59  		// any numeric bits, keeps these operands from unifying with the
    60  		// fixed-width (NEON/AVX) types that share types.yaml.
    61  		db.Add("bits", unify.NewValue(unify.NewStringExact("scalable")))
    62  	}
    63  	if op.ElemBits > 0 {
    64  		db.Add("elemBits", unify.NewValue(unify.NewStringExact(fmt.Sprint(op.ElemBits))))
    65  	}
    66  	if op.Predication != "" {
    67  		// "M" (merging) or "Z" (zeroing) for a governing predicate. Some SVE
    68  		// instructions support only one; this records which.
    69  		db.Add("predication", unify.NewValue(unify.NewStringExact(op.Predication)))
    70  	}
    71  	if op.role == "mask" {
    72  		// role "mask" is precisely the governing predicate: the operand named <Pg>
    73  		// (buildOperandList assigns the role; every instruction has at most one). It
    74  		// is implicit-all-true — dropped from the unpredicated Go API and
    75  		// synthesized as an all-true predicate at lowering, so predicated-only
    76  		// instructions (e.g. ZCMPGT) expose an unpredicated API. Flagging it here,
    77  		// not in the user's go_*.yaml, keeps the YAML unpredicated.
    78  		//
    79  		// The governing predicate is identified by name, not by a /Z or /M
    80  		// qualifier: most data-processing ops write <Pg>/Z or <Pg>/M, but some
    81  		// governing predicates have no qualifier (e.g. the store ST1B {<Zt>.B},
    82  		// <Pg>, [...]). Either way it is <Pg>. Source predicates <Pn>/<Pm> (e.g. in
    83  		// AND <Pd>.B, <Pg>/Z, <Pn>.B, <Pm>.B) are ordinary numbered inputs (role
    84  		// "opN"), real data, and are never flagged all-true.
    85  		db.Add("implicitAllTrue", unify.NewValue(unify.NewStringExact("true")))
    86  	}
    87  	if op.isList {
    88  		// This register came from a single-register list ("{ <Zt>.<T> }"), a
    89  		// distinct assembler encoding from a bare register.
    90  		db.Add("listNumber", unify.NewValue(unify.NewStringExact("0")))
    91  	}
    92  	db.Add("asmPos", unify.NewValue(unify.NewStringExact(fmt.Sprint(op.AsmPos))))
    93  	return unify.NewValue(db.Build())
    94  }
    95  
    96  // emitOne emits a single instruction def from a fully-instantiated operand list:
    97  // the destination is the output, every other operand (including a governing
    98  // predicate) is a literal input.
    99  //
   100  // An SVE predicate is a mandatory input, not an optional AVX-512-style K-mask, so
   101  // it goes in `in`; inVariant is emitted empty just to satisfy the types.yaml schema.
   102  func (inst *Instruction) emitOne(asm string, ops []Operand) *unify.Value {
   103  	var db unify.DefBuilder
   104  	db.Add("asm", unify.NewValue(unify.NewStringExact(asm)))
   105  	db.Add("goarch", unify.NewValue(unify.NewStringExact("arm64")))
   106  	db.Add("cpuFeature", unify.NewValue(unify.NewStringExact(inst.cpuFeature())))
   107  	if doc := inst.documentation(); doc != "" {
   108  		db.Add("details", unify.NewValue(unify.NewStringExact(asComment(doc, 80))))
   109  	}
   110  
   111  	var inOps, outOps []Operand
   112  	for _, op := range ops {
   113  		if op.role == "destination" {
   114  			outOps = append(outOps, op)
   115  		} else {
   116  			inOps = append(inOps, op)
   117  		}
   118  	}
   119  	priority := map[string]int{"immediate": 0, "vreg": 1, "greg": 1, "memory": 1, "mask": 2}
   120  	slices.SortStableFunc(inOps, func(a, b Operand) int {
   121  		pa := priority[a.Class]
   122  		pb := priority[b.Class]
   123  		if pa != pb {
   124  			return cmp.Compare(pa, pb)
   125  		}
   126  		return cmp.Compare(a.AsmPos, b.AsmPos)
   127  	})
   128  
   129  	var ins, outs []*unify.Value
   130  	for i := range inOps {
   131  		ins = append(ins, inOps[i].emit())
   132  	}
   133  	for i := range outOps {
   134  		outs = append(outs, outOps[i].emit())
   135  	}
   136  	db.Add("in", unify.NewValue(unify.NewTuple(ins...)))
   137  	db.Add("inVariant", unify.NewValue(unify.NewTuple()))
   138  	db.Add("out", unify.NewValue(unify.NewTuple(outs...)))
   139  	return unify.NewValue(db.Build())
   140  }
   141  
   142  // emitAll emits the unify defs for this instruction — the concrete variants of
   143  // the source template. See classify (used by both emitAll and analyze) for the
   144  // full disposition.
   145  func (inst *Instruction) emitAll() []*unify.Value {
   146  	// emitAll doesn't check the anomalies, that would be done by
   147  	// a full-corpus test in analyze_test.go.
   148  	defs, _, _ := inst.classify()
   149  	return defs
   150  }
   151  
   152  // lookup returns the element width for the given size key in a table.
   153  func lookup(rows []arngRow, size string) (int, bool) {
   154  	for _, r := range rows {
   155  		if r.size == size {
   156  			return r.bits, true
   157  		}
   158  	}
   159  	return 0, false
   160  }
   161  
   162  // emitVariants emits one def per (integer signedness × arrangement row ×
   163  // predication). Each operand's element width comes from its own arrangement
   164  // symbol's table, keyed by the shared size field, so uniform and non-uniform
   165  // (widening/narrowing) forms are handled the same way; operands with no
   166  // arrangement stay unsized. Each operand's base type is resolved per operand
   167  // (laneIsFloat) — floating-point lanes are always "float", integer lanes take
   168  // the signedness of the current variant — so this naturally extends to
   169  // conversions, whose lanes will differ.
   170  func (inst *Instruction) emitVariants(template []Operand) []*unify.Value {
   171  	asm := inst.goOpPrefix() + inst.mnemonic()
   172  
   173  	links := arngLinks(template)
   174  	tables := map[string][]arngRow{}
   175  	for _, l := range links {
   176  		tables[l] = inst.resolveArrangementTable(l)
   177  	}
   178  
   179  	// Rows to iterate: the primary (destination-first) symbol's size keys, or a
   180  	// single pass when there is no variable arrangement.
   181  	var sizes []string
   182  	if len(links) > 0 {
   183  		for _, r := range tables[links[0]] {
   184  			sizes = append(sizes, r.size)
   185  		}
   186  	} else {
   187  		sizes = []string{""}
   188  	}
   189  
   190  	signs := inst.integerSignedness(template)
   191  
   192  	// Governing-predicate qualifier(s) for this template: /M, /Z, both (a /<ZM>
   193  	// encoding), or a single no-op pass when there is no governing predicate.
   194  	preds := predicationVariants(template)
   195  
   196  	var defs []*unify.Value
   197  	for _, sign := range signs {
   198  		for _, size := range sizes {
   199  			ops := make([]Operand, len(template))
   200  			copy(ops, template)
   201  			skip := false
   202  			for i := range ops {
   203  				eb := ops[i].fixedElem
   204  				if ops[i].fixedBits > 0 {
   205  					// SIMD&FP scalar with a fixed width letter (<Dd> = 64), the
   206  					// same for every arrangement row.
   207  					eb = ops[i].fixedBits
   208  				} else if l := ops[i].arngLink; l != "" {
   209  					b, ok := lookup(tables[l], size)
   210  					if !ok {
   211  						// This operand's symbol has no element for this size
   212  						// (e.g. a RESERVED row on one side of a widening op).
   213  						skip = true
   214  						break
   215  					}
   216  					eb = b
   217  				}
   218  				base := sign
   219  				if inst.laneIsFloat(&ops[i]) {
   220  					base = "float"
   221  					if eb > 0 && eb < 16 {
   222  						// No half/quarter-word floating-point Go types.
   223  						skip = true
   224  						break
   225  					}
   226  				}
   227  				ops[i].instantiate(base, eb)
   228  			}
   229  			if skip {
   230  				continue
   231  			}
   232  			for _, pred := range preds {
   233  				variant := make([]Operand, len(ops))
   234  				copy(variant, ops)
   235  				for i := range variant {
   236  					if variant[i].Class == "mask" && variant[i].role == "mask" {
   237  						variant[i].Predication = pred
   238  					}
   239  				}
   240  				defs = append(defs, inst.emitOne(asm, variant))
   241  			}
   242  		}
   243  	}
   244  	return defs
   245  }
   246  

View as plain text