// Copyright 2026 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package sgutil provides shared utilities for SIMD file // generation across architectures. This includes // // - "natural" comparison for better ordering // - formatted-Go file saving // - file merging for simdgenericOps.go // - naming conventions and templates for the // bitwise vector reinterpretation no-op methods. package sgutil import ( "bufio" "bytes" "fmt" "os" "regexp" "slices" "sort" "strconv" "strings" "text/template" ) const simdGenericOpsTmpl = ` package main func simdGenericOps() []opData { return []opData{ {{- range .Ops }} {name: "{{.OpName}}", argLength: {{.OpInLen}}{{if .Comm}}, commutative: true{{end}}}, // ARCH:{{.ArchTag}} {{- end }} {{- range .OpsImm }} {name: "{{.OpName}}", argLength: {{.OpInLen}}{{if .Comm}}, commutative: true{{end}}, aux: "UInt8"}, // ARCH:{{.ArchTag}} {{- end }} } } ` // TemplateNamedreturns a parsed template from temp, named name. func TemplateNamed(name, temp string) *template.Template { t, err := template.New(name).Parse(temp) if err != nil { panic(fmt.Errorf("failed to parse template %s: %w", name, err)) } return t } // simdGenericOpsHeader is the arch-agnostic header for simdgenericOps.go. const simdGenericOpsHeader = "// Code generated by 'simdgen' (merged automatically); DO NOT EDIT.\n" // GenericOpsData holds one generic op entry for template rendering. type GenericOpsData struct { OpName string OpInLen int Comm bool HasAux bool Archs []string // e.g. ["amd64","arm64"] } // ArchTag returns the comma-separated arch list for the template comment. func (d GenericOpsData) ArchTag() string { return strings.Join(d.Archs, ",") } // Match lines like: // // {name: "GetElemFloat32x4", argLength: 2, aux: "UInt8"}, // ARCH:amd64,arm64 // {name: "MulInt8x16", argLength: 2, commutative: true}, // ARCH:arm64 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+))?`) // parseOps reads an existing simdgenericOps.go and extracts op entries along with // their ARCH: tags. It strips currentArch from all parsed entries and drops those // with no remaining tags (ops that belonged only to the current arch). func parseOps(oldFile, currentArch string) ([]GenericOpsData, error) { f, ferr := os.Open(oldFile) if ferr != nil { if os.IsNotExist(ferr) { return nil, nil } return nil, ferr } defer f.Close() untagged := 0 unmatched := 0 noarch := 0 var result []GenericOpsData scanner := bufio.NewScanner(f) for scanner.Scan() { line := scanner.Text() matches := reEntry.FindStringSubmatch(line) if matches == nil { unmatched++ continue } name := matches[1] argLen, _ := strconv.Atoi(matches[2]) comm := matches[3] == "true" // matches[3] may be empty if commutative is omitted hasAux := strings.Contains(line, `aux: "UInt8"`) archTag := matches[4] if archTag == "" { untagged++ // Skip untagged entries (shouldn't occur after initial run). continue } archs := slices.DeleteFunc(strings.Split(archTag, ","), func(a string) bool { return a == currentArch }) if len(archs) == 0 { noarch++ continue } result = append(result, GenericOpsData{ OpName: name, OpInLen: argLen, Comm: comm, HasAux: hasAux, Archs: archs, }) } if err := scanner.Err(); err != nil { return nil, err } return result, nil } // mergeOps merges existing and new op slices using a merge-sort walk. // Both inputs must be sorted by OpName (natural sort); the output is verified // to be sorted. // // existing must contain only tags from other architectures. // new entries must be tagged with currentArch. // // Overlapping names have their arch tags merged and properties verified for // consistency (argLength, commutativity, HasAux). func mergeOps(currentArch string, existing, new []GenericOpsData) ([]GenericOpsData, error) { var ( result []GenericOpsData i, j int ) for i < len(existing) || j < len(new) { var cmp int if i < len(existing) && j < len(new) { cmp = CompareNatural(existing[i].OpName, new[j].OpName) } else if i < len(existing) { cmp = -1 } else { cmp = 1 } var entry GenericOpsData switch cmp { case -1: entry = existing[i] i++ case 1: entry = new[j] j++ case 0: e, n := existing[i], new[j] if e.OpInLen != n.OpInLen { return nil, fmt.Errorf("simdgen: op %q has inconsistent argLength: existing=%d, new=%d", e.OpName, e.OpInLen, n.OpInLen) } if e.Comm != n.Comm { return nil, fmt.Errorf("simdgen: op %q has inconsistent commutativity: existing=%v, new=%v", e.OpName, e.Comm, n.Comm) } if e.HasAux != n.HasAux { return nil, fmt.Errorf("simdgen: op %q has inconsistent HasAux: existing=%v, new=%v", e.OpName, e.HasAux, n.HasAux) } entry = e entry.Archs = append(entry.Archs, currentArch) sort.Strings(entry.Archs) i++ j++ } if len(result) > 0 && CompareNatural(result[len(result)-1].OpName, entry.OpName) >= 0 { return nil, fmt.Errorf("simdgen: mergeOps: sort invariant violated between %q and %q", result[len(result)-1].OpName, entry.OpName) } result = append(result, entry) } return result, nil } // MergeSIMDGenericOps merges a new set of generic ops with the existing ones read // from goroot "/" file. func MergeSIMDGenericOps(newOps []GenericOpsData, oldFile, currentArch string) *bytes.Buffer { thisArch := []string{currentArch} for i := range newOps { newOps[i].Archs = thisArch } sortNatural := func(s []GenericOpsData) []GenericOpsData { sort.Slice(s, func(i, j int) bool { return CompareNatural(s[i].OpName, s[j].OpName) < 0 }) return s } existing, err := parseOps(oldFile, currentArch) if err != nil { panic(fmt.Errorf("failed to parse existing %s: %w", oldFile, err)) } merged, err := mergeOps(currentArch, sortNatural(existing), sortNatural(newOps)) if err != nil { panic(err) } // Split into ops/opsImm. type opData struct { Ops []GenericOpsData OpsImm []GenericOpsData } var opsData opData for _, gOp := range merged { if gOp.HasAux { opsData.OpsImm = append(opsData.OpsImm, gOp) } else { opsData.Ops = append(opsData.Ops, gOp) } } t := TemplateNamed("simdgenericOps", simdGenericOpsTmpl) buffer := new(bytes.Buffer) buffer.WriteString(simdGenericOpsHeader) err = t.Execute(buffer, opsData) if err != nil { panic(fmt.Errorf("failed to execute template: %w", err)) } return buffer }