| 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