proto, runtime/protoiface, internal/impl: add fast-path Merge

Comparing -tags=protoreflect to fast-path:

name                              old time/op    new time/op    delta
pkg:google.golang.org/protobuf/internal/benchmarks goos:linux goarch:amd64
/Clone/google_message1_proto2-12    1.70µs ± 1%    0.30µs ± 1%  -82.64%  (p=0.001 n=7+7)
/Clone/google_message1_proto3-12    1.01µs ± 1%    0.19µs ± 1%  -80.77%  (p=0.000 n=7+8)
/Clone/google_message2-12            818µs ± 8%     141µs ± 6%  -82.78%  (p=0.000 n=8+8)
pkg:google.golang.org/protobuf/internal/benchmarks/micro goos:linux goarch:amd64
EmptyMessage/Clone-12               51.1ns ± 1%    39.3ns ± 3%  -23.03%  (p=0.000 n=7+8)
RepeatedInt32/Clone-12              24.5µs ± 1%     1.1µs ± 3%  -95.64%  (p=0.000 n=8+8)
Required/Clone-12                    978ns ± 1%     132ns ± 2%  -86.46%  (p=0.000 n=8+8)

name                              old alloc/op   new alloc/op   delta
pkg:google.golang.org/protobuf/internal/benchmarks goos:linux goarch:amd64
/Clone/google_message1_proto2-12    1.08kB ± 0%    0.74kB ± 0%  -31.85%  (p=0.000 n=8+8)
/Clone/google_message1_proto3-12      872B ± 0%      544B ± 0%  -37.61%  (p=0.000 n=8+8)
/Clone/google_message2-12            602kB ± 0%     411kB ± 0%  -31.65%  (p=0.000 n=8+8)
pkg:google.golang.org/protobuf/internal/benchmarks/micro goos:linux goarch:amd64
EmptyMessage/Clone-12                96.0B ± 0%     64.0B ± 0%  -33.33%  (p=0.000 n=8+8)
RepeatedInt32/Clone-12              25.4kB ± 0%     3.2kB ± 0%  -87.33%  (p=0.000 n=8+8)
Required/Clone-12                     416B ± 0%      256B ± 0%  -38.46%  (p=0.000 n=8+8)

name                              old allocs/op  new allocs/op  delta
pkg:google.golang.org/protobuf/internal/benchmarks goos:linux goarch:amd64
/Clone/google_message1_proto2-12      52.0 ± 0%      21.0 ± 0%  -59.62%  (p=0.000 n=8+8)
/Clone/google_message1_proto3-12      33.0 ± 0%       3.0 ± 0%  -90.91%  (p=0.000 n=8+8)
/Clone/google_message2-12            22.3k ± 0%      7.5k ± 0%  -66.41%  (p=0.000 n=8+8)
pkg:google.golang.org/protobuf/internal/benchmarks/micro goos:linux goarch:amd64
EmptyMessage/Clone-12                 3.00 ± 0%      2.00 ± 0%  -33.33%  (p=0.000 n=8+8)
RepeatedInt32/Clone-12               1.51k ± 0%     0.00k ± 0%  -99.80%  (p=0.000 n=8+8)
Required/Clone-12                     51.0 ± 0%      18.0 ± 0%  -64.71%  (p=0.000 n=8+8)

Change-Id: Ife9018097c34cb025dc9c4fdd9a61b2f947853c6
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/219147
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
diff --git a/internal/impl/codec_field.go b/internal/impl/codec_field.go
index dd6a8a1..98992e7 100644
--- a/internal/impl/codec_field.go
+++ b/internal/impl/codec_field.go
@@ -45,7 +45,8 @@
 		// and dispatches to the field-specific marshaler in oneofFields.
 		cf := *mi.coderFields[num]
 		ot := si.oneofWrappersByNumber[num]
-		cf.mi, cf.funcs = fieldCoder(fd, ot.Field(0).Type)
+		cf.ft = ot.Field(0).Type
+		cf.mi, cf.funcs = fieldCoder(fd, cf.ft)
 		oneofFields[ot] = &cf
 		if cf.funcs.isInit != nil {
 			needIsInit = true
@@ -92,6 +93,18 @@
 		}
 		return info.funcs.marshal(b, p, info, opts)
 	}
+	first.funcs.merge = func(dst, src pointer, _ *coderFieldInfo, opts mergeOptions) {
+		srcp, srcinfo := getInfo(src)
+		if srcinfo == nil || srcinfo.funcs.merge == nil {
+			return
+		}
+		dstp, dstinfo := getInfo(dst)
+		if dstinfo != srcinfo {
+			dst.AsValueOf(ft).Elem().Set(reflect.New(src.AsValueOf(ft).Elem().Elem().Elem().Type()))
+			dstp = pointerOfValue(dst.AsValueOf(ft).Elem().Elem()).Apply(zeroOffset)
+		}
+		srcinfo.funcs.merge(dstp, srcp, srcinfo, opts)
+	}
 	if needIsInit {
 		first.funcs.isInit = func(p pointer, _ *coderFieldInfo) error {
 			p, info := getInfo(p)
@@ -113,10 +126,9 @@
 		})
 	}
 
