| // Copyright 2013 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // This file implements Sizes. |
| |
| package types2 |
| |
| // Sizes defines the sizing functions for package unsafe. |
| type Sizes interface { |
| // Alignof returns the alignment of a variable of type T. |
| // Alignof must implement the alignment guarantees required by the spec. |
| // The result must be >= 1. |
| Alignof(T Type) int64 |
| |
| // Offsetsof returns the offsets of the given struct fields, in bytes. |
| // Offsetsof must implement the offset guarantees required by the spec. |
| // A negative entry in the result indicates that the struct is too large. |
| Offsetsof(fields []*Var) []int64 |
| |
| // Sizeof returns the size of a variable of type T. |
| // Sizeof must implement the size guarantees required by the spec. |
| // A negative result indicates that T is too large. |
| Sizeof(T Type) int64 |
| } |
| |
| // StdSizes is a convenience type for creating commonly used Sizes. |
| // It makes the following simplifying assumptions: |
| // |
| // - The size of explicitly sized basic types (int16, etc.) is the |
| // specified size. |
| // - The size of strings and interfaces is 2*WordSize. |
| // - The size of slices is 3*WordSize. |
| // - The size of an array of n elements corresponds to the size of |
| // a struct of n consecutive fields of the array's element type. |
| // - The size of a struct is the offset of the last field plus that |
| // field's size. As with all element types, if the struct is used |
| // in an array its size must first be aligned to a multiple of the |
| // struct's alignment. |
| // - All other types have size WordSize. |
| // - Arrays and structs are aligned per spec definition; all other |
| // types are naturally aligned with a maximum alignment MaxAlign. |
| // |
| // *StdSizes implements Sizes. |
| type StdSizes struct { |
| WordSize int64 // word size in bytes - must be >= 4 (32bits) |
| MaxAlign int64 // maximum alignment in bytes - must be >= 1 |
| } |
| |
| func (s *StdSizes) Alignof(T Type) (result int64) { |
| defer func() { |
| assert(result >= 1) |
| }() |
| |
| // For arrays and structs, alignment is defined in terms |
| // of alignment of the elements and fields, respectively. |
| switch t := under(T).(type) { |
| case *Array: |
| // spec: "For a variable x of array type: unsafe.Alignof(x) |
| // is the same as unsafe.Alignof(x[0]), but at least 1." |
| return s.Alignof(t.elem) |
| case *Struct: |
| if len(t.fields) == 0 && IsSyncAtomicAlign64(T) { |
| // Special case: sync/atomic.align64 is an |
| // empty struct we recognize as a signal that |
| // the struct it contains must be |
| // 64-bit-aligned. |
| // |
| // This logic is equivalent to the logic in |
| // cmd/compile/internal/types/size.go:calcStructOffset |
| return 8 |
| } |
| |
| // spec: "For a variable x of struct type: unsafe.Alignof(x) |
| // is the largest of the values unsafe.Alignof(x.f) for each |
| // field f of x, but at least 1." |
| max := int64(1) |
| for _, f := range t.fields { |
| if a := s.Alignof(f.typ); a > max { |
| max = a |
| } |
| } |
| return max |
| case *Slice, *Interface: |
| // Multiword data structures are effectively structs |
| // in which each element has size WordSize. |
| // Type parameters lead to variable sizes/alignments; |
| // StdSizes.Alignof won't be called for them. |
| assert(!isTypeParam(T)) |
| return s.WordSize |
| case *Basic: |
| // Strings are like slices and interfaces. |
| if t.Info()&IsString != 0 { |
| return s.WordSize |
| } |
| case *TypeParam, *Union: |
| unreachable() |
| } |
| a := s.Sizeof(T) // may be 0 or negative |
| // spec: "For a variable x of any type: unsafe.Alignof(x) is at least 1." |
| if a < 1 { |
| return 1 |
| } |
| // complex{64,128} are aligned like [2]float{32,64}. |
| if isComplex(T) { |
| a /= 2 |
| } |
| if a > s.MaxAlign { |
| return s.MaxAlign |
| } |
| return a |
| } |
| |
| func IsSyncAtomicAlign64(T Type) bool { |
| named, ok := T.(*Named) |
| if !ok { |
| return false |
| } |
| obj := named.Obj() |
| return obj.Name() == "align64" && |
| obj.Pkg() != nil && |
| (obj.Pkg().Path() == "sync/atomic" || |
| obj.Pkg().Path() == "runtime/internal/atomic") |
| } |
| |
| func (s *StdSizes) Offsetsof(fields []*Var) []int64 { |
| offsets := make([]int64, len(fields)) |
| var offs int64 |
| for i, f := range fields { |
| if offs < 0 { |
| // all remaining offsets are too large |
| offsets[i] = -1 |
| continue |
| } |
| // offs >= 0 |
| a := s.Alignof(f.typ) |
| offs = align(offs, a) // possibly < 0 if align overflows |
| offsets[i] = offs |
| if d := s.Sizeof(f.typ); d >= 0 && offs >= 0 { |
| offs += d // ok to overflow to < 0 |
| } else { |
| offs = -1 // f.typ or offs is too large |
| } |
| } |
| return offsets |
| } |
| |
| var basicSizes = [...]byte{ |
| Bool: 1, |
| Int8: 1, |
| Int16: 2, |
| Int32: 4, |
| Int64: 8, |
| Uint8: 1, |
| Uint16: 2, |
| Uint32: 4, |
| Uint64: 8, |
| Float32: 4, |
| Float64: 8, |
| Complex64: 8, |
| Complex128: 16, |
| } |
| |
| func (s *StdSizes) Sizeof(T Type) int64 { |
| switch t := under(T).(type) { |
| case *Basic: |
| assert(isTyped(T)) |
| k := t.kind |
| if int(k) < len(basicSizes) { |
| if s := basicSizes[k]; s > 0 { |
| return int64(s) |
| } |
| } |
| if k == String { |
| return s.WordSize * 2 |
| } |
| case *Array: |
| n := t.len |
| if n <= 0 { |
| return 0 |
| } |
| // n > 0 |
| esize := s.Sizeof(t.elem) |
| if esize < 0 { |
| return -1 // element too large |
| } |
| if esize == 0 { |
| return 0 // 0-size element |
| } |
| // esize > 0 |
| a := s.Alignof(t.elem) |
| ea := align(esize, a) // possibly < 0 if align overflows |
| if ea < 0 { |
| return -1 |
| } |
| // ea >= 1 |
| n1 := n - 1 // n1 >= 0 |
| // Final size is ea*n1 + esize; and size must be <= maxInt64. |
| const maxInt64 = 1<<63 - 1 |
| if n1 > 0 && ea > maxInt64/n1 { |
| return -1 // ea*n1 overflows |
| } |
| return ea*n1 + esize // may still overflow to < 0 which is ok |
| case *Slice: |
| return s.WordSize * 3 |
| case *Struct: |
| n := t.NumFields() |
| if n == 0 { |
| return 0 |
| } |
| offsets := s.Offsetsof(t.fields) |
| offs := offsets[n-1] |
| size := s.Sizeof(t.fields[n-1].typ) |
| if offs < 0 || size < 0 { |
| return -1 // type too large |
| } |
| return offs + size // may overflow to < 0 which is ok |
| case *Interface: |
| // Type parameters lead to variable sizes/alignments; |
| // StdSizes.Sizeof won't be called for them. |
| assert(!isTypeParam(T)) |
| return s.WordSize * 2 |
| case *TypeParam, *Union: |
| unreachable() |
| } |
| return s.WordSize // catch-all |
| } |
| |
| // common architecture word sizes and alignments |
| var gcArchSizes = map[string]*StdSizes{ |
| "386": {4, 4}, |
| "amd64": {8, 8}, |
| "amd64p32": {4, 8}, |
| "arm": {4, 4}, |
| "arm64": {8, 8}, |
| "loong64": {8, 8}, |
| "mips": {4, 4}, |
| "mipsle": {4, 4}, |
| "mips64": {8, 8}, |
| "mips64le": {8, 8}, |
| "ppc64": {8, 8}, |
| "ppc64le": {8, 8}, |
| "riscv64": {8, 8}, |
| "s390x": {8, 8}, |
| "sparc64": {8, 8}, |
| "wasm": {8, 8}, |
| // When adding more architectures here, |
| // update the doc string of SizesFor below. |
| } |
| |
| // SizesFor returns the Sizes used by a compiler for an architecture. |
| // The result is nil if a compiler/architecture pair is not known. |
| // |
| // Supported architectures for compiler "gc": |
| // "386", "amd64", "amd64p32", "arm", "arm64", "loong64", "mips", "mipsle", |
| // "mips64", "mips64le", "ppc64", "ppc64le", "riscv64", "s390x", "sparc64", "wasm". |
| func SizesFor(compiler, arch string) Sizes { |
| var m map[string]*StdSizes |
| switch compiler { |
| case "gc": |
| m = gcArchSizes |
| case "gccgo": |
| m = gccgoArchSizes |
| default: |
| return nil |
| } |
| s, ok := m[arch] |
| if !ok { |
| return nil |
| } |
| return s |
| } |
| |
| // stdSizes is used if Config.Sizes == nil. |
| var stdSizes = SizesFor("gc", "amd64") |
| |
| func (conf *Config) alignof(T Type) int64 { |
| f := stdSizes.Alignof |
| if conf.Sizes != nil { |
| f = conf.Sizes.Alignof |
| } |
| if a := f(T); a >= 1 { |
| return a |
| } |
| panic("implementation of alignof returned an alignment < 1") |
| } |
| |
| func (conf *Config) offsetsof(T *Struct) []int64 { |
| var offsets []int64 |
| if T.NumFields() > 0 { |
| // compute offsets on demand |
| f := stdSizes.Offsetsof |
| if conf.Sizes != nil { |
| f = conf.Sizes.Offsetsof |
| } |
| offsets = f(T.fields) |
| // sanity checks |
| if len(offsets) != T.NumFields() { |
| panic("implementation of offsetsof returned the wrong number of offsets") |
| } |
| } |
| return offsets |
| } |
| |
| // offsetof returns the offset of the field specified via |
| // the index sequence relative to T. All embedded fields |
| // must be structs (rather than pointers to structs). |
| // If the offset is too large (because T is too large), |
| // the result is negative. |
| func (conf *Config) offsetof(T Type, index []int) int64 { |
| var offs int64 |
| for _, i := range index { |
| s := under(T).(*Struct) |
| d := conf.offsetsof(s)[i] |
| if d < 0 { |
| return -1 |
| } |
| offs += d |
| if offs < 0 { |
| return -1 |
| } |
| T = s.fields[i].typ |
| } |
| return offs |
| } |
| |
| // sizeof returns the size of T. |
| // If T is too large, the result is negative. |
| func (conf *Config) sizeof(T Type) int64 { |
| f := stdSizes.Sizeof |
| if conf.Sizes != nil { |
| f = conf.Sizes.Sizeof |
| } |
| return f(T) |
| } |
| |
| // align returns the smallest y >= x such that y % a == 0. |
| // a must be within 1 and 8 and it must be a power of 2. |
| // The result may be negative due to overflow. |
| func align(x, a int64) int64 { |
| assert(x >= 0 && 1 <= a && a <= 8 && a&(a-1) == 0) |
| return (x + a - 1) &^ (a - 1) |
| } |