proto: replace CachedSize fast-path method with UseCachedSize option

Using an option instead of a separate method has several useful properties:

It makes it explicit whether the fast-path AppendMarshal is expected to use
cached sizes or not.

It properly plumbs the decision to use cached sizes through the call stack.
Consider the case where message A includes B includes C: If A and C support
cached sizes but B does not, we would like to use the size cache in all
messages which support it. Placing this decision in the options allows this
to work properly with no additional effort.

Placing this option in MarshalOptions permits users to request use of
existing cached sizes. This is a two-edged sword: There are places where
this ability can permit substantial efficiencies, but this is also an
exceedingly sharp-edged API. I believe that on balance the benefits
outweigh the risks, especially since the prerequisites for using
cached sizes are intuitively obvious. (You must have called Size, and
you must not have changed the message.)

This CL adds a Size method to MarshalOptions, rather than adding a SizeOptions
type. Future additions to MarshalOptions may affect the size of the encoded
output (e.g., an option to skip encoding unknown fields) and using the same
options for both Marshal and Size makes it easier to use a consistent
configuration for each.

Change-Id: I6adbb55b717dd03d39f067e1d0b7381945000976
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/171119
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
diff --git a/proto/encode.go b/proto/encode.go
index 98410a6..b294392 100644
--- a/proto/encode.go
+++ b/proto/encode.go
@@ -47,6 +47,26 @@
 	// detail and subject to change.
 	Deterministic bool
 
+	// UseCachedSize indicates that the result of a previous Size call
+	// may be reused.
+	//
+	// Setting this option asserts that:
+	//
+	// 1. Size has previously been called on this message with identical
+	// options (except for UseCachedSize itself).
+	//
+	// 2. The message and all its submessages have not changed in any
+	// way since the Size call.
+	//
+	// If either of these invariants is broken, the results are undefined
+	// but may include panics or invalid output.
+	//
+	// Implementations MAY take this option into account to provide
+	// better performance, but there is no guarantee that they will do so.
+	// There is absolutely no guarantee that Size followed by Marshal with
+	// UseCachedSize set will perform equivalently to Marshal alone.
+	UseCachedSize bool
+
 	pragma.NoUnkeyedLiterals
 }
 
@@ -93,6 +113,7 @@
 			copy(x, b)
 			b = x
 		}
+		o.UseCachedSize = true
 	}
 	return methods.MarshalAppend(b, m, protoiface.MarshalOptions(o))
 }
diff --git a/proto/size.go b/proto/size.go
index a817cf7..ab21cb3 100644
--- a/proto/size.go
+++ b/proto/size.go
@@ -13,6 +13,11 @@
 
 // Size returns the size in bytes of the wire-format encoding of m.
 func Size(m Message) int {
+	return MarshalOptions{}.Size(m)
+}
+
+// Size returns the size in bytes of the wire-format encoding of m.
+func (o MarshalOptions) Size(m Message) int {
 	if size, err := sizeMessageFast(m); err == nil {
 		return size
 	}
diff --git a/runtime/protoiface/methods.go b/runtime/protoiface/methods.go
index e7d92db..42832de 100644
--- a/runtime/protoiface/methods.go
+++ b/runtime/protoiface/methods.go
@@ -27,10 +27,6 @@
 	// Size returns the size in bytes of the wire-format encoding of m.
 	Size func(m protoreflect.ProtoMessage) int
 
-	// CachedSize returns the result of the last call to Size.
-	// It must not be called if the message has been changed since the last call to Size.
-	CachedSize func(m protoreflect.ProtoMessage) int
-
 	// Unmarshal parses the wire-format message in b and places the result in m.
 	// It does not reset m or perform required field checks.
 	Unmarshal func(b []byte, m protoreflect.ProtoMessage, opts UnmarshalOptions) error
@@ -55,6 +51,7 @@
 type MarshalOptions struct {
 	AllowPartial  bool
 	Deterministic bool
+	UseCachedSize bool
 
 	pragma.NoUnkeyedLiterals
 }