Source file src/simd/archsimd/_gen/specgen/template_test.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 specgen
     6  
     7  import (
     8  	"simd/archsimd/_gen/specgen/specexpr"
     9  	"strings"
    10  	"testing"
    11  )
    12  
    13  func TestNewSpecTemplate(t *testing.T) {
    14  	tests := []struct {
    15  		tmpl    string
    16  		isTmpl  bool
    17  		wantErr bool
    18  	}{
    19  		{
    20  			tmpl:   "",
    21  			isTmpl: false,
    22  		},
    23  		{
    24  			tmpl:   "Convert",
    25  			isTmpl: false,
    26  		},
    27  		{
    28  			tmpl:   "Convert{{.zL}}To{{.zB}}{{.zN}}",
    29  			isTmpl: true,
    30  		},
    31  		{
    32  			tmpl:    "Convert{{.zL",
    33  			wantErr: true,
    34  		},
    35  		{
    36  			tmpl:    "Convert{{if}}",
    37  			wantErr: true,
    38  		},
    39  	}
    40  
    41  	for _, tc := range tests {
    42  		got, err := newSpecTemplate(tc.tmpl)
    43  		if (err != nil) != tc.wantErr {
    44  			t.Errorf("newSpecTemplate(%q) returned error: %v, wantErr: %v", tc.tmpl, err, tc.wantErr)
    45  			continue
    46  		}
    47  		if tc.wantErr {
    48  			continue
    49  		}
    50  		if got.raw != tc.tmpl {
    51  			t.Errorf("newSpecTemplate(%q) raw = %q, want %q", tc.tmpl, got.raw, tc.tmpl)
    52  		}
    53  		if (got.tmpl != nil) != tc.isTmpl {
    54  			t.Errorf("newSpecTemplate(%q) isTmpl = %v, want %v", tc.tmpl, got.tmpl != nil, tc.isTmpl)
    55  		}
    56  	}
    57  }
    58  
    59  func TestSpecTemplateExpand(t *testing.T) {
    60  	tmpl, err := newSpecTemplate("Convert{{.zL}}To{{.zB}}{{.zN}}")
    61  	if err != nil {
    62  		t.Fatalf("unexpected error parsing template: %v", err)
    63  	}
    64  
    65  	data := map[string]any{
    66  		"zL": specexpr.Int(4),
    67  		"zB": "Float",
    68  		"zN": specexpr.Int(32),
    69  	}
    70  
    71  	got, err := tmpl.expand(data)
    72  	if err != nil {
    73  		t.Fatalf("unexpected error expanding template: %v", err)
    74  	}
    75  	want := "Convert4ToFloat32"
    76  	if got != want {
    77  		t.Errorf("expected expanded string %q, got %q", want, got)
    78  	}
    79  }
    80  
    81  func TestSpecTemplateTitle(t *testing.T) {
    82  	tmpl, err := newSpecTemplate("ConvertTo{{.zE | title}}")
    83  	if err != nil {
    84  		t.Fatalf("unexpected error parsing template: %v", err)
    85  	}
    86  
    87  	data := map[string]any{
    88  		"zE": specexpr.Basic{Base: "float", Bits: 32},
    89  	}
    90  
    91  	got, err := tmpl.expand(data)
    92  	if err != nil {
    93  		t.Fatalf("unexpected error expanding template: %v", err)
    94  	}
    95  	want := "ConvertToFloat32"
    96  	if got != want {
    97  		t.Errorf("expected %q, got %q", want, got)
    98  	}
    99  }
   100  
   101  func TestSpecTemplateConditionals(t *testing.T) {
   102  	tmplStr := `Doc for {{.Name}}.
   103  {{if lt .zL .xL}}
   104  Upper elements of the result are zeroed.
   105  {{end}}`
   106  
   107  	tmpl, err := newSpecTemplate(tmplStr)
   108  	if err != nil {
   109  		t.Fatalf("unexpected error parsing template: %v", err)
   110  	}
   111  
   112  	// Case 1: zL < xL
   113  	data1 := map[string]any{
   114  		"Name": "ConvertLo4ToFloat32",
   115  		"zL":   specexpr.Int(4),
   116  		"xL":   specexpr.Int(8),
   117  	}
   118  	got1, err := tmpl.expand(data1)
   119  	if err != nil {
   120  		t.Fatalf("unexpected error: %v", err)
   121  	}
   122  	if !strings.Contains(got1, "Upper elements of the result are zeroed.") {
   123  		t.Errorf("expected conditional text when zL < xL, got: %q", got1)
   124  	}
   125  
   126  	// Case 2: zL >= xL
   127  	data2 := map[string]any{
   128  		"Name": "ConvertLo8ToFloat32",
   129  		"zL":   specexpr.Int(8),
   130  		"xL":   specexpr.Int(8),
   131  	}
   132  	got2, err := tmpl.expand(data2)
   133  	if err != nil {
   134  		t.Fatalf("unexpected error: %v", err)
   135  	}
   136  	if strings.Contains(got2, "Upper elements of the result are zeroed.") {
   137  		t.Errorf("expected no conditional text when zL == xL, got: %q", got2)
   138  	}
   139  }
   140  
   141  func TestSpecTemplateScalableWidthCompare(t *testing.T) {
   142  	tmpl, err := newSpecTemplate("{{if lt .zL .xL}}smaller{{else}}same-or-larger{{end}}")
   143  	if err != nil {
   144  		t.Fatalf("unexpected error: %v", err)
   145  	}
   146  
   147  	// zL = VW/32, xL = VW/16 => zL < xL
   148  	zL, err := specexpr.VW().Div(specexpr.Int(32))
   149  	if err != nil {
   150  		t.Fatal(err)
   151  	}
   152  	xL, err := specexpr.VW().Div(specexpr.Int(16))
   153  	if err != nil {
   154  		t.Fatal(err)
   155  	}
   156  
   157  	data := map[string]any{
   158  		"zL": zL,
   159  		"xL": xL,
   160  	}
   161  	got, err := tmpl.expand(data)
   162  	if err != nil {
   163  		t.Fatalf("unexpected error: %v", err)
   164  	}
   165  	if got != "smaller" {
   166  		t.Errorf("expected 'smaller', got %q", got)
   167  	}
   168  }
   169  
   170  func TestSpecTemplateMissingKey(t *testing.T) {
   171  	tmpl, err := newSpecTemplate("Convert{{.missing}}")
   172  	if err != nil {
   173  		t.Fatalf("unexpected error: %v", err)
   174  	}
   175  
   176  	_, err = tmpl.expand(map[string]any{})
   177  	if err == nil {
   178  		t.Errorf("expected error on missing key, got nil")
   179  	}
   180  }
   181  
   182  func TestCleanDocNewlines(t *testing.T) {
   183  	tests := []struct {
   184  		input string
   185  		want  string
   186  	}{
   187  		{
   188  			input: "hello\n\n\n\nworld\n",
   189  			want:  "hello\n\nworld\n",
   190  		},
   191  		{
   192  			input: "hello\n\n\n",
   193  			want:  "hello\n",
   194  		},
   195  		{
   196  			input: "",
   197  			want:  "",
   198  		},
   199  	}
   200  
   201  	for _, tc := range tests {
   202  		got := cleanDocNewlines(tc.input)
   203  		if got != tc.want {
   204  			t.Errorf("cleanDocNewlines(%q) = %q, want %q", tc.input, got, tc.want)
   205  		}
   206  	}
   207  }
   208  
   209  func TestReshapeDiagram(t *testing.T) {
   210  	vec := func(n, l int) specexpr.Vector {
   211  		return specexpr.Vector{Elem: specexpr.Basic{Base: "uint", Bits: specexpr.Int(n)}, Width: specexpr.Int(n * l)}
   212  	}
   213  	u8s := specexpr.Vector{Elem: specexpr.Basic{Base: "uint", Bits: 8}, Width: specexpr.VW()}
   214  	u16s := specexpr.Vector{Elem: specexpr.Basic{Base: "uint", Bits: 16}, Width: specexpr.VW()}
   215  
   216  	tests := []struct {
   217  		name string
   218  		x, z specexpr.Vector
   219  		want string
   220  	}{
   221  		{
   222  			// Basic, no elision case
   223  			name: "Uint32x4 -> Uint64x2",
   224  			x:    vec(32, 4),
   225  			z:    vec(64, 2),
   226  			want: "\t      x[3]      x[2]      x[1]      x[0]\n" +
   227  				"\t  | 31 .. 0 | 31 .. 0 | 31 .. 0 | 31 .. 0 |\n" +
   228  				"\t  | 63     ....     0 | 63     ....     0 |\n" +
   229  				"\t           z[1]                z[0]",
   230  		},
   231  		{
   232  			// Small to big, with elided middle
   233  			name: "Uint32x8 -> Uint64x4",
   234  			x:    vec(32, 8),
   235  			z:    vec(64, 4),
   236  			want: "\t      x[7]      x[6]    ⋯     x[1]      x[0]\n" +
   237  				"\t  | 31 .. 0 | 31 .. 0 | ⋯ | 31 .. 0 | 31 .. 0 |\n" +
   238  				"\t  | 63     ....     0 | ⋯ | 63     ....     0 |\n" +
   239  				"\t           z[3]         ⋯          z[0]",
   240  		},
   241  		{
   242  			// Big to small, with elided middle
   243  			name: "Uint64x4 -> Uint32x8",
   244  			x:    vec(64, 4),
   245  			z:    vec(32, 8),
   246  			want: "\t           x[3]         ⋯          x[0]\n" +
   247  				"\t  | 63     ....     0 | ⋯ | 63     ....     0 |\n" +
   248  				"\t  | 31 .. 0 | 31 .. 0 | ⋯ | 31 .. 0 | 31 .. 0 |\n" +
   249  				"\t      z[7]      z[6]    ⋯     z[1]      z[0]",
   250  		},
   251  		{
   252  			// Eliding elements within each big element
   253  			name: "Uint64x2 -> Uint8x16",
   254  			x:    vec(64, 2),
   255  			z:    vec(8, 16),
   256  			want: "\t                       x[1]                                        x[0]\n" +
   257  				"\t  | 63                 ....                 0 | 63                 ....                 0 |\n" +
   258  				"\t  | 7  .. 0 | 7  .. 0 | ⋯ | 7  .. 0 | 7  .. 0 | 7  .. 0 | 7  .. 0 | ⋯ | 7  .. 0 | 7  .. 0 |\n" +
   259  				"\t     z[15]     z[14]    ⋯     z[9]      z[8]      z[7]      z[6]    ⋯     z[1]      z[0]",
   260  		},
   261  		{
   262  			// Eliding elements within each big element, and elided middle
   263  			name: "Uint8x64 -> Uint64x8",
   264  			x:    vec(8, 64),
   265  			z:    vec(64, 8),
   266  			want: "\t     x[63]     x[62]    ⋯    x[57]     x[56]    ⋯     x[7]      x[6]    ⋯     x[1]      x[0]\n" +
   267  				"\t  | 7  .. 0 | 7  .. 0 | ⋯ | 7  .. 0 | 7  .. 0 | ⋯ | 7  .. 0 | 7  .. 0 | ⋯ | 7  .. 0 | 7  .. 0 |\n" +
   268  				"\t  | 63                 ....                 0 | ⋯ | 63                 ....                 0 |\n" +
   269  				"\t                       z[7]                     ⋯                      z[0]",
   270  		},
   271  		{
   272  			// Scalable
   273  			name: "Uint8s -> Uint16s (Scalable)",
   274  			x:    u8s,
   275  			z:    u16s,
   276  			want: "\t  ⋯     x[3]      x[2]      x[1]      x[0]\n" +
   277  				"\t  ⋯ | 7  .. 0 | 7  .. 0 | 7  .. 0 | 7  .. 0 |\n" +
   278  				"\t  ⋯ | 15     ....     0 | 15     ....     0 |\n" +
   279  				"\t  ⋯          z[1]                z[0]",
   280  		},
   281  	}
   282  
   283  	for _, tc := range tests {
   284  		t.Run(tc.name, func(t *testing.T) {
   285  			got := reshapeDiagram(tc.x, tc.z)
   286  			if got != tc.want {
   287  				t.Errorf("reshapeDiagram(%s, %s):\ngot:\n%s\nwant:\n%s", tc.x, tc.z, got, tc.want)
   288  			}
   289  		})
   290  	}
   291  }
   292  

View as plain text