Source file src/simd/_gen/simdgen/godefs.go

     1  // Copyright 2025 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 main
     6  
     7  import (
     8  	"fmt"
     9  	"log"
    10  	"regexp"
    11  	"slices"
    12  	"strconv"
    13  	"strings"
    14  	"unicode"
    15  
    16  	"simd/_gen/unify"
    17  )
    18  
    19  type Operation struct {
    20  	rawOperation
    21  
    22  	// Go is the Go method name of this operation.
    23  	//
    24  	// It is derived from the raw Go method name by adding optional suffixes.
    25  	// Currently, "Masked" is the only suffix.
    26  	Go string
    27  
    28  	// Documentation is the doc string for this API.
    29  	//
    30  	// It is computed from the raw documentation:
    31  	//
    32  	// - "NAME" is replaced by the Go method name.
    33  	//
    34  	// - For masked operation, a sentence about masking is added.
    35  	Documentation string
    36  
    37  	// In is the sequence of parameters to the Go method.
    38  	//
    39  	// For masked operations, this will have the mask operand appended.
    40  	In []Operand
    41  }
    42  
    43  // rawOperation is the unifier representation of an [Operation]. It is
    44  // translated into a more parsed form after unifier decoding.
    45  type rawOperation struct {
    46  	Go string // Base Go method name
    47  
    48  	GoArch       string  // GOARCH for this definition
    49  	Asm          string  // Assembly mnemonic
    50  	OperandOrder *string // optional Operand order for better Go declarations
    51  	// Optional tag to indicate this operation is paired with special generic->machine ssa lowering rules.
    52  	// Should be paired with special templates in gen_simdrules.go
    53  	SpecialLower *string
    54  
    55  	In              []Operand // Parameters
    56  	InVariant       []Operand // Optional parameters
    57  	Out             []Operand // Results
    58  	MemFeatures     *string   // The memory operand feature this operation supports
    59  	MemFeaturesData *string   // Additional data associated with MemFeatures
    60  	Commutative     bool      // Commutativity
    61  	CPUFeature      string    // CPUID/Has* feature name
    62  	Zeroing         *bool     // nil => use asm suffix ".Z"; false => do not use asm suffix ".Z"
    63  	Documentation   *string   // Documentation will be appended to the stubs comments.
    64  	AddDoc          *string   // Additional doc to be appended.
    65  	// ConstMask is a hack to reduce the size of defs the user writes for const-immediate
    66  	// If present, it will be copied to [In[0].Const].
    67  	ConstImm *string
    68  	// NameAndSizeCheck is used to check [BWDQ] maps to (8|16|32|64) elemBits.
    69  	NameAndSizeCheck *bool
    70  	// If non-nil, all generation in gen_simdTypes.go and gen_intrinsics will be skipped.
    71  	NoTypes *string
    72  	// If non-nil, all generation in gen_simdGenericOps and gen_simdrules will be skipped.
    73  	NoGenericOps *string
    74  	// If non-nil, this string will be attached to the machine ssa op name.  E.g. "const"
    75  	SSAVariant *string
    76  	// If true, do not emit method declarations, generic ops, or intrinsics for masked variants
    77  	// DO emit the architecture-specific opcodes and optimizations.
    78  	HideMaskMethods *bool
    79  }
    80  
    81  func (o *Operation) IsMasked() bool {
    82  	if len(o.InVariant) == 0 {
    83  		return false
    84  	}
    85  	if len(o.InVariant) == 1 && o.InVariant[0].Class == "mask" {
    86  		return true
    87  	}
    88  	panic(fmt.Errorf("unknown inVariant"))
    89  }
    90  
    91  func (o *Operation) SkipMaskedMethod() bool {
    92  	if o.HideMaskMethods == nil {
    93  		return false
    94  	}
    95  	if *o.HideMaskMethods && o.IsMasked() {
    96  		return true
    97  	}
    98  	return false
    99  }
   100  
   101  var reForName = regexp.MustCompile(`\bNAME\b`)
   102  
   103  func (o *Operation) DecodeUnified(v *unify.Value) error {
   104  	if err := v.Decode(&o.rawOperation); err != nil {
   105  		return err
   106  	}
   107  
   108  	isMasked := o.IsMasked()
   109  
   110  	// Compute full Go method name.
   111  	o.Go = o.rawOperation.Go
   112  	if isMasked {
   113  		o.Go += "Masked"
   114  	}
   115  
   116  	// Compute doc string.
   117  	if o.rawOperation.Documentation != nil {
   118  		o.Documentation = *o.rawOperation.Documentation
   119  	} else {
   120  		o.Documentation = "// UNDOCUMENTED"
   121  	}
   122  	o.Documentation = reForName.ReplaceAllString(o.Documentation, o.Go)
   123  	if isMasked {
   124  		o.Documentation += "\n//\n// This operation is applied selectively under a write mask."
   125  		// Suppress generic op and method declaration for exported methods, if a mask is present.
   126  		if unicode.IsUpper([]rune(o.Go)[0]) {
   127  			trueVal := "true"
   128  			o.NoGenericOps = &trueVal
   129  			o.NoTypes = &trueVal
   130  		}
   131  	}
   132  	if o.rawOperation.AddDoc != nil {
   133  		o.Documentation += "\n" + reForName.ReplaceAllString(*o.rawOperation.AddDoc, o.Go)
   134  	}
   135  
   136  	o.In = append(o.rawOperation.In, o.rawOperation.InVariant...)
   137  
   138  	return nil
   139  }
   140  
   141  func (o *Operation) VectorWidth() int {
   142  	out := o.Out[0]
   143  	if out.Class == "vreg" {
   144  		return *out.Bits
   145  	} else if out.Class == "greg" || out.Class == "mask" {
   146  		for i := range o.In {
   147  			if o.In[i].Class == "vreg" {
   148  				return *o.In[i].Bits
   149  			}
   150  		}
   151  	}
   152  	panic(fmt.Errorf("Figure out what the vector width is for %v and implement it", *o))
   153  }
   154  
   155  // Right now simdgen computes the machine op name for most instructions
   156  // as $Name$OutputSize, by this denotation, these instructions are "overloaded".
   157  // for example:
   158  // (Uint16x8) ConvertToInt8
   159  // (Uint16x16) ConvertToInt8
   160  // are both VPMOVWB128.
   161  // To make them distinguishable we need to append the input size to them as well.
   162  // TODO: document them well in the generated code.
   163  var demotingConvertOps = map[string]bool{
   164  	"VPMOVQD128": true, "VPMOVSQD128": true, "VPMOVUSQD128": true, "VPMOVQW128": true, "VPMOVSQW128": true,
   165  	"VPMOVUSQW128": true, "VPMOVDW128": true, "VPMOVSDW128": true, "VPMOVUSDW128": true, "VPMOVQB128": true,
   166  	"VPMOVSQB128": true, "VPMOVUSQB128": true, "VPMOVDB128": true, "VPMOVSDB128": true, "VPMOVUSDB128": true,
   167  	"VPMOVWB128": true, "VPMOVSWB128": true, "VPMOVUSWB128": true,
   168  	"VPMOVQDMasked128": true, "VPMOVSQDMasked128": true, "VPMOVUSQDMasked128": true, "VPMOVQWMasked128": true, "VPMOVSQWMasked128": true,
   169  	"VPMOVUSQWMasked128": true, "VPMOVDWMasked128": true, "VPMOVSDWMasked128": true, "VPMOVUSDWMasked128": true, "VPMOVQBMasked128": true,
   170  	"VPMOVSQBMasked128": true, "VPMOVUSQBMasked128": true, "VPMOVDBMasked128": true, "VPMOVSDBMasked128": true, "VPMOVUSDBMasked128": true,
   171  	"VPMOVWBMasked128": true, "VPMOVSWBMasked128": true, "VPMOVUSWBMasked128": true,
   172  }
   173  
   174  func machineOpName(maskType maskShape, gOp Operation) string {
   175  	asm := gOp.Asm
   176  	if maskType == OneMask {
   177  		asm += "Masked"
   178  	}
   179  	asm = fmt.Sprintf("%s%d", asm, gOp.VectorWidth())
   180  	if gOp.SSAVariant != nil {
   181  		asm += *gOp.SSAVariant
   182  	}
   183  	if demotingConvertOps[asm] {
   184  		// Need to append the size of the source as well.
   185  		// TODO: should be "%sto%d".
   186  		asm = fmt.Sprintf("%s_%d", asm, *gOp.In[0].Bits)
   187  	}
   188  	return asm
   189  }
   190  
   191  func compareStringPointers(x, y *string) int {
   192  	if x != nil && y != nil {
   193  		return compareNatural(*x, *y)
   194  	}
   195  	if x == nil && y == nil {
   196  		return 0
   197  	}
   198  	if x == nil {
   199  		return -1
   200  	}
   201  	return 1
   202  }
   203  
   204  func compareIntPointers(x, y *int) int {
   205  	if x != nil && y != nil {
   206  		return *x - *y
   207  	}
   208  	if x == nil && y == nil {
   209  		return 0
   210  	}
   211  	if x == nil {
   212  		return -1
   213  	}
   214  	return 1
   215  }
   216  
   217  func compareOperations(x, y Operation) int {
   218  	if c := compareNatural(x.Go, y.Go); c != 0 {
   219  		return c
   220  	}
   221  	xIn, yIn := x.In, y.In
   222  
   223  	if len(xIn) > len(yIn) && xIn[len(xIn)-1].Class == "mask" {
   224  		xIn = xIn[:len(xIn)-1]
   225  	} else if len(xIn) < len(yIn) && yIn[len(yIn)-1].Class == "mask" {
   226  		yIn = yIn[:len(yIn)-1]
   227  	}
   228  
   229  	if len(xIn) < len(yIn) {
   230  		return -1
   231  	}
   232  	if len(xIn) > len(yIn) {
   233  		return 1
   234  	}
   235  	if len(x.Out) < len(y.Out) {
   236  		return -1
   237  	}
   238  	if len(x.Out) > len(y.Out) {
   239  		return 1
   240  	}
   241  	for i := range xIn {
   242  		ox, oy := &xIn[i], &yIn[i]
   243  		if c := compareOperands(ox, oy); c != 0 {
   244  			return c
   245  		}
   246  	}
   247  	return 0
   248  }
   249  
   250  func compareOperands(x, y *Operand) int {
   251  	if c := compareNatural(x.Class, y.Class); c != 0 {
   252  		return c
   253  	}
   254  	if x.Class == "immediate" {
   255  		return compareStringPointers(x.ImmOffset, y.ImmOffset)
   256  	} else {
   257  		if c := compareStringPointers(x.Base, y.Base); c != 0 {
   258  			return c
   259  		}
   260  		if c := compareIntPointers(x.ElemBits, y.ElemBits); c != 0 {
   261  			return c
   262  		}
   263  		if c := compareIntPointers(x.Bits, y.Bits); c != 0 {
   264  			return c
   265  		}
   266  		return 0
   267  	}
   268  }
   269  
   270  type Operand struct {
   271  	Class string // One of "mask", "immediate", "vreg", "greg", and "mem"
   272  
   273  	Go     *string // Go type of this operand
   274  	AsmPos int     // Position of this operand in the assembly instruction
   275  
   276  	Base     *string // Base Go type ("int", "uint", "float")
   277  	ElemBits *int    // Element bit width
   278  	Bits     *int    // Total vector bit width
   279  
   280  	Const *string // Optional constant value for immediates.
   281  	// Optional immediate arg offsets. If this field is non-nil,
   282  	// This operand will be an immediate operand:
   283  	// The compiler will right-shift the user-passed value by ImmOffset and set it as the AuxInt
   284  	// field of the operation.
   285  	ImmOffset *string
   286  	Name      *string // optional name in the Go intrinsic declaration
   287  	Lanes     *int    // *Lanes equals Bits/ElemBits except for scalars, when *Lanes == 1
   288  	// TreatLikeAScalarOfSize means only the lower $TreatLikeAScalarOfSize bits of the vector
   289  	// is used, so at the API level we can make it just a scalar value of this size; Then we
   290  	// can overwrite it to a vector of the right size during intrinsics stage.
   291  	TreatLikeAScalarOfSize *int
   292  	// If non-nil, it means the [Class] field is overwritten here, right now this is used to
   293  	// overwrite the results of AVX2 compares to masks.
   294  	OverwriteClass *string
   295  	// If non-nil, it means the [Base] field is overwritten here. This field exist solely
   296  	// because Intel's XED data is inconsistent. e.g. VANDNP[SD] marks its operand int.
   297  	OverwriteBase *string
   298  	// If non-nil, it means the [ElementBits] field is overwritten. This field exist solely
   299  	// because Intel's XED data is inconsistent. e.g. AVX512 VPMADDUBSW marks its operand
   300  	// elemBits 16, which should be 8.
   301  	OverwriteElementBits *int
   302  	// FixedReg is the name of the fixed registers
   303  	FixedReg *string
   304  }
   305  
   306  // isDigit returns true if the byte is an ASCII digit.
   307  func isDigit(b byte) bool {
   308  	return b >= '0' && b <= '9'
   309  }
   310  
   311  // compareNatural performs a "natural sort" comparison of two strings.
   312  // It compares non-digit sections lexicographically and digit sections
   313  // numerically.  In the case of string-unequal "equal" strings like
   314  // "a01b" and "a1b", strings.Compare breaks the tie.
   315  //
   316  // It returns:
   317  //
   318  //	-1 if s1 < s2
   319  //	 0 if s1 == s2
   320  //	+1 if s1 > s2
   321  func compareNatural(s1, s2 string) int {
   322  	i, j := 0, 0
   323  	len1, len2 := len(s1), len(s2)
   324  
   325  	for i < len1 && j < len2 {
   326  		// Find a non-digit segment or a number segment in both strings.
   327  		if isDigit(s1[i]) && isDigit(s2[j]) {
   328  			// Number segment comparison.
   329  			numStart1 := i
   330  			for i < len1 && isDigit(s1[i]) {
   331  				i++
   332  			}
   333  			num1, _ := strconv.Atoi(s1[numStart1:i])
   334  
   335  			numStart2 := j
   336  			for j < len2 && isDigit(s2[j]) {
   337  				j++
   338  			}
   339  			num2, _ := strconv.Atoi(s2[numStart2:j])
   340  
   341  			if num1 < num2 {
   342  				return -1
   343  			}
   344  			if num1 > num2 {
   345  				return 1
   346  			}
   347  			// If numbers are equal, continue to the next segment.
   348  		} else {
   349  			// Non-digit comparison.
   350  			if s1[i] < s2[j] {
   351  				return -1
   352  			}
   353  			if s1[i] > s2[j] {
   354  				return 1
   355  			}
   356  			i++
   357  			j++
   358  		}
   359  	}
   360  
   361  	// deal with a01b vs a1b; there needs to be an order.
   362  	return strings.Compare(s1, s2)
   363  }
   364  
   365  const generatedHeader = `// Code generated by x/arch/internal/simdgen using 'go run . -xedPath $XED_PATH -o godefs -goroot $GOROOT go.yaml types.yaml categories.yaml'; DO NOT EDIT.
   366  `
   367  
   368  func writeGoDefs(path string, cl unify.Closure) error {
   369  	// TODO: Merge operations with the same signature but multiple
   370  	// implementations (e.g., SSE vs AVX)
   371  	var ops []Operation
   372  	for def := range cl.All() {
   373  		var op Operation
   374  		if !def.Exact() {
   375  			continue
   376  		}
   377  		if err := def.Decode(&op); err != nil {
   378  			log.Println(err.Error())
   379  			log.Println(def)
   380  			continue
   381  		}
   382  		// TODO: verify that this is safe.
   383  		op.sortOperand()
   384  		ops = append(ops, op)
   385  	}
   386  	slices.SortFunc(ops, compareOperations)
   387  	// The parsed XED data might contain duplicates, like
   388  	// 512 bits VPADDP.
   389  	deduped := dedup(ops)
   390  	slices.SortFunc(deduped, compareOperations)
   391  
   392  	if *Verbose {
   393  		log.Printf("dedup len: %d\n", len(ops))
   394  	}
   395  	var err error
   396  	if err = overwrite(deduped); err != nil {
   397  		return err
   398  	}
   399  	if *Verbose {
   400  		log.Printf("dedup len: %d\n", len(deduped))
   401  	}
   402  	if *Verbose {
   403  		log.Printf("dedup len: %d\n", len(deduped))
   404  	}
   405  	if !*FlagNoDedup {
   406  		// TODO: This can hide mistakes in the API definitions, especially when
   407  		// multiple patterns result in the same API unintentionally. Make it stricter.
   408  		if deduped, err = dedupGodef(deduped); err != nil {
   409  			return err
   410  		}
   411  	}
   412  	if *Verbose {
   413  		log.Printf("dedup len: %d\n", len(deduped))
   414  	}
   415  	if !*FlagNoConstImmPorting {
   416  		if err = copyConstImm(deduped); err != nil {
   417  			return err
   418  		}
   419  	}
   420  	if *Verbose {
   421  		log.Printf("dedup len: %d\n", len(deduped))
   422  	}
   423  	reportXEDInconsistency(deduped)
   424  	typeMap := parseSIMDTypes(deduped)
   425  
   426  	formatWriteAndClose(writeSIMDTypes(typeMap), path, "src/"+simdPackage+"/types_amd64.go")
   427  	formatWriteAndClose(writeSIMDFeatures(deduped), path, "src/"+simdPackage+"/cpu.go")
   428  	f, fI := writeSIMDStubs(deduped, typeMap)
   429  	formatWriteAndClose(f, path, "src/"+simdPackage+"/ops_amd64.go")
   430  	formatWriteAndClose(fI, path, "src/"+simdPackage+"/ops_internal_amd64.go")
   431  	formatWriteAndClose(writeSIMDIntrinsics(deduped, typeMap), path, "src/cmd/compile/internal/ssagen/simdintrinsics.go")
   432  	formatWriteAndClose(writeSIMDGenericOps(deduped), path, "src/cmd/compile/internal/ssa/_gen/simdgenericOps.go")
   433  	formatWriteAndClose(writeSIMDMachineOps(deduped), path, "src/cmd/compile/internal/ssa/_gen/simdAMD64ops.go")
   434  	formatWriteAndClose(writeSIMDSSA(deduped), path, "src/cmd/compile/internal/amd64/simdssa.go")
   435  	writeAndClose(writeSIMDRules(deduped).Bytes(), path, "src/cmd/compile/internal/ssa/_gen/simdAMD64.rules")
   436  
   437  	return nil
   438  }
   439  

View as plain text