Source file src/simd/archsimd/_gen/simdgen/sve/operands.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 "fmt" 9 "log" 10 "regexp" 11 "strings" 12 13 "golang.org/x/arch/arm64/instgen/xmlspec" 14 ) 15 16 // arngValueRe matches an arrangement symbol's displayed value: the vector forms 17 // <T>, <Ta>, <Tb>, and the <V> size specifier of a SIMD&FP scalar (<V><d>). Its 18 // <a> link identifies the size table that gives this operand's element widths 19 // (see Instruction.resolveArrangementTable). 20 var arngValueRe = regexp.MustCompile(`^<(T[a-z]*|V)>$`) 21 22 // fixedArngRe matches a hardcoded element specifier, e.g. the ".D" in <Zm>.D. 23 var fixedArngRe = regexp.MustCompile(`\.([BHSD])\b`) 24 25 // simdFPRe matches a SIMD&FP scalar register: a fixed-width form (<Dd>, <Sn>, 26 // <Hd>, <Bd>, <Qd>) or an element-sized form (<V><d>, <V><n>). These hold a 27 // single value (a reduction result, or a DUP source), not a scalable vector. 28 var simdFPRe = regexp.MustCompile(`^(<[BHSDQ][a-z]>|<V><[a-z]>)$`) 29 30 // OperandType classifies an SVE instruction operand. 31 type OperandType int 32 33 const ( 34 // OperandZReg is a scalable vector register (Z), e.g. <Zd>.<T>, <Zn>.<T>. 35 // It has no fixed total bit width: the width is the implementation-defined 36 // vector length. Only its element type and element width are known. 37 OperandZReg OperandType = iota 38 // OperandPReg is a scalable predicate register (P), e.g. <Pg>/M, <Pd>.<T>. 39 // A predicate is modeled as a Go mask value. 40 OperandPReg 41 // OperandGReg is a general-purpose scalar register (W/X/R). 42 OperandGReg 43 // OperandVFP is a SIMD&FP scalar register (<Dd>, <V><d>, ...): a single 44 // fixed-width value, such as a horizontal reduction's result (SADDV <Dd>) or 45 // a DUP scalar source. Unlike a Z register it is not scalable. 46 OperandVFP 47 // OperandImm is an immediate. 48 OperandImm 49 // OperandMem is a memory operand, e.g. [<Xn|SP>{, #<imm>, MUL VL}] or a 50 // gather/scatter address like [<Xn|SP>, <Zm>.D, SXTW]. simdgen does not yet 51 // distinguish the memory addressing modes; they are all one "mem" class. 52 OperandMem 53 // OperandList is a register list, e.g. { <Zt>.B } or { <Zt1>.D-<Zt2>.D }. 54 // TODO: register lists are not modeled yet; instructions carrying one are 55 // skipped (see classify). 56 OperandList 57 // OperandSpecial is a recognized but not-yet-detailed operand: an indexed 58 // register (<Zm>.<T>[<index>]), a register with an optional modifier 59 // ({, <pattern>}), or a special token (<prfop>, <vl>, <pattern>, <const>, 60 // <mod>, and NEON-style <Vd>/<Dd> reduction results). 61 OperandSpecial 62 // OperandUnknown is a token the classifier could not place at all; an anomaly. 63 OperandUnknown 64 ) 65 66 func (t OperandType) String() string { 67 switch t { 68 case OperandZReg: 69 return "ZReg" 70 case OperandPReg: 71 return "PReg" 72 case OperandGReg: 73 return "GReg" 74 case OperandVFP: 75 return "VFP" 76 case OperandImm: 77 return "Imm" 78 case OperandMem: 79 return "Mem" 80 case OperandList: 81 return "List" 82 case OperandSpecial: 83 return "Special" 84 default: 85 return "Unknown" 86 } 87 } 88 89 // Operand is an SVE instruction operand instantiated for a concrete element size. 90 type Operand struct { 91 Type OperandType 92 Class string // "vreg", "mask", "greg", "immediate", "mem", "reglist", "special" 93 BaseType string // "int", "uint", "float" (for vreg/mask/greg) 94 ElemBits int // element width in bits (8/16/32/64); 0 if unsized 95 // Bits and Lanes are set for a fixed-width scalar register — a general-purpose 96 // greg (<Xd>) or a SIMD&FP vreg (<Dd>): the total register width and lane 97 // count (always 1). A scalable Z-vector leaves them 0 and is marked 98 // "scalable" in the emitted def instead. 99 Bits int 100 Lanes int 101 102 // Predication is "M" (merging) or "Z" (zeroing) for governing predicates, 103 // otherwise "". 104 Predication string 105 // AsmPos is the position in the assembly syntax (0 for the destination 106 // register, 1+ for inputs). It mirrors the source template order and is the 107 // field simdgen uses to order operands. 108 AsmPos int 109 // Raw is the source operand token, retained for deferred (mem/list/special) 110 // and unknown operands so diagnostics can name what was skipped. 111 Raw string 112 113 // role is the operand's internal role: "destination" or "op0"/"op1"/.... 114 // It drives out/in partitioning at emit time but is NOT emitted (simdgen 115 // orders operands by AsmPos, so a role field in the YAML would be 116 // redundant). A governing predicate has no role; it is marked by governing. 117 role string 118 // governing marks the governing predicate — the operand selecting which 119 // lanes the instruction acts on, as opposed to a predicate read as data. 120 // It is classified from the spec's own explanation text for the symbol and 121 // emitted as the def's "governing" field. 122 governing bool 123 // arngLink is the <a> link of this operand's arrangement symbol (<T>/<Ta>/ 124 // <Tb>), used to resolve its per-operand element widths. Empty if the 125 // operand has a fixed or no arrangement. 126 arngLink string 127 // fixedElem is a hardcoded element width (from e.g. ".D"), or 0. 128 fixedElem int 129 // fixedBits is the fixed total width of a SIMD&FP scalar named by a size 130 // letter (<Dd> -> 64, <Sd> -> 32, ...), or 0 for an element-sized <V><d>. 131 fixedBits int 132 // isList reports that this register came from a single-register list 133 // ("{ <Zt>.<T> }"). It is a distinct assembler encoding from a bare register, 134 // so it is preserved (emitted as listNumber) even though the register is 135 // otherwise handled like any vreg. 136 isList bool 137 // regName is the inner register symbol, e.g. "Zdn", "Zm", "Pg". 138 regName string 139 // predRegName is the symbol this operand has in each paired predicated 140 // encoding, indexed to match the operation's predicated variants (and so the 141 // inVariant tuple the def carries). It is nil when the operation has no 142 // predicated form. 143 predRegName []string 144 } 145 146 // resultInArg0 reports whether this destination register is also read, i.e. it 147 // is written in place (an ARM <Zdn>/<Zda>-style operand). 148 func (op *Operand) resultInArg0() bool { 149 return op.role == "destination" && isInPlaceReg(op.regName) 150 } 151 152 // aElem is a single <a> symbol from an assembly template: its displayed value 153 // and its link. The link, not the value, is the stable key used to resolve a 154 // symbol's definition (see Instruction.findExplanation). 155 // 156 // For example, in the template "ADD <Zdn>.<T>, ..." the operand "<Zdn>.<T>" 157 // contributes two <a> elements: 158 // 159 // {value: "<Zdn>", link: "Zdn"} // the register symbol 160 // {value: "<T>", link: "T__3"} // the arrangement symbol 161 type aElem struct { 162 value string 163 link string 164 } 165 166 // rawTok is one operand's raw text plus the <a> symbols it contains, before 167 // classification. The <a> links let us resolve each operand's arrangement. 168 // 169 // For "ADD <Zdn>.<T>, <Pg>/M, <Zdn>.<T>, <Zm>.<T>", the third operand tokenizes 170 // to: 171 // 172 // rawTok{ 173 // text: "<Zdn>.<T>", 174 // asmPos: 2, // 0 = destination, 1+ = following operands 175 // aElems: [{"<Zdn>","Zdn"}, {"<T>","T__3"}], 176 // } 177 type rawTok struct { 178 text string 179 asmPos int 180 aElems []aElem 181 } 182 183 // tok is a rawTok after classification, before it is instantiated for 184 // a concrete element size. Examples of the interesting fields: 185 // 186 // "<Zdn>.<T>" -> {operandType: OperandZReg, isDestination: true, 187 // regName: "Zdn", arngLink: "T__3"} 188 // "<Zm>.<T>" -> {operandType: OperandZReg, isDestination: false, 189 // regName: "Zm", arngLink: "T__3"} 190 // "<Pg>/M" -> {operandType: OperandPReg, predication: "M", 191 // regName: "Pg"} // governing predicate ("Z"/"MZ" too) 192 // "<Zt>.D" -> {operandType: OperandZReg, fixedElem: 64} 193 // // hardcoded arrangement, so no arngLink 194 // "#<imm>" -> {operandType: OperandImm} 195 // "[<Xn|SP>{, #<imm>}]"-> {operandType: OperandMem} 196 // "<Zm>.<T>[<index>]" -> {operandType: OperandSpecial} // indexed, not modeled 197 type tok struct { 198 // text is the raw operand token, e.g. "<Zdn>.<T>". 199 text string 200 // asmPos is the position in the assembly syntax (0 = destination, 1+ = the 201 // following operands), mirroring the template order. 202 asmPos int 203 // operandType is the classification (OperandZReg, OperandPReg, OperandMem, 204 // OperandSpecial, ...). 205 operandType OperandType 206 // isDestination is true when the register is written (an ARM 'd'-role symbol 207 // such as <Zd>, <Zdn>, <Pd>). 208 isDestination bool 209 // predication is "M" (merging), "Z" (zeroing), or "MZ" (a <Pg>/<ZM> encoding 210 // selecting either) for a governing predicate; "" otherwise. 211 predication string 212 // regName is the inner register symbol, e.g. "Zdn", "Zm", "Pg". 213 regName string 214 // arngLink is the <a> link of this operand's variable arrangement symbol 215 // (<T>/<Ta>/<Tb>, or <V> for a SIMD&FP scalar), used to resolve its element 216 // widths; "" if the arrangement is fixed or absent. 217 arngLink string 218 // fixedElem is a hardcoded element width in bits from a literal ".B"/".H"/ 219 // ".S"/".D" (8/16/32/64), or 0. 220 fixedElem int 221 // fixedBits is the fixed total width of a SIMD&FP scalar named by a size 222 // letter (<Dd> -> 64, <Sd> -> 32, ...), or 0 for an element-sized <V><d>. 223 fixedBits int 224 // isList reports that this register came from a single-register list 225 isList bool 226 } 227 228 // operandsFromTextA parses operands from an assembly template's <text>/<a> 229 // sequence, preserving each operand's arrangement-symbol link. govern reports 230 // whether a predicate register symbol is the governing predicate; the real 231 // loader path passes the instruction's explanation lookup. 232 func operandsFromTextA(textA []xmlspec.TextA, govern func(regName string) (governing, found bool)) []Operand { 233 return buildOperandList(classifyToks(tokenizeTextA(textA)), govern) 234 } 235 236 // operands parses operands from a flattened template string. It cannot recover 237 // <a> links or explanations, so arrangement symbols resolve to empty links and 238 // the governing predicate is classified syntactically; it is used for 239 // classification-only paths and tests. The real loader path uses 240 // operandsFromTextA. 241 func operands(asmTemplate string) []Operand { 242 return buildOperandList(classifyToks(tokenizeString(asmTemplate)), nil) 243 } 244 245 // tokenizeTextA splits a <text>/<a> sequence into operand tokens on top-level 246 // commas, stripping the leading mnemonic and recording each <a> symbol. 247 func tokenizeTextA(textA []xmlspec.TextA) []rawTok { 248 var toks []rawTok 249 cur := rawTok{} 250 depth := 0 251 started := false // have we passed the mnemonic word? 252 flush := func() { 253 cur.text = strings.TrimSpace(cur.text) 254 if cur.text != "" || len(cur.aElems) > 0 { 255 cur.asmPos = len(toks) 256 toks = append(toks, cur) 257 } 258 cur = rawTok{} 259 } 260 for _, ta := range textA { 261 if ta.Link != "" { 262 cur.text += ta.Value 263 cur.aElems = append(cur.aElems, aElem{strings.TrimSpace(ta.Value), ta.Link}) 264 started = true 265 continue 266 } 267 s := ta.Value 268 if !started { 269 // Strip the mnemonic: keep everything after the first space. 270 if i := strings.IndexByte(s, ' '); i >= 0 { 271 s = s[i:] 272 } else { 273 s = "" 274 } 275 started = true 276 } 277 for _, r := range s { 278 switch r { 279 case '[', '{': 280 depth++ 281 case ']', '}': 282 depth-- 283 case ',': 284 if depth == 0 { 285 flush() 286 continue 287 } 288 } 289 cur.text += string(r) 290 } 291 } 292 flush() 293 return toks 294 } 295 296 // tokenizeString splits a flattened template string into operand tokens. It has 297 // no <a> link information. 298 func tokenizeString(template string) []rawTok { 299 template = stripMnemonic(template) 300 var toks []rawTok 301 depth := 0 302 var cur strings.Builder 303 flush := func() { 304 if s := strings.TrimSpace(cur.String()); s != "" { 305 toks = append(toks, rawTok{text: s, asmPos: len(toks)}) 306 } 307 cur.Reset() 308 } 309 for _, r := range template { 310 switch r { 311 case '[', '{': 312 depth++ 313 case ']', '}': 314 depth-- 315 case ',': 316 if depth == 0 { 317 flush() 318 continue 319 } 320 } 321 cur.WriteRune(r) 322 } 323 flush() 324 return toks 325 } 326 327 // stripMnemonic removes the leading mnemonic from an assembly template. A 328 // template with no space is a mnemonic-only (nullary) instruction. 329 func stripMnemonic(template string) string { 330 if _, after, ok := strings.Cut(strings.TrimSpace(template), " "); ok { 331 return strings.TrimSpace(after) 332 } 333 return "" 334 } 335 336 // classifyToks classifies each raw token and attaches its arrangement source. 337 func classifyToks(toks []rawTok) []tok { 338 parsed := make([]tok, 0, len(toks)) 339 for _, t := range toks { 340 p := classifyText(t.text, t.asmPos) 341 // Per-operand arrangement: could be a variable arrangement symbol (<T>/<Ta>/<Tb>) 342 // or a fixed element, or none, e.g. for a greg. 343 for _, a := range t.aElems { 344 if arngValueRe.MatchString(a.value) { 345 p.arngLink = a.link 346 } 347 } 348 if p.arngLink == "" { 349 if m := fixedArngRe.FindStringSubmatch(t.text); m != nil { 350 p.fixedElem = elemLetterBits(m[1]) 351 } 352 } 353 parsed = append(parsed, p) 354 } 355 return parsed 356 } 357 358 // classifyText determines an operand's type, destination-ness, predication and 359 // register symbol from its text. 360 // 361 // A register token counts as "clean" only if it has no index or optional 362 // modifier ('[' or '{'). Indexed/modified registers and other angle-bracket 363 // tokens (<prfop>, <vl>, <mod>, <Vd>, ...) are OperandSpecial; anything else is 364 // OperandUnknown. 365 func classifyText(text string, asmPos int) tok { 366 p := tok{text: text, asmPos: asmPos} 367 // A single-register list ("{ <Zt>.<T> }") is treated as its inner register 368 // (but flagged, as it is a distinct assembler encoding); multi-register lists 369 // remain OperandList (deferred). 370 reg := text 371 if inner, ok := singleRegList(text); ok { 372 reg = inner 373 p.isList = true 374 } 375 clean := !strings.ContainsAny(reg, "[{") 376 switch { 377 case strings.HasPrefix(reg, "["): 378 p.operandType = OperandMem 379 case strings.HasPrefix(reg, "{"): 380 p.operandType = OperandList 381 case strings.HasPrefix(reg, "#"), strings.HasPrefix(reg, "<const>"): 382 p.operandType = OperandImm 383 case simdFPRe.MatchString(reg): 384 // A SIMD&FP scalar register: a reduction result <Dd>/<V><d> or a DUP 385 // source <V><n>. Its width is fixed by the size letter, or element-sized 386 // for the <V> form (resolved via its <a> link like <T>). 387 p.operandType = OperandVFP 388 p.regName = regSymbol(reg) 389 p.isDestination = isDestinationReg(p.regName) || strings.Contains(reg, "<d>") 390 p.fixedBits = simdFPLetterBits(reg) 391 case clean && strings.HasPrefix(reg, "<Z"): 392 p.operandType = OperandZReg 393 p.regName = regSymbol(reg) 394 p.isDestination = isDestinationReg(p.regName) 395 case clean && strings.HasPrefix(reg, "<P"): 396 p.operandType = OperandPReg 397 p.regName = regSymbol(reg) 398 p.isDestination = isDestinationReg(p.regName) 399 switch { 400 case strings.Contains(reg, "/<ZM>"): 401 // A single encoding (MOVPRFX) whose bit selects merging or zeroing. 402 p.predication = "MZ" 403 case strings.HasSuffix(reg, "/M"): 404 p.predication = "M" 405 case strings.HasSuffix(reg, "/Z"): 406 p.predication = "Z" 407 } 408 case clean && (strings.HasPrefix(reg, "<W") || strings.HasPrefix(reg, "<X") || strings.HasPrefix(reg, "<R")): 409 p.operandType = OperandGReg 410 p.regName = regSymbol(reg) 411 p.isDestination = isDestinationReg(p.regName) 412 p.fixedBits = gregLetterBits(reg) 413 case strings.HasPrefix(reg, "<"): 414 p.operandType = OperandSpecial 415 // A special operand can still be a destination, e.g. an indexed 416 // destination <Zd>.<T>[<index>]. 417 p.regName = regSymbol(reg) 418 p.isDestination = isDestinationReg(p.regName) || strings.Contains(reg, "<d>") 419 default: 420 p.operandType = OperandUnknown 421 } 422 return p 423 } 424 425 // singleRegList reports whether text is a single-register list like 426 // "{ <Zt>.<T> }" and, if so, returns its inner register token. Multi-register 427 // lists (a comma-separated set or a "-" range) return false and stay opaque. 428 func singleRegList(text string) (string, bool) { 429 if !strings.HasPrefix(text, "{") || !strings.HasSuffix(text, "}") { 430 return "", false 431 } 432 inner := strings.TrimSpace(text[1 : len(text)-1]) 433 if strings.ContainsAny(inner, ",-") { // multiple registers or a range 434 return "", false 435 } 436 return inner, true 437 } 438 439 // simdFPLetterBits returns the fixed width of a size-lettered SIMD&FP scalar 440 // register (<Bd>=8, <Hd>=16, <Sd>=32, <Dd>=64, <Qd>=128), or 0 for the 441 // element-sized <V><d> form (whose width comes from its <V> arrangement link). 442 func simdFPLetterBits(text string) int { 443 if len(text) < 2 { 444 return 0 445 } 446 switch text[1] { 447 case 'B': 448 return 8 449 case 'H': 450 return 16 451 case 'S': 452 return 32 453 case 'D': 454 return 64 455 case 'Q': 456 return 128 457 } 458 return 0 459 } 460 461 // gregLetterBits returns the width of a general-purpose scalar register from its 462 // size letter (<Wd>=32, <Xd>=64), or 0 when the width is not fixed by the name 463 // (e.g. the width-variable <R> form). 464 func gregLetterBits(text string) int { 465 if len(text) < 2 { 466 return 0 467 } 468 switch text[1] { 469 case 'W': 470 return 32 471 case 'X': 472 return 64 473 } 474 return 0 475 } 476 477 // regSymbol extracts the inner register symbol from a token, e.g. "<Zdn>.<T>" -> 478 // "Zdn", "<Pg>/M" -> "Pg". 479 func regSymbol(text string) string { 480 if i := strings.IndexByte(text, '<'); i >= 0 { 481 text = text[i+1:] 482 } 483 if i := strings.IndexByte(text, '>'); i >= 0 { 484 text = text[:i] 485 } 486 return text 487 } 488 489 // isDestinationReg reports whether a register symbol names a destination 490 // register. The destination role letter 'd' appears either right after the 491 // class letter (Zd, Zda, Zdn), or as the trailing role letter (Pd, Wd, Xd, PNd). 492 func isDestinationReg(name string) bool { 493 if len(name) < 2 { 494 return false 495 } 496 return name[1] == 'd' || name[len(name)-1] == 'd' 497 } 498 499 // isInPlaceReg reports whether a destination register symbol is also a source 500 // (read-modify-write), such as <Zdn> or <Zda>. A bare <Zd> is a pure output. 501 func isInPlaceReg(name string) bool { 502 return len(name) >= 3 && name[1] == 'd' 503 } 504 505 // buildOperandList lowers tokens into Operands ordered as outputs then 506 // inputs, assigning roles and handling read-modify-write destinations. 507 // 508 // Unlike an AMD64 AVX-512 K-mask, an SVE governing predicate is NOT optional: 509 // there is no K0-style "no predicate" encoding, so it is a mandatory literal 510 // input (class "mask", marked governing), not an inVariant. See the discussion 511 // in emitOne. 512 func buildOperandList(parsed []tok, govern func(regName string) (governing, found bool)) []Operand { 513 var outs, ins []Operand 514 inputCount := 0 515 destAssigned := false 516 517 // place assigns op's role — the (single) destination if isDestination, 518 // otherwise the next numbered input "opN" (a repeated destination symbol is 519 // the in-place source) — and files it under outs or ins. 520 place := func(op Operand, isDestination bool) { 521 if isDestination && !destAssigned { 522 op.role = "destination" 523 destAssigned = true 524 outs = append(outs, op) 525 return 526 } 527 op.role = inputRole(inputCount) 528 inputCount++ 529 ins = append(ins, op) 530 } 531 532 deferredClass := map[OperandType]string{ 533 OperandMem: "mem", 534 OperandList: "reglist", 535 OperandSpecial: "special", 536 OperandUnknown: "unknown", 537 } 538 539 for _, p := range parsed { 540 // We don't model the details of these types yet, so just naively record them and continue. 541 // TODO: we might need at least the details of OperandMem soon. 542 if class, ok := deferredClass[p.operandType]; ok { 543 place(Operand{ 544 Type: p.operandType, Class: class, Raw: p.text, 545 AsmPos: p.asmPos, regName: p.regName, 546 }, p.isDestination) 547 continue 548 } 549 switch p.operandType { 550 case OperandPReg: 551 // The governing predicate is classified from the spec's own words: 552 // its explanation calls the symbol "the governing scalable predicate 553 // register". The syntactic signal — the symbol is <Pg>, or it carries 554 // a /M or /Z qualifier — is kept as a cross-check, so a shape where 555 // the two diverge fails loudly instead of misclassifying: the SME 556 // outer products govern with two predicates spelled <Pn>/<Pm> 557 // (SUMOPA <ZAda>.S, <Pn>/M, <Pm>/M, <Zn>.B, <Zm>.B), which does not 558 // fit the one-governing-predicate shape simdgen builds on and must 559 // be rejected here, not silently halved. The bare-string parse path 560 // (tests, diagnostics) has no explanations and uses the syntactic 561 // signal alone. 562 syntactic := p.regName == "Pg" || p.predication != "" 563 governing := syntactic 564 if govern != nil { 565 if verdict, found := govern(p.regName); found { 566 governing = verdict 567 if governing != syntactic { 568 // The explanation wins, but say so. Two shapes diverge in 569 // the ISA today, in opposite directions: the MOV alias of 570 // SEL (MOV <Zd>.<T>, <Pv>/M, <Zn>.<T>), whose <Pv> keeps 571 // its "select" description from SEL even though the alias 572 // writes it with a qualifier; and the bare <PNg> of the 573 // predicate-as-counter loads/stores, which the spec calls 574 // governing though it is neither <Pg> nor qualified. 575 // Neither instruction is emitted today, so nothing 576 // downstream sees the difference — but the second is the 577 // case the explanation gets right and the syntax cannot. 578 log.Printf("sve: symbol <%s> (qualifier %q): explanation says governing=%v, syntactic signal says %v; following the explanation", 579 p.regName, p.predication, governing, syntactic) 580 } 581 } 582 // No explanation for this symbol — an alias template, or a test 583 // fixture with the explanations stripped — so the syntactic 584 // signal stands alone. 585 } 586 if governing { 587 // A mandatory mask input, not a numbered opN. Most carry a /Z or 588 // /M qualifier (predicated data-processing ops), but some do not 589 // — the store ST1B {<Zt>.B}, <Pg>, [...] governs with a plain 590 // <Pg>. Source predicates <Pn>/<Pm> and the destination <Pd> are 591 // ordinary operands, filed by place() below. 592 ins = append(ins, Operand{ 593 Type: OperandPReg, Class: "mask", governing: true, 594 Predication: p.predication, AsmPos: p.asmPos, 595 arngLink: p.arngLink, fixedElem: p.fixedElem, regName: p.regName, 596 }) 597 continue 598 } 599 place(Operand{ 600 Type: OperandPReg, Class: "mask", AsmPos: p.asmPos, 601 arngLink: p.arngLink, fixedElem: p.fixedElem, isList: p.isList, regName: p.regName, 602 }, p.isDestination) 603 case OperandImm: 604 place(Operand{Type: OperandImm, Class: "immediate", AsmPos: p.asmPos}, false) 605 default: // OperandZReg, OperandGReg, OperandVFP 606 class := "vreg" 607 if p.operandType == OperandGReg { 608 // A general-purpose scalar register. 609 class = "greg" 610 } 611 // A SIMD&FP scalar (OperandVFP) stays "vreg": it lives in the FP/SIMD 612 // register bank, not the GP bank — just with a fixed width and lanes:1 613 // rather than a scalable length. 614 place(Operand{ 615 Type: p.operandType, Class: class, AsmPos: p.asmPos, 616 arngLink: p.arngLink, fixedElem: p.fixedElem, fixedBits: p.fixedBits, 617 isList: p.isList, regName: p.regName, 618 }, p.isDestination) 619 } 620 } 621 // An instruction has at most one governing predicate — everything simdgen 622 // derives from the field (implicitPredCount, regShape, the all-true 623 // synthesis) assumes it. The SME outer products break this (SUMOPA governs 624 // with <Pn>/M and <Pm>/M at once) and must be rejected here, not halved. 625 governCount := 0 626 for i := range ins { 627 if ins[i].governing { 628 governCount++ 629 } 630 } 631 if governCount > 1 { 632 panic(fmt.Sprintf("sve: %d governing predicates in one operand list; only one is supported", governCount)) 633 } 634 return append(outs, ins...) 635 } 636 637 // inputRole names an input operand: "op0", "op1", ... 638 func inputRole(index int) string { 639 return fmt.Sprintf("op%d", index) 640 } 641 642 // instantiate stamps a base type and element width into a typed operand. mem, 643 // immediate, reglist and special operands are opaque and left unchanged. 644 func (op *Operand) instantiate(baseType string, elemBits int) { 645 switch op.Type { 646 case OperandZReg: 647 // A scalable Z vector: only base type and element width; the total width 648 // is the (unknown) vector length. 649 op.BaseType = baseType 650 op.ElemBits = elemBits 651 case OperandGReg, OperandVFP: 652 // A scalar register — general-purpose (<Xd>) or SIMD&FP (<Dd>) — holds a 653 // single fixed-width value, so it has a concrete total width and lanes=1. 654 op.BaseType = baseType 655 op.ElemBits = elemBits 656 op.Bits = elemBits 657 op.Lanes = 1 658 case OperandPReg: 659 // Predicates are integer masks; their element width tracks the governed 660 // vector's element width. 661 op.BaseType = "int" 662 op.ElemBits = elemBits 663 } 664 } 665