pw_protobuf: More protobuf size reports

Further extends the pw_protobuf size reports to include decoders and
codegen size reports.

Change-Id: Id9b00e8b488520bc7e8d2cf9b8431dee083d63c2
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/110115
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
Reviewed-by: Keir Mierle <keir@google.com>
Pigweed-Auto-Submit: Armando Montanez <amontanez@google.com>
diff --git a/pw_protobuf/BUILD.bazel b/pw_protobuf/BUILD.bazel
index b4dbf07..7501c68 100644
--- a/pw_protobuf/BUILD.bazel
+++ b/pw_protobuf/BUILD.bazel
@@ -209,6 +209,7 @@
         "pw_protobuf_test_protos/optional.proto",
         "pw_protobuf_test_protos/proto2.proto",
         "pw_protobuf_test_protos/repeated.proto",
+        "pw_protobuf_test_protos/size_report.proto",
     ],
     strip_import_prefix = "//pw_protobuf",
     deps = [
diff --git a/pw_protobuf/BUILD.gn b/pw_protobuf/BUILD.gn
index b307c1d..bad3bd6 100644
--- a/pw_protobuf/BUILD.gn
+++ b/pw_protobuf/BUILD.gn
@@ -94,11 +94,20 @@
 }
 
 pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
+  sources = [
+    "docs.rst",
+    "size_report.rst",
+  ]
+  inputs = [
+    "pw_protobuf_test_protos/size_report.options",
+    "pw_protobuf_test_protos/size_report.proto",
+  ]
   report_deps = [
     "size_report:decoder_incremental",
     "size_report:decoder_partial",
+    "size_report:oneof_codegen_size_comparison",
     "size_report:protobuf_overview",
+    "size_report:simple_codegen_size_comparison",
   ]
 }
 
@@ -216,6 +225,7 @@
     "pw_protobuf_test_protos/optional.proto",
     "pw_protobuf_test_protos/proto2.proto",
     "pw_protobuf_test_protos/repeated.proto",
