| // 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 ( |
| "simd/archsimd/_gen/specgen/specexpr" |
| "strings" |
| "testing" |
| ) |
| |
| func TestNewSpecTemplate(t *testing.T) { |
| tests := []struct { |
| tmpl string |
| isTmpl bool |
| wantErr bool |
| }{ |
| { |
| tmpl: "", |
| isTmpl: false, |
| }, |
| { |
| tmpl: "Convert", |
| isTmpl: false, |
| }, |
| { |
| tmpl: "Convert{{.zL}}To{{.zB}}{{.zN}}", |
| isTmpl: true, |
| }, |
| { |
| tmpl: "Convert{{.zL", |
| wantErr: true, |
| }, |
| { |
| tmpl: "Convert{{if}}", |
| wantErr: true, |
| }, |
| } |
| |
| for _, tc := range tests { |
| got, err := newSpecTemplate(tc.tmpl) |
| if (err != nil) != tc.wantErr { |
| t.Errorf("newSpecTemplate(%q) returned error: %v, wantErr: %v", tc.tmpl, err, tc.wantErr) |
| continue |
| } |
| if tc.wantErr { |
| continue |
| } |
| if got.raw != tc.tmpl { |
| t.Errorf("newSpecTemplate(%q) raw = %q, want %q", tc.tmpl, got.raw, tc.tmpl) |
| } |
| if (got.tmpl != nil) != tc.isTmpl { |
| t.Errorf("newSpecTemplate(%q) isTmpl = %v, want %v", tc.tmpl, got.tmpl != nil, tc.isTmpl) |
| } |
| } |
| } |
| |
| func TestSpecTemplateExpand(t *testing.T) { |
| tmpl, err := newSpecTemplate("Convert{{.zL}}To{{.zB}}{{.zN}}") |
| if err != nil { |
| t.Fatalf("unexpected error parsing template: %v", err) |
| } |
| |
| data := map[string]any{ |
| "zL": specexpr.Int(4), |
| "zB": "Float", |
| "zN": specexpr.Int(32), |
| } |
| |
| got, err := tmpl.expand(data) |
| if err != nil { |
| t.Fatalf("unexpected error expanding template: %v", err) |
| } |
| want := "Convert4ToFloat32" |
| if got != want { |
| t.Errorf("expected expanded string %q, got %q", want, got) |
| } |
| } |
| |
| func TestSpecTemplateTitle(t *testing.T) { |
| tmpl, err := newSpecTemplate("ConvertTo{{.zE | title}}") |
| if err != nil { |
| t.Fatalf("unexpected error parsing template: %v", err) |
| } |
| |
| data := map[string]any{ |
| "zE": specexpr.Basic{Base: "float", Bits: 32}, |
| } |
| |
| got, err := tmpl.expand(data) |
| if err != nil { |
| t.Fatalf("unexpected error expanding template: %v", err) |
| } |
| want := "ConvertToFloat32" |
| if got != want { |
| t.Errorf("expected %q, got %q", want, got) |
| } |
| } |
| |
| func TestSpecTemplateConditionals(t *testing.T) { |
| tmplStr := `Doc for {{.Name}}. |
| {{if lt .zL .xL}} |
| Upper elements of the result are zeroed. |
| {{end}}` |
| |
| tmpl, err := newSpecTemplate(tmplStr) |
| if err != nil { |
| t.Fatalf("unexpected error parsing template: %v", err) |
| } |
| |
| // Case 1: zL < xL |
| data1 := map[string]any{ |
| "Name": "ConvertLo4ToFloat32", |
| "zL": specexpr.Int(4), |
| "xL": specexpr.Int(8), |
| } |
| got1, err := tmpl.expand(data1) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| if !strings.Contains(got1, "Upper elements of the result are zeroed.") { |
| t.Errorf("expected conditional text when zL < xL, got: %q", got1) |
| } |
| |
| // Case 2: zL >= xL |
| data2 := map[string]any{ |
| "Name": "ConvertLo8ToFloat32", |
| "zL": specexpr.Int(8), |
| "xL": specexpr.Int(8), |
| } |
| got2, err := tmpl.expand(data2) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| if strings.Contains(got2, "Upper elements of the result are zeroed.") { |
| t.Errorf("expected no conditional text when zL == xL, got: %q", got2) |
| } |
| } |
| |
| func TestSpecTemplateScalableWidthCompare(t *testing.T) { |
| tmpl, err := newSpecTemplate("{{if lt .zL .xL}}smaller{{else}}same-or-larger{{end}}") |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| |
| // zL = VW/32, xL = VW/16 => zL < xL |
| zL, err := specexpr.VW().Div(specexpr.Int(32)) |
| if err != nil { |
| t.Fatal(err) |
| } |
| xL, err := specexpr.VW().Div(specexpr.Int(16)) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| data := map[string]any{ |
| "zL": zL, |
| "xL": xL, |
| } |
| got, err := tmpl.expand(data) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| if got != "smaller" { |
| t.Errorf("expected 'smaller', got %q", got) |
| } |
| } |
| |
| func TestSpecTemplateMissingKey(t *testing.T) { |
| tmpl, err := newSpecTemplate("Convert{{.missing}}") |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| |
| _, err = tmpl.expand(map[string]any{}) |
| if err == nil { |
| t.Errorf("expected error on missing key, got nil") |
| } |
| } |
| |
| func TestCleanDocNewlines(t *testing.T) { |
| tests := []struct { |
| input string |
| want string |
| }{ |
| { |
| input: "hello\n\n\n\nworld\n", |
| want: "hello\n\nworld\n", |
| }, |
| { |
| input: "hello\n\n\n", |
| want: "hello\n", |
| }, |
| { |
| input: "", |
| want: "", |
| }, |
| } |
| |
| for _, tc := range tests { |
| got := cleanDocNewlines(tc.input) |
| if got != tc.want { |
| t.Errorf("cleanDocNewlines(%q) = %q, want %q", tc.input, got, tc.want) |
| } |
| } |
| } |
| |
| func TestReshapeDiagram(t *testing.T) { |
| vec := func(n, l int) specexpr.Vector { |
| return specexpr.Vector{Elem: specexpr.Basic{Base: "uint", Bits: specexpr.Int(n)}, Width: specexpr.Int(n * l)} |
| } |
| u8s := specexpr.Vector{Elem: specexpr.Basic{Base: "uint", Bits: 8}, Width: specexpr.VW()} |
| u16s := specexpr.Vector{Elem: specexpr.Basic{Base: "uint", Bits: 16}, Width: specexpr.VW()} |
| |
| tests := []struct { |
| name string |
| x, z specexpr.Vector |
| want string |
| }{ |
| { |
| // Basic, no elision case |
| name: "Uint32x4 -> Uint64x2", |
| x: vec(32, 4), |
| z: vec(64, 2), |
| want: "\t x[3] x[2] x[1] x[0]\n" + |
| "\t | 31 .. 0 | 31 .. 0 | 31 .. 0 | 31 .. 0 |\n" + |
| "\t | 63 .... 0 | 63 .... 0 |\n" + |
| "\t z[1] z[0]", |
| }, |
| { |
| // Small to big, with elided middle |
| name: "Uint32x8 -> Uint64x4", |
| x: vec(32, 8), |
| z: vec(64, 4), |
| want: "\t x[7] x[6] ⋯ x[1] x[0]\n" + |
| "\t | 31 .. 0 | 31 .. 0 | ⋯ | 31 .. 0 | 31 .. 0 |\n" + |
| "\t | 63 .... 0 | ⋯ | 63 .... 0 |\n" + |
| "\t z[3] ⋯ z[0]", |
| }, |
| { |
| // Big to small, with elided middle |
| name: "Uint64x4 -> Uint32x8", |
| x: vec(64, 4), |
| z: vec(32, 8), |
| want: "\t x[3] ⋯ x[0]\n" + |
| "\t | 63 .... 0 | ⋯ | 63 .... 0 |\n" + |
| "\t | 31 .. 0 | 31 .. 0 | ⋯ | 31 .. 0 | 31 .. 0 |\n" + |
| "\t z[7] z[6] ⋯ z[1] z[0]", |
| }, |
| { |
| // Eliding elements within each big element |
| name: "Uint64x2 -> Uint8x16", |
| x: vec(64, 2), |
| z: vec(8, 16), |
| want: "\t x[1] x[0]\n" + |
| "\t | 63 .... 0 | 63 .... 0 |\n" + |
| "\t | 7 .. 0 | 7 .. 0 | ⋯ | 7 .. 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | ⋯ | 7 .. 0 | 7 .. 0 |\n" + |
| "\t z[15] z[14] ⋯ z[9] z[8] z[7] z[6] ⋯ z[1] z[0]", |
| }, |
| { |
| // Eliding elements within each big element, and elided middle |
| name: "Uint8x64 -> Uint64x8", |
| x: vec(8, 64), |
| z: vec(64, 8), |
| want: "\t x[63] x[62] ⋯ x[57] x[56] ⋯ x[7] x[6] ⋯ x[1] x[0]\n" + |
| "\t | 7 .. 0 | 7 .. 0 | ⋯ | 7 .. 0 | 7 .. 0 | ⋯ | 7 .. 0 | 7 .. 0 | ⋯ | 7 .. 0 | 7 .. 0 |\n" + |
| "\t | 63 .... 0 | ⋯ | 63 .... 0 |\n" + |
| "\t z[7] ⋯ z[0]", |
| }, |
| { |
| // Scalable |
| name: "Uint8s -> Uint16s (Scalable)", |
| x: u8s, |
| z: u16s, |
| want: "\t ⋯ x[3] x[2] x[1] x[0]\n" + |
| "\t ⋯ | 7 .. 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 |\n" + |
| "\t ⋯ | 15 .... 0 | 15 .... 0 |\n" + |
| "\t ⋯ z[1] z[0]", |
| }, |
| } |
| |
| for _, tc := range tests { |
| t.Run(tc.name, func(t *testing.T) { |
| got := reshapeDiagram(tc.x, tc.z) |
| if got != tc.want { |
| t.Errorf("reshapeDiagram(%s, %s):\ngot:\n%s\nwant:\n%s", tc.x, tc.z, got, tc.want) |
| } |
| }) |
| } |
| } |