Skip to content

cmd/compile: pointer shaped type with more than one field causes allocation when cast to interface #74092

Open
@mcy

Description

@mcy

Consider the following program:

package main

import (
	"fmt"
	"unsafe"
)

func indir[T any]() bool {
	var z T
	y := any(z)

	type iface struct {
		itab uintptr
		ptr  unsafe.Pointer
	}

	p := (*iface)(unsafe.Pointer(&y)).ptr
	return p != nil
}

func main() {
	fmt.Println(
		indir[int](),
		indir[*int](),
		indir[map[int]int](),
		indir[struct{ _ *int }](),
		indir[struct {
			_ struct{}
			_ *int
		}](),
	)
}

indir() returns true if some type would need to allocate to be converted to an any. It does so by noting that a freshly created non-nil any's data pointer will never be nil if an allocation happened, but it will be nil if it didn't, because x is all zeros, and in the non-allocating case, this value gets splatted into the data pointer.

This prints true false false false true. However, there is no reason this could not print true false false false false, because the last of these types has the same shape as a pointer, and thus can be passed around as the unsafe.Pointer in an interface value without requiring an allocation, just like is already done for one field structs and arrays of pointer shape.

The change here is essentially applying this to anything that happens to have pointer shape, rather than the current mess of special cases in reflect (notably this excludes struct{*int; struct{}}, which is 16, not 8 bytes wide). Whether something has pointer shape is not visible to users, so this optimization shouldn't break anyone except for very naughty unsafe users like me.

My reason for interest in this is to have the ability to embed zero-size types into a struct, to simplify the implementation of interfaces with large method sets, or embedding e.g. _ [0]sync.Mutex to get govet to do the right thing, a fairly common idiom in concurrency primitives (again, a thing I wind up doing a lot).

I'm not expecting this to get fixed any time soon, because of repercussions across runtime, reflect, and internal/abi, but I wanted to write this up because it's a thorn in my side right now.

Metadata

Metadata

Assignees

Labels

BugReportIssues describing a possible bug in the Go implementation.NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.Performancecompiler/runtimeIssues related to the Go compiler and/or runtime.

Type

No type

Projects

Status

In Progress

Relationships

None yet

Development

No branches or pull requests

Issue actions