Source file src/simd/archsimd/_gen/simdgen/arm64/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 arm64
     6  
     7  import (
     8  	"fmt"
     9  	"strings"
    10  )
    11  
    12  // OperandType defines the type of an operand for ARM64 instruction generation.
    13  type OperandType int
    14  
    15  const (
    16  	OperandVReg  OperandType = iota // Vector register
    17  	OperandGReg                     // General register
    18  	OperandImm                      // Immediate
    19  	OperandVElem                    // Vector element (e.g., <Vm>.H[<index>]): early-lowered into immediate + vreg with same AsmPos
    20  	OperandList                     // List operand (e.g., { <Vn>.16B, <Vn+1>.16B }): early-lowered into vreg with ListNumber
    21  )
    22  
    23  func (t OperandType) String() string {
    24  	switch t {
    25  	case OperandVReg:
    26  		return "VReg"
    27  	case OperandGReg:
    28  		return "GReg"
    29  	case OperandImm:
    30  		return "Imm"
    31  	case OperandVElem:
    32  		return "VElem"
    33  	case OperandList:
    34  		return "List"
    35  	default:
    36  		return "Unknown"
    37  	}
    38  }
    39  
    40  // Operand represents an arm64 instruction operand instantiated for concrete arrangement.
    41  type Operand struct {
    42  	Type     OperandType
    43  	Class    string // "vreg", "greg", "immediate"
    44  	BaseType string // Base type ("int", "uint", "float")
    45  	ElemBits int    // Element bits (for vectors)
    46  	Bits     int    // Total bits
    47  	Lanes    int    // Number of lanes (for vectors)
    48  	ImmMax   int    // Immediate max value (for immediates)
    49  	// The operand's role. Possible values:
    50  	//   - "destination":      the output register
    51  	//   - "original":         the original SSA value of "destination" (for resultInArg0 instructions)
    52  	//   - ends with "_i":     vector element index: should get ImmMax = lanes-1
    53  	//   - "op0", "op1", ...:  input registers
    54  	//   - other strings:      immediate names (e.g. "immshift", "amount", "immzero")
    55  	Role       string
    56  	ListNumber int // List number for register list (e.g., useful for TBL/TBX instructions)
    57  	AsmPos     int // Assembly position (usually 0 for the destination register, 1+ for inputs).
    58  	// Input with AsmPos == 0 represents original value of the destination register for ssa form.
    59  	// Immediate with AsmPos == subsequent register operand's AsmPos represents a vector element (the immediate specifies the lane number).
    60  }
    61  
    62  // token represents a raw operand string from the assembly template.
    63  type token struct {
    64  	text   string // Raw operand text (e.g., "<Vd>.16B", "{ <Vn>.16B, <Vn+1>.16B }")
    65  	asmPos int    // Position in assembly syntax (0 for destination, 1+ for inputs)
    66  }
    67  
    68  // parsedOperand represents a classified but not lowered operand.
    69  type parsedOperand struct {
    70  	token                     // Embedded token with text and position
    71  	operandType   OperandType // Operand type (VReg, GReg, Imm, VElem, List)
    72  	isDestination bool        // True if this is a destination operand
    73  	immName       string      // Immediate role (for imm operands)
    74  	immMax        int         // Maximum immediate value (for imm operands)
    75  }
    76  
    77  // instantiate updates the operand's type information based on the given arrangement and instruction mnemonic.
    78  // This is used when generating instruction definitions for specific vector arrangements.
    79  func (op *Operand) instantiate(arrangement Arrangement, ashape ArngShape, vregPos int, mnemonic string) {
    80  	switch op.Type {
    81  	case OperandVReg:
    82  		switch {
    83  		case ashape == NarrowArngs && vregPos == 0:
    84  			op.ElemBits = arrangement.elemBits / 2
    85  			op.Bits = arrangement.bits
    86  			op.Lanes = arrangement.bits / (arrangement.elemBits / 2)
    87  		case ashape == LongArngs && vregPos == 0:
    88  			op.ElemBits = arrangement.elemBits * 2
    89  			op.Bits = min(arrangement.bits*2, 128)
    90  			op.Lanes = arrangement.bits / op.ElemBits
    91  		case ashape == WideArngs && vregPos == 2:
    92  			op.ElemBits = arrangement.elemBits / 2
    93  			op.Bits = arrangement.bits
    94  			op.Lanes = arrangement.bits / op.ElemBits
    95  		default:
    96  			op.ElemBits = arrangement.elemBits
    97  			op.Bits = arrangement.bits
    98  			op.Lanes = arrangement.lanes
    99  		}
   100  		op.BaseType = arrangement.baseType
   101  	case OperandImm:
   102  		// Update immediate operands based on arrangement
   103  		// For shift operations, set immediate max to element_bits - 1 (sometimes need element_bits?)
   104  		// For vector element indices, immediate max should be lanes - 1
   105  		if op.ImmMax == -1 {
   106  			// Check if this is a vector element immediate (role ends with "_i")
   107  			if strings.HasSuffix(op.Role, "_i") {
   108  				// Vector element index: max = lanes - 1
   109  				op.ImmMax = arrangement.lanes - 1
   110  			} else if mnemonic == "EXT" {
   111  				// EXT byte index: max = register size in bytes - 1 = 15 for 128-bit
   112  				op.ImmMax = arrangement.bits/8 - 1
   113  			} else if ashape == NarrowArngs {
   114  				// Narrow shift: max = destination element bits - 1 (half of source)
   115  				op.ImmMax = arrangement.elemBits/2 - 1
   116  			} else {
   117  				// Shift operation: max = element_bits - 1
   118  				op.ImmMax = arrangement.elemBits - 1
   119  			}
   120  		}
   121  	case OperandGReg:
   122  		op.BaseType = arrangement.baseType
   123  		if mnemonic == "UMOV" {
   124  			// Update general register width based on arrangement
   125  			// - 2D arrangement needs 64-bit (X register)
   126  			// - All other arrangements need 32-bit (W register)
   127  			if arrangement.arrangement == "2D" {
   128  				op.Bits = 64
   129  			} else {
   130  				op.Bits = 32
   131  			}
   132  		}
   133  		if mnemonic == "INS" {
   134  			op.Bits = arrangement.elemBits
   135  		}
   136  	case OperandVElem, OperandList:
   137  		panic("expected this operand type to be early-lowered")
   138  	}
   139  }
   140  
   141  // operands extracts operand information from the assembly template.
   142  func (instruction *Instruction) operands(asmTemplate string) []Operand {
   143  	tokens := tokenizeTemplate(asmTemplate)
   144  	parsed := classifyTokens(tokens)
   145  	return buildOperandList(parsed, instruction.ResultInArg0())
   146  }
   147  
   148  // tokenizeTemplate parses an assembly template string into a list of operand tokens.
   149  // It handles:
   150  // - Stripping the mnemonic from the first operand
   151  // - Register list operands like "{ <Vn>.16B, <Vn+1>.16B }" (preserves internal commas)
   152  // - Optional modifiers like "{, LSL #<amount>}" (merged with previous operand)
   153  func tokenizeTemplate(template string) []token {
   154  	template = stripMnemonic(template)
   155  	parts := strings.Split(template, ",")
   156  
   157  	var tokens []token
   158  	var listBuf strings.Builder
   159  	var inList bool
   160  
   161  	for _, part := range parts {
   162  		part = strings.TrimSpace(part)
   163  		if part == "" {
   164  			continue
   165  		}
   166  
   167  		// Register list: { <Vn>.16B, <Vn+1>.16B }
   168  		if isListStart(part) {
   169  			inList = true
   170  			listBuf.WriteString(part)
   171  			if strings.Contains(part, "}") {
   172  				tokens = append(tokens, token{text: listBuf.String(), asmPos: len(tokens)})
   173  				listBuf.Reset()
   174  				inList = false
   175  			}
   176  			continue
   177  		}
   178  		if inList {
   179  			listBuf.WriteString(", ")
   180  			listBuf.WriteString(part)
   181  			if strings.HasSuffix(part, "}") {
   182  				tokens = append(tokens, token{text: listBuf.String(), asmPos: len(tokens)})
   183  				listBuf.Reset()
   184  				inList = false
   185  			}
   186  			continue
   187  		}
   188  
   189  		// Optional modifier: previous ends with "{" or current starts with "{"
   190  		if shouldMergeWithPrevious(part, tokens) {
   191  			tokens[len(tokens)-1].text += ", " + part
   192  			continue
   193  		}
   194  
   195  		tokens = append(tokens, token{text: part, asmPos: len(tokens)})
   196  	}
   197  	return tokens
   198  }
   199  
   200  // stripMnemonic removes the instruction mnemonic from the template string.
   201  // For example, "ADD  <Vd>.<T>, <Vn>.<T>, <Vm>.<T>" becomes "<Vd>.<T>, <Vn>.<T>, <Vm>.<T>".
   202  func stripMnemonic(template string) string {
   203  	if _, after, ok := strings.Cut(template, " "); ok {
   204  		return strings.TrimSpace(after)
   205  	}
   206  	return template
   207  }
   208  
   209  // isListStart checks if a part is the start of a register list operand.
   210  // Register lists start with "{" and contain a vector register like "<V".
   211  func isListStart(part string) bool {
   212  	return strings.HasPrefix(part, "{") && strings.Contains(part, "<V")
   213  }
   214  
   215  // shouldMergeWithPrevious determines if the current part should be merged with the previous token.
   216  // This happens for optional modifiers like "{, LSL #<amount>}" that follow an operand.
   217  func shouldMergeWithPrevious(part string, tokens []token) bool {
   218  	if len(tokens) == 0 {
   219  		return false
   220  	}
   221  	return strings.HasPrefix(part, "{") || strings.HasSuffix(tokens[len(tokens)-1].text, "{")
   222  }
   223  
   224  // classifyTokens analyzes each token and determines its operand type and role.
   225  // It returns a slice of parsedOperand with type information and metadata.
   226  func classifyTokens(tokens []token) []parsedOperand {
   227  	parsed := make([]parsedOperand, len(tokens))
   228  	for i, tok := range tokens {
   229  		opType, isDest, immName, immMax := analyzeOperand(tok.text)
   230  		parsed[i] = parsedOperand{
   231  			token:         tok,
   232  			operandType:   opType,
   233  			isDestination: isDest,
   234  			immName:       immName,
   235  			immMax:        immMax,
   236  		}
   237  	}
   238  	return parsed
   239  }
   240  
   241  // buildOperandList constructs the final ordered list of operands from parsed operands.
   242  // It lowers compound operands (VElem, List) and orders them as: outputs + immediates + [original] + inputs.
   243  func buildOperandList(parsed []parsedOperand, resultInArg0 bool) []Operand {
   244  	var outs, ins, imms []Operand
   245  	inputCount := 0
   246  
   247  	for _, p := range parsed {
   248  		switch p.operandType {
   249  		case OperandVElem:
   250  			idx, reg := lowerVElem(p, &inputCount)
   251  			imms = append(imms, idx)
   252  			if p.isDestination {
   253  				outs = append(outs, reg)
   254  				resultInArg0 = true // element dest implies read-modify-write
   255  			} else {
   256  				ins = append(ins, reg)
   257  			}
   258  
   259  		case OperandList:
   260  			ins = append(ins, lowerList(p, inputCount))
   261  			inputCount++
   262  
   263  		case OperandImm:
   264  			imms = append(imms, makeImm(p, inputCount))
   265  			inputCount++
   266  
   267  		case OperandVReg, OperandGReg:
   268  			op := makeReg(p, p.operandType, inputCount)
   269  			if p.isDestination {
   270  				outs = append(outs, op)
   271  			} else {
   272  				ins = append(ins, op)
   273  				inputCount++
   274  			}
   275  		}
   276  	}
   277  
   278  	// Assemble final order: outs + imms + [original] + ins
   279  	result := append(outs, imms...)
   280  	if resultInArg0 && len(outs) > 0 {
   281  		original := outs[0]
   282  		original.Role = "original"
   283  		result = append(result, original)
   284  	}
   285  	return append(result, ins...)
   286  }
   287  
   288  // lowerVElem expands a vector element operand into an index immediate and a vector register.
   289  // For destination elements, both get AsmPos=0. For source elements, they share the original AsmPos.
   290  func lowerVElem(p parsedOperand, inputCount *int) (idx Operand, reg Operand) {
   291  	if p.isDestination {
   292  		idx = Operand{
   293  			Type: OperandImm, Class: "immediate",
   294  			Role: "destination_i", AsmPos: 0, ListNumber: -1, ImmMax: -1,
   295  		}
   296  		reg = Operand{
   297  			Type: OperandVReg, Class: "vreg",
   298  			Role: "destination", AsmPos: 0, ListNumber: -1,
   299  		}
   300  	} else {
   301  		role := inputRole(*inputCount)
   302  		idx = Operand{
   303  			Type: OperandImm, Class: "immediate",
   304  			Role: role + "_i", AsmPos: p.token.asmPos, ListNumber: -1, ImmMax: -1,
   305  		}
   306  		reg = Operand{
   307  			Type: OperandVReg, Class: "vreg",
   308  			Role: role, AsmPos: p.token.asmPos, ListNumber: -1,
   309  		}
   310  		*inputCount++
   311  	}
   312  	return
   313  }
   314  
   315  // lowerList expands a list operand into a vector register with ListNumber=0.
   316  func lowerList(p parsedOperand, inputCount int) Operand {
   317  	return Operand{
   318  		Type: OperandVReg, Class: "vreg",
   319  		Role: inputRole(inputCount), AsmPos: p.token.asmPos, ListNumber: 0,
   320  	}
   321  }
   322  
   323  // makeImm creates an immediate operand from a parsed operand.
   324  func makeImm(p parsedOperand, inputCount int) Operand {
   325  	return Operand{
   326  		Type: OperandImm, Class: "immediate",
   327  		Role: p.immName, AsmPos: p.token.asmPos, ListNumber: -1, ImmMax: p.immMax,
   328  	}
   329  }
   330  
   331  // makeReg creates a register operand (vector or general) from a parsed operand.
   332  func makeReg(p parsedOperand, opType OperandType, inputCount int) Operand {
   333  	class := "vreg"
   334  	if opType == OperandGReg {
   335  		class = "greg"
   336  	}
   337  	role := "destination"
   338  	if !p.isDestination {
   339  		role = inputRole(inputCount)
   340  	}
   341  	return Operand{
   342  		Type: opType, Class: class,
   343  		Role: role, AsmPos: p.token.asmPos, ListNumber: -1,
   344  	}
   345  }
   346  
   347  // inputRole generates a role name for an input operand at the given index: "op0", "op1", "op2", etc.
   348  // TODO: consider extracting these names from ARM64 register suffixes in templates.
   349  func inputRole(index int) string {
   350  	return fmt.Sprintf("op%d", index)
   351  }
   352  
   353  // extractImmediateInfo extracts immediate name and immMax value from immediate operand strings.
   354  func extractImmediateInfo(operandStr string) (string, int) {
   355  	if strings.Contains(operandStr, "#<") {
   356  		// Immediate operand: #<immediate_name>
   357  		// Extract the immediate name from between #< and >
   358  		start := strings.Index(operandStr, "#<") + 2
   359  		end := strings.Index(operandStr[start:], ">")
   360  		if end >= 0 {
   361  			immediateName := operandStr[start : start+end]
   362  			return immediateName, -1
   363  		}
   364  		return "immediate", -1
   365  	}
   366  	if strings.Contains(operandStr, "#0") {
   367  		return "immzero", 0
   368  	}
   369  	return "", 0
   370  }
   371  
   372  // detectOperandType determines the basic operand type (VReg, GReg, Imm) based on its string pattern.
   373  func detectOperandType(operandStr string) OperandType {
   374  	switch {
   375  	case strings.HasPrefix(operandStr, "<V"):
   376  		return OperandVReg
   377  	case strings.HasPrefix(operandStr, "<W") || strings.HasPrefix(operandStr, "<X") || strings.HasPrefix(operandStr, "<R"):
   378  		return OperandGReg
   379  	case strings.Contains(operandStr, "#<") || strings.Contains(operandStr, "#0"):
   380  		return OperandImm
   381  	default:
   382  		return OperandVReg
   383  	}
   384  }
   385  
   386  // analyzeOperand analyzes an operand string and returns the operand type, whether it's a destination, and the immediate name and immMax (if any).
   387  func analyzeOperand(operandStr string) (OperandType, bool, string, int) {
   388  	opType := detectOperandType(operandStr)
   389  	isDestination := strings.Contains(operandStr, "d>")
   390  	switch opType {
   391  	case OperandVReg:
   392  		if strings.HasPrefix(operandStr, "{") && strings.HasSuffix(operandStr, "}") {
   393  			return OperandList, false, "", 0
   394  		}
   395  		if strings.Contains(operandStr, "[<index") {
   396  			return OperandVElem, isDestination, "", 0
   397  		}
   398  	case OperandImm:
   399  		immediateName, immMax := extractImmediateInfo(operandStr)
   400  		return opType, false, immediateName, immMax
   401  	}
   402  	return opType, isDestination, "", 0
   403  }
   404  

View as plain text