Source file src/simd/archsimd/_gen/simdgen/sve/load.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  	"slices"
     9  	"sort"
    10  
    11  	"simd/archsimd/_gen/unify"
    12  
    13  	"golang.org/x/arch/arm64/instgen/xmlspec"
    14  )
    15  
    16  // parseInstructions parses the ARM64 ISA XML files at path and returns the
    17  // SVE / SVE2 instructions.
    18  func parseInstructions(path string) ([]*Instruction, error) {
    19  	xmlInsts := xmlspec.ParseXMLFiles(path)
    20  
    21  	// One XML section can hold several iclasses with distinct mnemonics
    22  	// (e.g. SUNPKHI + SUNPKLO), so expand to one logical instruction per iclass.
    23  	var insts []*Instruction
    24  	for _, xmlInst := range xmlInsts {
    25  		if xmlInst == nil {
    26  			continue
    27  		}
    28  		for i := range xmlInst.Instruction.Classes.Iclass {
    29  			inst := &Instruction{
    30  				Instruction: xmlInst.Instruction,
    31  				iclass:      &xmlInst.Instruction.Classes.Iclass[i],
    32  			}
    33  			if inst.mnemonic() == "" || !inst.isSVE() {
    34  				// TODO: handle more extensions?
    35  				continue
    36  			}
    37  			insts = append(insts, inst)
    38  		}
    39  	}
    40  
    41  	sort.Slice(insts, func(i, j int) bool {
    42  		return insts[i].mnemonic() < insts[j].mnemonic()
    43  	})
    44  	return insts, nil
    45  }
    46  
    47  // Load parses the ARM64 ISA XML files at path and returns the SVE / SVE2
    48  // instruction definitions as simdgen unify values.
    49  func Load(path string) ([]*unify.Value, error) {
    50  	insts, err := parseInstructions(path)
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  	covered := groupPredicationForms(insts)
    55  	var defs []*unify.Value
    56  	for _, inst := range insts {
    57  		if covered[inst] {
    58  			// The predicated half of a pair; it is emitted as an inVariant of its
    59  			// unpredicated sibling so the operation has a single unifier value.
    60  			continue
    61  		}
    62  		defs = append(defs, inst.emitAll()...)
    63  	}
    64  	return defs, nil
    65  }
    66  
    67  // groupPredicationForms pairs the predicated and unpredicated encodings of the
    68  // same operation and folds them into one definition, mirroring how the AMD64
    69  // loader treats an AVX-512 instruction's optional K-mask: the unpredicated form
    70  // supplies the operation (and therefore the single front-end API), and the
    71  // governing predicate becomes an inVariant that simdgen turns into predicated
    72  // machine ops plus peepholes.
    73  // However, different from AVX-512, where the predication mode is orthogonal to
    74  // the operation as an instruction suffix, SVE's predication modes are separate
    75  // instruction encodings, so the loader has to pair them up.
    76  //
    77  // A pair is only formed when both forms actually exist and their operand shapes
    78  // correspond; the returned set names the predicated instructions that the pair
    79  // covers, which the caller then skips. Everything else — an operation with only
    80  // a predicated form (whose predicate stays implicit-all-true), or only an
    81  // unpredicated one — is emitted unchanged.
    82  func groupPredicationForms(insts []*Instruction) map[*Instruction]bool {
    83  	type group struct{ unpred, pred []*Instruction }
    84  	groups := map[string]*group{}
    85  	for _, inst := range insts {
    86  		key := inst.predicationGroupKey()
    87  		if key == "" {
    88  			continue
    89  		}
    90  		g := groups[key]
    91  		if g == nil {
    92  			g = &group{}
    93  			groups[key] = g
    94  		}
    95  		if inst.predicationForm() == "unpredicated" {
    96  			g.unpred = append(g.unpred, inst)
    97  		} else {
    98  			g.pred = append(g.pred, inst)
    99  		}
   100  	}
   101  
   102  	covered := map[*Instruction]bool{}
   103  	for _, g := range groups {
   104  		if len(g.unpred) == 0 && len(g.pred) > 1 {
   105  			groupPredicatedOnly(g.pred, covered)
   106  			continue
   107  		}
   108  		if len(g.unpred) != 1 || len(g.pred) == 0 {
   109  			// Not a clean pair (a form is missing, or the title is ambiguous);
   110  			// leave both halves to be emitted as they are.
   111  			continue
   112  		}
   113  		un := g.unpred[0]
   114  		unOps := un.operands()
   115  		var variants []predVariant
   116  		for _, pr := range g.pred {
   117  			prOps := pr.operands()
   118  			if !sameOperandShape(unOps, prOps) {
   119  				continue
   120  			}
   121  			var quals string
   122  			for _, q := range predicationVariants(prOps) {
   123  				quals += q
   124  			}
   125  			if quals == "" {
   126  				continue
   127  			}
   128  			// Each encoding carries its own register symbols, so a machine op is
   129  			// always generated from the shape of the encoding it comes from.
   130  			outs, ins := splitRegNames(prOps)
   131  			variants = append(variants, predVariant{quals: quals, outRegNames: outs, inRegNames: ins, predAsmPos: governingAsmPos(prOps)})
   132  			covered[pr] = true
   133  		}
   134  		if len(variants) == 0 {
   135  			continue
   136  		}
   137  		un.predVariants = variants
   138  	}
   139  	return covered
   140  }
   141  
   142  // groupPredicatedOnly folds the encodings of an operation that has no
   143  // unpredicated form at all — SVE writes ABS as "ABS <Zd>.<T>, <Pg>/M, <Zn>.<T>"
   144  // and "ABS <Zd>.<T>, <Pg>/Z, <Zn>.<T>", and nothing else.
   145  //
   146  // There is no unpredicated encoding to carry the operation, so one of the
   147  // predicated encodings does. Its governing predicate stays implicit-all-true, so
   148  // the front-end API is still unpredicated, and every qualifier in the group —
   149  // its own included — becomes an inVariant qualifier, which simdgen turns into
   150  // one predicated machine op each for the peepholes to fold into.
   151  //
   152  // Only the merging encoding is used. Zeroing predication on these instructions
   153  // is an Armv9.6-A extension -- ABS assembles to a different opcode under /Z, and
   154  // baseline SVE hardware traps it -- while merging is available wherever SVE is.
   155  // Nothing in what the XML parser exposes tells the two apart: both carry
   156  // instr-class "sve", and the arch_variant element that does record the
   157  // difference is not surfaced. So the zeroing encodings are dropped here rather
   158  // than gated, to be folded in with the rest of SVE2.2 once simdgen can gate on
   159  // the SVE sub-level.
   160  //
   161  // The group is folded only when the encodings are variations on one predication
   162  // mode and nothing else: same operand shape, and one encoding per qualifier. Two
   163  // encodings sharing a qualifier are two different instructions that happen to
   164  // share a title (addressing modes of a load, say), and are left alone.
   165  func groupPredicatedOnly(pred []*Instruction, covered map[*Instruction]bool) {
   166  	byQual := map[string]*Instruction{}
   167  	shape := pred[0].operands()
   168  	for _, inst := range pred {
   169  		ops := inst.operands()
   170  		if !sameOperandShape(shape, ops) {
   171  			return
   172  		}
   173  		quals := predicationVariants(ops)
   174  		if len(quals) != 1 || quals[0] == "" {
   175  			return
   176  		}
   177  		if _, dup := byQual[quals[0]]; dup {
   178  			return
   179  		}
   180  		byQual[quals[0]] = inst
   181  	}
   182  	base, ok := byQual["M"]
   183  	if !ok {
   184  		return
   185  	}
   186  	baseOps := base.operands()
   187  	outs, ins := splitRegNames(baseOps)
   188  	base.predVariants = []predVariant{{quals: "M", outRegNames: outs, inRegNames: ins, predAsmPos: governingAsmPos(baseOps)}}
   189  	for _, inst := range byQual {
   190  		if inst != base {
   191  			covered[inst] = true
   192  		}
   193  	}
   194  }
   195  
   196  // governingAsmPos returns the assembly position of the governing predicate in
   197  // ops. Both callers work on encodings already classified as predicated, so a
   198  // missing governing predicate is a broken invariant, not a case.
   199  func governingAsmPos(ops []Operand) int {
   200  	for i := range ops {
   201  		if ops[i].governing {
   202  			return ops[i].AsmPos
   203  		}
   204  	}
   205  	panic("sve: predicated encoding has no governing predicate")
   206  }
   207  
   208  // splitRegNames returns an operand template's register symbols, results first
   209  // and then the non-predicate inputs, in the order sameOperandShape compares
   210  // them, so the two halves of a pair line up element by element.
   211  func splitRegNames(ops []Operand) (outs, ins []string) {
   212  	for i := range ops {
   213  		if ops[i].governing {
   214  			continue
   215  		}
   216  		if ops[i].role == "destination" {
   217  			outs = append(outs, ops[i].regName)
   218  		} else {
   219  			ins = append(ins, ops[i].regName)
   220  		}
   221  	}
   222  	return outs, ins
   223  }
   224  
   225  // sameOperandShape reports whether two operand templates describe the same
   226  // operation apart from a governing predicate: same result and same sequence of
   227  // non-predicate input classes. The predicated form of a destructive operation
   228  // names its destination twice (once as the in-place source), which
   229  // buildOperandList already turns into a regular input, so the two shapes line up.
   230  func sameOperandShape(a, b []Operand) bool {
   231  	split := func(ops []Operand) (outs, ins []string) {
   232  		for i := range ops {
   233  			if ops[i].governing {
   234  				continue // the governing predicate is what differs
   235  			}
   236  			if ops[i].role == "destination" {
   237  				outs = append(outs, ops[i].Class)
   238  			} else {
   239  				ins = append(ins, ops[i].Class)
   240  			}
   241  		}
   242  		return outs, ins
   243  	}
   244  	ao, ai := split(a)
   245  	bo, bi := split(b)
   246  	return slices.Equal(ao, bo) && slices.Equal(ai, bi)
   247  }
   248  

View as plain text