Source file src/simd/archsimd/_gen/sgutil/merge_generic_ops.go

     1  // Copyright 2026 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package sgutil provides shared utilities for SIMD file
     6  // generation across architectures.  This includes
     7  //
     8  // - "natural" comparison for better ordering
     9  // - formatted-Go file saving
    10  // - file merging for simdgenericOps.go
    11  // - naming conventions and templates for the
    12  //   bitwise vector reinterpretation no-op methods.
    13  
    14  package sgutil
    15  
    16  import (
    17  	"bufio"
    18  	"bytes"
    19  	"fmt"
    20  	"os"
    21  	"regexp"
    22  	"slices"
    23  	"sort"
    24  	"strconv"
    25  	"strings"
    26  	"text/template"
    27  )
    28  
    29  const simdGenericOpsTmpl = `
    30  package main
    31  
    32  func simdGenericOps() []opData {
    33  	return []opData{
    34  {{- range .Ops }}
    35  		{name: "{{.OpName}}", argLength: {{.OpInLen}}{{if .Comm}}, commutative: true{{end}}}, // ARCH:{{.ArchTag}}
    36  {{- end }}
    37  {{- range .OpsImm }}
    38  		{name: "{{.OpName}}", argLength: {{.OpInLen}}{{if .Comm}}, commutative: true{{end}}, aux: "UInt8"}, // ARCH:{{.ArchTag}}
    39  {{- end }}
    40  	}
    41  }
    42  `
    43  
    44  // TemplateNamedreturns a parsed template from temp, named name.
    45  func TemplateNamed(name, temp string) *template.Template {
    46  	t, err := template.New(name).Parse(temp)
    47  	if err != nil {
    48  		panic(fmt.Errorf("failed to parse template %s: %w", name, err))
    49  	}
    50  	return t
    51  }
    52  
    53  // simdGenericOpsHeader is the arch-agnostic header for simdgenericOps.go.
    54  const simdGenericOpsHeader = "// Code generated by 'simdgen' (merged automatically); DO NOT EDIT.\n"
    55  
    56  // GenericOpsData holds one generic op entry for template rendering.
    57  type GenericOpsData struct {
    58  	OpName  string
    59  	OpInLen int
    60  	Comm    bool
    61  	HasAux  bool
    62  	Archs   []string // e.g. ["amd64","arm64"]
    63  }
    64  
    65  // ArchTag returns the comma-separated arch list for the template comment.
    66  func (d GenericOpsData) ArchTag() string {
    67  	return strings.Join(d.Archs, ",")
    68  }
    69  
    70  // Match lines like:
    71  //
    72  //	{name: "GetElemFloat32x4", argLength: 2, aux: "UInt8"}, // ARCH:amd64,arm64
    73  //	{name: "MulInt8x16", argLength: 2, commutative: true}, // ARCH:arm64
    74  var reEntry = regexp.MustCompile(`^\s*\{name:\s*"([^"]+)",\s*argLength:\s*(\d+)(?:,\s*commutative:\s*(true|false))?(?:,\s*aux:\s*"[^"]+")?\s*\}\s*,\s*(?://\s*ARCH:(\S+))?`)
    75  
    76  // parseOps reads an existing simdgenericOps.go and extracts op entries along with
    77  // their ARCH: tags. It strips currentArch from all parsed entries and drops those
    78  // with no remaining tags (ops that belonged only to the current arch).
    79  func parseOps(oldFile, currentArch string) ([]GenericOpsData, error) {
    80  	f, ferr := os.Open(oldFile)
    81  	if ferr != nil {
    82  		if os.IsNotExist(ferr) {
    83  			return nil, nil
    84  		}
    85  		return nil, ferr
    86  	}
    87  	defer f.Close()
    88  
    89  	untagged := 0
    90  	unmatched := 0
    91  	noarch := 0
    92  
    93  	var result []GenericOpsData
    94  	scanner := bufio.NewScanner(f)
    95  	for scanner.Scan() {
    96  		line := scanner.Text()
    97  		matches := reEntry.FindStringSubmatch(line)
    98  		if matches == nil {
    99  			unmatched++
   100  			continue
   101  		}
   102  		name := matches[1]
   103  		argLen, _ := strconv.Atoi(matches[2])
   104  		comm := matches[3] == "true" // matches[3] may be empty if commutative is omitted
   105  		hasAux := strings.Contains(line, `aux: "UInt8"`)
   106  		archTag := matches[4]
   107  
   108  		if archTag == "" {
   109  			untagged++
   110  			// Skip untagged entries (shouldn't occur after initial run).
   111  			continue
   112  		}
   113  
   114  		archs := slices.DeleteFunc(strings.Split(archTag, ","), func(a string) bool { return a == currentArch })
   115  		if len(archs) == 0 {
   116  			noarch++
   117  			continue
   118  		}
   119  
   120  		result = append(result, GenericOpsData{
   121  			OpName:  name,
   122  			OpInLen: argLen,
   123  			Comm:    comm,
   124  			HasAux:  hasAux,
   125  			Archs:   archs,
   126  		})
   127  	}
   128  	if err := scanner.Err(); err != nil {
   129  		return nil, err
   130  	}
   131  
   132  	return result, nil
   133  }
   134  
   135  // mergeOps merges existing and new op slices using a merge-sort walk.
   136  // Both inputs must be sorted by OpName (natural sort); the output is verified
   137  // to be sorted.
   138  //
   139  // existing must contain only tags from other architectures.
   140  // new entries must be tagged with currentArch.
   141  //
   142  // Overlapping names have their arch tags merged and properties verified for
   143  // consistency (argLength, commutativity, HasAux).
   144  func mergeOps(currentArch string, existing, new []GenericOpsData) ([]GenericOpsData, error) {
   145  	var (
   146  		result []GenericOpsData
   147  		i, j   int
   148  	)
   149  	for i < len(existing) || j < len(new) {
   150  		var cmp int
   151  		if i < len(existing) && j < len(new) {
   152  			cmp = CompareNatural(existing[i].OpName, new[j].OpName)
   153  		} else if i < len(existing) {
   154  			cmp = -1
   155  		} else {
   156  			cmp = 1
   157  		}
   158  
   159  		var entry GenericOpsData
   160  		switch cmp {
   161  		case -1:
   162  			entry = existing[i]
   163  			i++
   164  
   165  		case 1:
   166  			entry = new[j]
   167  			j++
   168  
   169  		case 0:
   170  			e, n := existing[i], new[j]
   171  			if e.OpInLen != n.OpInLen {
   172  				return nil, fmt.Errorf("simdgen: op %q has inconsistent argLength: existing=%d, new=%d", e.OpName, e.OpInLen, n.OpInLen)
   173  			}
   174  			if e.Comm != n.Comm {
   175  				return nil, fmt.Errorf("simdgen: op %q has inconsistent commutativity: existing=%v, new=%v", e.OpName, e.Comm, n.Comm)
   176  			}
   177  			if e.HasAux != n.HasAux {
   178  				return nil, fmt.Errorf("simdgen: op %q has inconsistent HasAux: existing=%v, new=%v", e.OpName, e.HasAux, n.HasAux)
   179  			}
   180  			entry = e
   181  			entry.Archs = append(entry.Archs, currentArch)
   182  			sort.Strings(entry.Archs)
   183  			i++
   184  			j++
   185  		}
   186  		if len(result) > 0 && CompareNatural(result[len(result)-1].OpName, entry.OpName) >= 0 {
   187  			return nil, fmt.Errorf("simdgen: mergeOps: sort invariant violated between %q and  %q",
   188  				result[len(result)-1].OpName, entry.OpName)
   189  		}
   190  		result = append(result, entry)
   191  	}
   192  
   193  	return result, nil
   194  }
   195  
   196  // MergeSIMDGenericOps merges a new set of generic ops with the existing ones read
   197  // from goroot "/" file.
   198  func MergeSIMDGenericOps(newOps []GenericOpsData, oldFile, currentArch string) *bytes.Buffer {
   199  	thisArch := []string{currentArch}
   200  	for i := range newOps {
   201  		newOps[i].Archs = thisArch
   202  	}
   203  
   204  	sortNatural := func(s []GenericOpsData) []GenericOpsData {
   205  		sort.Slice(s, func(i, j int) bool {
   206  			return CompareNatural(s[i].OpName, s[j].OpName) < 0
   207  		})
   208  		return s
   209  	}
   210  
   211  	existing, err := parseOps(oldFile, currentArch)
   212  	if err != nil {
   213  		panic(fmt.Errorf("failed to parse existing %s: %w", oldFile, err))
   214  	}
   215  
   216  	merged, err := mergeOps(currentArch, sortNatural(existing), sortNatural(newOps))
   217  	if err != nil {
   218  		panic(err)
   219  	}
   220  
   221  	// Split into ops/opsImm.
   222  	type opData struct {
   223  		Ops    []GenericOpsData
   224  		OpsImm []GenericOpsData
   225  	}
   226  	var opsData opData
   227  	for _, gOp := range merged {
   228  		if gOp.HasAux {
   229  			opsData.OpsImm = append(opsData.OpsImm, gOp)
   230  		} else {
   231  			opsData.Ops = append(opsData.Ops, gOp)
   232  		}
   233  	}
   234  
   235  	t := TemplateNamed("simdgenericOps", simdGenericOpsTmpl)
   236  	buffer := new(bytes.Buffer)
   237  	buffer.WriteString(simdGenericOpsHeader)
   238  
   239  	err = t.Execute(buffer, opsData)
   240  	if err != nil {
   241  		panic(fmt.Errorf("failed to execute template: %w", err))
   242  	}
   243  
   244  	return buffer
   245  }
   246  

View as plain text