From 1f544ef0ebebd5bc19a8d4d1c6e09ccb01e59b95 Mon Sep 17 00:00:00 2001 From: Matthew Dempsky Date: Sun, 20 Aug 2023 13:14:44 -0700 Subject: [PATCH] cmd/compile/internal/types: refactor struct size calculation This CL simplifies how struct sizes and field offsets are calculated. Change-Id: If4af778cb49218d295277df596e45bdd8b23ed9d Reviewed-on: https://go-review.googlesource.com/c/go/+/521276 Run-TryBot: Matthew Dempsky TryBot-Result: Gopher Robot Reviewed-by: Than McIntosh Reviewed-by: Cuong Manh Le --- src/cmd/compile/internal/types/size.go | 159 +++++++++++-------------- 1 file changed, 70 insertions(+), 89 deletions(-) diff --git a/src/cmd/compile/internal/types/size.go b/src/cmd/compile/internal/types/size.go index d0ad2b13d5..59cf970795 100644 --- a/src/cmd/compile/internal/types/size.go +++ b/src/cmd/compile/internal/types/size.go @@ -157,85 +157,41 @@ func expandiface(t *Type) { t.SetAllMethods(methods) } -func calcStructOffset(errtype *Type, t *Type, o int64, flag int) int64 { - // flag is 0 (receiver), 1 (actual struct), or RegSize (in/out parameters) - isStruct := flag == 1 - starto := o - maxalign := int32(flag) - if maxalign < 1 { - maxalign = 1 - } - // 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 duplicated in go/types and cmd/compile/internal/types2. - if isStruct && t.NumFields() == 0 && t.Sym() != nil && t.Sym().Name == "align64" && isAtomicStdPkg(t.Sym().Pkg) { - maxalign = 8 - } - lastzero := int64(0) - for _, f := range t.Fields() { - if f.Type == nil { - // broken field, just skip it so that other valid fields - // get a width. - continue - } - +// calcStructOffset computes the offsets of a sequence of fields, +// starting at the given offset. It returns the resulting offset and +// maximum field alignment. +func calcStructOffset(t *Type, fields []*Field, offset int64) int64 { + for _, f := range fields { CalcSize(f.Type) - // If type T contains a field F marked as not-in-heap, - // then T must also be a not-in-heap type. Otherwise, - // you could heap allocate T and then get a pointer F, - // which would be a heap pointer to a not-in-heap type. - if f.Type.NotInHeap() { - t.SetNotInHeap(true) - } - if int32(f.Type.align) > maxalign { - maxalign = int32(f.Type.align) - } - if f.Type.align > 0 { - o = RoundUp(o, int64(f.Type.align)) - } - if isStruct { // For receiver/args/results, do not set, it depends on ABI - f.Offset = o - } + offset = RoundUp(offset, int64(f.Type.align)) - w := f.Type.width - if w < 0 { - base.Fatalf("invalid width %d", f.Type.width) - } - if w == 0 { - lastzero = o + if t.IsStruct() { // param offsets depend on ABI + f.Offset = offset + + // If type T contains a field F marked as not-in-heap, + // then T must also be a not-in-heap type. Otherwise, + // you could heap allocate T and then get a pointer F, + // which would be a heap pointer to a not-in-heap type. + if f.Type.NotInHeap() { + t.SetNotInHeap(true) + } } - o += w + + offset += f.Type.width + maxwidth := MaxWidth // On 32-bit systems, reflect tables impose an additional constraint // that each field start offset must fit in 31 bits. if maxwidth < 1<<32 { maxwidth = 1<<31 - 1 } - if o >= maxwidth { - base.ErrorfAt(typePos(errtype), 0, "type %L too large", errtype) - o = 8 // small but nonzero + if offset >= maxwidth { + base.ErrorfAt(typePos(t), 0, "type %L too large", t) + offset = 8 // small but nonzero } } - // For nonzero-sized structs which end in a zero-sized thing, we add - // an extra byte of padding to the type. This padding ensures that - // taking the address of the zero-sized thing can't manufacture a - // pointer to the next object in the heap. See issue 9401. - if flag == 1 && o > starto && o == lastzero { - o++ - } - - // final width is rounded - if flag != 0 { - o = RoundUp(o, int64(maxalign)) - } - t.align = uint8(maxalign) - - // type width only includes back to first field's offset - t.width = o - starto - - return o + return offset } func isAtomicStdPkg(p *Pkg) bool { @@ -411,11 +367,8 @@ func CalcSize(t *Type) { if t.IsFuncArgStruct() { base.Fatalf("CalcSize fn struct %v", t) } - // Recognize and mark runtime/internal/sys.nih as not-in-heap. - if sym := t.Sym(); sym != nil && sym.Pkg.Path == "runtime/internal/sys" && sym.Name == "nih" { - t.SetNotInHeap(true) - } - w = calcStructOffset(t, t, 0, 1) + CalcStructSize(t) + w = t.width // make fake type to check later to // trigger function argument computation. @@ -428,13 +381,13 @@ func CalcSize(t *Type) { // compute their widths as side-effect. case TFUNCARGS: t1 := t.FuncArgs() - w = calcStructOffset(t1, t1.recvsTuple(), 0, 0) - w = calcStructOffset(t1, t1.paramsTuple(), w, RegSize) - w = calcStructOffset(t1, t1.ResultsTuple(), w, RegSize) + // TODO(mdempsky): Should package abi be responsible for computing argwid? + w = calcStructOffset(t1, t1.Recvs(), 0) + w = calcStructOffset(t1, t1.Params(), w) + w = RoundUp(w, int64(RegSize)) + w = calcStructOffset(t1, t1.Results(), w) + w = RoundUp(w, int64(RegSize)) t1.extra.(*Func).Argwid = w - if w%int64(RegSize) != 0 { - base.Warn("bad type %v %d\n", t1, w) - } t.align = 1 } @@ -455,19 +408,47 @@ func CalcSize(t *Type) { ResumeCheckSize() } -// CalcStructSize calculates the size of s, -// filling in s.Width and s.Align, +// CalcStructSize calculates the size of t, +// filling in t.width and t.align, // even if size calculation is otherwise disabled. -func CalcStructSize(s *Type) { - s.width = calcStructOffset(s, s, 0, 1) // sets align -} +func CalcStructSize(t *Type) { + var maxAlign uint8 = 1 + + // Recognize special types. This logic is duplicated in go/types and + // cmd/compile/internal/types2. + if sym := t.Sym(); sym != nil { + switch { + case sym.Name == "align64" && isAtomicStdPkg(sym.Pkg): + maxAlign = 8 + case sym.Pkg.Path == "runtime/internal/sys" && sym.Name == "nih": + t.SetNotInHeap(true) + } + } + + fields := t.Fields() + size := calcStructOffset(t, fields, 0) + + // For non-zero-sized structs which end in a zero-sized field, we + // add an extra byte of padding to the type. This padding ensures + // that taking the address of a zero-sized field can't manufacture a + // pointer to the next object in the heap. See issue 9401. + if size > 0 && fields[len(fields)-1].Type.width == 0 { + size++ + } + + // The alignment of a struct type is the maximum alignment of its + // field types. + for _, field := range fields { + if align := field.Type.align; align > maxAlign { + maxAlign = align + } + } + + // Final size includes trailing padding. + size = RoundUp(size, int64(maxAlign)) -// RecalcSize is like CalcSize, but recalculates t's size even if it -// has already been calculated before. It does not recalculate other -// types. -func RecalcSize(t *Type) { - t.align = 0 - CalcSize(t) + t.width = size + t.align = maxAlign } func (t *Type) widthCalculated() bool { -- 2.48.1