+    "pw_protobuf_test_protos/size_report.proto",
   ]
   inputs = [
     "pw_protobuf_test_protos/full_test.options",
diff --git a/pw_protobuf/docs.rst b/pw_protobuf/docs.rst
index 0c192ae..c910772 100644
--- a/pw_protobuf/docs.rst
+++ b/pw_protobuf/docs.rst
@@ -54,6 +54,11 @@
 
   Customer.name max_size:32
 
+.. toctree::
+  :maxdepth: 1
+
+  size_report
+
 Message Structures
 ==================
 The highest level API is based around message structures created through C++
diff --git a/pw_protobuf/pw_protobuf_test_protos/size_report.options b/pw_protobuf/pw_protobuf_test_protos/size_report.options
new file mode 100644
index 0000000..2b63e69
--- /dev/null
+++ b/pw_protobuf/pw_protobuf_test_protos/size_report.options
@@ -0,0 +1,15 @@
+// Copyright 2022 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+pw.protobuf_size_report.Reponse.responses max_count:4
diff --git a/pw_protobuf/pw_protobuf_test_protos/size_report.proto b/pw_protobuf/pw_protobuf_test_protos/size_report.proto
new file mode 100644
index 0000000..55783f2
--- /dev/null
+++ b/pw_protobuf/pw_protobuf_test_protos/size_report.proto
@@ -0,0 +1,57 @@
+// Copyright 2019 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+syntax = "proto3";
+
+package pw.protobuf_size_report;
+
+message ItemInfo {
+  enum Access {
+    NONE = 0;
+    READ = 1;
+    WRITE = 2;
+    READ_AND_WRITE = 3;
+  }
+  uint64 offset = 1;
+  uint32 size = 2;
+  Access access_level = 3;
+}
+
+message ResponseInfo {
+  oneof key {
+    string key_string = 1;
+    fixed32 key_token = 2;
+  }
+
+  optional int64 timestamp = 3;
+  optional bool has_value = 4;
+  ItemInfo item_info = 5;
+}
+
+message Response {
+  repeated ResponseInfo responses = 1;
+}
+
+message LookupRequest {
+  message AuthInfo {
+    optional uint32 id = 1;
+    optional uint32 token = 2;
+  }
+  oneof key {
+    string key_string = 1;
+    fixed32 key_token = 2;
+  }
+  optional uint32 items_per_response = 3;
+  AuthInfo auth_info = 4;
+  bool add_timestamp = 5;
+}
diff --git a/pw_protobuf/size_report.rst b/pw_protobuf/size_report.rst
new file mode 100644
index 0000000..1adf6d2
--- /dev/null
+++ b/pw_protobuf/size_report.rst
@@ -0,0 +1,62 @@
+.. _module-pw_protobuf-size_report:
+
+================================
+pw_protobuf extended size report
+================================
+pw_protobuf can impact binary size very differently depending on how it's used.
+A series of examples are provided below to illustrate how much certain use cases
+affect binary size.
+
+--------
+Overview
+--------
+This module includes a proto encoder, two different proto decoders (one that
+operates on a ``pw::stream::StreamReader`` and another that operates on an in-
+memory buffer), codegen for direct wire-format encoders/decoders, and a
+table-based codegen system for constructing proto messages as in-memory structs.
+
+Here's a brief overview of the different encoder/decoder costs:
+
+.. include:: size_report/protobuf_overview
+
+.. note::
+
+  There's some overhead involved in ensuring all of the encoder/decoder
+  functionality is pulled in. Check the per-symbol breakdown for more details.
+
+--------------------------------
+Encoder/decoder codegen overhead
+--------------------------------
+The different proto serialization/deserialization codegen methods have different
+overhead. Some have a higher up-front cost, but lower complexity (and therefore
+smaller compiler generated code) at the sites of usage. Others trade lower
+up-front code size cost for more complexity at the proto construction and read
+sites.
+
+This example uses the following proto message to construct a variety of use
+cases to illustrate how code and memory requirements can change depending on
+the complexity of the proto message being encoded/decoded.
+
+.. literalinclude:: pw_protobuf_test_protos/size_report.proto
+  :language: protobuf
+  :lines: 14-
+
+This proto is configured with the following options file:
+
+.. literalinclude:: pw_protobuf_test_protos/size_report.options
+  :lines: 14-
+
+Trivial proto
+=============
+This is a size report for encoding/decoding the ``pw.protobuf.test.ItemInfo``
+message. This is a pretty trivial message with just a few integers.
+
+.. include:: size_report/simple_codegen_size_comparison
+
+Optional and oneof
+==================
+This is a size report for encoding/decoding the
+``pw.protobuf.test.ResponseInfo`` message. This is slightly more complex message
+that has a few explicitly optional fields, a oneof, and a submessage.
+
+.. include:: size_report/oneof_codegen_size_comparison
diff --git a/pw_protobuf/size_report/BUILD.bazel b/pw_protobuf/size_report/BUILD.bazel
index aea516d..3e4d7c0 100644
--- a/pw_protobuf/size_report/BUILD.bazel
+++ b/pw_protobuf/size_report/BUILD.bazel
@@ -62,6 +62,17 @@
 )
 
 pw_cc_binary(
+    name = "proto_base",
+    srcs = [
+        "proto_base.cc",
+    ],
+    deps = [
+        ":proto_bloat",
+        "//pw_bloat:bloat_this_binary",
+    ],
+)
+
+pw_cc_binary(
     name = "encode_decode_core",
     srcs = [
         "encode_decode_core.cc",
@@ -84,12 +95,67 @@
 )
 
 pw_cc_binary(
-    name = "proto_base",
-    srcs = [
-        "proto_base.cc",
-    ],
+    name = "messages_no_codegen",
+    srcs = ["simple_codegen_comparison.cc"],
+    defines = ["_PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN=1"],
     deps = [
         ":proto_bloat",
         "//pw_bloat:bloat_this_binary",
+        "//pw_protobuf:codegen_test_proto_cc.pwpb",
+    ],
+)
+
+pw_cc_binary(
+    name = "messages_wire_format",
+    srcs = ["simple_codegen_comparison.cc"],
+    defines = ["_PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT=1"],
+    deps = [
+        ":proto_bloat",
+        "//pw_bloat:bloat_this_binary",
+        "//pw_protobuf:codegen_test_proto_cc.pwpb",
+    ],
+)
+
+pw_cc_binary(
+    name = "messages_message",
+    srcs = ["simple_codegen_comparison.cc"],
+    defines = ["_PW_PROTOBUF_SIZE_REPORT_MESSAGE=1"],
+    deps = [
+        ":proto_bloat",
+        "//pw_bloat:bloat_this_binary",
+        "//pw_protobuf:codegen_test_proto_cc.pwpb",
+    ],
+)
+
+pw_cc_binary(
+    name = "oneof_no_codegen",
+    srcs = ["oneof_codegen_comparison.cc"],
+    defines = ["_PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN=1"],
+    deps = [
+        ":proto_bloat",
+        "//pw_bloat:bloat_this_binary",
+        "//pw_protobuf:codegen_test_proto_cc.pwpb",
+    ],
+)
+
+pw_cc_binary(
+    name = "oneof_wire_format",
+    srcs = ["oneof_codegen_comparison.cc"],
+    defines = ["_PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT=1"],
+    deps = [
+        ":proto_bloat",
+        "//pw_bloat:bloat_this_binary",
+        "//pw_protobuf:codegen_test_proto_cc.pwpb",
+    ],
+)
+
+pw_cc_binary(
+    name = "oneof_message",
+    srcs = ["oneof_codegen_comparison.cc"],
+    defines = ["_PW_PROTOBUF_SIZE_REPORT_MESSAGE=1"],
+    deps = [
+        ":proto_bloat",
+        "//pw_bloat:bloat_this_binary",
+        "//pw_protobuf:codegen_test_proto_cc.pwpb",
     ],
 )
diff --git a/pw_protobuf/size_report/BUILD.gn b/pw_protobuf/size_report/BUILD.gn
index dd3d310..a28f2da 100644
--- a/pw_protobuf/size_report/BUILD.gn
+++ b/pw_protobuf/size_report/BUILD.gn
@@ -62,6 +62,66 @@
   sources = [ "message_core.cc" ]
 }
 
