A long time go bug

August 27, 2021
go golang

Prologue

An user reported a weird behavior of go command in golang-dev mailing list.

Here’s an equivalent program on playground, or inlined below, to reproduce the bug (with go 1.17 and below):

package main

func main() {
	var s = []int{1, 2, 3}
	_ = (*[2]int)(s[1:])
}
-- go.mod --
module example.com

go 1.16

Running about program give an error:

$ go1.17 run -gcflags=-lang=go1.17 main.go
# command-line-arguments
./main.go:5:15: cannot convert s[1:] (type []int) to type *[2]int:
	conversion of slices to array pointers only supported as of -lang=go1.17

It’s weird, isn’t that we tell the compiler that we do want -lang=go1.17?

I pointed out why go behaves like that, and @ianlancetaylor suggested that it’s a bug.

The bug

Since then, an issue was opened on golang Github issues. And the conclusion is that cmd/go is passing the wrong order of flags to cmd/compile, which causes the bug happens. This bug exists at least since go version 1.5, and it’s surprised to me that no one has reported before.

Note that this affects not only -lang, but also any compiler flags, example:

$ cat main.go
package main

func main() {}
$ go1.17 build -a -x -work -gcflags=-buildid=foo main.go
WORK=/var/folders/rz/mxg9f82d2kl7jycb_rvrdp2r0000gn/T/go-build1278557172
...
<truncated output>
...
cd /Users/cuonglm
./sdk/go1.17/pkg/tool/darwin_arm64/compile -o $WORK/b001/_pkg_.a -trimpath "$WORK/b001=>" -shared -buildid=foo -p main -lang=go1.17 -complete -buildid VdM2tsNIpf8Qhrtrmssj/VdM2tsNIpf8Qhrtrmssj -goversion go1.17 -D _/Users/cuonglm -importcfg $WORK/b001/importcfg -pack ./main.go $WORK/b001/_gomod_.go
/Users/cuonglm/sdk/go1.17/pkg/tool/darwin_arm64/buildid -w $WORK/b001/_pkg_.a # internal
...
$ WORK=/var/folders/rz/mxg9f82d2kl7jycb_rvrdp2r0000gn/T/go-build1278557172
$ go1.17 tool buildid $WORK/b001/_pkg_.a
VdM2tsNIpf8Qhrtrmssj/VdM2tsNIpf8Qhrtrmssj

The buildid is not foo as expected.

Taking a quick look at the cmd/go code, I wonder whether we can just swap the order of gcflags and gcargs. @jayconrod said that (emphasis mine):

The flag package requires that flags appear before positional arguments.

It does not make sense, since when there’s no positional arguments involve in gcargs, it contains all the compiler flags that cmd/go wants to pass to the compiler.

So it’s clear that cmd/go is passing the wrong order of flags to the compiler.

Fixing

In CL 344909 that I sent to fix the bug, not only the wrong order was fixed, but also the misleading variable name gcargs was removed. With the fix, cmd/go can now get it right:

$ go build -a -x -work -gcflags=-buildid=foo main.go
WORK=/var/folders/rz/mxg9f82d2kl7jycb_rvrdp2r0000gn/T/go-build2599132168
...
<truncated output>
...
cd /Users/cuonglm
./sources/go/pkg/tool/darwin_arm64/compile -o $WORK/b001/_pkg_.a -trimpath "$WORK/b001=>" -p main -lang=go1.18 -complete -buildid zjrn3d_w3mjLM_-u-69Q/zjrn3d_w3mjLM_-u-69Q -shared -buildid=foo -D _/Users/cuonglm -importcfg $WORK/b001/importcfg -pack ./main.go $WORK/b001/_gomod_.go
/Users/cuonglm/sources/go/pkg/tool/darwin_arm64/buildid -w $WORK/b001/_pkg_.a # internal
...
$ WORK=/var/folders/rz/mxg9f82d2kl7jycb_rvrdp2r0000gn/T/go-build2599132168
$ go tool buildid $WORK/b001/_pkg_.a
foo

NOTE

While fixing this bug, I learn a cool trick to use go build -n for testing this kind of bug, instead of actually building the program. That would speed up go test cmd/go, which sometimes will give you timeout when you run in your local machine.


Epilogue

If you faced any issue after this fix (though, it’s very unlikely, my 2ยข), please file an issue or shoot me an email!

Thanks for reading so far.

Till next time!


GopherCon 2023

October 2, 2023
gophercon community go golang

Improving parallel calls from C to Go performance

June 29, 2023
go golang cgo runtime

A practical optimization for Go compiler

May 25, 2023
go golang compiler