// 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 ( *StdSizes) ( Type) int64 {
	// For arrays and structs, alignment is defined in terms
	// of alignment of the elements and fields, respectively.
	switch t := .Underlying().(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 .(.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."
		 := int64(1)
		for ,  := range .fields {
			if  := .(.typ);  >  {
				 = 
			}
		}
		return 
	case *Slice, *Interface:
		// Multiword data structures are effectively structs
		// in which each element has size WordSize.
		return .WordSize
	case *Basic:
		// Strings are like slices and interfaces.
		if .Info()&IsString != 0 {
			return .WordSize
		}
	}
	 := .Sizeof() // may be 0
	// spec: "For a variable x of any type: unsafe.Alignof(x) is at least 1."
	if  < 1 {
		return 1
	}
	// complex{64,128} are aligned like [2]float{32,64}.
	if isComplex() {
		 /= 2
	}
	if  > .MaxAlign {
		return .MaxAlign
	}
	return 
}

func ( *StdSizes) ( []*Var) []int64 {
	 := make([]int64, len())
	var  int64
	for ,  := range  {
		 := .Alignof(.typ)
		 = align(, )
		[] = 
		 += .Sizeof(.typ)
	}
	return 
}

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 ( *StdSizes) ( Type) int64 {
	switch t := .Underlying().(type) {
	case *Basic:
		assert(isTyped())
		 := .kind
		if int() < len(basicSizes) {
			if  := basicSizes[];  > 0 {
				return int64()
			}
		}
		if  == String {
			return .WordSize * 2
		}
	case *Array:
		 := .len
		if  <= 0 {
			return 0
		}
		// n > 0
		 := .Alignof(.elem)
		 := .(.elem)
		return align(, )*(-1) + 
	case *Slice:
		return .WordSize * 3
	case *Struct:
		 := .NumFields()
		if  == 0 {
			return 0
		}
		 := .Offsetsof(.fields)
		return [-1] + .(.fields[-1].typ)
	case *Interface:
		return .WordSize * 2
	}
	return .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 (,  string) Sizes {
	var  map[string]*StdSizes
	switch  {
	case "gc":
		 = gcArchSizes
	case "gccgo":
		 = gccgoArchSizes
	default:
		return nil
	}
	,  := []
	if ! {
		return nil
	}
	return 
}

// stdSizes is used if Config.Sizes == nil.
var stdSizes = SizesFor("gc", "amd64")

func ( *Config) ( Type) int64 {
	if  := .Sizes;  != nil {
		if  := .Alignof();  >= 1 {
			return 
		}
		panic("Config.Sizes.Alignof returned an alignment < 1")
	}
	return stdSizes.Alignof()
}

func ( *Config) ( *Struct) []int64 {
	var  []int64
	if .NumFields() > 0 {
		// compute offsets on demand
		if  := .Sizes;  != nil {
			 = .Offsetsof(.fields)
			// sanity checks
			if len() != .NumFields() {
				panic("Config.Sizes.Offsetsof returned the wrong number of offsets")
			}
			for ,  := range  {
				if  < 0 {
					panic("Config.Sizes.Offsetsof returned an offset < 0")
				}
			}
		} else {
			 = stdSizes.Offsetsof(.fields)
		}
	}
	return 
}

// 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 ( *Config) ( Type,  []int) int64 {
	var  int64
	for ,  := range  {
		 := .Underlying().(*Struct)
		 += .offsetsof()[]
		 = .fields[].typ
	}
	return 
}

func ( *Config) ( Type) int64 {
	if  := .Sizes;  != nil {
		if  := .Sizeof();  >= 0 {
			return 
		}
		panic("Config.Sizes.Sizeof returned a size < 0")
	}
	return stdSizes.Sizeof()
}

// align returns the smallest y >= x such that y % a == 0.
func align(,  int64) int64 {
	 :=  +  - 1
	return  - %
}