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