Skip to content

cmd/compile: composite literal fields built by inlined constructors are not initialized in place #81649

Description

@mvdan

Go version

go version go1.28-devel_ffc306b417 2026-09-21 16:13:07 -0700 linux/amd64

Output of go env in your module/workspace:

AR='ar'
CC='gcc'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='g++'
GCCGO='gccgo'
GO111MODULE=''
GOAMD64='v4'
GOARCH='amd64'
GOAUTH='netrc'
GOBIN='/home/gopher/go/bin'
GODEBUG=''
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build2170641377=/tmp/go-build -gno-record-gcc-switches'
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMOD='/home/gopher/repro/go.mod'
GOOS='linux'
GOPACKAGESDRIVER=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/home/gopher/tip'
GOSUMDB='sum.golang.org'
GOTELEMETRY='on'
GOTELEMETRYDIR='/home/gopher/.config/go/telemetry'
GOTOOLCHAIN='local'
GOTOOLDIR='/home/gopher/tip/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.28-devel_ffc306b417 2026-09-21 16:13:07 -0700'
GOWORK=''
PKG_CONFIG='pkg-config'

What did you do?

A code generator in CUE emits a large table of struct values, writing each field as a call to a one-line constructor such as func cstr(v string) sval { return sval{kind: concrete, value: v} }. Rewriting every one of those calls as the struct literal it returns — a no-op at the source level, since the constructors are trivially inlinable — took that function from 123KiB of machine code down to 52KiB, and 136KiB off the cue binary.

package main

type kind uint8

const (
	unset kind = iota
	concrete
	dflt
)

type sval struct {
	kind     kind
	value    string
	domain   []string
	excluded []string
}

// One-line constructors, of the kind a generator emits.
func cstr(v string) sval { return sval{kind: concrete, value: v} }
func dstr(v string) sval { return sval{kind: dflt, value: v} }

type entry struct {
	encoding       sval
	interpretation sval
	form           sval
}

// The two are marked noinline only so that they can be measured separately.

//go:noinline
func withCalls() *entry {
	return &entry{encoding: cstr("enc"), interpretation: dstr("interp"), form: cstr("form")}
}

//go:noinline
func withLits() *entry {
	return &entry{
		encoding:       sval{kind: concrete, value: "enc"},
		interpretation: sval{kind: dflt, value: "interp"},
		form:           sval{kind: concrete, value: "form"},
	}
}

var sink *entry

func main() {
	sink = withCalls()
	sink = withLits()
}
go build -o repro .
go tool nm -size repro | grep main.with

What did you see happen?

The two functions compute the same value, but the one that goes through the constructors is almost five times as much code:

  47a180        533 T main.withCalls
  47a3a0        112 T main.withLits

The constructors are inlined, but the inlined result is not used to initialize the destination field: each sval is zeroed and filled in a stack temporary, and the temporary is then copied into the heap object. withLits writes the fields straight into the new object.

The overhead is per field, so it scales with the size of the table, which is how one generated table came to be the largest function in the cue binary.

What did you expect to see?

withCalls and withLits to compile to the same code. A call whose body is a single return T{...} is inlined before walk, so by the time the composite literal is lowered the compiler has the field values in hand, and nothing about the semantics of the two spellings differs.

Constructor functions like these are idiomatic, and generated code leans on them heavily, so the cost is easy to pay without noticing.

Activity

  1. mvdan commented on Sep 22, 2026

    @mvdan
    MemberAuthor

    For further context, the commit which removed the func calls in CUE was cue-lang/cue@befe85b, which saved the ~135KiB in binary size.

  2. added this to the Unplanned milestone on Sep 22, 2026
  3. prattmic commented on Sep 29, 2026

    @prattmic
    Member

    cc @golang/compiler

  4. added
    NeedsDecisionFeedback is required from experts, contributors, and/or the community before a change can be made.
    on Sep 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    NeedsDecisionFeedback is required from experts, contributors, and/or the community before a change can be made.Performancecompiler/runtimeIssues related to the Go compiler and/or runtime.

    Projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions