Skip to content

structs: add HostLayout "directive" type #66408

Closed
@dr2chase

Description

@dr2chase

Proposal Details

Abstract

This proposes a new package for zero-sized types whose presence in a structure’s list of fields would control how the compiler lays out those fields, for the purpose of allowing programmers to indicate which structures are interchanged with the host platform and to request a host-compatible layout for those structures.

Background

While the Go language specifies very little about struct layout, in practice the Go implementation is tightly constrained to follow platform layout and alignment rules because of the few cases where a struct is interchanged with a platform API (and where this is not true it creates the possibility of incompatibility, for example, ppc64le, where the platform alignment for float64 fields is different from Go's default). This forces tradeoffs or potential problems on platforms whose constraints differ from common-case on other platforms (that is, what the Go compiler has adopted as its default) and prevents field reordering optimizations that can save memory and improve garbage collection performance.

Proposal

To address this, we propose a family of zero-sized types for struct fields to signal differences in layout or alignment where that matters. The change in the compiler’s behavior should be invisible to pure Go programs that do not use unsafe or interact with the host platform. The goal of the proposal is that programmers be able to ensure that data exchanged with the host platform have a host-compatible layout, both now, and in the face of future layout optimizations.

Subject to discussion, the proposal is this package and (for now) this one type:

package structlayout 

// Platform, as a field type, signals that the size, alignment,
// and order of fields conform to requirements of the GOOS-GOARCH
// target platform and may not match the Go compiler’s defaults.
type Platform struct {}

After reflecting on the discussion below, I would modify this to:

package structs 

// HostLayout, as a field type, signals that the size, alignment,
// and order of fields conform to requirements of the host
// platform and may not match the Go compiler’s defaults.
type HostLayout struct {}

the rationale for the name change is that structs is one word, and parallels strings, bytes, and slices, and is generic enough to include other (future) tags specifying "nocopy" or alignment. Furthermore, such type-modifying tags only work within structures; the package name strongly hints at this.

Rationale

One platform, WASM, has system interfaces that align 64-bit types to more than register size and another, ppc64le, has the possibility of non-Go interfaces that align some 64-bit types to less than register size, and both of these are contrary to the rules that Go normally follows (on ppc64le, we have handled this problem using luck). Signaling these constraints explicitly will help compatibility with these two platforms, preserve/allow implementation flexibility, perhaps make it easier to write checking tools, and perhaps (once types passed to all non-Go calls are properly tagged) allow the Go compiler to reorder structures to use less memory and save GC time by shuffling pointers as early as possible in untagged structures. This optimization is desirable because it automates something humans currently spend time on and don't always get right, and sometimes forces programmers to make compromises between most-readable code and best performance.

The most important part of this proposal is that unless someone is writing code that interacts with the platform, they do not need to know about this. If they are writing cgo, these signal types will be inserted for them.

The compiler will know the meaning of these types and modify struct alignment and layouts accordingly. It’s not clear to me whether Platform is adequate to capture all the cases of non-Go code, but for the current use cases (platform interfaces across all the various platforms, and cgo -- as far as I know “platform” describes their needs) it appears that it is.

Why signal types versus //go:platform ?

It is a better match for the Go type system if changes in types are expressed in the type system itself. Use of field signal types meets this requirement, since the Go type of a struct depends on the fields of the struct, even if they have zero width.

Why just one platform tag instead of finer control?

In practice, the use case is platform compatibility, and platform is a concept that the compiler can translate to the appropriate ARCH-OS combination without demanding that the user know the details, and those details also might not be portable across platforms even when the C type declarations are the same.

In the future, we could consider adding signal types for CacheAlign, AtomicAlign, or Packed but I would not include those at first because I'm not that sure we need them, we might argue about definitions, and their implementation (for Packed, at least) would be somewhat more costly. A non-layout signal type that might work well is “NoCopy” to indicate to vet that a type should not be copied once it has a non-zero value (this is currently implemented by vet knowing that certain types are “special”).

Related: “proposal: spec: define/imply memory layout of tagged struct fields #10014”. This was a very similar proposal, approaching the problem from a slightly different direction, but did not address the issue of "the platform does not match Go's defaults". The new proposal here is more concrete in “how”, includes tweaking alignments to conform to platform constraints, but does not expect someone using the platform tag to know precisely what rules a particular platform uses.

Related: “proposal: runtime: add AlignedN types that can be used to increase alignment #19057”. This was a proposal for a family of types for specifying specific alignments, perhaps of specific fields. That proposal had additional use cases -- specifying higher alignment for various fields -- but also did not address the problem of reduced platform alignment (e.g., ppc64le float64) and its application to specific platform interfaces would require that programmers know the details of that platform’s layout rules (instead of the Go compiler/runtime knowing those details once).

Related: “proposal: cmd/compile: make 64-bit fields be 64-bit aligned on 32-bit systems, add //go:packed directive on structs #36606”. This proposal took the opposite approach -- 64-bit atomics require 64-bit alignment on 32-bit processors, therefore Go should change its default layout, rather than signaling specific types that needed this alignment. It also included a secondary proposal for “packed” types that had a far more annoying implementation burden (how is the pointer addressed in a “packed” struct {uint8; *int}? How does the GC find this pointer?)

Compatibility

Working old code will continue to work properly.

Implementation

Besides the proposed package and type, cmd/compile/internal/types/size.go will need adjusting to follow the signal types. It already contains special case code for sync/atomic/align64, so this is not outlandish.

Open issues

The names. For example, “structlayout”, versus “typelayout”? If we decide that this is a good place for 0-width signal types, some of them (NoCopy) aren’t about type layout which means whatever-layout isn’t quite right.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions