Source file src/simd/archsimd/_gen/wasmgen/main.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  	"bytes"
     9  	"flag"
    10  	"fmt"
    11  	"log"
    12  	"os"
    13  	"path/filepath"
    14  	"slices"
    15  	"strings"
    16  	"text/template"
    17  	"unicode"
    18  
    19  	"_gen/sgutil"
    20  )
    21  
    22  var (
    23  	gorootsrc = flag.String("gorootsrc", "../../../../../src", "root of destination directory, normally GOROOT/src")
    24  
    25  	genTypesFile      = flag.String("types", "GOROOTSRC/simd/archsimd/types_wasm.go", "output file for simd types (e.g. types_wasm.go)")
    26  	genOpsFile        = flag.String("ops", "GOROOTSRC/simd/archsimd/ops_wasm.go", "output file for simd ops (e.g. ops_wasm.go)")
    27  	genSSAOpsFile     = flag.String("ssaops", "GOROOTSRC/cmd/compile/internal/ssa/_gen/simdWasmops.go", "output file for ssa ops (e.g. simdWasmops.go)")
    28  	genGenOpsFile     = flag.String("genops", "GOROOTSRC/cmd/compile/internal/ssa/_gen/simdgenericOps.go", "output file for generic ssa ops (e.g. simdgenericOps.go)")
    29  	genSSARulesFile   = flag.String("ssarules", "GOROOTSRC/cmd/compile/internal/ssa/_gen/simdWasm.rules", "output file for ssa rules (e.g. simdWasm.rules)")
    30  	genWasmSSAFile    = flag.String("wasmssa", "GOROOTSRC/cmd/compile/internal/wasm/simdssa.go", "output file for wasm ssa (e.g. simdssa.go)")
    31  	genIntrinsicsFile = flag.String("intrinsics", "GOROOTSRC/cmd/compile/internal/ssagen/simdWasmintrinsics.go", "output file for intrinsics (e.g. simdWasmintrinsics.go)")
    32  
    33  	list = flag.Bool("list", false, "list all the opcodes")
    34  )
    35  
    36  type simdType struct {
    37  	Name       string // e.g. "Int8x16"
    38  	Elem       string // e.g. "int8"
    39  	Count      int    // e.g. 2, 4, 8, 16
    40  	ElemSize   int    // e.g. 8, 16, 32, 64
    41  	Unsigned   bool
    42  	Float      bool
    43  	Methods    map[string]*wasmOp
    44  	IntShaped  *simdType // refers to the same-shape signed integer type
    45  	UintShaped *simdType // refers to the same-shape unsigned integer type
    46  }
    47  
    48  func (t *simdType) ElemBits() int {
    49  	return t.ElemSize
    50  }
    51  
    52  func (t *simdType) HalfCount() int {
    53  	return t.Count / 2
    54  }
    55  
    56  func (t *simdType) TwiceCount() int {
    57  	return t.Count * 2
    58  }
    59  
    60  func (t *simdType) Name_() string {
    61  	return t.Name
    62  }
    63  
    64  func (t *simdType) Article() string {
    65  	// uint => "you-int" => "a you-int"
    66  	if t.Elem[0] == 'i' {
    67  		return "an"
    68  	}
    69  	return "a"
    70  }
    71  
    72  func (a *simdType) Compare(b *simdType) int {
    73  	if a.Name == b.Name {
    74  		return 0
    75  	}
    76  	if d := a.ElemSize - b.ElemSize; d != 0 {
    77  		return d
    78  	}
    79  	if d := a.Count - b.Count; d != 0 {
    80  		// never happens for WASM
    81  		return d
    82  	}
    83  
    84  	ao := strings.Index("iIuUfFmM", a.Name[:1])
    85  	bo := strings.Index("iIuUfFmM", b.Name[:1])
    86  	if ao == -1 || bo == -1 {
    87  		panic(fmt.Errorf("a.Elem=%s, b.Elem=%s, unexpected first characters (should be in \"iIuUfFmM\")", a.Elem, b.Elem))
    88  	}
    89  	return ao - bo
    90  }
    91  
    92  // WasmUName returns the Capitalized wasm type name e.g. I64x2
    93  func (s *simdType) WasmUName() string {
    94  	T := s.Name[:1]
    95  	if T == "U" {
    96  		T = "I"
    97  	}
    98  	return fmt.Sprintf("%s%dx%d", T, s.ElemSize, s.Count)
    99  }
   100  
   101  // WasmUName returns the uncapitalized wasm type name e.g. i64x2
   102  func (s *simdType) WasmLName() string {
   103  	T := s.Elem[:1]
   104  	if T == "u" {
   105  		T = "i"
   106  	}
   107  	return fmt.Sprintf("%s%dx%d", T, s.ElemSize, s.Count)
   108  }
   109  
   110  func CapitalizeFirst(s string) string {
   111  	return strings.ToUpper(s[:1]) + s[1:]
   112  }
   113  
   114  func (s *simdType) MaskFor() *simdType {
   115  	return maskFor[s]
   116  }
   117  
   118  // WidenElements doubles the width of the elements, and changes the type
   119  func (s *simdType) WidenElements(newStem string) string {
   120  	return fmt.Sprintf("%s%dx%d", newStem, s.ElemSize*2, s.Count/2)
   121  }
   122  
   123  // ShrinkElements halves the width of the elements, and changes their type
   124  func (s *simdType) ShrinkElements(newStem string) string {
   125  	return fmt.Sprintf("%s%dx%d", newStem, s.ElemSize/2, s.Count*2)
   126  }
   127  
   128  func (s *simdType) IntFor() *simdType {
   129  	if s.IntShaped == nil {
   130  		return s
   131  	}
   132  	return s.IntShaped
   133  }
   134  
   135  func (s *simdType) UintFor() *simdType {
   136  	if s.UintShaped == nil {
   137  		return s
   138  	}
   139  	return s.UintShaped
   140  }
   141  
   142  func (s *simdType) String() string {
   143  	return s.Name
   144  }
   145  
   146  func (s *simdType) IsMask() bool {
   147  	return s.Name[0] == 'M'
   148  }
   149  
   150  func (u *simdType) setUintShaped(s *simdType) *simdType {
   151  	s.UintShaped = u
   152  	return u
   153  }
   154  
   155  const pkg = "simd/archsimd"
   156  
   157  var (
   158  	vi8  = &simdType{"Int8x16", "int8", 16, 8, false, false, make(map[string]*wasmOp), nil, nil}
   159  	vi16 = &simdType{"Int16x8", "int16", 8, 16, false, false, make(map[string]*wasmOp), nil, nil}
   160  	vi32 = &simdType{"Int32x4", "int32", 4, 32, false, false, make(map[string]*wasmOp), nil, nil}
   161  	vi64 = &simdType{"Int64x2", "int64", 2, 64, false, false, make(map[string]*wasmOp), nil, nil}
   162  
   163  	vu8  = (&simdType{"Uint8x16", "uint8", 16, 8, true, false, make(map[string]*wasmOp), vi8, nil}).setUintShaped(vi8) // For sign-ignoring operations (add, sub), use these types
   164  	vu16 = (&simdType{"Uint16x8", "uint16", 8, 16, true, false, make(map[string]*wasmOp), vi16, nil}).setUintShaped(vi16)
   165  	vu32 = (&simdType{"Uint32x4", "uint32", 4, 32, true, false, make(map[string]*wasmOp), vi32, nil}).setUintShaped(vi32)
   166  	vu64 = (&simdType{"Uint64x2", "uint64", 2, 64, true, false, make(map[string]*wasmOp), vi64, nil}).setUintShaped(vi64)
   167  
   168  	vf32 = &simdType{"Float32x4", "float32", 4, 32, false, true, make(map[string]*wasmOp), vi32, vu32}
   169  	vf64 = &simdType{"Float64x2", "float64", 2, 64, false, true, make(map[string]*wasmOp), vi64, vu64}
   170  
   171  	vm8  = &simdType{"Mask8x16", "int8", 16, 8, false, false, make(map[string]*wasmOp), vi8, vu8} // for non-bitwise operations (eq, ne), use these types
   172  	vm16 = &simdType{"Mask16x8", "int16", 8, 16, false, false, make(map[string]*wasmOp), vi16, vu16}
   173  	vm32 = &simdType{"Mask32x4", "int32", 4, 32, false, false, make(map[string]*wasmOp), vi32, vu32}
   174  	vm64 = &simdType{"Mask64x2", "int64", 2, 64, false, false, make(map[string]*wasmOp), vi64, vu64}
   175  )
   176  
   177  var maskFor map[*simdType]*simdType = map[*simdType]*simdType{
   178  	vm8:  vm8,
   179  	vi8:  vm8,
   180  	vu8:  vm8,
   181  	vm16: vm16,
   182  	vi16: vm16,
   183  	vu16: vm16,
   184  	vm32: vm32,
   185  	vi32: vm32,
   186  	vu32: vm32,
   187  	vf32: vm32,
   188  	vm64: vm64,
   189  	vi64: vm64,
   190  	vu64: vm64,
   191  	vf64: vm64,
   192  }
   193  
   194  type OpFlags uint16
   195  
   196  const (
   197  	IsConst = OpFlags(1) << iota
   198  	IsLoad
   199  	IsStore
   200  	IsShift
   201  	IsSplat
   202  	IsBitwise
   203  	IsRelation
   204  	IsTest
   205  	IsExtract // Returns element type
   206  	IsCommutative
   207  	IsConversion  // "Wasm type" is result type, "Go method type" is input time
   208  	NameHasFormat //
   209  	NonSigned     // Same bitwise operation whether signed or unsigned.  E.g. 2's complement addition.
   210  	EmulatedRule  // Emulation occurs at rule expansion.
   211  )
   212  
   213  func (o OpFlags) OneString() string {
   214  	switch o {
   215  	case IsConst:
   216  		return "IsConst"
   217  	case IsLoad:
   218  		return "IsLoad"
   219  	case IsStore:
   220  		return "IsStore"
   221  	case IsShift:
   222  		return "IsShift"
   223  	case IsSplat:
   224  		return "IsSplat"
   225  	case IsBitwise:
   226  		return "IsBitwise"
   227  	case IsRelation:
   228  		return "IsRelation"
   229  	case IsTest:
   230  		return "IsTest"
   231  	case IsExtract:
   232  		return "IsExtract"
   233  	case IsCommutative:
   234  		return "IsCommutative"
   235  	case IsConversion:
   236  		return "IsConversion"
   237  	case NameHasFormat:
   238  		return "NameHasFormat"
   239  	case NonSigned:
   240  		return "NonSigned"
   241  	case EmulatedRule:
   242  		return "RmulatedRule"
   243  	}
   244  	return fmt.Sprintf("0x%x", o)
   245  }
   246  
   247  var allFlags = []OpFlags{
   248  	IsConst,
   249  	IsLoad,
   250  	IsStore,
   251  	IsShift,
   252  	IsSplat,
   253  	IsBitwise,
   254  	IsRelation,
   255  	IsTest,
   256  	IsExtract,
   257  	IsCommutative,
   258  	IsConversion,
   259  	NameHasFormat,
   260  	NonSigned,
   261  	EmulatedRule,
   262  }
   263  
   264  func (o OpFlags) String() string {
   265  	sep := ""
   266  	ret := ""
   267  
   268  	for _, x := range allFlags {
   269  		if x&o != 0 {
   270  			ret += sep + x.OneString()
   271  			sep = "+"
   272  		}
   273  	}
   274  	return ret
   275  }
   276  
   277  // wasmOp represents a WebAssembly SIMD instruction.
   278  type wasmOp struct {
   279  	t          *simdType // the receiver and default arg type
   280  	op         string    // the basic Op type, e.g. "load", "add"
   281  	argCount   int       // Number of arguments (inputs)
   282  	argType    string    // (Binary) arg type (e.g., "v128", "i32", "void") -- defaults to t
   283  	resultType string    // Result type (e.g., "v128", "i32", "void") -- defaults to t
   284  	opFlags    OpFlags
   285  	doc        string
   286  
   287  	immRange uint8  // Max immediate value; for lane-oriented operations. 0 => no immediate.
   288  	immName  string // The parameter name for an immediate operations
   289  	arg1Name string // The 1st (non-immediate) arg name. Arg1Name() defaults to "y"
   290  	arg2Name string // The 2nd (non-immediate) arg name. Arg2Name() defaults to "z"
   291  }
   292  
   293  func (o *wasmOp) String() string {
   294  	return fmt.Sprintf(
   295  		"t=%s, op=%s, arity=%d, Method=%s, ArgType=%s, ResultType=%s, GenOp=%s, SsaWasmOp=%s, AsmOp=%s, WasmInstruction=%s, ImmRange=%d, ImmName=%s, Flags=%s",
   296  		o.Type().Name, o.Op(), o.ArgCount(), o.Method(), o.ArgType(), o.ResultType(), o.SsaGenOp(),
   297  		o.SsaWasmOp(), o.AsmOp(), o.WasmInstruction(), o.ImmRange(), o.ImmName(), o.OpFlags())
   298  }
   299  
   300  func compareWasmOps(a, b *wasmOp) int {
   301  	am, bm := a.NUMethod(), b.NUMethod()
   302  	if cmp := strings.Compare(am, bm); cmp != 0 {
   303  		return cmp
   304  	}
   305  	if cmp := a.t.Compare(b.t); cmp != 0 {
   306  		return cmp
   307  	}
   308  	return strings.Compare(a.op, b.op)
   309  }
   310  
   311  func (o *wasmOp) Type() *simdType {
   312  	return o.t
   313  }
   314  
   315  func (o *wasmOp) ArgCount() int {
   316  	return o.argCount
   317  }
   318  
   319  func (o *wasmOp) OpFlags() OpFlags {
   320  	return o.opFlags
   321  }
   322  
   323  func (o *wasmOp) Flag(f OpFlags) bool {
   324  	return o.opFlags&f != 0
   325  }
   326  
   327  func (o *wasmOp) ImmRange() uint8 {
   328  	return o.immRange
   329  }
   330  
   331  func (o *wasmOp) ImmName() string {
   332  	if o.immName == "" {
   333  		return "_"
   334  	}
   335  	return o.immName
   336  }
   337  
   338  func snakeToCamel(s string) string {
   339  	capnext := true
   340  	result := ""
   341  	for _, c := range s {
   342  		if c == '_' {
   343  			capnext = true
   344  			continue
   345  		}
   346  		if '0' <= c && c <= '9' {
   347  			capnext = true
   348  		} else {
   349  			if capnext {
   350  				c = unicode.ToUpper(c)
   351  				capnext = false
   352  			}
   353  		}
   354  		result += string(c)
   355  	}
   356  	return result
   357  }
   358  
   359  // Op returns the snakeToCamel version of the WASM operation,
   360  // e.g. return_call_indirect
   361  func (o *wasmOp) Op() string {
   362  	return snakeToCamel(o.op)
   363  }
   364  
   365  func (o *wasmOp) T() *simdType {
   366  	return o.t
   367  }
   368  
   369  // Sub, AddSaturated
   370  // this appears in the Go API declaration files and in the intrinsic registration files.
   371  func (o *wasmOp) Method() string {
   372  	goname := gonames[o.op]
   373  	if o.Flag(NameHasFormat) {
   374  		// This is an extend method of the form Extend{Lo,Hi}%dTo%s
   375  		count := o.T().Count / 2
   376  		x := strings.Index(o.ResultType(), "x")
   377  		typ := o.ResultType()[:x]
   378  		return fmt.Sprintf(goname, count, typ)
   379  	}
   380  	if len(goname) >= 1 {
   381  		if len(goname) == 1 {
   382  			// it's something like "-" or "?" or "!"
   383  			return ""
   384  		}
   385  		return goname
   386  	}
   387  	return snakeToCamel(o.op)
   388  }
   389  
   390  func (o *wasmOp) NUMethod() string {
   391  	s := o.Method()
   392  	if s != "" && s[0] == '_' {
   393  		s = s[1:]
   394  	}
   395  	return s
   396  }
   397  
   398  func (o *wasmOp) SsaResultType() string {
   399  	if o.Flag(IsTest) {
   400  		return "Bool"
   401  	}
   402  	return "Vec128" // TODO this is not always right
   403  }
   404  
   405  func (o *wasmOp) RegInfo() string {
   406  	if o.argType == "" {
   407  		if o.resultType == "" {
   408  			switch o.argCount {
   409  			case 1:
   410  				return "v11"
   411  			case 2:
   412  				return "v21"
   413  			case 3:
   414  				return "v31"
   415  			}
   416  		} else if o.Flag(IsConversion) {
   417  			if o.argCount == 1 {
   418  				return "v11"
   419  			} else if o.argCount == 2 {
   420  				// widening multiplies
   421  				return "v21"
   422  			}
   423  		} else {
   424  			if o.argCount == 1 {
   425  				// extract lane
   426  				if o.resultType[0] == 'i' || o.resultType[0] == 'u' {
   427  					return "v11gp"
   428  				}
   429  				if o.resultType == "float32" {
   430  					return "v11fp32"
   431  				}
   432  				if o.resultType == "float64" {
   433  					return "v11fp64"
   434  				}
   435  			}
   436  		}
   437  	} else if o.argCount == 2 {
   438  		// replace lane
   439  		if o.argType[0] == 'i' || o.argType[0] == 'u' {
   440  			return "v1gpv"
   441  		} else if o.argType == "float32" {
   442  			return "v1fp32v"
   443  		} else if o.argType == "float64" {
   444  			return "v1fp64v"
   445  		}
   446  	} else if o.argCount == 3 {
   447  		// bitSelect
   448  		return "v31"
   449  	} else if o.Flag(IsSplat) {
   450  		if o.argType[0] == 'i' {
   451  			return "gpv"
   452  		} else if o.argType == "float32" {
   453  			return "fp32v"
   454  		} else {
   455  			return "fp64v"
   456  		}
   457  	}
   458  
   459  	panic("RegInfo not implemented for " + o.String())
   460  }
   461  
   462  func (o *wasmOp) DefinesGeneric() bool {
   463  	return o.Method() != "" && !o.T().IsMask() && !o.Flag(NonSigned)
   464  }
   465  
   466  // SubInt8x16
   467  func (o *wasmOp) SsaGenOp() string {
   468  	m := o.Method()
   469  	if m == "" {
   470  		return ""
   471  	}
   472  	// if o.Flag(IsBitwise) {
   473  	// 	return m + "V128"
   474  	// }
   475  	if m[0] == '_' {
   476  		// strip leading underscore from name, for generics.
   477  		m = m[1:]
   478  	}
   479  	t := o.T()
   480  	if t.IsMask() || o.Flag(NonSigned) {
   481  		t = t.IntShaped
   482  	}
   483  	// Rotate instructions on amd 64 are single op + immediate.
   484  	if strings.HasPrefix(o.op, "RotateAll") {
   485  		m += "Var"
   486  	}
   487  	r := m + t.Name
   488  	return r
   489  }
   490  
   491  // conversionCorrectedOp separates the s/u suffix from the operation
   492  // for example input "foo_s" -> returns "foo", "_s", "S"
   493  func (o *wasmOp) conversionCorrectedOp() (op, lowerSuffix, upperSuffix string) {
   494  	op = o.op
   495  	if strings.HasSuffix(op, "_s") {
   496  		op = op[:len(op)-2]
   497  		upperSuffix = "S"
   498  		lowerSuffix = "_s"
   499  	} else if strings.HasSuffix(op, "_u") {
   500  		op = op[:len(op)-2]
   501  		upperSuffix = "U"
   502  		lowerSuffix = "_u"
   503  	}
   504  	return
   505  }
   506  
   507  // SsaWasmOp returns the name of the WASM-specific SSA Op
   508  // examples: I8x16Sub, I64x2ExtendLowI32x4U
   509  func (o *wasmOp) SsaWasmOp() string {
   510  	// Wasm puts the main input type first
   511  	// Except conversions start with their result type
   512  	oop := o.Op()
   513  	if oop == "BitSelect" {
   514  		// Good names for methods versus consistent names for ASM, resolved here.
   515  		// Snake_To_Camel => Bitselect but BitSelect is better and Go programmers are
   516  		// more important than the naming choices of (virtual) hardware designers.
   517  		oop = "Bitselect"
   518  	} else if oop == "Shuffle" {
   519  		oop += "16"
   520  	}
   521  
   522  	if o.Flag(IsBitwise) {
   523  		return "V128" + oop
   524  	}
   525  	if o.Flag(IsConversion) {
   526  		// I64x2ExtendLowI32x4U
   527  		op, _, suffix := o.conversionCorrectedOp()
   528  		op = snakeToCamel(op)
   529  		return CapitalizeFirst(wasmResultType(o.ResultType())) + op + o.T().WasmUName() + suffix
   530  	}
   531  	r := o.T().WasmUName() + oop
   532  	return r
   533  }
   534  
   535  func wasmResultType(s string) string {
   536  	ti := strings.Index(s, "t") // Uint, Int, Float
   537  	if s[ti-1] == 'n' {         // Uint, Int
   538  		s = "i" + s[ti+1:]
   539  	} else {
   540  		s = "f" + s[ti+1:]
   541  	}
   542  	return s
   543  }
   544  
   545  // WasmInstruction returns the from-the-Wasm-spec assembler instruction
   546  // examples: i8x16.sub, i64x2.extend_low.i32x4_u
   547  func (o *wasmOp) WasmInstruction() string {
   548  	oop := o.op
   549  	if o.Flag(IsBitwise) {
   550  		return "v128" + "." + oop
   551  	}
   552  	if o.Flag(IsConversion) {
   553  		// i64x2.extend_low
   554  		op, suffix, _ := o.conversionCorrectedOp()
   555  		return wasmResultType(o.ResultType()) + "." + op + "_" + o.T().WasmLName() + suffix
   556  	}
   557  	r := o.T().WasmLName() + "." + oop
   558  	return r
   559  }
   560  
   561  func (o *wasmOp) RcvrType() string {
   562  	return o.T().Name
   563  }
   564  
   565  func (o *wasmOp) Arg2Name() string {
   566  	if n := o.arg2Name; n != "" {
   567  		return n
   568  	}
   569  	return "z"
   570  }
   571  
   572  func (o *wasmOp) Arg1Name() string {
   573  	if n := o.arg1Name; n != "" {
   574  		return n
   575  	}
   576  	return "y"
   577  }
   578  
   579  // v128
   580  func (o *wasmOp) ResultType() string {
   581  	if o.Flag(IsRelation) {
   582  		return o.T().MaskFor().Name
   583  	}
   584  	if o.resultType == "" {
   585  		return o.T().Name
   586  	}
   587  	return o.resultType
   588  }
   589  
   590  func (o *wasmOp) ArgType() string {
   591  	if o.argType == "" {
   592  		return o.T().Name
   593  	}
   594  	return o.argType
   595  }
   596  
   597  // I8x16Sub -- no need to prefix the "A", that is handled by the SSA rules generator
   598  func (o *wasmOp) AsmOp() string {
   599  	r := o.SsaWasmOp()
   600  	return r
   601  }
   602  
   603  var (
   604  	// Sets of types assocated with various (groups of) wasm instructions
   605  
   606  	allTypes = []*simdType{vi8, vi16, vi32, vi64, vu8, vu16, vu32, vu64, vf32, vf64}
   607  	signed   = []*simdType{vi8, vi16, vi32, vi64}
   608  	unsigned = []*simdType{vu8, vu16, vu32, vu64}
   609  	ints     = []*simdType{vi8, vi16, vi32, vi64, vu8, vu16, vu32, vu64}
   610  	floats   = []*simdType{vf32, vf64}
   611  	sle16    = []*simdType{vi8, vi16}                          // signed LEQ 16 bits
   612  	ule16    = []*simdType{vu8, vu16}                          // unsigned LEQ 16 bits
   613  	ige16    = []*simdType{vi16, vi32, vi64, vu16, vu32, vu64} // signed GEQ 16 bits
   614  	nge32    = []*simdType{vi32, vi64, vu32, vu64, vf32, vf64} // numbers GEQ 32 bits
   615  	sle32    = []*simdType{vi8, vi16, vi32}                    // signed LEQ 32 bits
   616  	ule32    = []*simdType{vu8, vu16, vu32}                    // unsigned LEQ 32 bits
   617  	ieq32    = []*simdType{vi32, vu32}                         // integer EQ 32 bits (convert_lo)
   618  	masks    = []*simdType{vm8, vm16, vm32, vm64}
   619  
   620  	// All these names are transcribed roughly directly from the WASM 3 list of vector instructions on page 22
   621  	// prefix_suffix translates to receivertype_category
   622  	//
   623  	// receiver type = i(int), s(signed), u(unsigned), f(float), n(number); w/ possible size restriction suffix.
   624  	//
   625  	// 1, 2, 3 = unary, binary, ternary; suffix in number indicates arg type(s)
   626  	// t = test (unary w/ signed scalar result);
   627  	// r = relation (binary w/ signed vector result);
   628  	// c = convert (unary w/ type change)
   629  	// Suffix "q" means "have not figured out what this operation does yet"
   630  
   631  	iv_1 = []string{"not"}                        // vector, unary -- but not float
   632  	iv_2 = []string{"and", "andnot", "or", "xor"} // vector, binary -- but not float
   633  	v_3  = []string{"bitSelect"}                  // vector, ternary
   634  
   635  	v_t = []string{"any_true"} // vector, test (scalar result)
   636  
   637  	s_1  = []string{"abs", "neg"}                                              // integer, unary
   638  	f_1  = []string{"abs", "neg", "sqrt", "ceil", "floor", "trunc", "nearest"} // float, unary
   639  	i8_1 = []string{"popcnt"}                                                  // int8, unary
   640  
   641  	i_2     = []string{"add", "sub"}                       // integer, binary
   642  	sle16_2 = []string{"add_sat_s", "sub_sat_s"}           // signed 8, 16, binary
   643  	ule16_2 = []string{"add_sat_u", "sub_sat_u", "avgr_u"} // unsigned 8, 16, binary
   644  	ige16_2 = []string{"mul"}                              // integer 16, 32, 64, binary
   645  	s16_2   = []string{"q15mulr_sat_s", "relaxed_q15mulr_s"}
   646  	sle32_2 = []string{"min_s", "max_s"}
   647  	ule32_2 = []string{"min_u", "max_u"}
   648  
   649  	f_2 = []string{"add", "sub", "mul", "div", "min", "max", "pmin", "pmax", "relaxed_min", "relaxed_max"}
   650  
   651  	i_3 = []string{"relaxed_laneselect"}
   652  
   653  	f_3 = []string{"relaxed_madd", "relaxed_nmadd"}
   654  
   655  	i_t = []string{"all_true"} // shape, test (scalar result)
   656  
   657  	// WASM SIMD relations return vectors of integer 0/1-valued lanes.  To use these as bitwise masks,
   658  	// they need to be negated.
   659  
   660  	i_r     = []string{"eq", "ne"}
   661  	s_r     = []string{"lt_s", "gt_s", "le_s", "ge_s"}
   662  	ule32_r = []string{"lt_u", "gt_u", "le_u", "ge_u"}
   663  
   664  	f_r = []string{"eq", "ne", "lt", "gt", "le", "ge"}
   665  
   666  	// TODO see if semantics can be derived from spec.
   667  	i8_swiz = []string{"swizzle"} //, "relaxed_swizzle"}
   668  	i8_shuf = []string{"shuffle"}
   669  
   670  	// shift ops; integer arg, vector result
   671  	u_s = []string{"shl", "shr_u"}
   672  	s_s = []string{"shl", "shr_s"}
   673  
   674  	sle16_q1 = []string{"extadd_pairwise_s"}
   675  	ule16_q1 = []string{"extadd_pairwise_u"}
   676  
   677  	s_q2   = []string{"extmul_low_s", "extmul_high_s"}
   678  	u_q2   = []string{"extmul_low_u", "extmul_high_u"}
   679  	s16_q2 = []string{"dot_s"}
   680  	s8_q2  = []string{"relaxed_dot_s"}
   681  
   682  	s8_q3 = []string{"relaxed_dot_add_s"}
   683  
   684  	// extend_{u,s} widen integers x2; convert_halfs widen integers to floats x2
   685  	// naming, to match amd64, is
   686  	// ExtendLo<N>To<Type> where result type is <Type>x<N>
   687  	extend_u      = []string{"extend_low_u", "extend_high_u"}
   688  	extend_s      = []string{"extend_low_s", "extend_high_s"}
   689  	convert_low_u = []string{"convert_low_u"}
   690  	convert_low_s = []string{"convert_low_s"}
   691  
   692  	// In same size, 32 bit integer to float.
   693  	convert_s = []string{"convert_s"}
   694  	convert_u = []string{"convert_u"}
   695  
   696  	f_c   = []string{"trunc_sat_s", "trunc_sat_u"} // f32 -> s/u 32 (2 choices)
   697  	f32_c = []string{"promote_low"}                // produces f64 vector
   698  	f64_c = []string{"demote_zero"}                // produces f32 vector
   699  
   700  	// these have an immediate operand, and return an element_type operand
   701  	nge32_x = []string{"extract_lane"}
   702  	sle16_x = []string{"extract_lane_s"}
   703  	ule16_x = []string{"extract_lane_u"}
   704  
   705  	// has an immediate operand, and an element type operand
   706  	n_x = []string{"replace_lane"}
   707  
   708  	rotates = []string{"RotateAllLeft", "RotateAllRight"}
   709  
   710  	// TODO, once the semantics are understood.
   711  	// N1xM1.narrow_N2xM2_{S,U}
   712  	// N1xM1 is the result type.  There are two N2xM2 inputs, where N1 = N2/2 and M1 = M2*2
   713  	// Each N2-sized input element is narrow, saturating signed or unsigned in the process.
   714  
   715  	// Some operations are renamed, and some are not directly present as an intrinsic
   716  	// or generic SIMD operation.  A name beginning "_" means the intrinsic begins with
   717  	// that underscore and there must be an emulation written in terms of that intrinsic.
   718  	gonames = map[string]string{
   719  		// not a direct intrinsic
   720  		"any_true": "-", // not IsZero()
   721  		// rename
   722  		"add_sat_s":    "AddSaturated",
   723  		"add_sat_u":    "AddSaturated",
   724  		"sub_sat_s":    "SubSaturated",
   725  		"sub_sat_u":    "SubSaturated",
   726  		"andnot":       "AndNot",
   727  		"nearest":      "Round",
   728  		"popcnt":       "OnesCount",
   729  		"avgr_u":       "Average",
   730  		"eq":           "Equal",
   731  		"ne":           "NotEqual",
   732  		"le":           "LessEqual",
   733  		"ge":           "GreaterEqual",
   734  		"lt":           "Less",
   735  		"gt":           "Greater",
   736  		"le_s":         "LessEqual",
   737  		"ge_s":         "GreaterEqual",
   738  		"lt_s":         "Less",
   739  		"gt_s":         "Greater",
   740  		"le_u":         "LessEqual",
   741  		"ge_u":         "GreaterEqual",
   742  		"lt_u":         "Less",
   743  		"gt_u":         "Greater",
   744  		"relaxed_madd": "MulAdd",
   745  		"shl":          "ShiftAllLeft",
   746  
   747  		"extract_lane":   "GetElem",
   748  		"extract_lane_s": "GetElem",
   749  		"extract_lane_u": "GetElem",
   750  
   751  		"replace_lane": "SetElem",
   752  
   753  		"bitselect": "BitSelect",
   754  
   755  		// remove sign/unsigned
   756  		"min_s": "Min",
   757  		"max_s": "Max",
   758  		"min_u": "Min",
   759  		"max_u": "Max",
   760  		"shr_u": "ShiftAllRight",
   761  		"shr_s": "ShiftAllRight",
   762  
   763  		// need to figure out what these are.
   764  		"q15mulr_sat_s":     "?",
   765  		"relaxed_q15mulr_s": "?",
   766  		// not sure we need these
   767  		"pmin":        "-",
   768  		"pmax":        "-",
   769  		"relaxed_min": "-",
   770  		"relaxed_max": "-",
   771  		// rename
   772  		// available via optimization.
   773  		"relaxed_nmadd": "-",
   774  		// not sure of the exact name
   775  		"all_true": "?",
   776  		// need to verify semantics
   777  		"extadd_pairwise_s": "?",
   778  		"extadd_pairwise_u": "?",
   779  		"extmul_low_s":      "MulWidenLo",
   780  		"extmul_low_u":      "MulWidenLo",
   781  		"extmul_high_s":     "MulWidenHi",
   782  		"extmul_high_u":     "MulWidenHi",
   783  		"dot_s":             "?",
   784  		"relaxed_dot_s":     "?",
   785  
   786  		// widen integer types
   787  		"extend_low_s":  "ExtendLo%dTo%s",
   788  		"extend_high_s": "ExtendHi%dTo%s",
   789  		"extend_low_u":  "ExtendLo%dTo%s",
   790  		"extend_high_u": "ExtendHi%dTo%s",
   791  
   792  		// [u]int32x4 to float32x4
   793  		"convert_s": "ConvertToFloat32",
   794  		"convert_u": "ConvertToFloat32",
   795  
   796  		// [u]int32x4 to float32x4
   797  		"trunc_sat_s": "ConvertToInt32",
   798  		"trunc_sat_u": "ConvertToUint32",
   799  
   800  		// [u]int32x4 to float64x2
   801  		"convert_low_s": "ConvertLo2ToFloat64",
   802  		"convert_low_u": "ConvertLo2ToFloat64",
   803  
   804  		"swizzle": "LookupOrZero",
   805  		"splat":   "Broadcast",
   806  	}
   807  )
   808  
   809  func initWasmOps() {
   810  	// Local flag-setting functions handle various special cases.
   811  	isBitwise := func(s string, _ *simdType) OpFlags {
   812  		return IsBitwise
   813  	}
   814  	isTest := func(s string, _ *simdType) OpFlags {
   815  		if s == "any_true" {
   816  			return IsBitwise | IsTest
   817  		}
   818  		return IsTest
   819  	}
   820  	binBitwise := func(s string, t *simdType) OpFlags {
   821  		if s == "andnot" {
   822  			return IsBitwise
   823  		}
   824  		return IsBitwise | IsCommutative
   825  	}
   826  	unShape := func(s string, t *simdType) OpFlags {
   827  		return 0
   828  	}
   829  	binShape := func(s string, t *simdType) OpFlags {
   830  		if !t.Float && t.IntShaped != nil {
   831  			if s == "add" {
   832  				return IsCommutative | NonSigned
   833  			}
   834  			if s == "sub" {
   835  				return NonSigned
   836  			}
   837  		}
   838  		if strings.HasPrefix(s, "sub") || s == "div" {
   839  			return 0
   840  		}
   841  		return IsCommutative
   842  	}
   843  	isMask := func(s string, t *simdType) OpFlags {
   844  		flags := IsRelation
   845  		if s == "eq" || s == "ne" {
   846  			flags |= NonSigned
   847  		} else {
   848  			flags |= IsBitwise
   849  		}
   850  		if s == "ne" || s == "eq" || s == "and" || s == "or" || s == "xor" {
   851  			flags |= IsCommutative
   852  		}
   853  		return flags
   854  	}
   855  
   856  	isRelation := func(s string, t *simdType) OpFlags {
   857  		flags := IsRelation
   858  
   859  		if s == "ne" || s == "eq" || s == "and" || s == "or" || s == "xor" {
   860  			flags |= IsCommutative
   861  		}
   862  		return flags
   863  	}
   864  
   865  	bitSelect := func(op *wasmOp) {
   866  		op.opFlags = IsBitwise
   867  		op.arg2Name = "cond"
   868  	}
   869  
   870  	addWasmOps(ints, iv_1, 1, isBitwise)  // because reasons, not floats
   871  	addWasmOps(ints, iv_2, 2, binBitwise) // because reasons, not floats
   872  	addWasmOpsDetail(ints, v_3, 3, bitSelect)
   873  	addWasmOps(allTypes, v_t, 1, isTest)
   874  	addWasmOps(signed, s_1, 1, unShape)
   875  	addWasmOps(floats, f_1, 1, unShape)
   876  	addWasmOps([]*simdType{vi8}, i8_1, 1, nil)
   877  
   878  	addWasmOps(ints, i_2, 2, binShape)
   879  	addWasmOps(sle16, sle16_2, 2, binShape)
   880  	addWasmOps(ule16, ule16_2, 2, binShape)
   881  	addWasmOps(ige16, ige16_2, 2, binShape)
   882  
   883  	addWasmOps([]*simdType{vi16}, s16_2, 2, nil) // afaik not commutative
   884  
   885  	addWasmOps(sle32, sle32_2, 2, binShape)
   886  	addWasmOps(ule32, ule32_2, 2, binShape)
   887  
   888  	addWasmOps(floats, f_2, 2, binShape)
   889  
   890  	addWasmOps(floats, f_3, 3, nil)
   891  	// addWasmOps(ints, i_3, 3, nil)
   892  	addWasmOps(ints, i_t, 1, isTest)
   893  
   894  	addWasmOps(ints, i_r, 2, isRelation)
   895  	addWasmOps(signed, s_r, 2, isRelation)
   896  	addWasmOps(ule32, ule32_r, 2, isRelation)
   897  
   898  	addWasmOps(floats, f_r, 2, isRelation)
   899  
   900  	// Shuffle is a mess, it takes a 8x16 vector in and SIXTEEN immediates specifying the indices.
   901  	// addWasmOps([]*simdType{vi8}, i8_shuf, 1, nil)
   902  	addWasmOpsDetail([]*simdType{vi8}, i8_swiz, 2, func(op *wasmOp) { op.arg1Name = "i" })
   903  
   904  	// Masks have some operations.
   905  	addWasmOps(masks, iv_2, 2, isMask)
   906  
   907  	extractImmediate := func(op *wasmOp) {
   908  		op.resultType = op.t.Elem
   909  		op.immRange = uint8(op.t.Count)
   910  		op.immName = "index"
   911  	}
   912  
   913  	addWasmOpsDetail(nge32, nge32_x, 1, extractImmediate)
   914  	addWasmOpsDetail(sle16, sle16_x, 1, extractImmediate)
   915  	addWasmOpsDetail(ule16, ule16_x, 1, extractImmediate)
   916  
   917  	replaceImmediate := func(op *wasmOp) {
   918  		op.argType = op.t.Elem
   919  		op.immRange = uint8(op.t.Count)
   920  		op.immName = "index"
   921  	}
   922  	addWasmOpsDetail(allTypes, n_x, 2, replaceImmediate)
   923  
   924  	shift := func(op *wasmOp) {
   925  		op.argType = "uint64"
   926  		op.opFlags = IsShift
   927  	}
   928  	addWasmOpsDetail(signed, s_s, 2, shift)
   929  	addWasmOpsDetail(unsigned, u_s, 2, shift)
   930  
   931  	splat := func(op *wasmOp) {
   932  		op.opFlags = IsSplat
   933  		op.argType = op.t.Elem
   934  		op.resultType = op.t.Name
   935  		if op.argType[0] == 'u' {
   936  			op.opFlags |= NonSigned
   937  		}
   938  	}
   939  	addWasmOpsDetail(allTypes, []string{"splat"}, 1, splat)
   940  
   941  	// To match the extend patterns for amd64, signed extends to signed, unsigned extends to unsigned
   942  	extendHalf := func(op *wasmOp) {
   943  		t := op.t
   944  		op.opFlags = IsConversion | NameHasFormat
   945  		// result type is twice the width, signedness from the op, half the count
   946  		stem := "Int"
   947  		if op.op[len(op.op)-1] == 'u' {
   948  			stem = "Uint"
   949  		}
   950  		op.resultType = t.WidenElements(stem)
   951  	}
   952  	mulHalf := func(op *wasmOp) {
   953  		t := op.t
   954  		op.opFlags = IsConversion | IsCommutative // this is ALSO a conversion, with same naming conventions.
   955  		// result type is twice the width, signedness from the op, half the count
   956  		stem := "Int"
   957  		if op.op[len(op.op)-1] == 'u' {
   958  			stem = "Uint"
   959  		}
   960  		op.resultType = t.WidenElements(stem)
   961  	}
   962  	convertHalf := func(op *wasmOp) {
   963  		// amd64 has these instructions but we use the vector-register-widening ones,
   964  		// thus the generics are not defined by amd64.
   965  		op.opFlags = IsConversion
   966  		op.resultType = "Float64x2"
   967  	}
   968  	convert := func(op *wasmOp) {
   969  		op.opFlags = IsConversion
   970  		op.resultType = "Float32x4"
   971  	}
   972  	truncSat := func(op *wasmOp) {
   973  		op.opFlags = IsConversion
   974  		if op.op == "trunc_sat_s" {
   975  			op.resultType = "Int32x4"
   976  		} else {
   977  			op.resultType = "Uint32x4"
   978  		}
   979  	}
   980  
   981  	rotate := func(op *wasmOp) {
   982  		op.opFlags = EmulatedRule | IsShift
   983  		op.argType = "uint64"
   984  		op.arg1Name = "shift"
   985  	}
   986  
   987  	addWasmOpsDetail(ule32, extend_u, 1, extendHalf)
   988  	addWasmOpsDetail(sle32, extend_s, 1, extendHalf)
   989  	addWasmOpsDetail([]*simdType{vi32}, convert_low_s, 1, convertHalf)
   990  	addWasmOpsDetail([]*simdType{vu32}, convert_low_u, 1, convertHalf)
   991  	addWasmOpsDetail([]*simdType{vi32}, convert_s, 1, convert)
   992  	addWasmOpsDetail([]*simdType{vu32}, convert_u, 1, convert)
   993  
   994  	addWasmOpsDetail([]*simdType{vf32}, f_c, 1, truncSat)
   995  
   996  	addWasmOpsDetail(sle32, s_q2, 2, mulHalf)
   997  	addWasmOpsDetail(ule32, u_q2, 2, mulHalf)
   998  
   999  	addWasmOpsDetail(ints, rotates, 2, rotate)
  1000  
  1001  	slices.SortFunc(wasmOps, compareWasmOps)
  1002  
  1003  	for i := 1; i < len(wasmOps); i++ {
  1004  		c := compareWasmOps(wasmOps[i-1], wasmOps[i])
  1005  		if c >= 0 {
  1006  			d := compareWasmOps(wasmOps[i-1], wasmOps[i])
  1007  			fmt.Printf("Two wasm ops compared out of order, c=%d, \n%v\n%v\n", d, wasmOps[i-1], wasmOps[i])
  1008  		}
  1009  	}
  1010  }
  1011  
  1012  var wasmOps = []*wasmOp{}
  1013  
  1014  // Given a slice of simd types and a slice of operations with the specified argCount,
  1015  // add the resulting wasm operations to wasmOps.  after is a function that applies
  1016  // operation-specific customization to the generated wasmOp.
  1017  func addWasmOpsDetail(types []*simdType, ops []string, argCount int, after func(op *wasmOp)) {
  1018  	for _, t := range types {
  1019  		for _, o := range ops {
  1020  			op := &wasmOp{t: t, op: o, argCount: argCount}
  1021  			after(op)
  1022  			if t.Methods[o] != nil {
  1023  				panic("Double addition of method " + o + " for " + t.Name)
  1024  			}
  1025  			t.Methods[o] = op
  1026  			wasmOps = append(wasmOps, op)
  1027  		}
  1028  	}
  1029  
  1030  }
  1031  
  1032  // Given a slice of simd types and a slice of operatinos with the specified argCount,
  1033  // add the resulting wasm operations to wasmOps.  flags is an optional function that
  1034  // adjusts the flags of the WasmOp.
  1035  func addWasmOps(types []*simdType, ops []string, argCount int, flags func(op string, ty *simdType) OpFlags) {
  1036  	addWasmOpsDetail(types, ops, argCount, func(op *wasmOp) {
  1037  		if flags == nil {
  1038  			return
  1039  		}
  1040  		op.opFlags = flags(op.op, op.T())
  1041  	})
  1042  }
  1043  
  1044  func must(err error) {
  1045  	if err != nil {
  1046  		panic(err)
  1047  	}
  1048  }
  1049  
  1050  func mustVal[T any](v T, err error) T {
  1051  	if err != nil {
  1052  		panic(err)
  1053  	}
  1054  	return v
  1055  }
  1056  
  1057  func main() {
  1058  
  1059  	flag.Parse()
  1060  
  1061  	initWasmOps()
  1062  
  1063  	if *list {
  1064  		// Helper to list opcodes for a.out.go
  1065  		for i := range wasmOps {
  1066  			fmt.Println(wasmOps[i])
  1067  		}
  1068  		return
  1069  	}
  1070  
  1071  	*genTypesFile = mustVal(filepath.Abs(strings.ReplaceAll(*genTypesFile, "GOROOTSRC", *gorootsrc)))
  1072  	*genOpsFile = mustVal(filepath.Abs(strings.ReplaceAll(*genOpsFile, "GOROOTSRC", *gorootsrc)))
  1073  	*genSSAOpsFile = mustVal(filepath.Abs(strings.ReplaceAll(*genSSAOpsFile, "GOROOTSRC", *gorootsrc)))
  1074  	*genGenOpsFile = mustVal(filepath.Abs(strings.ReplaceAll(*genGenOpsFile, "GOROOTSRC", *gorootsrc)))
  1075  	*genSSARulesFile = mustVal(filepath.Abs(strings.ReplaceAll(*genSSARulesFile, "GOROOTSRC", *gorootsrc)))
  1076  	*genWasmSSAFile = mustVal(filepath.Abs(strings.ReplaceAll(*genWasmSSAFile, "GOROOTSRC", *gorootsrc)))
  1077  	*genIntrinsicsFile = mustVal(filepath.Abs(strings.ReplaceAll(*genIntrinsicsFile, "GOROOTSRC", *gorootsrc)))
  1078  
  1079  	log.Println("types file =", *genTypesFile)
  1080  	log.Println("ops file =", *genOpsFile)
  1081  	log.Println("ssa ops file =", *genSSAOpsFile)
  1082  	log.Println("ssa generic ops file =", *genGenOpsFile)
  1083  	log.Println("ssa rules file =", *genSSARulesFile)
  1084  	log.Println("ssa wasm ops file =", *genWasmSSAFile)
  1085  	log.Println("intrinsics file =", *genIntrinsicsFile)
  1086  
  1087  	log.Println("Generating WASM SIMD files...")
  1088  
  1089  	sgutil.FormatWriteAndClose(genTypes(), *genTypesFile)
  1090  	sgutil.FormatWriteAndClose(genOps(), *genOpsFile)
  1091  	sgutil.FormatWriteAndClose(genSSAOps(), *genSSAOpsFile)
  1092  	sgutil.FormatWriteAndClose(genWasmSSA(), *genWasmSSAFile)
  1093  	sgutil.FormatWriteAndClose(genIntrinsics(), *genIntrinsicsFile)
  1094  	sgutil.FormatWriteAndClose(genGenerics(), *genGenOpsFile)
  1095  
  1096  	genSSARules()
  1097  
  1098  }
  1099  
  1100  func templateOf(name, text string) *template.Template {
  1101  	return template.Must(template.New(name).Parse(text))
  1102  }
  1103  
  1104  var loadDecl = templateOf("load from array", `
  1105  // Load{{.Name}}Array loads {{.Article}} {{.Name}} from a [{{.Count}}]{{.Elem}}.
  1106  //
  1107  //go:noescape
  1108  func Load{{.Name}}Array(y *[{{.Count}}]{{.Elem}}) {{.Name}}
  1109  
  1110  // Load{{.Name}} loads {{.Article}} {{.Name}} from a slice of at least {{.Count}} {{.Elem}}s.
  1111  func Load{{.Name}}(s []{{.Elem}}) {{.Name}} {
  1112  	return Load{{.Name}}Array((*[{{.Count}}]{{.Elem}})(s))
  1113  }
  1114  `)
  1115  
  1116  var storeDecl = templateOf("store to array", `
  1117  // StoreArray stores {{.Article}} {{.Name}} to a [{{.Count}}]{{.Elem}}.
  1118  //
  1119  //go:noescape
  1120  func (x {{.Name}}) StoreArray(y *[{{.Count}}]{{.Elem}})
  1121  
  1122  // Store stores x into a slice of at least {{.Count}} {{.Elem}}s.
  1123  func (x {{.Name}}) Store(s []{{.Elem}}) {
  1124  	x.StoreArray((*[{{.Count}}]{{.Elem}})(s))
  1125  }
  1126  `)
  1127  
  1128  var splatDecl = templateOf("broadcast an element", `
  1129  // Broadcast{{.Name}} broadcasts {{.Article}} {{.Elem}} to all elements of {{.Article}} {{.Name}} vector.
  1130  func Broadcast{{.Name}}(x {{.Elem}}) {{.Name}}
  1131  `)
  1132  
  1133  var typeDecl = templateOf("type decl", `
  1134  // {{.Name}} is a 128-bit SIMD vector of {{.Count}} {{.Elem}}s.
  1135  type {{.Name}} struct {
  1136  	{{.Elem}}x{{.Count}} v128
  1137  	vals      [{{.Count}}]{{.Elem}}
  1138  }
  1139  `)
  1140  
  1141  var maskTypeDecl = templateOf("type decl", `
  1142  // {{.Name}} is a 128-bit SIMD mask of {{.Count}} {{.Elem}}s.
  1143  type {{.Name}} struct {
  1144  	{{.Elem}}x{{.Count}} v128
  1145  	vals      [{{.Count}}]{{.Elem}}
  1146  }
  1147  `)
  1148  
  1149  var lenDecl = templateOf("len decl", `
  1150  // Len returns the number of elements in {{.Article}} {{.Name}}.
  1151  func (x {{.Name}}) Len() int { return {{.Count}} }
  1152  `)
  1153  
  1154  func genTypes() (f *bytes.Buffer) {
  1155  	f = new(bytes.Buffer)
  1156  	fmt.Fprintln(f, "// Code generated by 'wasmgen'; DO NOT EDIT.")
  1157  	fmt.Fprintln(f)
  1158  	fmt.Fprintln(f, "//go:build goexperiment.simd && wasm")
  1159  	fmt.Fprintln(f)
  1160  	fmt.Fprintln(f, "package archsimd")
  1161  	fmt.Fprintln(f)
  1162  	fmt.Fprintln(f, "// v128 is a tag type that tells the compiler that this is really 128-bit SIMD")
  1163  	fmt.Fprintln(f, "type v128 struct {")
  1164  	fmt.Fprintln(f, "\t_128 [0]func() // uncomparable")
  1165  	fmt.Fprintln(f, "}")
  1166  
  1167  	for _, t := range allTypes {
  1168  		typeDecl.Execute(f, t)
  1169  		lenDecl.Execute(f, t)
  1170  		loadDecl.Execute(f, t)
  1171  		storeDecl.Execute(f, t)
  1172  		splatDecl.Execute(f, t)
  1173  	}
  1174  	for _, t := range masks {
  1175  		maskTypeDecl.Execute(f, t)
  1176  	}
  1177  	return
  1178  }
  1179  
  1180  var docForOp map[string]string = map[string]string{
  1181  	"Add":                 " returns the result of adding x and y, elementwise.",
  1182  	"Sub":                 " returns the result of subtracting y from x, elementwise.",
  1183  	"Mul":                 " returns the result of multiplying x and y, elementwise.",
  1184  	"Div":                 " returns the result of dividing x by y, elementwise.",
  1185  	"Neg":                 " returns the elementwise negation of x.",
  1186  	"Abs":                 " returns the elementwise absolute value of x.",
  1187  	"Sqrt":                " returns the elementwise square root of x.",
  1188  	"Not":                 " returns the bitwise NOT of x.",
  1189  	"And":                 " returns the bitwise AND of x and y.",
  1190  	"Or":                  " returns the bitwise OR of x and y.",
  1191  	"Xor":                 " returns the bitwise XOR of x and y.",
  1192  	"AndNot":              " returns the bitwise AND NOT of x and y (x & ^y).",
  1193  	"Min":                 " returns the elementwise minimum of x and y.",
  1194  	"Max":                 " returns the elementwise maximum of x and y.",
  1195  	"Round":               " returns the elementwise nearest integer, rounding ties to even.",
  1196  	"OnesCount":           " returns the elementwise population count (number of bits set).",
  1197  	"Average":             " returns the elementwise average of unsigned integers in x and y.",
  1198  	"Equal":               " returns true if x equals y, elementwise.",
  1199  	"NotEqual":            " returns true if x does not equal y, elementwise.",
  1200  	"Less":                " returns true if x is less than y, elementwise.",
  1201  	"Greater":             " returns true if x is greater than y, elementwise.",
  1202  	"LessEqual":           " returns true if x is less than or equal to y, elementwise.",
  1203  	"GreaterEqual":        " returns true if x is greater than or equal to y, elementwise.",
  1204  	"MulAdd":              " returns the elementwise multiply-add of x, y, and z.",
  1205  	"ShiftAllLeft":        " returns the elementwise left shift of x by y bits.",
  1206  	"ShiftAllRight":       " returns the elementwise right shift of x by y bits.",
  1207  	"Ceil":                " returns the elementwise ceiling of x.",
  1208  	"Floor":               " returns the elementwise floor of x.",
  1209  	"Trunc":               " returns the elementwise truncation of x.",
  1210  	"BitSelect":           " returns the bitwise selection if mask[i] then x[i] else y[i]",
  1211  	"GetElem":             " gets the lane value at the given index.",
  1212  	"SetElem":             " sets the lane at the given index to y.",
  1213  	"TruncSatS":           " returns the elementwise saturating signed conversion to integer.",
  1214  	"TruncSatU":           " returns the elementwise saturating unsigned conversion to integer.",
  1215  	"PromoteLow":          " promotes the lower half elements of x to double width values.",
  1216  	"DemoteZero":          " demotes elements of x to half width values in lower elements of result, with zeroes in the upper elements",
  1217  	"ConvertToFloat32":    " converts elements of x to Float32x4.",
  1218  	"ConvertToInt32":      " converts elements of x to Int32x4.",
  1219  	"ConvertToUint32":     " converts elements of x to Uint32x4.",
  1220  	"ConvertLo2ToFloat64": " converts the first two elements of x to Float64x2.",
  1221  	"MulWidenHi": ` returns the doubled-width product of respective elements of the upper halves of x and y.
  1222  //
  1223  //	Result[i] = x[i+{{.Type.HalfCount}}] * y[i+{{.Type.HalfCount}}], for 0 <= i < {{.Type.HalfCount}} == |x|/2.`,
  1224  	"MulWidenLo": ` returns the doubled-width product of respective elements of the lower halves of x and y.
  1225  //
  1226  //	Result[i] = x[i] * y[i], for 0 <= i < {{.Type.HalfCount}} == |x|/2.`,
  1227  	// Size-specific extend operations
  1228  	"ExtendLo8ToInt16":   " extends the lower 8 elements of x to 16-bit integers.",
  1229  	"ExtendLo16ToInt32":  " extends the lower 4 elements of x to 32-bit integers.",
  1230  	"ExtendLo32ToInt64":  " extends the lower 2 elements of x to 64-bit integers.",
  1231  	"ExtendHi8ToInt16":   " extends the higher 8 elements of x to 16-bit integers.",
  1232  	"ExtendHi16ToInt32":  " extends the higher 4 elements of x to 32-bit integers.",
  1233  	"ExtendHi32ToInt64":  " extends the higher 2 elements of x to 64-bit integers.",
  1234  	"ExtendLo8ToUint16":  " extends the lower 8 elements of x to 16-bit unsigned integers.",
  1235  	"ExtendLo16ToUint32": " extends the lower 4 elements of x to 32-bit unsigned integers.",
  1236  	"ExtendLo32ToUint64": " extends the lower 2 elements of x to 64-bit unsigned integers.",
  1237  	"ExtendHi8ToUint16":  " extends the higher 8 elements of x to 16-bit unsigned integers.",
  1238  	"ExtendHi16ToUint32": " extends the higher 4 elements of x to 32-bit unsigned integers.",
  1239  	"ExtendHi32ToUint64": " extends the higher 2 elements of x to 64-bit unsigned integers.",
  1240  	"AddSaturated":       " returns the result of adding x and y, saturating instead of overflowing, elementwise.",
  1241  	"SubSaturated":       " returns the result of subtracting x and y, saturating instead of overflowing, elementwise.",
  1242  	"Shuffle":            " returns the elements of y concatenated with z that are selected by elements of x",
  1243  	"LookupOrZero": ` returns the elements of x as indexed by the elements of i. If an index is out of range, its result is 0.
  1244  //
  1245  //	if 0 <= indices[i] && indices[i] < len(table) {
  1246  //	    result[i] = table[indices[i]]
  1247  //	} else {
  1248  //	    result[i] = 0
  1249  //	}`,
  1250  	"RelaxedSwizzle":    "",
  1251  	"RelaxedLaneselect": "",
  1252  }
  1253  
  1254  func (w *wasmOp) DocRest() string {
  1255  	m := w.Method()
  1256  	d := docForOp[m]
  1257  	if d == "" {
  1258  		return ""
  1259  	}
  1260  
  1261  	var buf bytes.Buffer
  1262  	if e := templateOf(m, d).Execute(&buf, w); e != nil {
  1263  		panic(e)
  1264  	}
  1265  	return buf.String()
  1266  }
  1267  
  1268  var unOp = templateOf("unaryOp", `
  1269  	// {{.Method}}{{.DocRest}}
  1270  	//
  1271  	// Asm: {{.AsmOp}}
  1272  	func (x {{.RcvrType}}) {{.Method}}() {{.ResultType}}
  1273  `)
  1274  
  1275  var binOp = templateOf("binaryOp", `
  1276  	// {{.Method}}{{.DocRest}}
  1277  	//
  1278  	// Asm: {{.AsmOp}}
  1279  	func (x {{.RcvrType}}) {{.Method}}({{.Arg1Name}} {{.ArgType}}) {{.ResultType}}
  1280  `)
  1281  
  1282  var ternOp = templateOf("ternaryOp", `
  1283  	// {{.Method}}{{.DocRest}}
  1284  	//
  1285  	// Asm: {{.AsmOp}}
  1286  	func (x {{.RcvrType}}) {{.Method}}(y {{.RcvrType}}, {{.Arg2Name}} {{.ArgType}}) {{.ResultType}}
  1287  `)
  1288  
  1289  var unOpImm = templateOf("unaryOpImm", `
  1290  	// {{.Method}}{{.DocRest}}
  1291  	//
  1292  	// Asm: {{.AsmOp}}
  1293  	func (x {{.RcvrType}}) {{.Method}}({{.ImmName}} uint8) {{.ResultType}}
  1294  `)
  1295  
  1296  var binOpImm = templateOf("binaryOpImm", `
  1297  	// {{.Method}}{{.DocRest}}
  1298  	//
  1299  	// Asm: {{.AsmOp}}
  1300  	func (x {{.RcvrType}}) {{.Method}}({{.ImmName}} uint8, y {{.ArgType}}) {{.ResultType}}
  1301  `)
  1302  
  1303  var toMask = templateOf("toMask", `
  1304  	// ToMask translates {{.Article}} {{.From.Name}} vector to a {{.To.Name}} mask vector
  1305  	// zero becomes false, not-zero becomes true
  1306  	func (x {{.From.Name}}) ToMask() {{.To.Name}}
  1307  `)
  1308  
  1309  var fromMask = templateOf("fromMask", `
  1310  	// To{{.To.Name}} translates a {{.From.Name}} mask vector to {{.Article}} {{.To.Name}} int vector
  1311  	// false becomes zero, true becomes -1
  1312  	func (x {{.From.Name}}) To{{.To.Name}}() {{.To.Name}}
  1313  `)
  1314  
  1315  var maskMergeUnsigned = templateOf("maskMerge",
  1316  	`// Masked returns x but with elements zeroed where mask is false.
  1317  func (x {{.Name}}) Masked(mask Mask{{.ElemSize}}x{{.Count}}) {{.Name}} {
  1318  	im := mask.ToInt{{.ElemSize}}x{{.Count}}().ToBits()
  1319  	return im.And(x)
  1320  }
  1321  
  1322  // IfElse returns x but with elements set to y where mask is false.
  1323  func (x {{.Name}}) IfElse(mask Mask{{.ElemSize}}x{{.Count}}, y {{.Name}}) {{.Name}} {
  1324  	im := mask.ToInt{{.ElemSize}}x{{.Count}}().ToBits()
  1325  	return x.BitSelect(y, im)
  1326  }
  1327  `)
  1328  
  1329  var maskMergeFloat = templateOf("maskMerge",
  1330  	`// Masked returns x but with elements zeroed where mask is false.
  1331  func (x {{.Name}}) Masked(mask Mask{{.ElemSize}}x{{.Count}}) {{.Name}} {
  1332  	im := mask.ToInt{{.ElemSize}}x{{.Count}}().ToBits()
  1333  	return im.And(x.ToBits()).BitsToFloat{{.ElemSize}}()
  1334  }
  1335  
  1336  // IfElse returns x but with elements set to y where mask is false.
  1337  func (x {{.Name}}) IfElse(mask Mask{{.ElemSize}}x{{.Count}}, y {{.Name}}) {{.Name}} {
  1338  	im := mask.ToInt{{.ElemSize}}x{{.Count}}().ToBits()
  1339  	ix := x.ToBits()
  1340  	iy := y.ToBits()
  1341  	return ix.BitSelect(iy, im).BitsToFloat{{.ElemSize}}()
  1342  }
  1343  `)
  1344  
  1345  var maskMergeInt = templateOf("maskMergeInt",
  1346  	`// Masked returns x but with elements zeroed where mask is false.
  1347  func (x {{.Name}}) Masked(mask Mask{{.ElemSize}}x{{.Count}}) {{.Name}} {
  1348  	im := mask.ToInt{{.ElemSize}}x{{.Count}}()
  1349  	return im.And(x)
  1350  }
  1351  
  1352  // IfElse returns x but with elements set to y where mask is false.
  1353  func (x {{.Name}}) IfElse(mask Mask{{.ElemSize}}x{{.Count}}, y {{.Name}}) {{.Name}} {
  1354  	im := mask.ToInt{{.ElemSize}}x{{.Count}}()
  1355  	return x.BitSelect(y, im)
  1356  }
  1357  `)
  1358  
  1359  var toString = templateOf("toString",
  1360  	`// String returns a string representation of SIMD vector x.
  1361  func (x {{.Name}}) String() string {
  1362  	var s [{{.Count}}]{{.Elem}}
  1363  	x.StoreArray(&s)
  1364  	return sliceToString(s[:])
  1365  }
  1366  `)
  1367  
  1368  var maskToString = templateOf("maskToString",
  1369  	`// String returns a string representation of SIMD mask x.
  1370  func (x Mask{{.ElemSize}}x{{.Count}}) String() string {
  1371  	var s [{{.Count}}]{{.Elem}}
  1372  	x.ToInt{{.ElemSize}}x{{.Count}}().Neg().StoreArray(&s)
  1373  	return sliceToString(s[:])
  1374  }
  1375  `)
  1376  
  1377  type asConversion struct {
  1378  	From, To *simdType
  1379  	Article  string
  1380  }
  1381  
  1382  func forAllAsConversions(f func(from, to *simdType)) {
  1383  	for _, from := range allTypes {
  1384  		for _, to := range allTypes {
  1385  			if from == to {
  1386  				continue
  1387  			}
  1388  			f(from, to)
  1389  		}
  1390  	}
  1391  }
  1392  
  1393  func fromUnsignedToFloats(f func(from, to *simdType)) {
  1394  	for _, to := range floats {
  1395  		from := to.UintFor()
  1396  		f(from, to)
  1397  	}
  1398  }
  1399  
  1400  func fromUnsignedToInts(f func(from, to *simdType)) {
  1401  	for _, to := range signed {
  1402  		from := to.UintFor()
  1403  		f(from, to)
  1404  	}
  1405  }
  1406  
  1407  func forAllReshape(f func(from, to *simdType)) {
  1408  	for _, from := range unsigned {
  1409  		for _, to := range unsigned {
  1410  			if from == to {
  1411  				continue
  1412  			}
  1413  			f(from, to)
  1414  		}
  1415  	}
  1416  }
  1417  
  1418  func genOps() (f *bytes.Buffer) {
  1419  	f = new(bytes.Buffer)
  1420  	fmt.Fprintln(f, "// Code generated by 'wasmgen'; DO NOT EDIT.")
  1421  	fmt.Fprintln(f)
  1422  	fmt.Fprintln(f, "//go:build goexperiment.simd && wasm")
  1423  	fmt.Fprintln(f)
  1424  	fmt.Fprintln(f, "package archsimd")
  1425  	fmt.Fprintln(f)
  1426  
  1427  	// Basic operations
  1428  	for _, op := range wasmOps {
  1429  		if op.OpFlags()&(IsLoad|IsStore|IsSplat) != 0 {
  1430  			continue // Handled elsewhere or skipped for methods
  1431  		}
  1432  
  1433  		if op.Method() == "" {
  1434  			continue
  1435  		}
  1436  
  1437  		if op.ImmRange() > 0 {
  1438  			switch op.ArgCount() {
  1439  			case 1:
  1440  				unOpImm.Execute(f, op)
  1441  			case 2:
  1442  				binOpImm.Execute(f, op)
  1443  			default:
  1444  				panic(fmt.Errorf("Unexpected arg count %d for %v", op.ArgCount(), op))
  1445  			}
  1446  
  1447  		} else {
  1448  			switch op.ArgCount() {
  1449  			case 1:
  1450  				unOp.Execute(f, op)
  1451  			case 2:
  1452  				binOp.Execute(f, op)
  1453  			case 3:
  1454  				ternOp.Execute(f, op)
  1455  			default:
  1456  				panic(fmt.Errorf("Unexpected arg count %d for %v", op.ArgCount(), op))
  1457  			}
  1458  		}
  1459  	}
  1460  
  1461  	for _, t := range signed {
  1462  		// Conversions to/from mask types
  1463  		// func (x Int8x16) ToMask() Mask8x16
  1464  		// func (x Mask8x16) ToInt8x16() Int8x16
  1465  		toMask.Execute(f, &asConversion{t, t.MaskFor(), t.Article()})
  1466  		fromMask.Execute(f, &asConversion{t.MaskFor(), t, t.Article()})
  1467  	}
  1468  
  1469  	// Mask and Merge ops
  1470  	for _, t := range allTypes {
  1471  		if t.Name[0] == 'I' {
  1472  			maskMergeInt.Execute(f, t)
  1473  		} else if t.Name[0] == 'F' {
  1474  			maskMergeFloat.Execute(f, t)
  1475  		} else {
  1476  			maskMergeUnsigned.Execute(f, t)
  1477  		}
  1478  	}
  1479  
  1480  	// String
  1481  	for _, t := range allTypes {
  1482  		toString.Execute(f, t)
  1483  	}
  1484  
  1485  	// Mask to String
  1486  	for _, t := range signed {
  1487  		maskToString.Execute(f, t)
  1488  	}
  1489  
  1490  	fromUnsignedToFloats(func(from, to *simdType) {
  1491  		sgutil.ToFloatsDcl.Execute(f, sgutil.Conversion(from, to))
  1492  		sgutil.ToBitsDcl.Execute(f, sgutil.Conversion(to, from))
  1493  	})
  1494  
  1495  	fromUnsignedToInts(func(from, to *simdType) {
  1496  		sgutil.ToIntsDcl.Execute(f, sgutil.Conversion(from, to))
  1497  		sgutil.ToBitsDcl.Execute(f, sgutil.Conversion(to, from))
  1498  	})
  1499  
  1500  	forAllReshape(func(from, to *simdType) {
  1501  		sgutil.ReshapeDcl.Execute(f, sgutil.Conversion(from, to))
  1502  	})
  1503  	return
  1504  }
  1505  
  1506  // genSSARules generates the definitions for WASM-specific SIMD SSA operations.
  1507  // The expected target directory is cmd/compile/internal/ssa/_gen
  1508  func genSSAOps() (f *bytes.Buffer) {
  1509  	f = new(bytes.Buffer)
  1510  	fmt.Fprintln(f, "// Code generated by 'wasmgen'; DO NOT EDIT.")
  1511  	fmt.Fprintln(f)
  1512  	fmt.Fprintln(f, "package main")
  1513  	fmt.Fprintln(f)
  1514  
  1515  	fmt.Fprintln(f)
  1516  	fmt.Fprintln(f, "func simdWasmOps(vload, vstore, v11, v21, v31, v11gp, v11fp32, v11fp64, v1gpv, v1fp32v, v1fp64v, gpv, fp32v, fp64v regInfo) []opData {")
  1517  	fmt.Fprintln(f, "\treturn []opData{")
  1518  
  1519  	done := make(map[string]string)
  1520  
  1521  	for _, op := range wasmOps {
  1522  		if op.Flag(NonSigned | EmulatedRule) {
  1523  			// There's only one (signed input) version of the op, or no op at all.
  1524  			continue
  1525  		}
  1526  		var toPrint string
  1527  		ssaWasmOp := op.SsaWasmOp()
  1528  		// TODO also incorporate immediate operands.
  1529  		if op.ImmRange() > 0 {
  1530  			// These are currently not commutative
  1531  			toPrint = fmt.Sprintf("\t\t{name: \"%s\", argLength: %d, reg: %s, asm: \"%s\", aux: \"UInt8\", typ: \"%s\"},\n", ssaWasmOp, op.ArgCount(), op.RegInfo(), op.AsmOp(), op.SsaResultType())
  1532  		} else if op.Flag(IsCommutative) {
  1533  			toPrint = fmt.Sprintf("\t\t{name: \"%s\", argLength: %d, reg: %s, asm: \"%s\", commutative: true, typ: \"%s\"},\n", ssaWasmOp, op.ArgCount(), op.RegInfo(), op.AsmOp(), op.SsaResultType())
  1534  		} else {
  1535  			toPrint = fmt.Sprintf("\t\t{name: \"%s\", argLength: %d, reg: %s, asm: \"%s\", typ: \"%s\"},\n", ssaWasmOp, op.ArgCount(), op.RegInfo(), op.AsmOp(), op.SsaResultType())
  1536  		}
  1537  		if old := done[ssaWasmOp]; old != "" {
  1538  			if old != toPrint {
  1539  				panic(fmt.Errorf("Second definition of SSA WASM Op %s differed: \nold: %s\nnew: %s\n", ssaWasmOp, old, toPrint))
  1540  			}
  1541  			continue
  1542  		}
  1543  		done[ssaWasmOp] = toPrint
  1544  		fmt.Fprint(f, toPrint)
  1545  
  1546  	}
  1547  
  1548  	fmt.Fprintln(f, "\t}")
  1549  	fmt.Fprintln(f, "}")
  1550  	return
  1551  }
  1552  
  1553  // genSSARules generates the rules that convert SSA generic (SIMD) operations
  1554  // into WASM-specific SIMD SSA operations.
  1555  // The expected target directory is cmd/compile/internal/ssa/_gen
  1556  func genSSARules() {
  1557  	f, err := os.Create(*genSSARulesFile)
  1558  	if err != nil {
  1559  		log.Fatal(err)
  1560  	}
  1561  	defer f.Close()
  1562  
  1563  	fmt.Fprintln(f, "// Code generated by 'wasmgen'; DO NOT EDIT.")
  1564  	fmt.Fprintln(f)
  1565  
  1566  	for _, op := range wasmOps {
  1567  		// (GoOp x y) => (WasmOp x y)
  1568  		if op.Flag(NonSigned) {
  1569  			// skip these generics
  1570  			continue
  1571  		}
  1572  		g := op.SsaGenOp()
  1573  		if g == "" {
  1574  			continue
  1575  		}
  1576  		if op.T().IsMask() {
  1577  			continue // mask ops use Int generics
  1578  		}
  1579  		switch op.ArgCount() {
  1580  		case 1, 2, 3:
  1581  			wasmOp := op.SsaWasmOp()
  1582  			if op.Flag(IsShift) {
  1583  				t := op.T()
  1584  				elemSize := t.ElemSize
  1585  				if op.Op()[0] == 'S' { // shifts, not rotates
  1586  					fmt.Fprintf(f, "(%s x d:(Const64 [c])) && uint64(c) < %d => (%s x (I64Const [c]))\n", g, elemSize, wasmOp)
  1587  					fmt.Fprintf(f, "(%s x d:(I64Const [c])) && uint64(c) < %d => (%s x d)\n", g, elemSize, wasmOp)
  1588  					if op.op != "shr_s" { // shift right signed
  1589  						fmt.Fprintf(f, "// TODO need to do 'shiftIsBounded' for WASM SIMD Shifts\n")
  1590  						fmt.Fprintf(f, "(%s x y) => (SelectV (%s x y) (%s x x) (I64LtU y (I64Const [%d])))\n", g, wasmOp, t.Methods["xor"].SsaWasmOp(), elemSize)
  1591  					} else {
  1592  						// Signed, smear the sign bit
  1593  						fmt.Fprintf(f, "// TODO need to do 'shiftIsBounded' for WASM SIMD Shifts\n")
  1594  						fmt.Fprintf(f, "(%s x y) => (SelectV (%s x y) (%s x (I64Const [%d])) (I64LtU y (I64Const [%d])))\n", g, wasmOp, wasmOp, elemSize-1, elemSize)
  1595  					}
  1596  				} else { // rotates are okay with the implicit WASM modulus
  1597  					shl := t.UintFor().Methods["shl"].SsaWasmOp()
  1598  					shr := t.UintFor().Methods["shr_u"].SsaWasmOp()
  1599  					or := t.UintFor().Methods["or"].SsaWasmOp()
  1600  					if strings.Contains(op.Op(), "Left") {
  1601  						fmt.Fprintf(f, "(%s x y) => (%s (%s x y) (%s x (I64Sub (I64Const [%d]) y)))\n", g, or, shl, shr, elemSize)
  1602  					} else {
  1603  						fmt.Fprintf(f, "(%s x y) => (%s (%s x y) (%s x (I64Sub (I64Const [%d]) y)))\n", g, or, shr, shl, elemSize)
  1604  					}
  1605  				}
  1606  				continue
  1607  			}
  1608  			if op.Flag(NonSigned) {
  1609  				// hop over to the non-signed version of the type and use that method.
  1610  				wasmOp = op.T().IntShaped.Methods[op.op].SsaWasmOp()
  1611  			}
  1612  			fmt.Fprintf(f, "(%s ...) => (%s ...)\n", g, wasmOp)
  1613  			continue
  1614  		default:
  1615  			panic("Haven't figured out SSA rule for " + op.String())
  1616  		}
  1617  
  1618  	}
  1619  }
  1620  
  1621  // genWasmSSA creates the file/function that converts SSA nodes for WASM SIMD operations
  1622  // into the appropriate assembly language.
  1623  // The expected target directory is cmd/compile/internal/wasm
  1624  func genWasmSSA() (f *bytes.Buffer) {
  1625  	f = new(bytes.Buffer)
  1626  
  1627  	fmt.Fprintln(f, "// Code generated by 'wasmgen'; DO NOT EDIT.")
  1628  	fmt.Fprintln(f)
  1629  	fmt.Fprintln(f, "package wasm")
  1630  	fmt.Fprintln(f)
  1631  	fmt.Fprintln(f, "import (")
  1632  	fmt.Fprintln(f, "\t\"cmd/compile/internal/ssa\"")
  1633  	fmt.Fprintln(f, "\t\"cmd/compile/internal/ssagen\"")
  1634  	fmt.Fprintln(f, "\t\"cmd/internal/obj\"")
  1635  	fmt.Fprintln(f, "\t\"cmd/internal/obj/wasm\"")
  1636  	fmt.Fprintln(f, ")")
  1637  	fmt.Fprintln(f)
  1638  	fmt.Fprintln(f, "func ssaGenSIMDValue(s *ssagen.State, v *ssa.Value, extend bool) bool {")
  1639  	fmt.Fprintln(f, "\tswitch v.Op {")
  1640  
  1641  	const (
  1642  		NONE = iota
  1643  		IMM1_64
  1644  		IMM1_U
  1645  		IMM1_S
  1646  
  1647  		IMM2_32
  1648  		IMM2_64
  1649  
  1650  		// splat operations
  1651  		I32V
  1652  		I64V
  1653  		F32V
  1654  		F64V
  1655  
  1656  		V
  1657  		VV
  1658  		V32
  1659  		VVV
  1660  		OP
  1661  		DONE
  1662  	)
  1663  
  1664  	type classifiedOP struct {
  1665  		op    *wasmOp
  1666  		class int
  1667  	}
  1668  	var ssagenWasmOps []classifiedOP
  1669  
  1670  	// Classify mimics the decision tree for how to convert
  1671  	// SSA HW-specific op into ASM, returning small integers
  1672  	// that can be sorted so that like operations are all
  1673  	// grouped together.
  1674  	classify := func(op *wasmOp) int {
  1675  		if op.ImmRange() > 0 {
  1676  			switch op.ArgCount() {
  1677  			case 1:
  1678  				if op.T().ElemSize < 64 {
  1679  					switch op.T().Elem[0] {
  1680  					case 'u':
  1681  						return IMM1_U
  1682  					case 'i':
  1683  						return IMM1_S
  1684  					}
  1685  				}
  1686  				return IMM1_64
  1687  			case 2:
  1688  				if op.T().ElemSize < 64 && !op.T().Float { // empirically, float32 is 64 bits wide.
  1689  					return IMM2_32
  1690  				}
  1691  				return IMM2_64
  1692  			}
  1693  		} else {
  1694  			switch op.ArgCount() {
  1695  			case 1:
  1696  				if op.Flag(IsSplat) {
  1697  					switch op.ArgType() {
  1698  					case "int8", "int16", "int32":
  1699  						return I32V
  1700  					case "int64":
  1701  						return I64V
  1702  					case "float32":
  1703  						return F32V
  1704  					case "float64":
  1705  						return F64V
  1706  					default:
  1707  						panic(fmt.Errorf("op %s has unexpected splat arg type", op.String()))
  1708  					}
  1709  				}
  1710  				return V
  1711  			case 2:
  1712  				if c := op.ArgType()[0]; c == 'i' || c == 'u' {
  1713  					return V32
  1714  				} else {
  1715  					return VV
  1716  				}
  1717  			case 3:
  1718  				return VVV
  1719  			default:
  1720  				return OP
  1721  			}
  1722  		}
  1723  		panic(fmt.Errorf("op %s has class NONE", op.String()))
  1724  	}
  1725  
  1726  	done := make(map[string]bool)
  1727  
  1728  	for _, op := range wasmOps {
  1729  		if op.Flag(NonSigned | EmulatedRule) {
  1730  			// no hardware-specific op to generate asm from
  1731  			continue
  1732  		}
  1733  		if done[op.SsaWasmOp()] {
  1734  			continue
  1735  		}
  1736  		done[op.SsaWasmOp()] = true
  1737  		ssagenWasmOps = append(ssagenWasmOps, classifiedOP{op, classify(op)})
  1738  	}
  1739  
  1740  	slices.SortFunc(ssagenWasmOps, func(a, b classifiedOP) int {
  1741  		if c := a.class - b.class; c != 0 {
  1742  			return c
  1743  		}
  1744  		return compareWasmOps(a.op, b.op)
  1745  	})
  1746  
  1747  	ssagenWasmOps = append(ssagenWasmOps, classifiedOP{nil, DONE})
  1748  
  1749  	lastClass := NONE
  1750  	lastCR := 0
  1751  	for i, op := range ssagenWasmOps {
  1752  		if op.class != lastClass {
  1753  			lastCR = i
  1754  			// Print the appropriate action for ssagen
  1755  			switch lastClass {
  1756  			case NONE:
  1757  			case IMM1_64:
  1758  				fmt.Fprintf(f,
  1759  					`		getValue128(s, v.Args[0])
  1760  		p := s.Prog(v.Op.Asm())
  1761  		p.To = obj.Addr{Type: obj.TYPE_CONST, Offset: v.AuxInt}
  1762  `)
  1763  			case IMM1_U:
  1764  				fmt.Fprintf(f,
  1765  					`		getValue128(s, v.Args[0])
  1766  		p := s.Prog(v.Op.Asm())
  1767  		p.To = obj.Addr{Type: obj.TYPE_CONST, Offset: v.AuxInt}
  1768  `)
  1769  				fmt.Fprintf(f, "\tif extend {\n\t\ts.Prog(wasm.AI64ExtendI32U)\n\t}\n")
  1770  
  1771  			case IMM1_S:
  1772  				fmt.Fprintf(f,
  1773  					`		getValue128(s, v.Args[0])
  1774  		p := s.Prog(v.Op.Asm())
  1775  		p.To = obj.Addr{Type: obj.TYPE_CONST, Offset: v.AuxInt}
  1776  `)
  1777  				fmt.Fprintf(f, "\tif extend {\n\t\ts.Prog(wasm.AI64ExtendI32S)\n\t}\n")
  1778  
  1779  			case IMM2_32:
  1780  				fmt.Fprintf(f,
  1781  					`		getValue128(s, v.Args[0])
  1782  		getValue32(s, v.Args[1])
  1783  		p := s.Prog(v.Op.Asm())
  1784  		p.To = obj.Addr{Type: obj.TYPE_CONST, Offset: v.AuxInt}
  1785  `)
  1786  			case IMM2_64:
  1787  				fmt.Fprintf(f,
  1788  					`		getValue128(s, v.Args[0])
  1789  		getValue64(s, v.Args[1])
  1790  		p := s.Prog(v.Op.Asm())
  1791  		p.To = obj.Addr{Type: obj.TYPE_CONST, Offset: v.AuxInt}
  1792  `)
  1793  
  1794  			case I32V:
  1795  				fmt.Fprintf(f,
  1796  					`		getValue32(s, v.Args[0])
  1797  		s.Prog(v.Op.Asm())
  1798  `)
  1799  			case I64V:
  1800  				fmt.Fprintf(f,
  1801  					`		getValue64(s, v.Args[0])
  1802  		s.Prog(v.Op.Asm())
  1803  `)
  1804  			case F32V:
  1805  				fmt.Fprintf(f,
  1806  					`		getValueFxx(s, v.Args[0])
  1807  		s.Prog(v.Op.Asm())
  1808  `)
  1809  			case F64V:
  1810  				fmt.Fprintf(f,
  1811  					`		getValueFxx(s, v.Args[0])
  1812  		s.Prog(v.Op.Asm())
  1813  `)
  1814  
  1815  			case V:
  1816  				fmt.Fprintf(f,
  1817  					`		getValue128(s, v.Args[0])
  1818  		s.Prog(v.Op.Asm())
  1819  `)
  1820  			case VV:
  1821  				fmt.Fprintf(f,
  1822  					`		getValue128(s, v.Args[0])
  1823  		getValue128(s, v.Args[1])
  1824  		s.Prog(v.Op.Asm())
  1825  `)
  1826  			case V32:
  1827  				// shifts, 32-bit operand
  1828  				fmt.Fprintf(f,
  1829  					`		getValue128(s, v.Args[0])
  1830  		getValue32(s, v.Args[1])
  1831  		s.Prog(v.Op.Asm())
  1832  `)
  1833  			case VVV:
  1834  				fmt.Fprintf(f,
  1835  					`		getValue128(s, v.Args[0])
  1836  		getValue128(s, v.Args[1])
  1837  		getValue128(s, v.Args[2])
  1838  		s.Prog(v.Op.Asm())
  1839  `)
  1840  			case OP:
  1841  				fmt.Fprintf(f,
  1842  					`		s.Prog(v.Op.Asm())
  1843  `)
  1844  			}
  1845  			if op.class == DONE {
  1846  				fmt.Fprintln(f, `
  1847  	default:
  1848  		return false
  1849  	}
  1850  	return true
  1851  }`)
  1852  				return
  1853  			}
  1854  			// Otherwise begin the next case
  1855  			fmt.Fprint(f, "case ")
  1856  			lastCR = i - 1
  1857  			lastClass = op.class
  1858  		}
  1859  
  1860  		sep := ","
  1861  		if op.class != ssagenWasmOps[i+1].class {
  1862  			sep = ":"
  1863  		}
  1864  		fmt.Fprintf(f, "ssa.OpWasm%s%s", op.op.SsaWasmOp(), sep)
  1865  		if i >= lastCR+3 {
  1866  			fmt.Fprintln(f)
  1867  			lastCR = i
  1868  		}
  1869  	}
  1870  	return f
  1871  }
  1872  
  1873  // genGenerics creates SSA generic ops that are implied by WASM SIMD instructions
  1874  // that were not previously implied by AMD64 SIMD instructions.
  1875  // The expected target directory is cmd/compile/internal/ssa/_gen
  1876  func genGenerics() *bytes.Buffer {
  1877  	var newOps []sgutil.GenericOpsData
  1878  
  1879  	for _, op := range wasmOps {
  1880  		if op.SsaGenOp() == "" {
  1881  			continue
  1882  		}
  1883  		if !op.DefinesGeneric() {
  1884  			continue
  1885  		}
  1886  		newOp := sgutil.GenericOpsData{
  1887  			OpName:  op.SsaGenOp(),
  1888  			OpInLen: op.ArgCount(),
  1889  			Comm:    op.Flag(IsCommutative),
  1890  			HasAux:  op.ImmRange() > 0,
  1891  		}
  1892  		newOps = append(newOps, newOp)
  1893  	}
  1894  
  1895  	buf := sgutil.MergeSIMDGenericOps(newOps, *genGenOpsFile, "wasm")
  1896  
  1897  	return buf
  1898  }
  1899  
  1900  // genIntrinsics creates the function that registers all of the WASM SIMD intrinsics.
  1901  // The expected target directory is cmd/compile/internal/ssagen
  1902  func genIntrinsics() (f *bytes.Buffer) {
  1903  	f = new(bytes.Buffer)
  1904  
  1905  	fmt.Fprint(f, `// Code generated by 'wasmgen'; DO NOT EDIT.
  1906  
  1907  package ssagen
  1908  
  1909  import (
  1910  	"cmd/compile/internal/ir"
  1911  	"cmd/compile/internal/ssa"
  1912  	"cmd/compile/internal/types"
  1913  	"cmd/internal/sys"
  1914  )
  1915  
  1916  func initWasmSIMD() {
  1917  	makeSimdOp1 := func(op ssa.Op) func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value {
  1918  		return func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value {
  1919  			return s.newValue1(op, types.TypeVec128, args[0])
  1920  		}
  1921  	}
  1922  	makeSimdOp2 := func(op ssa.Op) func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value {
  1923  		return func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value {
  1924  			return s.newValue2(op, types.TypeVec128, args[0], args[1])
  1925  		}
  1926  	}
  1927  	makeSimdOp3 := func(op ssa.Op) func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value {
  1928  		return func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value {
  1929  			return s.newValue3(op, types.TypeVec128, args[0], args[1], args[2])
  1930  		}
  1931  	}
  1932  
  1933  	// "As" is a type pun, just return the bits
  1934  	makeAsOp := func() func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value {
  1935  		return func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value {
  1936  			return args[0]
  1937  		}
  1938  	}
  1939  
  1940  	// converting to a mask is an not-equals comparison with zero, zero obtained by x XOR x.
  1941  	makeToMask := func(op, xor ssa.Op) func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value {
  1942  		return func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value {
  1943  			return s.newValue2(op, types.TypeVec128, args[0], s.newValue2(xor, n.Type(), args[0], args[0]))
  1944  		}
  1945  	}
  1946  
  1947  	makeSimdOp1Imm8 := func(op ssa.Op, immLimit uint64) func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value {
  1948  		return func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value {
  1949  			t := n.Type()
  1950  			if args[1].Op == ssa.OpConst8 {
  1951  				return s.newValue1I(op, t, args[1].AuxInt, args[0])
  1952  			}
  1953  			return immJumpTableN(s, args[1], n, immLimit, func(sNew *state, idx int) {
  1954  				// Encode as int8 due to requirement of AuxInt, check its comment for details.
  1955  				s.vars[n] = sNew.newValue1I(op, t, int64(int8(idx)), args[0])
  1956  			})
  1957  		}
  1958  	}
  1959  
  1960  	makeSimdOp2Imm8 := func(op ssa.Op, immLimit uint64) func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value {
  1961  		return func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value {
  1962  			t := types.TypeVec128
  1963  			if args[1].Op == ssa.OpConst8 {
  1964  				return s.newValue2I(op, t, args[1].AuxInt, args[0], args[2])
  1965  			}
  1966  			return immJumpTableN(s, args[1], n, immLimit, func(sNew *state, idx int) {
  1967  				// Encode as int8 due to requirement of AuxInt, check its comment for details.
  1968  				s.vars[n] = sNew.newValue2I(op, t, int64(int8(idx)), args[0], args[2])
  1969  			})
  1970  		}
  1971  	}
  1972  
  1973  	addWasmSIMD := func(pkg, fn string, builder func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value) {
  1974  		intrinsics.add(sys.ArchWasm, pkg, fn, builder)
  1975  	}
  1976  
  1977  `)
  1978  
  1979  	for _, op := range wasmOps {
  1980  
  1981  		typeName := op.RcvrType()
  1982  		funcName := op.Method()
  1983  
  1984  		// We want <Type>.<Method>
  1985  		// The key in intrinsics map is pkg, name.
  1986  		// For methods, name is "Type.Method".
  1987  
  1988  		fullname := typeName + "." + funcName
  1989  
  1990  		g := op.SsaGenOp()
  1991  		if g == "" || op.Flag(IsSplat) {
  1992  			continue
  1993  		}
  1994  		genOp := "ssa.Op" + g
  1995  
  1996  		if op.ImmRange() > 0 {
  1997  			switch op.ArgCount() {
  1998  			case 1:
  1999  				fmt.Fprintf(f, "\taddWasmSIMD(\"%s\", \"%s\", makeSimdOp1Imm8(%s, %d))\n", pkg, fullname, genOp, op.ImmRange())
  2000  			case 2:
  2001  				fmt.Fprintf(f, "\taddWasmSIMD(\"%s\", \"%s\", makeSimdOp2Imm8(%s, %d))\n", pkg, fullname, genOp, op.ImmRange())
  2002  			default:
  2003  				panic("unexpected wasm simd intrinsic " + op.String())
  2004  			}
  2005  		} else {
  2006  			switch op.ArgCount() {
  2007  			case 1:
  2008  				fmt.Fprintf(f, "\taddWasmSIMD(\"%s\", \"%s\", makeSimdOp1(%s))\n", pkg, fullname, genOp)
  2009  			case 2:
  2010  				fmt.Fprintf(f, "\taddWasmSIMD(\"%s\", \"%s\", makeSimdOp2(%s))\n", pkg, fullname, genOp)
  2011  			case 3:
  2012  				fmt.Fprintf(f, "\taddWasmSIMD(\"%s\", \"%s\",makeSimdOp3(%s))\n", pkg, fullname, genOp)
  2013  			default:
  2014  				panic("unexpected wasm simd intrinsic " + op.String())
  2015  			}
  2016  		}
  2017  
  2018  	}
  2019  
  2020  	for _, t := range signed {
  2021  		// Conversions to/from mask types
  2022  		// func (x Int8x16) ToMask() Mask8x16 -> x.Ne(x xor x)
  2023  		// func (x Mask8x16) ToInt8x16() Int8x16 -> AsInt8x16 (just a pun, masks are negative)
  2024  		fmt.Fprintf(f, "\taddWasmSIMD(\"%s\", \"%s\", makeToMask(%s, %s))\n", pkg, t.Name+".ToMask", "ssa.Op"+t.Methods["ne"].SsaGenOp(), "ssa.Op"+t.Methods["xor"].SsaGenOp())
  2025  		fmt.Fprintf(f, "\taddWasmSIMD(\"%s\", \"%s\", makeAsOp())\n", pkg, t.MaskFor().Name+".To"+t.Name)
  2026  	}
  2027  
  2028  	// load and store intrinsics
  2029  	for _, t := range allTypes {
  2030  		u := t
  2031  		if t.Unsigned {
  2032  			u = t.IntShaped
  2033  		}
  2034  		fmt.Fprintf(f, "\taddWasmSIMD(\"%s\", \"%s\", simdLoad())\n", pkg, "Load"+t.Name+"Array")
  2035  		fmt.Fprintf(f, "\taddWasmSIMD(\"%s\", \"%s\", simdBroadcast(ssa.OpBroadcast%s))\n", pkg, "Broadcast"+t.Name, u.Name)
  2036  		fmt.Fprintf(f, "\taddWasmSIMD(\"%s\", \"%s\", simdStore())\n", pkg, t.Name+".StoreArray")
  2037  	}
  2038  
  2039  	// As conversions
  2040  	forAllAsConversions(func(from, to *simdType) {
  2041  		fullname := from.Name + ".As" + to.Name
  2042  		fmt.Fprintf(f, "\taddWasmSIMD(\"%s\", \"%s\",makeAsOp())\n", pkg, fullname)
  2043  	})
  2044  
  2045  	var WasmTypeDotMethodIntrinsic = templateOf("wasm bit pun intrinsic", `addWasmSIMD("`+pkg+`", "{{.TypeDotMethod}}", makeAsOp())
  2046  	`)
  2047  
  2048  	// Factored bitwise reinterpretation methods
  2049  	// these produces a much smaller API
  2050  	fromUnsignedToFloats(func(from, to *simdType) {
  2051  		sgutil.Conversion(from, to).ExecuteIntrinsicTemplateOfTypeDotMethod(f, WasmTypeDotMethodIntrinsic)
  2052  	})
  2053  
  2054  	fromUnsignedToInts(func(from, to *simdType) {
  2055  		sgutil.Conversion(from, to).ExecuteIntrinsicTemplateOfTypeDotMethod(f, WasmTypeDotMethodIntrinsic)
  2056  	})
  2057  
  2058  	forAllReshape(func(from, to *simdType) {
  2059  		sgutil.Conversion(from, to).ExecuteIntrinsicTemplateOfTypeDotMethod(f, WasmTypeDotMethodIntrinsic)
  2060  	})
  2061  
  2062  	fmt.Fprintln(f, "}")
  2063  	return
  2064  }
  2065  

View as plain text