cmd/compile/internal/walk: expand the number of cases where the makeslicecopy function is generated.

114 views
Skip to first unread message

Oleg Aleksandrov

unread,
Jul 22, 2026, 12:06:00 PM (2 days ago) Jul 22
to golang-dev
Hello,
I've been experimenting with generating the makeslicecopy function at the IR stage of the Go compiler. Generation for this function already exists (link to CL), but it 
is currently limited to cases where both dst and src are simple ir.Name nodes. My experiment extends this to handle more complex left-hand side expressions (such as pointer dereferencing, struct field access, and array indexing) without function calls.

To ensure correctness in these new cases, I am using ir.SameSafeExpr() to verify that src doesn't alias dst. Is this check sufficient, or are there aliasing scenarios it might miss? My change passes all tests of the Go compiler, but I would still appreciate confirmation that my approach is sound.

Finally, could you suggest which benchmarks I should use to evaluate the real-world performance of this change, beyond synthetic microbenchmarks?

Keith Randall

unread,
Jul 22, 2026, 7:16:15 PM (2 days ago) Jul 22
to Oleg Aleksandrov, golang-dev
The generic answer is that you can assume SameSafeExpr does what its spec says, and nothing more.
The intent of that function is for code like

a, b := someExpression1, someExpression2

if SameSafeExpr(someExpression1, someExpression2) == true, then you can instead do
a := someExpression1
b := a

I don't think that says anything about aliasing. For instance, SameSafeExpr(a, a) == true. For something like:

a := []byte{3,4,5}
a = make([]byte, 5)
copy(a, a)

then you can't use the original value of `a` as the argument to makeslicecopy.

(The aliasing part in SameSafeExpr's spec is about the fact that a, b := make([]byte, 5), make([]byte, 5) must still make 2 byte slices.)

As far as benchmarks, a microbenchmark is fine to use for this case. But I'd also like a few references to where it actually triggers in real code. Not a benchmark per se, but some realistic code that would be helped by the optimization.



--
You received this message because you are subscribed to the Google Groups "golang-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-dev+...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/golang-dev/8a5b4754-f826-46e2-8948-446bc0636b7bn%40googlegroups.com.
Message has been deleted

Oleg Aleksandrov

unread,
Jul 23, 2026, 11:04:53 AM (13 hours ago) Jul 23
to golang-dev
[DUBLICATE]

Thanks for the explanation.

After a brief search, I found the following code examples in large projects written in Go.

Case 1: Kubernetes

1) Many packages contain a zz_generated_deepcopy.go file. In this file you can find the following code examples:

*out = make([]Capability, len(*in))
copy(*out, *in)

or

*out = make([]byte, len(*in))
copy(*out, *in)

Links to the lines in code:
2) You can find this pattern:

out.Except = make([]string, len(in.Except))
copy(out.Except, in.Except)

Link: https://github.com/kubernetes/kubernetes/blob/master/pkg/apis/extensions/v1beta1/conversion.go#L149-L150

3) Next pattern:

newMExps[i].Values = make([]string, len(me.Values))
copy(newMExps[i].Values, me.Values)

Link: https://github.com/kubernetes/kubernetes/blob/master/pkg/util/labels/labels.go#L94-L95

Case 2: moby

Code segments that can potentially be optimized:

p.PluginObj.Settings.Mounts = make([]plugin.Mount, len(p.PluginObj.Config.Mounts))
copy(p.PluginObj.Settings.Mounts, p.PluginObj.Config.Mounts)

p.PluginObj.Settings.Devices = make([]plugin.Device, len(p.PluginObj.Config.Linux.Devices))
copy(p.PluginObj.Settings.Devices, p.PluginObj.Config.Linux.Devices)

p.PluginObj.Settings.Args = make([]string, len(p.PluginObj.Config.Args.Value))
copy(p.PluginObj.Settings.Args, p.PluginObj.Config.Args.Value)

Links:

Case 3: go-ethereum

1) Code segments in accounts/abi/abigen/bind.go file:

normalized.Inputs = make([]abi.Argument, len(original.Inputs))
copy(normalized.Inputs, original.Inputs)

and

normalized.Outputs = make([]abi.Argument, len(original.Outputs))
copy(normalized.Outputs, original.Outputs)

Links:

2) Code segments in core/types/block.go file:

b.transactions = make(Transactions, len(txs))
copy(b.transactions, txs)

and

cpy.Extra = make([]byte, len(h.Extra))
copy(cpy.Extra, h.Extra)

Links:
четверг, 23 июля 2026 г. в 02:16:15 UTC+3, Keith Randall:

Oleg Aleksandrov

unread,
Jul 23, 2026, 6:21:31 PM (6 hours ago) Jul 23
to golang-dev
Hello,

Following up on previous discussion, I came up with an edge case regarding aliasing when dealing with indirect expressions/dereferences:

var buffer []byte
a := &buffer
*a = []byte{3, 4, 5}
b := a
*a = make([]byte, 5)
copy(*a, *b)

In this scenario, *a and *b evaluate to the same slice reference, which causes *b to copy pre-exisiting bytes [3, 4, 5] over the newly allocated slice.

Since the current conservative check at the IR stage prevents transformation for complex or indirect expressions to avoid this exact pitfall, 
I'm trying to figure out how we can safely enable it for these cases.

I wanted to ask:
  1. Is there a viable way at the IR level to prove non-aliasing for indirect expressions without full data-flow analysis, or is trying to resolve this in walk inherently too fragile?
  2. Would moving/extending this transformation in the SSA backend be the preferred path forward to handle these cases safely using the memory graph?
Thanks!
четверг, 23 июля 2026 г. в 18:04:53 UTC+3, Oleg Aleksandrov:

Keith Randall

unread,
Jul 23, 2026, 7:00:24 PM (5 hours ago) Jul 23
to Oleg Aleksandrov, golang-dev
On Thu, Jul 23, 2026 at 3:21 PM Oleg Aleksandrov <aleksandr...@gmail.com> wrote:
Hello,

Following up on previous discussion, I came up with an edge case regarding aliasing when dealing with indirect expressions/dereferences:

var buffer []byte
a := &buffer
*a = []byte{3, 4, 5}
b := a
*a = make([]byte, 5)
copy(*a, *b)

In this scenario, *a and *b evaluate to the same slice reference, which causes *b to copy pre-exisiting bytes [3, 4, 5] over the newly allocated slice.

Since the current conservative check at the IR stage prevents transformation for complex or indirect expressions to avoid this exact pitfall, 
I'm trying to figure out how we can safely enable it for these cases.

I wanted to ask:
  1. Is there a viable way at the IR level to prove non-aliasing for indirect expressions without full data-flow analysis, or is trying to resolve this in walk inherently too fragile?
For

a = make([]byte, ...)
copy(a, b)

It is certainly tricky if both a and b are complex expressions. 

I think you could pretty easily handle the cases where either `a` or `b` are simple variables that are not addrtaken. (Now we require both to be simple? Just one should be enough.)
That would handle a couple of your examples, I think. But not the majority.
Beyond that, it does look pretty hard.
 
See the disjoint function in cmd/compile/internal/ssa/rewrite.go. It handles a few additional reasonable cases, like when a is *p, with p being an argument, and
b is *q, with q being the address of a local variable.

Reply all
Reply to author
Forward
0 new messages