+pw_executable("messages_no_codegen") {
+  defines = [ "_PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN=1" ]
+  deps = [
+    ":proto_bloat",
+    "$dir_pw_bloat:bloat_this_binary",
+    "..:codegen_test_protos.pwpb",
+  ]
+  sources = [ "simple_codegen_comparison.cc" ]
+}
+
+pw_executable("messages_wire_format") {
+  defines = [ "_PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT=1" ]
+  deps = [
+    ":proto_bloat",
+    "$dir_pw_bloat:bloat_this_binary",
+    "..:codegen_test_protos.pwpb",
+  ]
+  sources = [ "simple_codegen_comparison.cc" ]
+}
+
+pw_executable("messages_message") {
+  defines = [ "_PW_PROTOBUF_SIZE_REPORT_MESSAGE=1" ]
+  deps = [
+    ":proto_bloat",
+    "$dir_pw_bloat:bloat_this_binary",
+    "..:codegen_test_protos.pwpb",
+  ]
+  sources = [ "simple_codegen_comparison.cc" ]
+}
+
+pw_executable("oneof_no_codegen") {
+  defines = [ "_PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN=1" ]
+  deps = [
+    ":proto_bloat",
+    "$dir_pw_bloat:bloat_this_binary",
+    "..:codegen_test_protos.pwpb",
+  ]
+  sources = [ "oneof_codegen_comparison.cc" ]
+}
+
+pw_executable("oneof_wire_format") {
+  defines = [ "_PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT=1" ]
+  deps = [
+    ":proto_bloat",
+    "$dir_pw_bloat:bloat_this_binary",
+    "..:codegen_test_protos.pwpb",
+  ]
+  sources = [ "oneof_codegen_comparison.cc" ]
+}
+
+pw_executable("oneof_message") {
+  defines = [ "_PW_PROTOBUF_SIZE_REPORT_MESSAGE=1" ]
+  deps = [
+    ":proto_bloat",
+    "$dir_pw_bloat:bloat_this_binary",
+    "..:codegen_test_protos.pwpb",
+  ]
+  sources = [ "oneof_codegen_comparison.cc" ]
+}
+
 pw_toolchain_size_diff("decoder_partial") {
   base_executable = pw_bloat_empty_base
   diff_executable = _decoder_partial
@@ -81,6 +141,66 @@
   title = "Adding more fields to decode callback"
 }
 
