internal/impl: add MessageState to every generated message

We define MessageState, which is essentially an atomically set *MessageInfo.
By nesting this as the first field in every generated message, we can
implement the reflective methods on a *MessageState when obtained by
unsafe casting a concrete message pointer as a *MessageState.
The MessageInfo held by MessageState provides additional Go type information
to interpret the memory that comes after the contents of the MessageState.

Since we are nesting a MessageState in every message,
the memory use of every message instance grows by 8B.

On average, the body of ProtoReflect grows from 133B to 202B (+50%).
However, this is offset by XXX_Methods, which is 108B and
will be removed in a future CL. Taking into account the eventual removal
of XXX_Methods, this is a net reduction of 25%.

name          old time/op    new time/op    delta
Name/Value-4    70.3ns ± 2%    17.5ns ± 6%   -75.08%  (p=0.000 n=10+10)
Name/Nil-4      70.6ns ± 3%    33.4ns ± 2%   -52.66%  (p=0.000 n=10+10)

name          old alloc/op   new alloc/op   delta
Name/Value-4     16.0B ± 0%      0.0B       -100.00%  (p=0.000 n=10+10)
Name/Nil-4       16.0B ± 0%      0.0B       -100.00%  (p=0.000 n=10+10)

name          old allocs/op  new allocs/op  delta
Name/Value-4      1.00 ± 0%      0.00       -100.00%  (p=0.000 n=10+10)
Name/Nil-4        1.00 ± 0%      0.00       -100.00%  (p=0.000 n=10+10)

Change-Id: I92bd58dc681c57c92612fd5ba7fc066aea34e95a
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/185460
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/cmd/protoc-gen-go/internal_gengo/main.go b/cmd/protoc-gen-go/internal_gengo/main.go
index 219d79a..67f3071 100644
--- a/cmd/protoc-gen-go/internal_gengo/main.go
+++ b/cmd/protoc-gen-go/internal_gengo/main.go
@@ -48,10 +48,14 @@
 	// ExtensionRangeArray method for messages that support extensions.
 	generateExtensionRangeMethods = true
 
-	// generateWKTMarkerMethods specifes whether to generate
+	// generateWKTMarkerMethods specifies whether to generate
 	// XXX_WellKnownType methods on well-known types.
 	generateWKTMarkerMethods = false
 
+	// generateMessateStateFields specifies whether to generate an unexported
+	// protoimpl.MessageState as the first field.
+	generateMessateStateFields = true
+
 	// generateNoUnkeyedLiteralFields specifies whether to generate
 	// the XXX_NoUnkeyedLiteral field.
 	generateNoUnkeyedLiteralFields = false
@@ -395,6 +399,10 @@
 	g.Annotate(message.GoIdent.GoName, message.Location)
 	g.P("type ", message.GoIdent, " struct {")
 	sf := f.allMessageFieldsByPtr[message]
+	if generateMessateStateFields {
+		g.P("state ", protoimplPackage.Ident("MessageState"))
+		sf.append("state")
+	}
 	for _, field := range message.Fields {
 		if field.Oneof != nil {
 			// It would be a bit simpler to iterate over the oneofs below,
diff --git a/cmd/protoc-gen-go/internal_gengo/reflect.go b/cmd/protoc-gen-go/internal_gengo/reflect.go
index b7c5261..43099b2 100644
--- a/cmd/protoc-gen-go/internal_gengo/reflect.go
+++ b/cmd/protoc-gen-go/internal_gengo/reflect.go
@@ -293,12 +293,25 @@
 
 	// ProtoReflect method.
 	g.P("func (x *", message.GoIdent, ") ProtoReflect() ", protoreflectPackage.Ident("Message"), " {")
-	g.P("return ", typesVar, "[", idx, "].MessageOf(x)")
+	g.P("mi := &", typesVar, "[", idx, "]")
+	if generateMessateStateFields {
+		g.P("if ", protoimplPackage.Ident("UnsafeEnabled"), " && x != nil {")
+		g.P("ms := ", protoimplPackage.Ident("X"), ".MessageStateOf(", protoimplPackage.Ident("Pointer"), "(x))")
+		g.P("if ms.LoadMessageInfo() == nil {")
+		g.P("ms.StoreMessageInfo(mi)")
+		g.P("}")
+		g.P("return ms")
+		g.P("}")
+	}
+	g.P("return mi.MessageOf(x)")
 	g.P("}")
 	g.P()
-	g.P("func (m *", message.GoIdent, ") XXX_Methods() *", protoifacePackage.Ident("Methods"), " {")
+
+	// XXX_Methods method.
+	g.P("func (x *", message.GoIdent, ") XXX_Methods() *", protoifacePackage.Ident("Methods"), " {")
 	g.P("return ", typesVar, "[", idx, "].Methods()")
 	g.P("}")
+	g.P()
 }
 
 func fileVarName(f *protogen.File, suffix string) string {