1 | // Copyright 2013 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 asmdecl defines an Analyzer that reports mismatches between |
6 | // assembly files and Go declarations. |
7 | package asmdecl |
8 | |
9 | import ( |
10 | "bytes" |
11 | "fmt" |
12 | "go/ast" |
13 | "go/build" |
14 | "go/token" |
15 | "go/types" |
16 | "log" |
17 | "regexp" |
18 | "strconv" |
19 | "strings" |
20 | |
21 | "golang.org/x/tools/go/analysis" |
22 | "golang.org/x/tools/go/analysis/passes/internal/analysisutil" |
23 | ) |
24 | |
25 | const Doc = "report mismatches between assembly files and Go declarations" |
26 | |
27 | var Analyzer = &analysis.Analyzer{ |
28 | Name: "asmdecl", |
29 | Doc: Doc, |
30 | Run: run, |
31 | } |
32 | |
33 | // 'kind' is a kind of assembly variable. |
34 | // The kinds 1, 2, 4, 8 stand for values of that size. |
35 | type asmKind int |
36 | |
37 | // These special kinds are not valid sizes. |
38 | const ( |
39 | asmString asmKind = 100 + iota |
40 | asmSlice |
41 | asmArray |
42 | asmInterface |
43 | asmEmptyInterface |
44 | asmStruct |
45 | asmComplex |
46 | ) |
47 | |
48 | // An asmArch describes assembly parameters for an architecture |
49 | type asmArch struct { |
50 | name string |
51 | bigEndian bool |
52 | stack string |
53 | lr bool |
54 | // retRegs is a list of registers for return value in register ABI (ABIInternal). |
55 | // For now, as we only check whether we write to any result, here we only need to |
56 | // include the first integer register and first floating-point register. Accessing |
57 | // any of them counts as writing to result. |
58 | retRegs []string |
59 | // calculated during initialization |
60 | sizes types.Sizes |
61 | intSize int |
62 | ptrSize int |
63 | maxAlign int |
64 | } |
65 | |
66 | // An asmFunc describes the expected variables for a function on a given architecture. |
67 | type asmFunc struct { |
68 | arch *asmArch |
69 | size int // size of all arguments |
70 | vars map[string]*asmVar |
71 | varByOffset map[int]*asmVar |
72 | } |
73 | |
74 | // An asmVar describes a single assembly variable. |
75 | type asmVar struct { |
76 | name string |
77 | kind asmKind |
78 | typ string |
79 | off int |
80 | size int |
81 | inner []*asmVar |
82 | } |
83 | |
84 | var ( |
85 | asmArch386 = asmArch{name: "386", bigEndian: false, stack: "SP", lr: false} |
86 | asmArchArm = asmArch{name: "arm", bigEndian: false, stack: "R13", lr: true} |
87 | asmArchArm64 = asmArch{name: "arm64", bigEndian: false, stack: "RSP", lr: true, retRegs: []string{"R0", "F0"}} |
88 | asmArchAmd64 = asmArch{name: "amd64", bigEndian: false, stack: "SP", lr: false, retRegs: []string{"AX", "X0"}} |
89 | asmArchMips = asmArch{name: "mips", bigEndian: true, stack: "R29", lr: true} |
90 | asmArchMipsLE = asmArch{name: "mipsle", bigEndian: false, stack: "R29", lr: true} |
91 | asmArchMips64 = asmArch{name: "mips64", bigEndian: true, stack: "R29", lr: true} |
92 | asmArchMips64LE = asmArch{name: "mips64le", bigEndian: false, stack: "R29", lr: true} |
93 | asmArchPpc64 = asmArch{name: "ppc64", bigEndian: true, stack: "R1", lr: true, retRegs: []string{"R3", "F1"}} |
94 | asmArchPpc64LE = asmArch{name: "ppc64le", bigEndian: false, stack: "R1", lr: true, retRegs: []string{"R3", "F1"}} |
95 | asmArchRISCV64 = asmArch{name: "riscv64", bigEndian: false, stack: "SP", lr: true, retRegs: []string{"X10", "F10"}} |
96 | asmArchS390X = asmArch{name: "s390x", bigEndian: true, stack: "R15", lr: true} |
97 | asmArchWasm = asmArch{name: "wasm", bigEndian: false, stack: "SP", lr: false} |
98 | |
99 | arches = []*asmArch{ |
100 | &asmArch386, |
101 | &asmArchArm, |
102 | &asmArchArm64, |
103 | &asmArchAmd64, |
104 | &asmArchMips, |
105 | &asmArchMipsLE, |
106 | &asmArchMips64, |
107 | &asmArchMips64LE, |
108 | &asmArchPpc64, |
109 | &asmArchPpc64LE, |
110 | &asmArchRISCV64, |
111 | &asmArchS390X, |
112 | &asmArchWasm, |
113 | } |
114 | ) |
115 | |
116 | func init() { |
117 | arches = append(arches, additionalArches()...) |
118 | for _, arch := range arches { |
119 | arch.sizes = types.SizesFor("gc", arch.name) |
120 | if arch.sizes == nil { |
121 | // TODO(adonovan): fix: now that asmdecl is not in the standard |
122 | // library we cannot assume types.SizesFor is consistent with arches. |
123 | // For now, assume 64-bit norms and print a warning. |
124 | // But this warning should really be deferred until we attempt to use |
125 | // arch, which is very unlikely. Better would be |
126 | // to defer size computation until we have Pass.TypesSizes. |
127 | arch.sizes = types.SizesFor("gc", "amd64") |
128 | log.Printf("unknown architecture %s", arch.name) |
129 | } |
130 | arch.intSize = int(arch.sizes.Sizeof(types.Typ[types.Int])) |
131 | arch.ptrSize = int(arch.sizes.Sizeof(types.Typ[types.UnsafePointer])) |
132 | arch.maxAlign = int(arch.sizes.Alignof(types.Typ[types.Int64])) |
133 | } |
134 | } |
135 | |
136 | var ( |
137 | re = regexp.MustCompile |
138 | asmPlusBuild = re(`//\s+\+build\s+([^\n]+)`) |
139 | asmTEXT = re(`\bTEXT\b(.*)ยท([^\(]+)\(SB\)(?:\s*,\s*([0-9A-Z|+()]+))?(?:\s*,\s*\$(-?[0-9]+)(?:-([0-9]+))?)?`) |
140 | asmDATA = re(`\b(DATA|GLOBL)\b`) |
141 | asmNamedFP = re(`\$?([a-zA-Z0-9_\xFF-\x{10FFFF}]+)(?:\+([0-9]+))\(FP\)`) |
142 | asmUnnamedFP = re(`[^+\-0-9](([0-9]+)\(FP\))`) |
143 | asmSP = re(`[^+\-0-9](([0-9]+)\(([A-Z0-9]+)\))`) |
144 | asmOpcode = re(`^\s*(?:[A-Z0-9a-z_]+:)?\s*([A-Z]+)\s*([^,]*)(?:,\s*(.*))?`) |
145 | ppc64Suff = re(`([BHWD])(ZU|Z|U|BR)?$`) |
146 | abiSuff = re(`^(.+)<(ABI.+)>$`) |
147 | ) |
148 | |
149 | func run(pass *analysis.Pass) (interface{}, error) { |
150 | // No work if no assembly files. |
151 | var sfiles []string |
152 | for _, fname := range pass.OtherFiles { |
153 | if strings.HasSuffix(fname, ".s") { |
154 | sfiles = append(sfiles, fname) |
155 | } |
156 | } |
157 | if sfiles == nil { |
158 | return nil, nil |
159 | } |
160 | |
161 | // Gather declarations. knownFunc[name][arch] is func description. |
162 | knownFunc := make(map[string]map[string]*asmFunc) |
163 | |
164 | for _, f := range pass.Files { |
165 | for _, decl := range f.Decls { |
166 | if decl, ok := decl.(*ast.FuncDecl); ok && decl.Body == nil { |
167 | knownFunc[decl.Name.Name] = asmParseDecl(pass, decl) |
168 | } |
169 | } |
170 | } |
171 | |
172 | Files: |
173 | for _, fname := range sfiles { |
174 | content, tf, err := analysisutil.ReadFile(pass.Fset, fname) |
175 | if err != nil { |
176 | return nil, err |
177 | } |
178 | |
179 | // Determine architecture from file name if possible. |
180 | var arch string |
181 | var archDef *asmArch |
182 | for _, a := range arches { |
183 | if strings.HasSuffix(fname, "_"+a.name+".s") { |
184 | arch = a.name |
185 | archDef = a |
186 | break |
187 | } |
188 | } |
189 | |
190 | lines := strings.SplitAfter(string(content), "\n") |
191 | var ( |
192 | fn *asmFunc |
193 | fnName string |
194 | abi string |
195 | localSize, argSize int |
196 | wroteSP bool |
197 | noframe bool |
198 | haveRetArg bool |
199 | retLine []int |
200 | ) |
201 | |
202 | flushRet := func() { |
203 | if fn != nil && fn.vars["ret"] != nil && !haveRetArg && len(retLine) > 0 { |
204 | v := fn.vars["ret"] |
205 | resultStr := fmt.Sprintf("%d-byte ret+%d(FP)", v.size, v.off) |
206 | if abi == "ABIInternal" { |
207 | resultStr = "result register" |
208 | } |
209 | for _, line := range retLine { |
210 | pass.Reportf(analysisutil.LineStart(tf, line), "[%s] %s: RET without writing to %s", arch, fnName, resultStr) |
211 | } |
212 | } |
213 | retLine = nil |
214 | } |
215 | trimABI := func(fnName string) (string, string) { |
216 | m := abiSuff.FindStringSubmatch(fnName) |
217 | if m != nil { |
218 | return m[1], m[2] |
219 | } |
220 | return fnName, "" |
221 | } |
222 | for lineno, line := range lines { |
223 | lineno++ |
224 | |
225 | badf := func(format string, args ...interface{}) { |
226 | pass.Reportf(analysisutil.LineStart(tf, lineno), "[%s] %s: %s", arch, fnName, fmt.Sprintf(format, args...)) |
227 | } |
228 | |
229 | if arch == "" { |
230 | // Determine architecture from +build line if possible. |
231 | if m := asmPlusBuild.FindStringSubmatch(line); m != nil { |
232 | // There can be multiple architectures in a single +build line, |
233 | // so accumulate them all and then prefer the one that |
234 | // matches build.Default.GOARCH. |
235 | var archCandidates []*asmArch |
236 | for _, fld := range strings.Fields(m[1]) { |
237 | for _, a := range arches { |
238 | if a.name == fld { |
239 | archCandidates = append(archCandidates, a) |
240 | } |
241 | } |
242 | } |
243 | for _, a := range archCandidates { |
244 | if a.name == build.Default.GOARCH { |
245 | archCandidates = []*asmArch{a} |
246 | break |
247 | } |
248 | } |
249 | if len(archCandidates) > 0 { |
250 | arch = archCandidates[0].name |
251 | archDef = archCandidates[0] |
252 | } |
253 | } |
254 | } |
255 | |
256 | // Ignore comments and commented-out code. |
257 | if i := strings.Index(line, "//"); i >= 0 { |
258 | line = line[:i] |
259 | } |
260 | |
261 | if m := asmTEXT.FindStringSubmatch(line); m != nil { |
262 | flushRet() |
263 | if arch == "" { |
264 | // Arch not specified by filename or build tags. |
265 | // Fall back to build.Default.GOARCH. |
266 | for _, a := range arches { |
267 | if a.name == build.Default.GOARCH { |
268 | arch = a.name |
269 | archDef = a |
270 | break |
271 | } |
272 | } |
273 | if arch == "" { |
274 | log.Printf("%s: cannot determine architecture for assembly file", fname) |
275 | continue Files |
276 | } |
277 | } |
278 | fnName = m[2] |
279 | if pkgPath := strings.TrimSpace(m[1]); pkgPath != "" { |
280 | // The assembler uses Unicode division slash within |
281 | // identifiers to represent the directory separator. |
282 | pkgPath = strings.Replace(pkgPath, "โ", "/", -1) |
283 | if pkgPath != pass.Pkg.Path() { |
284 | // log.Printf("%s:%d: [%s] cannot check cross-package assembly function: %s is in package %s", fname, lineno, arch, fnName, pkgPath) |
285 | fn = nil |
286 | fnName = "" |
287 | abi = "" |
288 | continue |
289 | } |
290 | } |
291 | // Trim off optional ABI selector. |
292 | fnName, abi = trimABI(fnName) |
293 | flag := m[3] |
294 | fn = knownFunc[fnName][arch] |
295 | if fn != nil { |
296 | size, _ := strconv.Atoi(m[5]) |
297 | if size != fn.size && (flag != "7" && !strings.Contains(flag, "NOSPLIT") || size != 0) { |
298 | badf("wrong argument size %d; expected $...-%d", size, fn.size) |
299 | } |
300 | } |
301 | localSize, _ = strconv.Atoi(m[4]) |
302 | localSize += archDef.intSize |
303 | if archDef.lr && !strings.Contains(flag, "NOFRAME") { |
304 | // Account for caller's saved LR |
305 | localSize += archDef.intSize |
306 | } |
307 | argSize, _ = strconv.Atoi(m[5]) |
308 | noframe = strings.Contains(flag, "NOFRAME") |
309 | if fn == nil && !strings.Contains(fnName, "<>") && !noframe { |
310 | badf("function %s missing Go declaration", fnName) |
311 | } |
312 | wroteSP = false |
313 | haveRetArg = false |
314 | continue |
315 | } else if strings.Contains(line, "TEXT") && strings.Contains(line, "SB") { |
316 | // function, but not visible from Go (didn't match asmTEXT), so stop checking |
317 | flushRet() |
318 | fn = nil |
319 | fnName = "" |
320 | abi = "" |
321 | continue |
322 | } |
323 | |
324 | if strings.Contains(line, "RET") && !strings.Contains(line, "(SB)") { |
325 | // RET f(SB) is a tail call. It is okay to not write the results. |
326 | retLine = append(retLine, lineno) |
327 | } |
328 | |
329 | if fnName == "" { |
330 | continue |
331 | } |
332 | |
333 | if asmDATA.FindStringSubmatch(line) != nil { |
334 | fn = nil |
335 | } |
336 | |
337 | if archDef == nil { |
338 | continue |
339 | } |
340 | |
341 | if strings.Contains(line, ", "+archDef.stack) || strings.Contains(line, ",\t"+archDef.stack) || strings.Contains(line, "NOP "+archDef.stack) || strings.Contains(line, "NOP\t"+archDef.stack) { |
342 | wroteSP = true |
343 | continue |
344 | } |
345 | |
346 | if arch == "wasm" && strings.Contains(line, "CallImport") { |
347 | // CallImport is a call out to magic that can write the result. |
348 | haveRetArg = true |
349 | } |
350 | |
351 | if abi == "ABIInternal" && !haveRetArg { |
352 | for _, reg := range archDef.retRegs { |
353 | if strings.Contains(line, reg) { |
354 | haveRetArg = true |
355 | break |
356 | } |
357 | } |
358 | } |
359 | |
360 | for _, m := range asmSP.FindAllStringSubmatch(line, -1) { |
361 | if m[3] != archDef.stack || wroteSP || noframe { |
362 | continue |
363 | } |
364 | off := 0 |
365 | if m[1] != "" { |
366 | off, _ = strconv.Atoi(m[2]) |
367 | } |
368 | if off >= localSize { |
369 | if fn != nil { |
370 | v := fn.varByOffset[off-localSize] |
371 | if v != nil { |
372 | badf("%s should be %s+%d(FP)", m[1], v.name, off-localSize) |
373 | continue |
374 | } |
375 | } |
376 | if off >= localSize+argSize { |
377 | badf("use of %s points beyond argument frame", m[1]) |
378 | continue |
379 | } |
380 | badf("use of %s to access argument frame", m[1]) |
381 | } |
382 | } |
383 | |
384 | if fn == nil { |
385 | continue |
386 | } |
387 | |
388 | for _, m := range asmUnnamedFP.FindAllStringSubmatch(line, -1) { |
389 | off, _ := strconv.Atoi(m[2]) |
390 | v := fn.varByOffset[off] |
391 | if v != nil { |
392 | badf("use of unnamed argument %s; offset %d is %s+%d(FP)", m[1], off, v.name, v.off) |
393 | } else { |
394 | badf("use of unnamed argument %s", m[1]) |
395 | } |
396 | } |
397 | |
398 | for _, m := range asmNamedFP.FindAllStringSubmatch(line, -1) { |
399 | name := m[1] |
400 | off := 0 |
401 | if m[2] != "" { |
402 | off, _ = strconv.Atoi(m[2]) |
403 | } |
404 | if name == "ret" || strings.HasPrefix(name, "ret_") { |
405 | haveRetArg = true |
406 | } |
407 | v := fn.vars[name] |
408 | if v == nil { |
409 | // Allow argframe+0(FP). |
410 | if name == "argframe" && off == 0 { |
411 | continue |
412 | } |
413 | v = fn.varByOffset[off] |
414 | if v != nil { |
415 | badf("unknown variable %s; offset %d is %s+%d(FP)", name, off, v.name, v.off) |
416 | } else { |
417 | badf("unknown variable %s", name) |
418 | } |
419 | continue |
420 | } |
421 | asmCheckVar(badf, fn, line, m[0], off, v, archDef) |
422 | } |
423 | } |
424 | flushRet() |
425 | } |
426 | return nil, nil |
427 | } |
428 | |
429 | func asmKindForType(t types.Type, size int) asmKind { |
430 | switch t := t.Underlying().(type) { |
431 | case *types.Basic: |
432 | switch t.Kind() { |
433 | case types.String: |
434 | return asmString |
435 | case types.Complex64, types.Complex128: |
436 | return asmComplex |
437 | } |
438 | return asmKind(size) |
439 | case *types.Pointer, *types.Chan, *types.Map, *types.Signature: |
440 | return asmKind(size) |
441 | case *types.Struct: |
442 | return asmStruct |
443 | case *types.Interface: |
444 | if t.Empty() { |
445 | return asmEmptyInterface |
446 | } |
447 | return asmInterface |
448 | case *types.Array: |
449 | return asmArray |
450 | case *types.Slice: |
451 | return asmSlice |
452 | } |
453 | panic("unreachable") |
454 | } |
455 | |
456 | // A component is an assembly-addressable component of a composite type, |
457 | // or a composite type itself. |
458 | type component struct { |
459 | size int |
460 | offset int |
461 | kind asmKind |
462 | typ string |
463 | suffix string // Such as _base for string base, _0_lo for lo half of first element of [1]uint64 on 32 bit machine. |
464 | outer string // The suffix for immediately containing composite type. |
465 | } |
466 | |
467 | func newComponent(suffix string, kind asmKind, typ string, offset, size int, outer string) component { |
468 | return component{suffix: suffix, kind: kind, typ: typ, offset: offset, size: size, outer: outer} |
469 | } |
470 | |
471 | // componentsOfType generates a list of components of type t. |
472 | // For example, given string, the components are the string itself, the base, and the length. |
473 | func componentsOfType(arch *asmArch, t types.Type) []component { |
474 | return appendComponentsRecursive(arch, t, nil, "", 0) |
475 | } |
476 | |
477 | // appendComponentsRecursive implements componentsOfType. |
478 | // Recursion is required to correct handle structs and arrays, |
479 | // which can contain arbitrary other types. |
480 | func appendComponentsRecursive(arch *asmArch, t types.Type, cc []component, suffix string, off int) []component { |
481 | s := t.String() |
482 | size := int(arch.sizes.Sizeof(t)) |
483 | kind := asmKindForType(t, size) |
484 | cc = append(cc, newComponent(suffix, kind, s, off, size, suffix)) |
485 | |
486 | switch kind { |
487 | case 8: |
488 | if arch.ptrSize == 4 { |
489 | w1, w2 := "lo", "hi" |
490 | if arch.bigEndian { |
491 | w1, w2 = w2, w1 |
492 | } |
493 | cc = append(cc, newComponent(suffix+"_"+w1, 4, "half "+s, off, 4, suffix)) |
494 | cc = append(cc, newComponent(suffix+"_"+w2, 4, "half "+s, off+4, 4, suffix)) |
495 | } |
496 | |
497 | case asmEmptyInterface: |
498 | cc = append(cc, newComponent(suffix+"_type", asmKind(arch.ptrSize), "interface type", off, arch.ptrSize, suffix)) |
499 | cc = append(cc, newComponent(suffix+"_data", asmKind(arch.ptrSize), "interface data", off+arch.ptrSize, arch.ptrSize, suffix)) |
500 | |
501 | case asmInterface: |
502 | cc = append(cc, newComponent(suffix+"_itable", asmKind(arch.ptrSize), "interface itable", off, arch.ptrSize, suffix)) |
503 | cc = append(cc, newComponent(suffix+"_data", asmKind(arch.ptrSize), "interface data", off+arch.ptrSize, arch.ptrSize, suffix)) |
504 | |
505 | case asmSlice: |
506 | cc = append(cc, newComponent(suffix+"_base", asmKind(arch.ptrSize), "slice base", off, arch.ptrSize, suffix)) |
507 | cc = append(cc, newComponent(suffix+"_len", asmKind(arch.intSize), "slice len", off+arch.ptrSize, arch.intSize, suffix)) |
508 | cc = append(cc, newComponent(suffix+"_cap", asmKind(arch.intSize), "slice cap", off+arch.ptrSize+arch.intSize, arch.intSize, suffix)) |
509 | |
510 | case asmString: |
511 | cc = append(cc, newComponent(suffix+"_base", asmKind(arch.ptrSize), "string base", off, arch.ptrSize, suffix)) |
512 | cc = append(cc, newComponent(suffix+"_len", asmKind(arch.intSize), "string len", off+arch.ptrSize, arch.intSize, suffix)) |
513 | |
514 | case asmComplex: |
515 | fsize := size / 2 |
516 | cc = append(cc, newComponent(suffix+"_real", asmKind(fsize), fmt.Sprintf("real(complex%d)", size*8), off, fsize, suffix)) |
517 | cc = append(cc, newComponent(suffix+"_imag", asmKind(fsize), fmt.Sprintf("imag(complex%d)", size*8), off+fsize, fsize, suffix)) |
518 | |
519 | case asmStruct: |
520 | tu := t.Underlying().(*types.Struct) |
521 | fields := make([]*types.Var, tu.NumFields()) |
522 | for i := 0; i < tu.NumFields(); i++ { |
523 | fields[i] = tu.Field(i) |
524 | } |
525 | offsets := arch.sizes.Offsetsof(fields) |
526 | for i, f := range fields { |
527 | cc = appendComponentsRecursive(arch, f.Type(), cc, suffix+"_"+f.Name(), off+int(offsets[i])) |
528 | } |
529 | |
530 | case asmArray: |
531 | tu := t.Underlying().(*types.Array) |
532 | elem := tu.Elem() |
533 | // Calculate offset of each element array. |
534 | fields := []*types.Var{ |
535 | types.NewVar(token.NoPos, nil, "fake0", elem), |
536 | types.NewVar(token.NoPos, nil, "fake1", elem), |
537 | } |
538 | offsets := arch.sizes.Offsetsof(fields) |
539 | elemoff := int(offsets[1]) |
540 | for i := 0; i < int(tu.Len()); i++ { |
541 | cc = appendComponentsRecursive(arch, elem, cc, suffix+"_"+strconv.Itoa(i), off+i*elemoff) |
542 | } |
543 | } |
544 | |
545 | return cc |
546 | } |
547 | |
548 | // asmParseDecl parses a function decl for expected assembly variables. |
549 | func asmParseDecl(pass *analysis.Pass, decl *ast.FuncDecl) map[string]*asmFunc { |
550 | var ( |
551 | arch *asmArch |
552 | fn *asmFunc |
553 | offset int |
554 | ) |
555 | |
556 | // addParams adds asmVars for each of the parameters in list. |
557 | // isret indicates whether the list are the arguments or the return values. |
558 | // TODO(adonovan): simplify by passing (*types.Signature).{Params,Results} |
559 | // instead of list. |
560 | addParams := func(list []*ast.Field, isret bool) { |
561 | argnum := 0 |
562 | for _, fld := range list { |
563 | t := pass.TypesInfo.Types[fld.Type].Type |
564 | |
565 | // Work around https://golang.org/issue/28277. |
566 | if t == nil { |
567 | if ell, ok := fld.Type.(*ast.Ellipsis); ok { |
568 | t = types.NewSlice(pass.TypesInfo.Types[ell.Elt].Type) |
569 | } |
570 | } |
571 | |
572 | align := int(arch.sizes.Alignof(t)) |
573 | size := int(arch.sizes.Sizeof(t)) |
574 | offset += -offset & (align - 1) |
575 | cc := componentsOfType(arch, t) |
576 | |
577 | // names is the list of names with this type. |
578 | names := fld.Names |
579 | if len(names) == 0 { |
580 | // Anonymous args will be called arg, arg1, arg2, ... |
581 | // Similarly so for return values: ret, ret1, ret2, ... |
582 | name := "arg" |
583 | if isret { |
584 | name = "ret" |
585 | } |
586 | if argnum > 0 { |
587 | name += strconv.Itoa(argnum) |
588 | } |
589 | names = []*ast.Ident{ast.NewIdent(name)} |
590 | } |
591 | argnum += len(names) |
592 | |
593 | // Create variable for each name. |
594 | for _, id := range names { |
595 | name := id.Name |
596 | for _, c := range cc { |
597 | outer := name + c.outer |
598 | v := asmVar{ |
599 | name: name + c.suffix, |
600 | kind: c.kind, |
601 | typ: c.typ, |
602 | off: offset + c.offset, |
603 | size: c.size, |
604 | } |
605 | if vo := fn.vars[outer]; vo != nil { |
606 | vo.inner = append(vo.inner, &v) |
607 | } |
608 | fn.vars[v.name] = &v |
609 | for i := 0; i < v.size; i++ { |
610 | fn.varByOffset[v.off+i] = &v |
611 | } |
612 | } |
613 | offset += size |
614 | } |
615 | } |
616 | } |
617 | |
618 | m := make(map[string]*asmFunc) |
619 | for _, arch = range arches { |
620 | fn = &asmFunc{ |
621 | arch: arch, |
622 | vars: make(map[string]*asmVar), |
623 | varByOffset: make(map[int]*asmVar), |
624 | } |
625 | offset = 0 |
626 | addParams(decl.Type.Params.List, false) |
627 | if decl.Type.Results != nil && len(decl.Type.Results.List) > 0 { |
628 | offset += -offset & (arch.maxAlign - 1) |
629 | addParams(decl.Type.Results.List, true) |
630 | } |
631 | fn.size = offset |
632 | m[arch.name] = fn |
633 | } |
634 | |
635 | return m |
636 | } |
637 | |
638 | // asmCheckVar checks a single variable reference. |
639 | func asmCheckVar(badf func(string, ...interface{}), fn *asmFunc, line, expr string, off int, v *asmVar, archDef *asmArch) { |
640 | m := asmOpcode.FindStringSubmatch(line) |
641 | if m == nil { |
642 | if !strings.HasPrefix(strings.TrimSpace(line), "//") { |
643 | badf("cannot find assembly opcode") |
644 | } |
645 | return |
646 | } |
647 | |
648 | addr := strings.HasPrefix(expr, "$") |
649 | |
650 | // Determine operand sizes from instruction. |
651 | // Typically the suffix suffices, but there are exceptions. |
652 | var src, dst, kind asmKind |
653 | op := m[1] |
654 | switch fn.arch.name + "." + op { |
655 | case "386.FMOVLP": |
656 | src, dst = 8, 4 |
657 | case "arm.MOVD": |
658 | src = 8 |
659 | case "arm.MOVW": |
660 | src = 4 |
661 | case "arm.MOVH", "arm.MOVHU": |
662 | src = 2 |
663 | case "arm.MOVB", "arm.MOVBU": |
664 | src = 1 |
665 | // LEA* opcodes don't really read the second arg. |
666 | // They just take the address of it. |
667 | case "386.LEAL": |
668 | dst = 4 |
669 | addr = true |
670 | case "amd64.LEAQ": |
671 | dst = 8 |
672 | addr = true |
673 | default: |
674 | switch fn.arch.name { |
675 | case "386", "amd64": |
676 | if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "D") || strings.HasSuffix(op, "DP")) { |
677 | // FMOVDP, FXCHD, etc |
678 | src = 8 |
679 | break |
680 | } |
681 | if strings.HasPrefix(op, "P") && strings.HasSuffix(op, "RD") { |
682 | // PINSRD, PEXTRD, etc |
683 | src = 4 |
684 | break |
685 | } |
686 | if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "F") || strings.HasSuffix(op, "FP")) { |
687 | // FMOVFP, FXCHF, etc |
688 | src = 4 |
689 | break |
690 | } |
691 | if strings.HasSuffix(op, "SD") { |
692 | // MOVSD, SQRTSD, etc |
693 | src = 8 |
694 | break |
695 | } |
696 | if strings.HasSuffix(op, "SS") { |
697 | // MOVSS, SQRTSS, etc |
698 | src = 4 |
699 | break |
700 | } |
701 | if op == "MOVO" || op == "MOVOU" { |
702 | src = 16 |
703 | break |
704 | } |
705 | if strings.HasPrefix(op, "SET") { |
706 | // SETEQ, etc |
707 | src = 1 |
708 | break |
709 | } |
710 | switch op[len(op)-1] { |
711 | case 'B': |
712 | src = 1 |
713 | case 'W': |
714 | src = 2 |
715 | case 'L': |
716 | src = 4 |
717 | case 'D', 'Q': |
718 | src = 8 |
719 | } |
720 | case "ppc64", "ppc64le": |
721 | // Strip standard suffixes to reveal size letter. |
722 | m := ppc64Suff.FindStringSubmatch(op) |
723 | if m != nil { |
724 | switch m[1][0] { |
725 | case 'B': |
726 | src = 1 |
727 | case 'H': |
728 | src = 2 |
729 | case 'W': |
730 | src = 4 |
731 | case 'D': |
732 | src = 8 |
733 | } |
734 | } |
735 | case "loong64", "mips", "mipsle", "mips64", "mips64le": |
736 | switch op { |
737 | case "MOVB", "MOVBU": |
738 | src = 1 |
739 | case "MOVH", "MOVHU": |
740 | src = 2 |
741 | case "MOVW", "MOVWU", "MOVF": |
742 | src = 4 |
743 | case "MOVV", "MOVD": |
744 | src = 8 |
745 | } |
746 | case "s390x": |
747 | switch op { |
748 | case "MOVB", "MOVBZ": |
749 | src = 1 |
750 | case "MOVH", "MOVHZ": |
751 | src = 2 |
752 | case "MOVW", "MOVWZ", "FMOVS": |
753 | src = 4 |
754 | case "MOVD", "FMOVD": |
755 | src = 8 |
756 | } |
757 | } |
758 | } |
759 | if dst == 0 { |
760 | dst = src |
761 | } |
762 | |
763 | // Determine whether the match we're holding |
764 | // is the first or second argument. |
765 | if strings.Index(line, expr) > strings.Index(line, ",") { |
766 | kind = dst |
767 | } else { |
768 | kind = src |
769 | } |
770 | |
771 | vk := v.kind |
772 | vs := v.size |
773 | vt := v.typ |
774 | switch vk { |
775 | case asmInterface, asmEmptyInterface, asmString, asmSlice: |
776 | // allow reference to first word (pointer) |
777 | vk = v.inner[0].kind |
778 | vs = v.inner[0].size |
779 | vt = v.inner[0].typ |
780 | case asmComplex: |
781 | // Allow a single instruction to load both parts of a complex. |
782 | if int(kind) == vs { |
783 | kind = asmComplex |
784 | } |
785 | } |
786 | if addr { |
787 | vk = asmKind(archDef.ptrSize) |
788 | vs = archDef.ptrSize |
789 | vt = "address" |
790 | } |
791 | |
792 | if off != v.off { |
793 | var inner bytes.Buffer |
794 | for i, vi := range v.inner { |
795 | if len(v.inner) > 1 { |
796 | fmt.Fprintf(&inner, ",") |
797 | } |
798 | fmt.Fprintf(&inner, " ") |
799 | if i == len(v.inner)-1 { |
800 | fmt.Fprintf(&inner, "or ") |
801 | } |
802 | fmt.Fprintf(&inner, "%s+%d(FP)", vi.name, vi.off) |
803 | } |
804 | badf("invalid offset %s; expected %s+%d(FP)%s", expr, v.name, v.off, inner.String()) |
805 | return |
806 | } |
807 | if kind != 0 && kind != vk { |
808 | var inner bytes.Buffer |
809 | if len(v.inner) > 0 { |
810 | fmt.Fprintf(&inner, " containing") |
811 | for i, vi := range v.inner { |
812 | if i > 0 && len(v.inner) > 2 { |
813 | fmt.Fprintf(&inner, ",") |
814 | } |
815 | fmt.Fprintf(&inner, " ") |
816 | if i > 0 && i == len(v.inner)-1 { |
817 | fmt.Fprintf(&inner, "and ") |
818 | } |
819 | fmt.Fprintf(&inner, "%s+%d(FP)", vi.name, vi.off) |
820 | } |
821 | } |
822 | badf("invalid %s of %s; %s is %d-byte value%s", op, expr, vt, vs, inner.String()) |
823 | } |
824 | } |
825 |
Members