go: cmd/compile: multiple compiler crashes and failures with -G=3

Background

My main motivation for unified IR is to reduce complexity and duplicated code, under the expectation that this will reduce the frequency of compiler issues and ultimately reduce the engineering cost of having a production-quality generics implementation ready to ship for Go 1.18.

After getting unified IR to its first milestone of feature completion and review readiness, I decided to do a first round of aggressive testing to help empirically test my hypothesis. Up to this point, I’d mostly just relied on existing tests and toolstash -cmp. The only additional test case I’d written specifically for unified IR was nested.go, to test type shadowing and generic types declared within generic functions (as those are intentionally not supported by -G=3 mode).

Methodology

I set a goal of finding 10 issues. I wrote test cases manually and avoided local type definitions (again, because -G=3 intentionally doesn’t support them). I skimmed through the -G=3 code for inspiration (e.g., that’s how I found the comparable failure in case 8), but largely focused on variations of cases that I know from experience have been a sore point for the Go compiler.

I did not specifically set out looking for compiler crashes. I was focused on broad testing of functionality, not deep testing, so whenever I found an issue I just minimized it, and then moved on to trying other ideas. I did not explore if any of the compiler crashes could be turned into miscompilation errors.

Results

I found 10 issues within 3 hours. They’re available at https://go-review.googlesource.com/c/go/+/326169 as test/run.go compatible tests. @cuonglm also reported an 11th in the comments.

All 11 issues affect -G=3 mode. Only 1 of them affects unified IR (see below for details). I think these findings support my hypothesis that unified IR’s design reduces the frequency of compiler issues.

9 of the issues are the compiler crashing on valid code. 1 issue is the compiler accepting invalid code. 1 issue is the compiler crashing on invalid code, rather than printing a proper diagnostic.

None of the tests are particularly long, but here’s an overview of the failure cases (caveat: I haven’t investigated most of these, so take these descriptions as educated guesses at the root cause):

  1. Importing an inlinable function body that contains new of an instantiated type crashes.
  2. Recursive instantiation of a generic method crashes during dictionary insertion.
  3. Importer crashes on channel types (and probably others) embedded into a type parameter constraint.
  4. Importer/exporter don’t support labeled for loops, so instantiating an imported generic function that contains one crashes during SSA because the label references are unresolved.
  5. Inconsistent mangling when instantiating a type with anonymous interface as a type argument: X[interface{}] vs X[interface {}].
  6. Method expressions involving an instantiated interface type fail.
  7. Importer crashes on importing an instantiated interface.
  8. Embedded comparable constraint is lost during -G=3 noding, so consequently it isn’t exported or re-imported. This causes incorrect code to be accepted, and could also (potentially) cause correct code to be misoptimized (e.g., assuming that simple GC-shape stenciling is appropriate, rather distinguishing types that require different equality operations, like uint32 and float32 or string and struct { *byte; int }).
  9. Exporter does not include intermediary types for composite literal keyed elements.
  10. Method expression involving an imported instantiated interface types result in a linker error because of a missing wrapper function. (Note: currently masked by issue 6.)
  11. Compiler crash on invalid code involving //go:notinheap rather than an error message.

Issue 10 and Unified IR

I knew one backend feature I needed for unified IR was to create type descriptors for imported generic types and mark them as DUPOK. While looking at the reflectdata code to make this happen, I saw a Type.IsFullyInstantiated flag had been added for -G=3 and was used to handle this already. I also saw other code checked this. Rather than duplicate this logic, I thought it would be less invasive to arrange for Type.IsFullyInstantiated to report true for unified IR’s instantiated types too.

However, as issue 10 demonstrates, the existing support for that flag is incomplete. In particular, reflectdata.methodWrapper was extended for -G=3 to generate method wrappers for imported generic concrete types, but not to generate method wrappers for imported generic interface types:

https://github.com/golang/go/blob/b20747334a4a3dee51759369a098ef2a0c9dbcff/src/cmd/compile/internal/reflectdata/reflect.go#L1794-L1806

Future work

I think fuzz testing would be very useful (/cc @thepudds). I think there’s a lot of room for testing more complex language features (e.g., function literals; go, defer, range, and select statements; multi-package tests with re-exported generic types and functions; tests with identically named packages).

I also think as dictionary and GC shape support continues, actually fuzz testing the resulting code (i.e., as opposed to just verifying the compiler doesn’t crash) will be important too.

/cc @danscales @randall77 @griesemer @ianlancetaylor @rsc @eliben

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 3
  • Comments: 17 (14 by maintainers)

Commits related to this issue

Most upvoted comments

@anacrolix Your problem with stm.test is fixed by the fix of #48042 https://github.com/golang/go/commit/ecfff58fb8c3aabbce7b15c850210485c1f09d61 , just checked in.

If you have any other problem with type params, please file a new issue mentioning -G=3 mode. Thanks!

@anacrolix You need GOEXPERIMENT=unified go test -vet=off . for now.

@cuonglm That looks pretty similar to test case 1’s failure. Maybe the issue is related to the blank assignment, not the new call like I initially conjectured.

After #46725 fixed, test/fixedbugs/issue46725.go is also failed with -G=3