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