-	num := fd.Number()
 	return pointerCoderFuncs{
 		size: func(p pointer, f *coderFieldInfo, opts marshalOptions) int {
-			m, ok := p.WeakFields().get(num)
+			m, ok := p.WeakFields().get(f.num)
 			if !ok {
 				return 0
 			}
@@ -127,7 +139,7 @@
 			return sizeMessage(m, f.tagsize, opts)
 		},
 		marshal: func(b []byte, p pointer, f *coderFieldInfo, opts marshalOptions) ([]byte, error) {
-			m, ok := p.WeakFields().get(num)
+			m, ok := p.WeakFields().get(f.num)
 			if !ok {
 				return b, nil
 			}
@@ -139,24 +151,40 @@
 		},
 		unmarshal: func(b []byte, p pointer, wtyp wire.Type, f *coderFieldInfo, opts unmarshalOptions) (unmarshalOutput, error) {
 			fs := p.WeakFields()
-			m, ok := fs.get(num)
+			m, ok := fs.get(f.num)
 			if !ok {
 				lazyInit()
 				if messageType == nil {
 					return unmarshalOutput{}, errUnknown
 				}
 				m = messageType.New().Interface()
-				fs.set(num, m)
+				fs.set(f.num, m)
 			}
 			return consumeMessage(b, m, wtyp, opts)
 		},
 		isInit: func(p pointer, f *coderFieldInfo) error {
-			m, ok := p.WeakFields().get(num)
+			m, ok := p.WeakFields().get(f.num)
 			if !ok {
 				return nil
 			}
 			return proto.IsInitialized(m)
 		},
+		merge: func(dst, src pointer, f *coderFieldInfo, opts mergeOptions) {
+			sm, ok := src.WeakFields().get(f.num)
+			if !ok {
+				return
+			}
+			dm, ok := dst.WeakFields().get(f.num)
+			if !ok {
+				lazyInit()
+				if messageType == nil {
+					panic(fmt.Sprintf("weak message %v is not linked in", fd.Message().FullName()))
+				}
+				dm = messageType.New().Interface()
+				dst.WeakFields().set(f.num, dm)
+			}
+			opts.Merge(dm, sm)
+		},
 	}
 }
 
@@ -166,6 +194,7 @@
 			size:      sizeMessageInfo,
 			marshal:   appendMessageInfo,
 			unmarshal: consumeMessageInfo,
+			merge:     mergeMessage,
 		}
 		if needsInitCheck(mi.Desc) {
 			funcs.isInit = isInitMessageInfo
@@ -192,6 +221,7 @@
 				m := asMessage(p.AsValueOf(ft).Elem())
 				return proto.IsInitialized(m)
 			},
+			merge: mergeMessage,
 		}
 	}
 }
@@ -285,6 +315,7 @@
 	marshal:   appendMessageValue,
 	unmarshal: consumeMessageValue,
 	isInit:    isInitMessageValue,
+	merge:     mergeMessageValue,
 }
 
 func sizeGroupValue(v pref.Value, tagsize int, opts marshalOptions) int {
@@ -308,6 +339,7 @@
 	marshal:   appendGroupValue,
 	unmarshal: consumeGroupValue,
 	isInit:    isInitMessageValue,
+	merge:     mergeMessageValue,
 }
 
 func makeGroupFieldCoder(fd pref.FieldDescriptor, ft reflect.Type) pointerCoderFuncs {
@@ -317,6 +349,7 @@
 			size:      sizeGroupType,
 			marshal:   appendGroupType,
 			unmarshal: consumeGroupType,
+			merge:     mergeMessage,
 		}
 		if needsInitCheck(mi.Desc) {
 			funcs.isInit = isInitMessageInfo
@@ -343,6 +376,7 @@
 				m := asMessage(p.AsValueOf(ft).Elem())
 				return proto.IsInitialized(m)
 			},
+			merge: mergeMessage,
 		}
 	}
 }
@@ -404,6 +438,7 @@
 			size:      sizeMessageSliceInfo,
 			marshal:   appendMessageSliceInfo,
 			unmarshal: consumeMessageSliceInfo,
+			merge:     mergeMessageSlice,
 		}
 		if needsInitCheck(mi.Desc) {
 			funcs.isInit = isInitMessageSliceInfo
@@ -423,6 +458,7 @@
 		isInit: func(p pointer, f *coderFieldInfo) error {
 			return isInitMessageSlice(p, ft)
 		},
+		merge: mergeMessageSlice,
 	}
 }
 
@@ -605,6 +641,7 @@
 	marshal:   appendMessageSliceValue,
 	unmarshal: consumeMessageSliceValue,
 	isInit:    isInitMessageSliceValue,
+	merge:     mergeMessageListValue,
 }
 
 func sizeGroupSliceValue(listv pref.Value, tagsize int, opts marshalOptions) int {
@@ -660,6 +697,7 @@
 	marshal:   appendGroupSliceValue,
 	unmarshal: consumeGroupSliceValue,
 	isInit:    isInitMessageSliceValue,
+	merge:     mergeMessageListValue,
 }
 
 func makeGroupSliceFieldCoder(fd pref.FieldDescriptor, ft reflect.Type) pointerCoderFuncs {
@@ -669,6 +707,7 @@
 			size:      sizeGroupSliceInfo,
 			marshal:   appendGroupSliceInfo,
 			unmarshal: consumeGroupSliceInfo,
+			merge:     mergeMessageSlice,
 		}
 		if needsInitCheck(mi.Desc) {
 			funcs.isInit = isInitMessageSliceInfo
@@ -688,6 +727,7 @@
 		isInit: func(p pointer, f *coderFieldInfo) error {
 			return isInitMessageSlice(p, ft)
 		},
+		merge: mergeMessageSlice,
 	}
 }