| // 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 specgen | |
| import ( | |
| "bytes" | |
| "cmp" | |
| "fmt" | |
| "reflect" | |
| "regexp" | |
| "simd/archsimd/_gen/specgen/specexpr" | |
| "slices" | |
| "strings" | |
| "text/template" | |
| "unicode/utf8" | |
| ) | |
| // specTemplate wraps a parsed text/template.Template, or a plain string if no | |
| // template actions were present. | |
| type specTemplate struct { | |
| raw string | |
| tmpl *template.Template | |
| } | |
| var templateFuncs = template.FuncMap{ | |
| "title": func(v any) string { | |
| s := fmt.Sprint(v) | |
| if len(s) == 0 { | |
| return "" | |
| } | |
| return strings.ToTitle(s[:1]) + s[1:] | |
| }, | |
| "lt": func(a, b any) (bool, error) { | |
| res, ok := compare(a, b) | |
| if !ok { | |
| return false, fmt.Errorf("incomparable types: %T and %T", a, b) | |
| } | |
| return res < 0, nil | |
| }, | |
| "le": func(a, b any) (bool, error) { | |
| res, ok := compare(a, b) | |
| if !ok { | |
| return false, fmt.Errorf("incomparable types: %T and %T", a, b) | |
| } | |
| return res <= 0, nil | |
| }, | |
| "gt": func(a, b any) (bool, error) { | |
| res, ok := compare(a, b) | |
| if !ok { | |
| return false, fmt.Errorf("incomparable types: %T and %T", a, b) | |
| } | |
| return res > 0, nil | |
| }, | |
| "ge": func(a, b any) (bool, error) { | |
| res, ok := compare(a, b) | |
| if !ok { | |
| return false, fmt.Errorf("incomparable types: %T and %T", a, b) | |
| } | |
| return res >= 0, nil | |
| }, | |
| "eq": func(a, b any) bool { | |
| if res, ok := compare(a, b); ok { | |
| return res == 0 | |
| } | |
| return reflect.DeepEqual(a, b) | |
| }, | |
| "ne": func(a, b any) bool { | |
| if res, ok := compare(a, b); ok { | |
| return res != 0 | |
| } | |
| return !reflect.DeepEqual(a, b) | |
| }, | |
| // lanes returns shape.Lanes() if shape is non-scalable; or else "name.Len()" | |
| "lanes": func(shape, name any) (string, error) { | |
| s, ok := shape.(specexpr.Vector) | |
| if !ok { | |
| return "", fmt.Errorf("want Vector, got %T", shape) | |
| } | |
| if !s.Scalable() { | |
| return s.Elems().String(), nil | |
| } | |
| return name.(string) + ".Len()", nil | |
| }, | |
| "reshapeDiagram": func(x, z any) (string, error) { | |
| xx, ok := x.(specexpr.Vector) | |
| if !ok { | |
| return "", fmt.Errorf("want Vector, got %T", x) | |
| } | |
| zz, ok := z.(specexpr.Vector) | |
| if !ok { | |
| return "", fmt.Errorf("want Vector, got %T", z) | |
| } | |
| if xx.Width != zz.Width { | |
| return "", fmt.Errorf("Vectors must have equal widths, got %s and %s", xx, zz) | |
| } | |
| return reshapeDiagram(xx, zz), nil | |
| }, | |
| } | |
| func toSpecNum(v any) (specexpr.Num, bool) { | |
| switch v := v.(type) { | |
| case specexpr.Num: | |
| return v, true | |
| case int: | |
| return specexpr.Int(v), true | |
| case int64: | |
| return specexpr.Int(v), true | |
| default: | |
| return nil, false | |
| } | |
| } | |
| func compare(a, b any) (int, bool) { | |
| if na, okA := toSpecNum(a); okA { | |
| if nb, okB := toSpecNum(b); okB { | |
| return na.Compare(nb) | |
| } | |
| } | |
| if sa, okA := a.(string); okA { | |
| if sb, okB := b.(string); okB { | |
| return cmp.Compare(sa, sb), true | |
| } | |
| } | |
| return 0, false | |
| } | |
| // reshapeDiagram returns a diagram showing how vector elements are | |
| // reinterpreted by ReshapeToUints. For example, for Uint32x8 -> Uint64x4, it | |
| // returns: | |
| // | |
| // x[7] x[6] ⋯ x[1] x[0] | |
| // | 31 .. 0 | 31 .. 0 | ⋯ | 31 .. 0 | 31 .. 0 | | |
| // | 63 .... 0 | ⋯ | 63 .... 0 | | |
| // z[3] ⋯ z[0] | |
| func reshapeDiagram(x, z specexpr.Vector) string { | |
| scalable := x.Scalable() || z.Scalable() | |
| if !scalable { | |
| xW, okXW := x.Width.(specexpr.Int) | |
| zW, okZW := z.Width.(specexpr.Int) | |
| if !okXW || !okZW || xW != zW { | |
| return "" | |
| } | |
| } | |
| // Order x and z by which is smaller and larger | |
| s, l := x, z | |
| sName, lName := "x", "z" | |
| if s.Elem.Bits > l.Elem.Bits { | |
| s, l = l, s | |
| sName, lName = lName, sName | |
| } | |
| R := int(l.Elem.Bits / s.Elem.Bits) | |
| // Table formatting helpers | |
| type table []string | |
| // vstack combines two tables vertically. | |
| vstack := func(tables ...table) table { | |
| return slices.Concat(tables...) | |
| } | |
| // hstack combines two tables horizontally. | |
| hstack := func(tables ...table) table { | |
| out := make(table, len(tables[0])) | |
| for row := range out { | |
| for _, table := range tables { | |
| out[row] += table[row] | |
| } | |
| } | |
| return out | |
| } | |
| repeat4 := func(x string) table { return table{x, x, x, x} } | |
| // format returns left+pad+center+pad+right, such that the length is at least width. | |
| format := func(left, center, right string, width int) string { | |
| pad := max(2, width-len(left)-len(center)-len(right)-2) | |
| // The (pad+1)/2 is because pad == pad/2 + (pad+1)/2 with truncating division. | |
| return " " + left + strings.Repeat(" ", (pad+1)/2) + center + strings.Repeat(" ", pad/2) + right + " " | |
| } | |
| // formatSmallCells formats the labels and boxes for small vector indexes [from, to]. | |
| sBox := format(fmt.Sprintf("%d", s.Elem.Bits-1), "..", "0", 9) | |
| formatSmallCells := func(from, to int) table { | |
| var boxes, labels string | |
| for sIdx := from; sIdx >= to; sIdx-- { | |
| labels += format("", fmt.Sprintf("%s[%d]", sName, sIdx), "", len(sBox)) + " " | |
| boxes += sBox + "|" | |
| } | |
| return table{labels, boxes} | |
| } | |
| // formatCol constructs a full table column with small and large cells for a | |
| // single large vector index. | |
| formatCol := func(largeIdx int) table { | |
| high := (largeIdx+1)*R - 1 | |
| low := largeIdx * R | |
| // Format the small cells that fan-out to this large cell, This gives us | |
| // metrics for formatting the large cell. | |
| var small table | |
| if R <= 4 { | |
| // There's enough room to show all of the small elements that form | |
| // one large element. | |
| small = formatSmallCells(high, low) | |
| } else { | |
| // The fan-out is too high. Show the left and right 2 small cells | |
| // and elide the middle. | |
| small = hstack(formatSmallCells(high, high-1), table{" ⋯ ", " ⋯ |"}, formatSmallCells(low+1, low)) | |
| } | |
| // Format the large cell. | |
| width := utf8.RuneCountInString(small[0]) | |
| large := table{ | |
| format(fmt.Sprintf("%d", l.Elem.Bits-1), "....", "0", width-1) + "|", | |
| format("", fmt.Sprintf("%s[%d]", lName, largeIdx), "", width-1) + " "} | |
| // Stack small and large cells | |
| return vstack(small, large) | |
| } | |
| // Format columns for exactly two large vector elements. | |
| var left, right table | |
| cellSeps := table{" ", "|", "|", " "} | |
| prefix := cellSeps | |
| colSep := table{"", "", "", ""} | |
| if scalable { | |
| left, right = formatCol(1), formatCol(0) | |
| // For scalable vectors, we format large elements 0 and 1 and then show | |
| // it goes on forever to the left. | |
| prefix = hstack(repeat4("⋯ "), prefix) | |
| } else { | |
| leftIndex := int(l.Elems().(specexpr.Int)) - 1 | |
| left, right = formatCol(leftIndex), formatCol(0) | |
| if leftIndex > 1 { | |
| // We're only showing the top and bottom large element, so show that | |
| // we're eliding the middle elements. | |
| colSep = hstack(repeat4(" ⋯ "), cellSeps) | |
| } | |
| } | |
| // Put it all together | |
| tab := hstack(prefix, left, colSep, right) | |
| // We built the smaller elements on top, so flip the table if they should be | |
| // on bottom. | |
| if x.Elem.Bits > z.Elem.Bits { | |
| slices.Reverse(tab) | |
| } | |
| // Finally, emit | |
| for i := range tab { | |
| tab[i] = strings.TrimRight(tab[i], " ") | |
| } | |
| return "\t " + strings.Join(tab, "\n\t ") | |
| } | |
| // newSpecTemplate parses a spec template string. If tmpl does not contain "{{", | |
| // it is treated as a raw string literal without template overhead. | |
| func newSpecTemplate(tmpl string) (specTemplate, error) { | |
| if !strings.Contains(tmpl, "{{") { | |
| return specTemplate{raw: tmpl, tmpl: nil}, nil | |
| } | |
| t, err := template.New("").Option("missingkey=error").Funcs(templateFuncs).Parse(tmpl) | |
| if err != nil { | |
| return specTemplate{}, err | |
| } | |
| return specTemplate{raw: tmpl, tmpl: t}, nil | |
| } | |
| func (s *specTemplate) expand(data any) (string, error) { | |
| if s.tmpl == nil { | |
| return s.raw, nil | |
| } | |
| var buf bytes.Buffer | |
| if err := s.tmpl.Execute(&buf, data); err != nil { | |
| return "", err | |
| } | |
| return buf.String(), nil | |
| } | |
| var reMultipleNewlines = regexp.MustCompile(`\n{3,}`) | |
| func cleanDocNewlines(s string) string { | |
| s = reMultipleNewlines.ReplaceAllString(s, "\n\n") | |
| trimmed := strings.TrimRight(s, " \t\n") | |
| if trimmed == "" { | |
| return "" | |
| } | |
| return trimmed + "\n" | |
| } | |
| // expandNameAndDoc instantiates the API function name and doc comment for sFn | |
| // using the solved variable bindings in b. | |
| func (sFn *specFunc) expandNameAndDoc(ctx context, b *specexpr.Bindings) (name, doc string) { | |
| data := make(map[string]any) | |
| for v, val := range b.All() { | |
| data[string(v)] = val | |
| if s, ok := strings.CutPrefix(string(v), "$"); ok { | |
| data[s] = val | |
| } | |
| } | |
| var err error | |
| name, err = sFn.NameTmpl.expand(data) | |
| if err != nil { | |
| ctx.errorf("expanding function name template: %s", err) | |
| name = sFn.Name | |
| } | |
| data["Name"] = name | |
| doc, err = sFn.Doc.expand(data) | |
| if err != nil { | |
| ctx.errorf("expanding doc template: %s", err) | |
| doc = sFn.Doc.raw | |
| } | |
| doc = cleanDocNewlines(doc) | |
| if name != sFn.Name { | |
| doc = regexp.MustCompile(`\b`+regexp.QuoteMeta(sFn.Name)+`\b`).ReplaceAllLiteralString(doc, name) | |
| } | |
| return name, doc | |
| } |