// 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 types // 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. 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. Offsetsof(fields []*Var) []int64 // Sizeof returns the size of a variable of type T. // Sizeof must implement the size guarantees required by the spec. 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) int64 { // 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: // 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 // 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 (s *StdSizes) Offsetsof(fields []*Var) []int64 { offsets := make([]int64, len(fields)) var o int64 for i, f := range fields { a := s.Alignof(f.typ) o = align(o, a) offsets[i] = o o += s.Sizeof(f.typ) } 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 a := s.Alignof(t.elem) z := s.Sizeof(t.elem) return align(z, a)*(n-1) + z case *Slice: return s.WordSize * 3 case *Struct: n := t.NumFields() if n == 0 { return 0 } offsets := s.Offsetsof(t.fields) return offsets[n-1] + s.Sizeof(t.fields[n-1].typ) 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}, "arm": {4, 4}, "arm64": {8, 8}, "amd64": {8, 8}, "amd64p32": {4, 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", "arm", "arm64", "amd64", "amd64p32", "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 { if s := conf.Sizes; s != nil { if a := s.Alignof(T); a >= 1 { return a } panic("Config.Sizes.Alignof returned an alignment < 1") } return stdSizes.Alignof(T) } func (conf *Config) offsetsof(T *Struct) []int64 { var offsets []int64 if T.NumFields() > 0 { // compute offsets on demand if s := conf.Sizes; s != nil { offsets = s.Offsetsof(T.fields) // sanity checks if len(offsets) != T.NumFields() { panic("Config.Sizes.Offsetsof returned the wrong number of offsets") } for _, o := range offsets { if o < 0 { panic("Config.Sizes.Offsetsof returned an offset < 0") } } } else { offsets = stdSizes.Offsetsof(T.fields) } } return offsets } // offsetof returns the offset of the field specified via // the index sequence relative to typ. All embedded fields // must be structs (rather than pointer to structs). func (conf *Config) offsetof(typ Type, index []int) int64 { var o int64 for _, i := range index { s := under(typ).(*Struct) o += conf.offsetsof(s)[i] typ = s.fields[i].typ } return o } func (conf *Config) sizeof(T Type) int64 { if s := conf.Sizes; s != nil { if z := s.Sizeof(T); z >= 0 { return z } panic("Config.Sizes.Sizeof returned a size < 0") } return stdSizes.Sizeof(T) } // align returns the smallest y >= x such that y % a == 0. func align(x, a int64) int64 { y := x + a - 1 return y - y%a }