Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd/compile: zero-sized type increases size of struct when it's used as a field #58483

Closed
badisch87 opened this issue Feb 12, 2023 · 3 comments
Closed
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. FrozenDueToAge

Comments

@badisch87
Copy link

What version of Go are you using (go version)?

$ go version
go version go1.20 windows/amd64

Does this issue reproduce with the latest release?

yes

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
...
set GOARCH=amd64
...
set GOHOSTARCH=amd64
set GOHOSTOS=windows
...
set GOOS=windows
...

What did you do?

package main

import (
	"fmt"
	"unsafe"
)

type s1 struct {
	a uint16
}

type s2 struct {
	a uint16
	_ [0]int16
}

func main() {
	fmt.Println(unsafe.Sizeof(s1{}))
	fmt.Println(unsafe.Sizeof(s2{}))
}

What did you expect to see?

2
2

What did you see instead?

2
4
@gopherbot gopherbot added the compiler/runtime Issues related to the Go compiler and/or runtime. label Feb 12, 2023
@robpike
Copy link
Contributor

robpike commented Feb 12, 2023

Not always. See https://go.dev/play/p/b-Cwofn7RmC

Putting the zero-length field in the middle has no effect on the size in this example.

@Jorropo
Copy link
Member

Jorropo commented Feb 13, 2023

This an edge case of an edge case.

The original reason this is done to is to prevent issues when doing stuff like this:

type zero struct{}

type S struct {
	a uint16
	b zero
}

func F(s *S) *zero {
  return &s.b
}

If the struct were the same size of uint16 the pointer returned by F would point to s + sizeof(uint16) this means it would actually point outside of the allocation.
Unlike many other languages, (C, C++, Rust, ...) where creating pointer past the allocation (but not using it) is legal. In go it isn't because the GC may at anytime inspect any pointer.

This is the same reason why slice[x:] compiles to a handfull instruction instead of the 1 or 2 programers may expects (if you reslice a slice to nothing, the pointer to the data get zeroed out in order to not confuse the GC).

In your case your extra empty field is using the blank identifier making expressions like &s._ impossible, so I think for the precise case where the tail zero sized type using using _, the struct could be not extended.
I wonder how much that really happens in practice tho (using traling zero sized types with _ identifier).


Edit: after searching turns out this a real thing being done because of the _ [0]func() pattern which allows to opt out of comparability on your struct types.
https://grep.app/search?q=%5B0%5Dfunc%28%29&filter[repo][0]=golang/go
I see most usages of it in the go std put the blank field as the first field of the struct (probably because they know about this issue). But I found at least one usecase where the order is "wrong" and force a size extension:

@gopherbot
Copy link

Change https://go.dev/cl/467616 mentions this issue: crypto: make edwards25519.Point smaller by reodering blank incomparable field

gopherbot pushed a commit that referenced this issue Feb 13, 2023
Updates #58483

Tested on Linux amd64:
  type Element struct {
    l0, l1, l2, l3, l4 uint64
  }

  type PointAfter struct {
    x, y, z, t Element
    _          incomparable
  }

  type PointBefore struct {
    _          incomparable
    x, y, z, t Element
  }

  type incomparable [0]func()

  func main() {
    fmt.Println(unsafe.Sizeof(PointAfter{})) // 168
    fmt.Println(unsafe.Sizeof(PointBefore{})) // 160
  }

Change-Id: I6c4fcb586bbf3febf62b6e54608496ff81685e43
Reviewed-on: https://go-review.googlesource.com/c/go/+/467616
Reviewed-by: Roland Shoemaker <roland@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Damien Neil <dneil@google.com>
Run-TryBot: Filippo Valsorda <filippo@golang.org>
johanbrandhorst pushed a commit to Pryz/go that referenced this issue Feb 22, 2023
Updates golang#58483

Tested on Linux amd64:
  type Element struct {
    l0, l1, l2, l3, l4 uint64
  }

  type PointAfter struct {
    x, y, z, t Element
    _          incomparable
  }

  type PointBefore struct {
    _          incomparable
    x, y, z, t Element
  }

  type incomparable [0]func()

  func main() {
    fmt.Println(unsafe.Sizeof(PointAfter{})) // 168
    fmt.Println(unsafe.Sizeof(PointBefore{})) // 160
  }

Change-Id: I6c4fcb586bbf3febf62b6e54608496ff81685e43
Reviewed-on: https://go-review.googlesource.com/c/go/+/467616
Reviewed-by: Roland Shoemaker <roland@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Damien Neil <dneil@google.com>
Run-TryBot: Filippo Valsorda <filippo@golang.org>
@golang golang locked and limited conversation to collaborators Feb 15, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. FrozenDueToAge
Projects
None yet
Development

No branches or pull requests

5 participants