github.com/twelsh-aw/go/src@v0.0.0-20230516233729-a56fe86a7c81/runtime/align_test.go (about) 1 // Copyright 2022 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 runtime_test 6 7 import ( 8 "go/ast" 9 "go/build" 10 "go/importer" 11 "go/parser" 12 "go/printer" 13 "go/token" 14 "go/types" 15 "internal/testenv" 16 "os" 17 "regexp" 18 "runtime" 19 "strings" 20 "testing" 21 ) 22 23 // Check that 64-bit fields on which we apply atomic operations 24 // are aligned to 8 bytes. This can be a problem on 32-bit systems. 25 func TestAtomicAlignment(t *testing.T) { 26 testenv.MustHaveGoBuild(t) // go command needed to resolve std .a files for importer.Default(). 27 28 // Read the code making the tables above, to see which fields and 29 // variables we are currently checking. 30 checked := map[string]bool{} 31 x, err := os.ReadFile("./align_runtime_test.go") 32 if err != nil { 33 t.Fatalf("read failed: %v", err) 34 } 35 fieldDesc := map[int]string{} 36 r := regexp.MustCompile(`unsafe[.]Offsetof[(](\w+){}[.](\w+)[)]`) 37 matches := r.FindAllStringSubmatch(string(x), -1) 38 for i, v := range matches { 39 checked["field runtime."+v[1]+"."+v[2]] = true 40 fieldDesc[i] = v[1] + "." + v[2] 41 } 42 varDesc := map[int]string{} 43 r = regexp.MustCompile(`unsafe[.]Pointer[(]&(\w+)[)]`) 44 matches = r.FindAllStringSubmatch(string(x), -1) 45 for i, v := range matches { 46 checked["var "+v[1]] = true 47 varDesc[i] = v[1] 48 } 49 50 // Check all of our alignments. This is the actual core of the test. 51 for i, d := range runtime.AtomicFields { 52 if d%8 != 0 { 53 t.Errorf("field alignment of %s failed: offset is %d", fieldDesc[i], d) 54 } 55 } 56 for i, p := range runtime.AtomicVariables { 57 if uintptr(p)%8 != 0 { 58 t.Errorf("variable alignment of %s failed: address is %x", varDesc[i], p) 59 } 60 } 61 62 // The code above is the actual test. The code below attempts to check 63 // that the tables used by the code above are exhaustive. 64 65 // Parse the whole runtime package, checking that arguments of 66 // appropriate atomic operations are in the list above. 67 fset := token.NewFileSet() 68 m, err := parser.ParseDir(fset, ".", nil, 0) 69 if err != nil { 70 t.Fatalf("parsing runtime failed: %v", err) 71 } 72 pkg := m["runtime"] // Note: ignore runtime_test and main packages 73 74 // Filter files by those for the current architecture/os being tested. 75 fileMap := map[string]bool{} 76 for _, f := range buildableFiles(t, ".") { 77 fileMap[f] = true 78 } 79 var files []*ast.File 80 for fname, f := range pkg.Files { 81 if fileMap[fname] { 82 files = append(files, f) 83 } 84 } 85 86 // Call go/types to analyze the runtime package. 87 var info types.Info 88 info.Types = map[ast.Expr]types.TypeAndValue{} 89 conf := types.Config{Importer: importer.Default()} 90 _, err = conf.Check("runtime", fset, files, &info) 91 if err != nil { 92 t.Fatalf("typechecking runtime failed: %v", err) 93 } 94 95 // Analyze all atomic.*64 callsites. 96 v := Visitor{t: t, fset: fset, types: info.Types, checked: checked} 97 ast.Walk(&v, pkg) 98 } 99 100 type Visitor struct { 101 fset *token.FileSet 102 types map[ast.Expr]types.TypeAndValue 103 checked map[string]bool 104 t *testing.T 105 } 106 107 func (v *Visitor) Visit(n ast.Node) ast.Visitor { 108 c, ok := n.(*ast.CallExpr) 109 if !ok { 110 return v 111 } 112 f, ok := c.Fun.(*ast.SelectorExpr) 113 if !ok { 114 return v 115 } 116 p, ok := f.X.(*ast.Ident) 117 if !ok { 118 return v 119 } 120 if p.Name != "atomic" { 121 return v 122 } 123 if !strings.HasSuffix(f.Sel.Name, "64") { 124 return v 125 } 126 127 a := c.Args[0] 128 129 // This is a call to atomic.XXX64(a, ...). Make sure a is aligned to 8 bytes. 130 // XXX = one of Load, Store, Cas, etc. 131 // The arg we care about the alignment of is always the first one. 132 133 if u, ok := a.(*ast.UnaryExpr); ok && u.Op == token.AND { 134 v.checkAddr(u.X) 135 return v 136 } 137 138 // Other cases there's nothing we can check. Assume we're ok. 139 v.t.Logf("unchecked atomic operation %s %v", v.fset.Position(n.Pos()), v.print(n)) 140 141 return v 142 } 143 144 // checkAddr checks to make sure n is a properly aligned address for a 64-bit atomic operation. 145 func (v *Visitor) checkAddr(n ast.Node) { 146 switch n := n.(type) { 147 case *ast.IndexExpr: 148 // Alignment of an array element is the same as the whole array. 149 v.checkAddr(n.X) 150 return 151 case *ast.Ident: 152 key := "var " + v.print(n) 153 if !v.checked[key] { 154 v.t.Errorf("unchecked variable %s %s", v.fset.Position(n.Pos()), key) 155 } 156 return 157 case *ast.SelectorExpr: 158 t := v.types[n.X].Type 159 if t == nil { 160 // Not sure what is happening here, go/types fails to 161 // type the selector arg on some platforms. 162 return 163 } 164 if p, ok := t.(*types.Pointer); ok { 165 // Note: we assume here that the pointer p in p.foo is properly 166 // aligned. We just check that foo is at a properly aligned offset. 167 t = p.Elem() 168 } else { 169 v.checkAddr(n.X) 170 } 171 if t.Underlying() == t { 172 v.t.Errorf("analysis can't handle unnamed type %s %v", v.fset.Position(n.Pos()), t) 173 } 174 key := "field " + t.String() + "." + n.Sel.Name 175 if !v.checked[key] { 176 v.t.Errorf("unchecked field %s %s", v.fset.Position(n.Pos()), key) 177 } 178 default: 179 v.t.Errorf("unchecked atomic address %s %v", v.fset.Position(n.Pos()), v.print(n)) 180 181 } 182 } 183 184 func (v *Visitor) print(n ast.Node) string { 185 var b strings.Builder 186 printer.Fprint(&b, v.fset, n) 187 return b.String() 188 } 189 190 // buildableFiles returns the list of files in the given directory 191 // that are actually used for the build, given GOOS/GOARCH restrictions. 192 func buildableFiles(t *testing.T, dir string) []string { 193 ctxt := build.Default 194 ctxt.CgoEnabled = true 195 pkg, err := ctxt.ImportDir(dir, 0) 196 if err != nil { 197 t.Fatalf("can't find buildable files: %v", err) 198 } 199 return pkg.GoFiles 200 }