+pw_size_diff("simple_codegen_size_comparison") {
+  title = "Pigweed protobuf codegen size report"
+  source_filter = "pw::protobuf_size_report::*"
+  binaries = [
+    {
+      target = ":messages_no_codegen"
+      base = ":message_core"
+      label = "Direct wire-format proto encoder"
+    },
+    {
+      target = ":messages_wire_format"
+      base = ":message_core"
+      label = "Generated wrapped wire-format encoder"
+    },
+    {
+      target = ":messages_message"
+      base = ":message_core"
+      label = "Generated message encoder"
+    },
+  ]
+}
+
+pw_size_diff("oneof_codegen_size_comparison") {
+  title = "Pigweed protobuf codegen size report"
+  source_filter = "pw::protobuf_size_report::*"
+  binaries = [
+    {
+      target = ":oneof_no_codegen"
+      base = ":message_core"
+      label = "Direct wire-format proto encoder"
+    },
+    {
+      target = ":oneof_wire_format"
+      base = ":message_core"
+      label = "Generated wrapped wire-format encoder"
+    },
+    {
+      target = ":oneof_message"
+      base = ":message_core"
+      label = "Generated message encoder"
+    },
+  ]
+}
+
+pw_size_diff("message_size_report") {
+  title = "Pigweed protobuf message size report"
+  binaries = [
+    {
+      target = ":one_message_struct_write_vs_base"
+      base = ":proto_base"
+      label = "Message encoder flash cost (incl. wire-format encoder)"
+    },
+    {
+      target = ":one_message_struct_write_vs_encoder"
+      base = ":encoder_full"
+      label = "Message encoder flash cost (excl. wire-format encoder)"
+    },
+  ]
+}
+
 pw_size_diff("protobuf_overview") {
   title = "Pigweed protobuf encoder size report"
   source_filter = "pw::protobuf::*|section .code"
diff --git a/pw_protobuf/size_report/oneof_codegen_comparison.cc b/pw_protobuf/size_report/oneof_codegen_comparison.cc
new file mode 100644
index 0000000..8c06b45
--- /dev/null
+++ b/pw_protobuf/size_report/oneof_codegen_comparison.cc
@@ -0,0 +1,387 @@
+// Copyright 2022 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "proto_bloat.h"
+#include "pw_bloat/bloat_this_binary.h"
+#include "pw_protobuf/decoder.h"
+#include "pw_protobuf/encoder.h"
+#include "pw_protobuf/stream_decoder.h"
+#include "pw_protobuf_test_protos/size_report.pwpb.h"
+#include "pw_result/result.h"
+#include "pw_status/status.h"
+
+#ifndef _PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN
+#define _PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN 0
+#endif  // _PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN
+
+#ifndef _PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT
+#define _PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT 0
+#endif  // _PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT
+
+#ifndef _PW_PROTOBUF_SIZE_REPORT_MESSAGE
+#define _PW_PROTOBUF_SIZE_REPORT_MESSAGE 0
+#endif  // _PW_PROTOBUF_SIZE_REPORT_MESSAGE
+
+namespace pw::protobuf_size_report {
+namespace {
+
+template <typename T>
+PW_NO_INLINE void ConsumeValue(T val) {
+  [[maybe_unused]] volatile T no_optimize = val;
+}
+
+#if _PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN
+
+std::array<std::byte, ItemInfo::kMaxEncodedSizeBytes> encode_buffer;
+pw::protobuf::MemoryEncoder encoder(encode_buffer);
+
+PW_NO_INLINE void BasicEncode() {
+  pw::Status status;
+  volatile enum KeyType : uint32_t {
+    NONE = 0,
+    KEY_STRING = 1,
+    KEY_TOKEN = 2,
+  } which_key = KeyType::KEY_STRING;
+  volatile bool has_timestamp = true;
+  volatile bool has_has_value = false;
+  if (which_key == KeyType::KEY_STRING) {
+    encoder.WriteString(1, "test");
+  } else if (which_key == KeyType::KEY_TOKEN) {
+    encoder.WriteFixed32(2, 99999);
+  }
+
+  if (has_timestamp) {
+    encoder.WriteInt64(3, 1663003467);
+  }
+
+  if (has_has_value) {
+    encoder.WriteBool(4, true);
+  }
+
+  {
+    pw::protobuf::StreamEncoder submessage_encoder =
+        encoder.GetNestedEncoder(5);
+    status.Update(submessage_encoder.WriteInt64(1, 0x5001DBADFEEDBEE5));
+    status.Update(submessage_encoder.WriteInt32(2, 128));
+    status.Update(submessage_encoder.WriteInt32(3, 2));
+  }
+  ConsumeValue(status);
+}
+
+std::array<std::byte, ItemInfo::kMaxEncodedSizeBytes> decode_buffer;
+pw::protobuf::Decoder decoder(decode_buffer);
+
+PW_NO_INLINE void DecodeItemInfo(pw::ConstByteSpan data) {
+  pw::protobuf::Decoder submessage_decoder(data);
+  while (submessage_decoder.Next().ok()) {
+    switch (submessage_decoder.FieldNumber()) {
+      case static_cast<uint32_t>(ItemInfo::Fields::OFFSET): {
+        uint64_t value;
+        if (submessage_decoder.ReadUint64(&value).ok()) {
+          ConsumeValue(value);
+        }
+        break;
+      }
+      case static_cast<uint32_t>(ItemInfo::Fields::SIZE): {
+        uint32_t value;
+        if (submessage_decoder.ReadUint32(&value).ok()) {
+          ConsumeValue(value);
+        }
+        break;
+      }
+      case static_cast<uint32_t>(ItemInfo::Fields::ACCESS_LEVEL): {
+        uint32_t value;
+
+        if (submessage_decoder.ReadUint32(&value).ok()) {
+          ConsumeValue(value);
+        }
+        break;
+      }
+    }
+  }
+}
+
+PW_NO_INLINE void BasicDecode() {
+  volatile enum KeyType : uint32_t {
+    NONE = 0,
+    KEY_STRING = 1,
+    KEY_TOKEN = 2,
+  } which_key = KeyType::NONE;
+  volatile bool has_timestamp = false;
+  volatile bool has_has_value = false;
+
+  while (decoder.Next().ok()) {
+    switch (decoder.FieldNumber()) {
+      case static_cast<uint32_t>(ResponseInfo::Fields::KEY_STRING): {
+        which_key = KeyType::KEY_STRING;
+        std::string_view value;
+        if (decoder.ReadString(&value).ok()) {
+          ConsumeValue(value);
+        }
+        break;
+      }
+      case static_cast<uint32_t>(ResponseInfo::Fields::KEY_TOKEN): {
+        which_key = KeyType::KEY_TOKEN;
+        uint32_t value;
+        if (decoder.ReadUint32(&value).ok()) {
+          ConsumeValue(value);
+        }
+        break;
+      }
+      case static_cast<uint32_t>(ResponseInfo::Fields::TIMESTAMP): {
+        uint64_t value;
+        has_timestamp = true;
+        if (decoder.ReadUint64(&value).ok()) {
+          ConsumeValue(value);
+        }
+        break;
+      }
+      case static_cast<uint32_t>(ResponseInfo::Fields::HAS_VALUE): {
+        bool value;
+        has_has_value = true;
+        if (decoder.ReadBool(&value).ok()) {
+          ConsumeValue(value);
+        }
+        break;
+      }
+      case static_cast<uint32_t>(ResponseInfo::Fields::ITEM_INFO): {
+        pw::ConstByteSpan value;
+        if (decoder.ReadBytes(&value).ok()) {
+          DecodeItemInfo(value);
+        }
+        break;
+      }
+    }
+  }
+  ConsumeValue(which_key);
+  ConsumeValue(has_timestamp);
+  ConsumeValue(has_has_value);
+}
+
+#endif  // _PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN
+
+#if _PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT
+
+std::array<std::byte, ResponseInfo::kMaxEncodedSizeBytes> encode_buffer;
+ResponseInfo::MemoryEncoder encoder(encode_buffer);
+
+PW_NO_INLINE void BasicEncode() {
+  pw::Status status;
+  volatile enum KeyType : uint32_t {
+    NONE = 0,
+    KEY_STRING = 1,
+    KEY_TOKEN = 2,
+  } which_key = KeyType::KEY_STRING;
+  volatile bool has_timestamp = true;
+  volatile bool has_has_value = false;
+  if (which_key == KeyType::KEY_STRING) {
+    encoder.WriteKeyString("test");
+  } else if (which_key == KeyType::KEY_TOKEN) {
+    encoder.WriteKeyToken(99999);
+  }
+
+  if (has_timestamp) {
+    encoder.WriteTimestamp(1663003467);
+  }
+
+  if (has_has_value) {
+    encoder.WriteHasValue(true);
+  }
+
+  {
+    ItemInfo::StreamEncoder submessage_encoder = encoder.GetItemInfoEncoder();
+    status.Update(submessage_encoder.WriteOffset(0x5001DBADFEEDBEE5));
+    status.Update(submessage_encoder.WriteSize(128));
+    status.Update(submessage_encoder.WriteAccessLevel(ItemInfo::Access::WRITE));
+  }
+  ConsumeValue(status);
+}
+
+std::array<std::byte, ResponseInfo::kMaxEncodedSizeBytes> decode_buffer;
+pw::stream::MemoryReader reader(decode_buffer);
+ResponseInfo::StreamDecoder decoder(reader);
+
+PW_NO_INLINE void DecodeItemInfo(ItemInfo::StreamDecoder& submessage_decoder) {
+  while (submessage_decoder.Next().ok()) {
+    pw::Result<ItemInfo::Fields> field = submessage_decoder.Field();
+    if (!field.ok()) {
+      ConsumeValue(field.status());
+      return;
+    }
+
+    switch (field.value()) {
+      case ItemInfo::Fields::OFFSET: {
+        pw::Result<uint64_t> value = submessage_decoder.ReadOffset();
+        if (value.ok()) {
+          ConsumeValue(value);
+        }
+        break;
+      }
+      case ItemInfo::Fields::SIZE: {
+        pw::Result<uint32_t> value = submessage_decoder.ReadSize();
+        if (value.ok()) {
+          ConsumeValue(value);
+        }
+        break;
+      }
+      case ItemInfo::Fields::ACCESS_LEVEL: {
+        pw::Result<ItemInfo::Access> value =
+            submessage_decoder.ReadAccessLevel();
+        if (value.ok()) {
+          ConsumeValue(value);
+        }
+        break;
+      }
+    }
+  }
+}
+
+PW_NO_INLINE void BasicDecode() {
+  volatile enum KeyType : uint32_t {
+    NONE = 0,
+    KEY_STRING = 1,
+    KEY_TOKEN = 2,
+  } which_key = KeyType::NONE;
+  volatile bool has_timestamp = false;
+  volatile bool has_has_value = false;
+
+  while (decoder.Next().ok()) {
+    while (decoder.Next().ok()) {
+      pw::Result<ResponseInfo::Fields> field = decoder.Field();
+      if (!field.ok()) {
+        ConsumeValue(field.status());
+        return;
+      }
+
+      switch (field.value()) {
+        case ResponseInfo::Fields::KEY_STRING: {
+          which_key = KeyType::KEY_STRING;
+          std::array<char, 8> value;
+          pw::StatusWithSize status = decoder.ReadKeyString(value);
+          if (status.ok()) {
+            ConsumeValue(pw::span(value));
+          }
+          break;
+        }
+        case ResponseInfo::Fields::KEY_TOKEN: {
+          which_key = KeyType::KEY_TOKEN;
+          pw::Result<uint32_t> value = decoder.ReadKeyToken();
+          if (value.ok()) {
+            ConsumeValue(value);
+          }
+          break;
+        }
+        case ResponseInfo::Fields::TIMESTAMP: {
+          has_timestamp = true;
+          pw::Result<int64_t> value = decoder.ReadTimestamp();
+          if (value.ok()) {
+            ConsumeValue(value);
+          }
+          break;
+        }
+        case ResponseInfo::Fields::HAS_VALUE: {
+          has_has_value = true;
+          pw::Result<bool> value = decoder.ReadHasValue();
+          if (value.ok()) {
+            ConsumeValue(value);
+          }
+          break;
+        }
+        case ResponseInfo::Fields::ITEM_INFO: {
+          ItemInfo::StreamDecoder submessage_decoder =
+              decoder.GetItemInfoDecoder();
+          DecodeItemInfo(submessage_decoder);
+          break;
+        }
+      }
+    }
+  }
+  ConsumeValue(which_key);
+  ConsumeValue(has_timestamp);
+  ConsumeValue(has_has_value);
+}
+#endif  // _PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT
+
+#if _PW_PROTOBUF_SIZE_REPORT_MESSAGE
+
+ResponseInfo::Message message;
+
+std::array<std::byte, ResponseInfo::kMaxEncodedSizeBytes> encode_buffer;
+ResponseInfo::MemoryEncoder encoder(encode_buffer);
+
+PW_NO_INLINE void BasicEncode() {
+  volatile enum KeyType : uint32_t {
+    NONE = 0,
+    KEY_STRING = 1,
+    KEY_TOKEN = 2,
+  } which_key = KeyType::KEY_STRING;
+  volatile bool has_timestamp = true;
+  volatile bool has_has_value = false;
+  if (which_key == KeyType::KEY_STRING) {
+    message.key_string.SetEncoder(
+        [](ResponseInfo::StreamEncoder& key_string_encoder) -> pw::Status {
+          key_string_encoder.WriteKeyString("test");
+          return pw::OkStatus();
+        });
+  } else if (which_key == KeyType::KEY_TOKEN) {
+    message.key_token = 99999;
+  }
+  message.timestamp =
+      has_timestamp ? std::optional<uint32_t>(1663003467) : std::nullopt;
+  message.has_value = has_has_value ? std::optional<bool>(false) : std::nullopt;
+
+  message.item_info.offset = 0x5001DBADFEEDBEE5;
+  message.item_info.size = 128;
+  message.item_info.access_level = ItemInfo::Access::WRITE;
+  ConsumeValue(encoder.Write(message));
+}
+
+std::array<std::byte, ResponseInfo::kMaxEncodedSizeBytes> decode_buffer;
+pw::stream::MemoryReader reader(decode_buffer);
+ResponseInfo::StreamDecoder decoder(reader);
+
+PW_NO_INLINE void BasicDecode() {
+  volatile enum KeyType : uint32_t {
+    NONE = 0,
+    KEY_STRING = 1,
+    KEY_TOKEN = 2,
+  } which_key = KeyType::NONE;
+  volatile bool has_timestamp = false;
+  volatile bool has_has_value = false;
+  if (pw::Status status = decoder.Read(message); status.ok()) {
+    ConsumeValue(status);
+    has_timestamp = message.timestamp.has_value();
+    has_has_value = message.has_value.has_value();
+  }
+  ConsumeValue(which_key);
+  ConsumeValue(has_timestamp);
+  ConsumeValue(has_has_value);
+}
+#endif  // _PW_PROTOBUF_SIZE_REPORT_MESSAGE
+
+}  // namespace
+}  // namespace pw::protobuf_size_report
+
+int main() {
+  pw::bloat::BloatThisBinary();
+  pw::protobuf_size_report::BloatWithBase();
+  pw::protobuf_size_report::BloatWithEncoder();
+  pw::protobuf_size_report::BloatWithStreamDecoder();
+  pw::protobuf_size_report::BloatWithDecoder();
+  pw::protobuf_size_report::BloatWithTableEncoder();
+  pw::protobuf_size_report::BloatWithTableDecoder();
+  pw::protobuf_size_report::BasicEncode();
+  pw::protobuf_size_report::BasicDecode();
+  return 0;
+}
diff --git a/pw_protobuf/size_report/simple_codegen_comparison.cc b/pw_protobuf/size_report/simple_codegen_comparison.cc
new file mode 100644
index 0000000..277a8f1
--- /dev/null
+++ b/pw_protobuf/size_report/simple_codegen_comparison.cc
@@ -0,0 +1,181 @@
+// Copyright 2022 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "proto_bloat.h"
+#include "pw_bloat/bloat_this_binary.h"
+#include "pw_protobuf/decoder.h"
+#include "pw_protobuf/encoder.h"
+#include "pw_protobuf/stream_decoder.h"
+#include "pw_protobuf_test_protos/size_report.pwpb.h"
+#include "pw_result/result.h"
+#include "pw_status/status.h"
+
+#ifndef _PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN
+#define _PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN 0
+#endif  // _PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN
+
+#ifndef _PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT
+#define _PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT 0
+#endif  // _PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT
+
+#ifndef _PW_PROTOBUF_SIZE_REPORT_MESSAGE
+#define _PW_PROTOBUF_SIZE_REPORT_MESSAGE 0
+#endif  // _PW_PROTOBUF_SIZE_REPORT_MESSAGE
+
+namespace pw::protobuf_size_report {
+namespace {
+
+template <typename T>
+PW_NO_INLINE void ConsumeValue(T val) {
+  [[maybe_unused]] volatile T no_optimize = val;
+}
+
+#if _PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN
+
+std::array<std::byte, ItemInfo::kMaxEncodedSizeBytes> encode_buffer;
+pw::protobuf::MemoryEncoder generic_encoder(encode_buffer);
+
+PW_NO_INLINE void BasicEncode() {
+  pw::Status status;
+  status.Update(generic_encoder.WriteInt64(1, 0x5001DBADFEEDBEE5));
+  status.Update(generic_encoder.WriteInt32(2, 128));
+  status.Update(generic_encoder.WriteInt32(3, 2));
+  ConsumeValue(status);
+}
+
+std::array<std::byte, ItemInfo::kMaxEncodedSizeBytes> decode_buffer;
+pw::protobuf::Decoder generic_decoder(decode_buffer);
+
+PW_NO_INLINE void BasicDecode() {
+  while (generic_decoder.Next().ok()) {
+    switch (generic_decoder.FieldNumber()) {
+      case static_cast<uint32_t>(ItemInfo::Fields::OFFSET): {
+        uint64_t value;
+        if (generic_decoder.ReadUint64(&value).ok()) {
+          ConsumeValue(value);
+        }
+        break;
+      }
+      case static_cast<uint32_t>(ItemInfo::Fields::SIZE): {
+        uint32_t value;
+        if (generic_decoder.ReadUint32(&value).ok()) {
+          ConsumeValue(value);
+        }
+        break;
+      }
+      case static_cast<uint32_t>(ItemInfo::Fields::ACCESS_LEVEL): {
+        uint32_t value;
+
+        if (generic_decoder.ReadUint32(&value).ok()) {
+          ConsumeValue(value);
+        }
+        break;
+      }
+    }
+  }
+}
+#endif  // _PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN
+
+#if _PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT
+
+std::array<std::byte, ItemInfo::kMaxEncodedSizeBytes> encode_buffer;
+ItemInfo::MemoryEncoder encoder(encode_buffer);
+
+PW_NO_INLINE void BasicEncode() {
+  pw::Status status;
+  status.Update(encoder.WriteOffset(0x5001DBADFEEDBEE5));
+  status.Update(encoder.WriteSize(128));
+  status.Update(encoder.WriteAccessLevel(ItemInfo::Access::WRITE));
+  ConsumeValue(status);
+}
+
+std::array<std::byte, ItemInfo::kMaxEncodedSizeBytes> decode_buffer;
+pw::stream::MemoryReader reader(decode_buffer);
+ItemInfo::StreamDecoder decoder(reader);
+
+PW_NO_INLINE void BasicDecode() {
+  while (decoder.Next().ok()) {
+    pw::Result<ItemInfo::Fields> field = decoder.Field();
+    if (!field.ok()) {
+      ConsumeValue(field.status());
+      return;
+    }
+
+    switch (field.value()) {
+      case ItemInfo::Fields::OFFSET: {
+        pw::Result<uint64_t> value = decoder.ReadOffset();
+        if (value.ok()) {
+          ConsumeValue(value);
+        }
+        break;
+      }
+      case ItemInfo::Fields::SIZE: {
+        pw::Result<uint32_t> value = decoder.ReadSize();
+        if (value.ok()) {
+          ConsumeValue(value);
+        }
+        break;
+      }
+      case ItemInfo::Fields::ACCESS_LEVEL: {
+        pw::Result<ItemInfo::Access> value = decoder.ReadAccessLevel();
+        if (value.ok()) {
+          ConsumeValue(value);
+        }
+        break;
+      }
+    }
+  }
+}
+#endif  // _PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT
+
+#if _PW_PROTOBUF_SIZE_REPORT_MESSAGE
+
+ItemInfo::Message message;
+
+std::array<std::byte, ItemInfo::kMaxEncodedSizeBytes> encode_buffer;
+ItemInfo::MemoryEncoder encoder(encode_buffer);
+
+PW_NO_INLINE void BasicEncode() {
+  message.offset = 0x5001DBADFEEDBEE5;
+  message.size = 128;
+  message.access_level = ItemInfo::Access::WRITE;
+  ConsumeValue(encoder.Write(message));
+}
+
+std::array<std::byte, ItemInfo::kMaxEncodedSizeBytes> decode_buffer;
+pw::stream::MemoryReader reader(decode_buffer);
+ItemInfo::StreamDecoder decoder(reader);
+
+PW_NO_INLINE void BasicDecode() {
+  if (pw::Status status = decoder.Read(message); status.ok()) {
+    ConsumeValue(status);
+  }
+}
+#endif  // _PW_PROTOBUF_SIZE_REPORT_MESSAGE
+
+}  // namespace
+}  // namespace pw::protobuf_size_report
+
+int main() {
+  pw::bloat::BloatThisBinary();
+  pw::protobuf_size_report::BloatWithBase();
+  pw::protobuf_size_report::BloatWithEncoder();
+  pw::protobuf_size_report::BloatWithStreamDecoder();
+  pw::protobuf_size_report::BloatWithDecoder();
+  pw::protobuf_size_report::BloatWithTableEncoder();
+  pw::protobuf_size_report::BloatWithTableDecoder();
+  pw::protobuf_size_report::BasicEncode();
+  pw::protobuf_size_report::BasicDecode();
+  return 0;
+}