1
2
3
4
5 package specgen
6
7 import (
8 "fmt"
9 "go/ast"
10 "go/build"
11 "go/importer"
12 "go/parser"
13 "go/token"
14 "go/types"
15 "path/filepath"
16 "simd/archsimd/_gen/specgen/specexpr"
17 "strings"
18 )
19
20
21 type specPackage struct {
22 Fset *token.FileSet
23 Pkg *types.Package
24 TypesInfo *types.Info
25 Funcs []*specFunc
26
27 TypeElems map[types.Type]specexpr.Basic
28 TypeWidths map[types.Type]specexpr.Num
29
30 ElemTypes map[specexpr.Basic]types.Type
31 WidthTypes map[specexpr.Num]types.Type
32
33 VecType types.Type
34 ArrayType types.Type
35 UintNType types.Type
36 }
37
38
39 type specFunc struct {
40 Pkg *specPackage
41 Name string
42 NameTmpl specTemplate
43 Pos token.Pos
44 Doc specTemplate
45 Category string
46 Commutative bool
47 Sig *types.Signature
48 TypeParams []*types.TypeParam
49 Params []*types.Var
50 Results []*types.Var
51 Requirements []specexpr.Expr
52 }
53
54
55
56 type specTemplate struct {
57 tmpl string
58 fields [][2]int
59 }
60
61
62
63 const specGoVersion = "go1.26"
64
65
66
67 func loadAndTypeCheck(ctx context, dir string) (*types.Package, *types.Info, []*ast.File) {
68 bp, err := build.ImportDir(dir, 0)
69 if err != nil {
70 ctx.errorf("failed to import spec directory %s: %s", dir, err)
71 return nil, nil, nil
72 }
73 if len(bp.AllTags) > 0 {
74 ctx.errorf("internal/spec must be target-independent, but found build tags: %v", bp.AllTags)
75 return nil, nil, nil
76 }
77
78 var astFiles []*ast.File
79 for _, name := range bp.GoFiles {
80 filePath := filepath.Join(bp.Dir, name)
81 file, err := parser.ParseFile(&ctx.root.fset, filePath, nil, parser.ParseComments)
82 if err != nil {
83 ctx.errorf("failed to parse %s: %s", filePath, err)
84 continue
85 }
86 astFiles = append(astFiles, file)
87 }
88
89 if len(astFiles) == 0 {
90 ctx.errorf("no Go source files found in directory %s", dir)
91 return nil, nil, nil
92 }
93
94 info := &types.Info{
95 Types: make(map[ast.Expr]types.TypeAndValue),
96 Defs: make(map[*ast.Ident]types.Object),
97 Uses: make(map[*ast.Ident]types.Object),
98 Implicits: make(map[ast.Node]types.Object),
99 Selections: make(map[*ast.SelectorExpr]*types.Selection),
100 Scopes: make(map[ast.Node]*types.Scope),
101 Instances: make(map[*ast.Ident]types.Instance),
102 }
103
104
105
106
107 conf := types.Config{
108 GoVersion: specGoVersion,
109 Importer: importer.ForCompiler(&ctx.root.fset, "source", nil),
110 Error: func(err error) {
111 ctx.errorf("%s", err)
112 },
113 }
114
115 typesPkg, err := conf.Check("simd/internal/spec", &ctx.root.fset, astFiles, info)
116 if err != nil && typesPkg == nil {
117 return nil, nil, nil
118 }
119 if len(ctx.root.errors) > 0 {
120 return nil, nil, nil
121 }
122
123 return typesPkg, info, astFiles
124 }
125
126
127 func loadSpecPackage(ctx context, dir string, opts *LoadOptions) *specPackage {
128 typesPkg, info, astFiles := loadAndTypeCheck(ctx, dir)
129 if typesPkg == nil {
130 return nil
131 }
132
133 var pkg specPackage
134
135
136 var funcs []*specFunc
137 for _, file := range astFiles {
138
139 var category string
140 directives := make(map[*ast.Comment]ast.Directive)
141 for _, cg := range file.Comments {
142 for _, comment := range cg.List {
143 if dir, ok := ast.ParseDirective(comment.Slash, comment.Text); ok && dir.Tool == "specgen" {
144 switch dir.Name {
145 case "category":
146
147 if category != "" {
148 ctx.at(dir.Pos()).errorf("multiple category directives in file")
149 }
150 category = dir.Args
151 case "name", "commutative", "require":
152
153 directives[comment] = dir
154 default:
155 ctx.at(dir.Pos()).errorf("unknown //specgen directive")
156 }
157 }
158 }
159 }
160
161 for _, decl := range file.Decls {
162 d, ok := decl.(*ast.FuncDecl)
163 if !ok || !d.Name.IsExported() {
164 continue
165 }
166
167 obj := typesPkg.Scope().Lookup(d.Name.Name)
168 if obj == nil {
169 continue
170 }
171 fn, ok := obj.(*types.Func)
172 if !ok {
173 continue
174 }
175
176 sig := fn.Type().(*types.Signature)
177
178 var typeParams []*types.TypeParam
179 tparams := sig.TypeParams()
180 for tparam := range tparams.TypeParams() {
181 typeParams = append(typeParams, tparam)
182 }
183
184 var params []*types.Var
185 p := sig.Params()
186 for v := range p.Variables() {
187 params = append(params, v)
188 }
189
190 var results []*types.Var
191 r := sig.Results()
192 for v := range r.Variables() {
193 results = append(results, v)
194 }
195
196 f := &specFunc{
197 Pkg: &pkg,
198 Name: d.Name.Name,
199 Pos: decl.Pos(),
200 Sig: sig,
201 TypeParams: typeParams,
202 Params: params,
203 Results: results,
204 Category: category,
205 }
206 f.NameTmpl = specTemplate{tmpl: f.Name}
207 if d.Doc != nil {
208 var err error
209 f.Doc, err = newSpecTemplate(d.Doc.Text())
210 if err != nil {
211 ctx.at(d.Doc.Pos()).errorf("malformed doc comment: %s", err)
212 }
213 for _, comment := range d.Doc.List {
214 if dir, ok := directives[comment]; ok {
215 delete(directives, comment)
216 switch dir.Name {
217 default:
218 panic("directive lists out of sync")
219 case "name":
220 f.NameTmpl, err = newSpecTemplate(dir.Args)
221 if err != nil {
222 ctx.at(dir.Pos()).errorf("malformed //specgen:name directive: %s", err)
223 }
224 case "commutative":
225 if dir.Args != "" {
226 ctx.at(dir.Pos()).errorf("malformed //specgen:commutative directive: expected no argument")
227 }
228 f.Commutative = true
229 case "require":
230 args, err := dir.ParseArgs()
231 if err != nil {
232 ctx.at(dir.Pos()).errorf("malformed //specgen:require directive: %s", err)
233 break
234 }
235 for _, arg := range args {
236 expr, err := specexpr.ParseExpr(arg.Arg)
237 if err != nil {
238 ctx.at(arg.Pos).errorf("failed to parse require argument %q: %s", arg.Arg, err)
239 continue
240 }
241 f.Requirements = append(f.Requirements, expr)
242 }
243 }
244 }
245 }
246 }
247
248 if opts.Filter != nil && !opts.Filter(d) {
249 continue
250 }
251
252 funcs = append(funcs, f)
253 }
254
255 for _, dir := range directives {
256 ctx.at(dir.Pos()).errorf("//%s:%s directive must be attached to a function", dir.Tool, dir.Name)
257 }
258 }
259
260 lookupType := func(name string) types.Type {
261 obj := typesPkg.Scope().Lookup(name)
262 if obj == nil {
263 ctx.errorf("type %q missing from package %s", name, typesPkg.Path())
264 return nil
265 }
266 tn, ok := obj.(*types.TypeName)
267 if !ok {
268 ctx.at(obj.Pos()).errorf("%s expected to be a type", obj.String())
269 return nil
270 }
271 return tn.Type()
272 }
273
274
275 typeElems := make(map[types.Type]specexpr.Basic)
276 elemTypes := make(map[specexpr.Basic]types.Type)
277 if eltOrMask := lookupType("EltOrMask"); eltOrMask != nil {
278 for _, elt := range typeSet(eltOrMask) {
279 basic := shapeElemType(elt)
280 typeElems[elt] = basic
281 elemTypes[basic] = elt
282 }
283 }
284 typeWidths := make(map[types.Type]specexpr.Num)
285 widthTypes := make(map[specexpr.Num]types.Type)
286 if width := lookupType("Width"); width != nil {
287 for _, width := range typeSet(width) {
288 val := shapeWidthVal(width)
289 typeWidths[width] = val
290 widthTypes[val] = width
291 }
292 }
293
294
295 vecType := lookupType("Vec")
296 arrayType := lookupType("Array")
297 uintNType := lookupType("UintN")
298
299 pkg = specPackage{
300 Fset: &ctx.root.fset,
301 Pkg: typesPkg,
302 TypesInfo: info,
303 Funcs: funcs,
304 TypeElems: typeElems,
305 TypeWidths: typeWidths,
306 ElemTypes: elemTypes,
307 WidthTypes: widthTypes,
308 VecType: vecType,
309 ArrayType: arrayType,
310 UintNType: uintNType,
311 }
312 return &pkg
313 }
314
315
316 func newSpecTemplate(tmpl string) (specTemplate, error) {
317 if !strings.ContainsAny(tmpl, "{}") {
318 return specTemplate{tmpl, nil}, nil
319 }
320
321 var fields [][2]int
322 for i := 0; i < len(tmpl); i++ {
323 switch tmpl[i] {
324 case '{':
325 j := i + strings.IndexByte(tmpl[i:], '}') + 1
326 if j <= i {
327 return specTemplate{}, fmt.Errorf("unclosed '{' in template %q", tmpl)
328 }
329 fields = append(fields, [2]int{i, j})
330 i = j - 1
331 case '}':
332 return specTemplate{}, fmt.Errorf("unmatched '}' in template %q", tmpl)
333 }
334 }
335 return specTemplate{
336 tmpl: tmpl,
337 fields: fields,
338 }, nil
339 }
340
341
342
343 func (s *specTemplate) expand(lookup func(string) string) string {
344 if len(s.fields) == 0 {
345 return s.tmpl
346 }
347 var buf strings.Builder
348 pos := 0
349 for _, field := range s.fields {
350 buf.WriteString(s.tmpl[pos:field[0]])
351 val := lookup(s.tmpl[field[0]+1 : field[1]-1])
352 buf.WriteString(val)
353 pos = field[1]
354 }
355 buf.WriteString(s.tmpl[pos:])
356 return buf.String()
357 }
358
View as plain text