|
| 1 | +// Copyright 2023 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 unitchecker |
| 6 | + |
| 7 | +import ( |
| 8 | + "fmt" |
| 9 | + "go/types" |
| 10 | +) |
| 11 | + |
| 12 | +type gcSizes struct { |
| 13 | + WordSize int64 // word size in bytes - must be >= 4 (32bits) |
| 14 | + MaxAlign int64 // maximum alignment in bytes - must be >= 1 |
| 15 | +} |
| 16 | + |
| 17 | +func (s *gcSizes) Alignof(T types.Type) int64 { |
| 18 | + // For arrays and structs, alignment is defined in terms |
| 19 | + // of alignment of the elements and fields, respectively. |
| 20 | + switch t := T.Underlying().(type) { |
| 21 | + case *types.Array: |
| 22 | + // spec: "For a variable x of array type: unsafe.Alignof(x) |
| 23 | + // is the same as unsafe.Alignof(x[0]), but at least 1." |
| 24 | + return s.Alignof(t.Elem()) |
| 25 | + case *types.Struct: |
| 26 | + if t.NumFields() == 0 && isSyncAtomicAlign64(T) { |
| 27 | + // Special case: sync/atomic.align64 is an |
| 28 | + // empty struct we recognize as a signal that |
| 29 | + // the struct it contains must be |
| 30 | + // 64-bit-aligned. |
| 31 | + // |
| 32 | + // This logic is equivalent to the logic in |
| 33 | + // cmd/compile/internal/types/size.go:calcStructOffset |
| 34 | + return 8 |
| 35 | + } |
| 36 | + |
| 37 | + // spec: "For a variable x of struct type: unsafe.Alignof(x) |
| 38 | + // is the largest of the values unsafe.Alignof(x.f) for each |
| 39 | + // field f of x, but at least 1." |
| 40 | + max := int64(1) |
| 41 | + for i, nf := 0, t.NumFields(); i < nf; i++ { |
| 42 | + if a := s.Alignof(t.Field(i).Type()); a > max { |
| 43 | + max = a |
| 44 | + } |
| 45 | + } |
| 46 | + return max |
| 47 | + case *types.Slice, *types.Interface: |
| 48 | + // Multiword data structures are effectively structs |
| 49 | + // in which each element has size PtrSize. |
| 50 | + return s.WordSize |
| 51 | + case *types.Basic: |
| 52 | + // Strings are like slices and interfaces. |
| 53 | + if t.Info()&types.IsString != 0 { |
| 54 | + return s.WordSize |
| 55 | + } |
| 56 | + } |
| 57 | + a := s.Sizeof(T) // may be 0 |
| 58 | + // spec: "For a variable x of any type: unsafe.Alignof(x) is at least 1." |
| 59 | + if a < 1 { |
| 60 | + return 1 |
| 61 | + } |
| 62 | + // complex{64,128} are aligned like [2]float{32,64}. |
| 63 | + if isComplex(T) { |
| 64 | + a /= 2 |
| 65 | + } |
| 66 | + if a > s.MaxAlign { |
| 67 | + return s.MaxAlign |
| 68 | + } |
| 69 | + return a |
| 70 | +} |
| 71 | + |
| 72 | +func isComplex(T types.Type) bool { |
| 73 | + basic, ok := T.Underlying().(*types.Basic) |
| 74 | + return ok && basic.Info()&types.IsComplex != 0 |
| 75 | +} |
| 76 | + |
| 77 | +func (s *gcSizes) Offsetsof(fields []*types.Var) []int64 { |
| 78 | + offsets := make([]int64, len(fields)) |
| 79 | + var offs int64 |
| 80 | + for i, f := range fields { |
| 81 | + if offs < 0 { |
| 82 | + // all remaining offsets are too large |
| 83 | + offsets[i] = -1 |
| 84 | + continue |
| 85 | + } |
| 86 | + // offs >= 0 |
| 87 | + typ := f.Type() |
| 88 | + a := s.Alignof(typ) |
| 89 | + offs = roundUp(offs, a) // possibly < 0 if align overflows |
| 90 | + offsets[i] = offs |
| 91 | + if d := s.Sizeof(typ); d >= 0 && offs >= 0 { |
| 92 | + offs += d // ok to overflow to < 0 |
| 93 | + } else { |
| 94 | + offs = -1 |
| 95 | + } |
| 96 | + } |
| 97 | + return offsets |
| 98 | +} |
| 99 | + |
| 100 | +func (s *gcSizes) Sizeof(T types.Type) int64 { |
| 101 | + switch t := T.Underlying().(type) { |
| 102 | + case *types.Basic: |
| 103 | + k := t.Kind() |
| 104 | + if int(k) < len(basicSizes) { |
| 105 | + if s := basicSizes[k]; s > 0 { |
| 106 | + return int64(s) |
| 107 | + } |
| 108 | + } |
| 109 | + switch k { |
| 110 | + case types.String: |
| 111 | + return s.WordSize * 2 |
| 112 | + case types.Int, types.Uint, types.Uintptr, types.UnsafePointer: |
| 113 | + return s.WordSize |
| 114 | + } |
| 115 | + panic(fmt.Sprintf("unimplemented basic: %v (kind %v)", T, k)) |
| 116 | + case *types.Array: |
| 117 | + n := t.Len() |
| 118 | + if n <= 0 { |
| 119 | + return 0 |
| 120 | + } |
| 121 | + // n > 0 |
| 122 | + // gc: Size includes alignment padding. |
| 123 | + esize := s.Sizeof(t.Elem()) |
| 124 | + if esize < 0 { |
| 125 | + return -1 // array element too large |
| 126 | + } |
| 127 | + if esize == 0 { |
| 128 | + return 0 // 0-size element |
| 129 | + } |
| 130 | + // esize > 0 |
| 131 | + // Final size is esize * n; and size must be <= maxInt64. |
| 132 | + const maxInt64 = 1<<63 - 1 |
| 133 | + if esize > maxInt64/n { |
| 134 | + return -1 // esize * n overflows |
| 135 | + } |
| 136 | + return esize * n |
| 137 | + case *types.Slice: |
| 138 | + return s.WordSize * 3 |
| 139 | + case *types.Struct: |
| 140 | + n := t.NumFields() |
| 141 | + if n == 0 { |
| 142 | + return 0 |
| 143 | + } |
| 144 | + // n > 0 |
| 145 | + fields := make([]*types.Var, n) |
| 146 | + for i := range fields { |
| 147 | + fields[i] = t.Field(i) |
| 148 | + } |
| 149 | + offsets := s.Offsetsof(fields) |
| 150 | + // gc: The last field of a non-zero-sized struct is not allowed to |
| 151 | + // have size 0. |
| 152 | + last := s.Sizeof(fields[n-1].Type()) |
| 153 | + if last == 0 && offsets[n-1] > 0 { |
| 154 | + last = 1 |
| 155 | + } |
| 156 | + // gc: Size includes alignment padding. |
| 157 | + return roundUp(offsets[n-1]+last, s.Alignof(t)) // may overflow to < 0 which is ok |
| 158 | + case *types.Interface: |
| 159 | + return s.WordSize * 2 |
| 160 | + case *types.Chan, *types.Map, *types.Pointer, *types.Signature: |
| 161 | + return s.WordSize |
| 162 | + default: |
| 163 | + panic(fmt.Sprintf("Sizeof(%T) unimplemented", t)) |
| 164 | + } |
| 165 | +} |
| 166 | + |
| 167 | +func isSyncAtomicAlign64(T types.Type) bool { |
| 168 | + named, ok := T.(*types.Named) |
| 169 | + if !ok { |
| 170 | + return false |
| 171 | + } |
| 172 | + obj := named.Obj() |
| 173 | + return obj.Name() == "align64" && |
| 174 | + obj.Pkg() != nil && |
| 175 | + (obj.Pkg().Path() == "sync/atomic" || |
| 176 | + obj.Pkg().Path() == "runtime/internal/atomic") |
| 177 | +} |
| 178 | + |
| 179 | +// roundUp rounds o to a multiple of r, r is a power of 2. |
| 180 | +func roundUp(o int64, r int64) int64 { |
| 181 | + if r < 1 || r > 8 || r&(r-1) != 0 { |
| 182 | + panic(fmt.Sprintf("Round %d", r)) |
| 183 | + } |
| 184 | + return (o + r - 1) &^ (r - 1) |
| 185 | +} |
| 186 | + |
| 187 | +var basicSizes = [...]byte{ |
| 188 | + types.Invalid: 1, |
| 189 | + types.Bool: 1, |
| 190 | + types.Int8: 1, |
| 191 | + types.Int16: 2, |
| 192 | + types.Int32: 4, |
| 193 | + types.Int64: 8, |
| 194 | + types.Uint8: 1, |
| 195 | + types.Uint16: 2, |
| 196 | + types.Uint32: 4, |
| 197 | + types.Uint64: 8, |
| 198 | + types.Float32: 4, |
| 199 | + types.Float64: 8, |
| 200 | + types.Complex64: 8, |
| 201 | + types.Complex128: 16, |
| 202 | +} |
| 203 | + |
| 204 | +// common architecture word sizes and alignments |
| 205 | +var gcArchSizes = map[string]*gcSizes{ |
| 206 | + "386": {4, 4}, |
| 207 | + "amd64": {8, 8}, |
| 208 | + "amd64p32": {4, 8}, |
| 209 | + "arm": {4, 4}, |
| 210 | + "arm64": {8, 8}, |
| 211 | + "loong64": {8, 8}, |
| 212 | + "mips": {4, 4}, |
| 213 | + "mipsle": {4, 4}, |
| 214 | + "mips64": {8, 8}, |
| 215 | + "mips64le": {8, 8}, |
| 216 | + "ppc64": {8, 8}, |
| 217 | + "ppc64le": {8, 8}, |
| 218 | + "riscv64": {8, 8}, |
| 219 | + "s390x": {8, 8}, |
| 220 | + "sparc64": {8, 8}, |
| 221 | + "wasm": {8, 8}, |
| 222 | + // When adding more architectures here, |
| 223 | + // update the doc string of sizesFor below. |
| 224 | +} |
| 225 | + |
| 226 | +// sizesFor returns the go/types.Sizes used by a compiler for an architecture. |
| 227 | +// The result is nil if a compiler/architecture pair is not known. |
| 228 | +func sizesFor(compiler, arch string) types.Sizes { |
| 229 | + if compiler != "gc" { |
| 230 | + return nil |
| 231 | + } |
| 232 | + s, ok := gcArchSizes[arch] |
| 233 | + if !ok { |
| 234 | + return nil |
| 235 | + } |
| 236 | + return s |
| 237 | +} |
0 commit comments