diff options
| author | Ian Lance Taylor <iant@golang.org> | 2022-02-14 14:56:34 -0800 |
|---|---|---|
| committer | Ian Lance Taylor <iant@golang.org> | 2022-07-15 16:22:32 +0000 |
| commit | 5aaf86f411be33d4996dec7d000284ffe60dbe5c (patch) | |
| tree | a2b779ef11a105deaff594f152f47a1d00cd8a1f | |
| parent | 67efb5547ba261c1f8626396c1c3502a0aa0979b (diff) | |
| download | go-x-proposal-5aaf86f411be33d4996dec7d000284ffe60dbe5c.tar.xz | |
design/4238-go12nil: copy from go.dev/s/go12nil
A step toward gathering design docs in one place.
Change-Id: Ic4c0d2b27d4e91a30272b260c891cffe42c371e6
Reviewed-on: https://go-review.googlesource.com/c/proposal/+/385794
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Auto-Submit: Ian Lance Taylor <iant@golang.org>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
| -rw-r--r-- | design/4238-go12nil.md | 192 |
1 files changed, 192 insertions, 0 deletions
diff --git a/design/4238-go12nil.md b/design/4238-go12nil.md new file mode 100644 index 0000000..a19f66e --- /dev/null +++ b/design/4238-go12nil.md @@ -0,0 +1,192 @@ +# Go 1.2 Field Selectors and Nil Checks + +Author: Russ Cox + +Last updated: July 2013 + +Discussion at https://go.dev/issue/4238. + +Originally at https://go.dev/s/go12nil. + +Implemented in Go 1.2 release. + +## Abstract + +For Go 1.2, we need to define that, if `x` is a pointer to a struct +type and `x == nil`, `&x.Field` causes a runtime panic rather than +silently producing an unusable pointer. + +## Background + + +Today, if you have: + +```Go +package main + +type T struct { + Field1 int32 + Field2 int32 +} + +type T2 struct { + X [1<<24]byte + Field int32 +} + +func main() { + var x *T + p1 := &x.Field1 + p2 := &x.Field2 + var x2 *T2 + p3 := &x2.Field +} +``` + +then: + +* `p1 == nil`; dereferencing it causes a panic +* `p2 != nil` (it has pointer value 4); but dereferencing it still + causes a panic +* p3 is not computed: `&x2.Field` panics to avoid producing a pointer + that might point into mapped memory. + +The spec does not define what should happen when `&x.Field` is evaluated +for `x == nil`. +The answer probably should not depend on `Field`’s offset within the +struct. +The current behavior is at best merely historical accident; it was +definitely not thought through or discussed. + +Those three behaviors are three possible definitions. +The behavior for `p2` is clearly undesirable, since it creates +unusable pointers that cannot be detected as unusable. +hat leaves `p1` (`&x.Field` is `nil` if `x` is `nil`) and `p3` +(`&x.Field` panics if `x` is `nil`). + +An analogous form of the question concerns `&x[i]` where `x` is a +`nil` pointer to an array. +he current behaviors match those of the struct exactly, depending in +the same way on both the offset of the field and the overall size of +the array. + +A related question is how `&*x` should evaluate when `x` is `nil`. +In C, `&*x == x` even when `x` is `nil`. +The spec again is silent. +The gc compilers go out of their way to implement the C rule (it +seemed like a good idea at a time). + +A simplified version of a recent example is: + +```Go + type T struct { + f int64 + sync.Mutex + } + + var x *T + + x.Lock() +``` + +The method call turns into `(&x.Mutex).Lock()`, which today is passed +a receiver with pointer value `8` and panics inside the method, +accessing a `sync.Mutex` field. + + +## Proposed Definition + +If `x` is a `nil` pointer to a struct, then evaluating `&x.Field` +always panics. + +If `x` is a `nil` pointer to an array, then evaluating `&x[i]` panics +or `x[i:j]` panics. + +If `x` is a `nil` pointer, then evaluating `&*x` panics. + +In general, the result of an evaluation of `&expr` either panics or +returns a non-nil pointer. + +## Rationale + +The alternative, defining `&x.Field == nil` when `x` is `nil`, delays +the error check. +That feels more like something that belongs in a dynamically typed +language like Python or JavaScript than in Go. +Put another way, it pushes the panic farther away from the problem. + +We have not seen a compelling use case for allowing `&x.Field == nil`. + +Panicking during `&x.Field` is no more expensive (perhaps less) than +defining `&x.Field == nil`. + +It is difficult to justify allowing `&*x` but not `&x.Field`. +They are different expressions of the same computation. + +The guarantee that `&expr`—when it evaluates successfully—is always a +non-nil pointer makes intuitive sense and avoids a surprise: how can +you take the address of something and get `nil`? + +## Implementation + +The addressable expressions are: “a variable, pointer indirection, or +slice indexing operation; or a field selector of an addressable struct +operand; or an array indexing operation of an addressable array.” + +The address of a variable can never be `nil`; the address of a slice +indexing operation is already checked because a `nil` slice will have +`0` length, so any index is invalid. + +That leaves pointer indirections, field selector of struct, and index +of array, confirming at least that we’re considering the complete set +of cases. + +Assuming `x` is in register AX, the current x86 implementation of case +`p3` is to read from the memory `x` points at: + +``` + TEST 0(AX), AX +``` + +That causes a fault when `x` is nil. +Unfortunately, it also causes a read from the memory location `x`, +even if the actual field being addressed is later in memory. +This can cause unnecessary cache conflicts if different goroutines own +different sections of a large array and one is writing to the first +entry. + +(It is tempting to use a conditional move instruction: + +``` + TEST AX, AX + CMOVZ 0, AX +``` + +Unfortunately, the definition of the conditional move is that the load +is unconditional and only the assignment is conditional, so the fault +at address `0` would happen always.) + +An alternate implementation would be to test `x` itself and use a +conditional jump: + +``` + TEST AX, AX + JNZ ok (branch hint: likely) + MOV $0, 0 +ok: +``` + +This is more code (something like 7 bytes instead of 3) but may run +more efficiently, as it avoids spurious memory references and will be +predicted easily. + +(Note that defining `&x.Field == nil` would require at least that much +code, if not a little more, except when the offset is `0`.) + +It will probably be important to have a basic flow analysis for +variables, so that the compiler can avoid re-testing the same pointer +over and over in a given function. +I started on that general topic a year ago and got a prototype working +but then put it aside (the goal then was index bounds check +elimination). +It could be adapted easily for nil check elimination. |
