Merge pull request #10268 from tonydnewell/bugfix/issue-8101

Bugfix/issue 8101
diff --git a/.gitignore b/.gitignore
index d8b0bd3..cc881f3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,6 +19,23 @@
 m4/lt~obsolete.m4
 autom4te.cache
 
+# CMake-generated files
+.ninja_deps
+.ninja_logs
+cmake/protobuf/*.cmake
+cmake_install.cmake
+CMakeCache.txt
+CTestTestfile.cmake
+CMakeFiles/*
+Testing/Temporary/*
+
+/core
+/protoc
+/test_plugin
+/tests
+/lite-test
+/protoc-*.*
+
 # downloaded files
 /gmock
 
@@ -41,6 +58,7 @@
 *.la
 src/.libs
 *.so
+*.a
 
 .dirstamp
 
diff --git a/BUILD.bazel b/BUILD.bazel
index 1a305e7..a14476a 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -266,7 +266,7 @@
 alias(
     name = "well_known_types_py_pb2",
     actual = "//python:well_known_types_py_pb2",
-    visibility = ["@upb//:__subpackages__"],
+    visibility = ["//visibility:public"],
 )
 
 alias(
diff --git a/CHANGES.txt b/CHANGES.txt
index 1b83184..a7d74a7 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,18 +1,62 @@
 2022-07-01 Unreleased version
+
   C++
   * Reduced .pb.o object file size slightly by explicitly instantiating
     InternalMetadata templates in the runtime.
   * Add C++20 keywords guarded by PROTOBUF_FUTURE_CPP20_KEYWORDS
   * Fixed crash in ThreadLocalStorage for pre-C++17 compilers on 32-bit ARM.
   * Clarified that JSON API non-OK statuses are not a stable API.
+  * Added a default implementation of MessageDifferencer::Reporter methods.
+  * proto2::MapPair is now an alias to std::pair.
+  * Hide C++ RepeatedField::UnsafeArenaSwap
 
   Kotlin
   * Kotlin generated code comments now use kdoc format instead of javadoc.
   * Escape keywords in package names in proto generated code
+  * Add Kotlin enum int value getters and setters
 
   Java
   * Performance improvement for repeated use of FieldMaskUtil#merge by caching
     constructed FieldMaskTrees.
+  * Optimized Java proto serialization gencode for protos having many extension ranges with few fields in between.
+
+  Python
+  * Changes ordering of printed fields in .pyi files from lexicographic to the same ordering found in the proto descriptor.
+
+  Compiler
+  * Print full path name of source .proto file on error
+
+
+2022-07-25 version 21.4 (C++/Java/Python/PHP/Objective-C/C#/Ruby)
+
+  C++
+  * Reduce the required alignment of ArenaString from 8 to 4 (#10298)
+
+
+2022-07-19 version 21.3 (C++/Java/Python/PHP/Objective-C/C#/Ruby)
+  C++
+  * Add header search paths to Protobuf-C++.podspec (#10024)
+  * Fixed Visual Studio constinit errors (#10232)
+  * Fix #9947: make the ABI compatible between debug and non-debug builds (#10271)
+
+  UPB
+  * Allow empty package names (fixes behavior regression in 4.21.0)
+  * Fix a SEGV bug when comparing a non-materialized sub-message (#10208)
+  * Fix several bugs in descriptor mapping containers (eg. descriptor.services_by_name)
+    * for x in mapping now yields keys rather than values, to match Python conventions and the behavior of the old library.
+    * Lookup operations now correctly reject unhashable types as map keys.
+    * We implement repr() to use the same format as dict.
+  * Fix maps to use the ScalarMapContainer class when appropriate
+  * Fix bug when parsing an unknown value in a proto2 enum extension (protocolbuffers/upb#717)
+
+  PHP
+  * Add "readonly" as a keyword for PHP and add previous classnames to descriptor pool (#10041)
+
+  Python
+  * Make //:protobuf_python and //:well_known_types_py_pb2 public (#10118)
+
+  Bazel
+  * Add back a filegroup for :well_known_protos (#10061)
 
 2022-06-27 version 21.2 (C++/Java/Python/PHP/Objective-C/C#/Ruby)
   C++
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 04cb330..2ed5ca1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -57,9 +57,11 @@
 option(protobuf_BUILD_TESTS "Build tests" ON)
 option(protobuf_BUILD_CONFORMANCE "Build conformance tests" OFF)
 option(protobuf_BUILD_EXAMPLES "Build examples" OFF)
+option(protobuf_BUILD_PROTOBUF_BINARIES "Build protobuf libraries and protoc compiler" ON)
 option(protobuf_BUILD_PROTOC_BINARIES "Build libprotoc and protoc compiler" ON)
 option(protobuf_BUILD_LIBPROTOC "Build libprotoc" OFF)
 option(protobuf_DISABLE_RTTI "Remove runtime type information in the binaries" OFF)
+option(protobuf_TEST_XML_OUTDIR "Output directory for XML logs from tests." "")
 if (BUILD_SHARED_LIBS)
   set(protobuf_BUILD_SHARED_LIBS_DEFAULT ON)
 else (BUILD_SHARED_LIBS)
@@ -81,6 +83,9 @@
 if (protobuf_BUILD_PROTOC_BINARIES OR protobuf_BUILD_TESTS)
   set(protobuf_BUILD_LIBPROTOC ON)
 endif ()
+if (NOT protobuf_BUILD_PROTOBUF_BINARIES)
+  set(protobuf_INSTALL OFF)
+endif()
 # Path to main configure script
 set(protobuf_CONFIGURE_SCRIPT "${protobuf_SOURCE_DIR}/configure.ac")
 
@@ -242,12 +247,12 @@
 if (MSVC)
   if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
     # Build with multiple processes
-    add_definitions(/MP)
+    add_compile_options(/MP)
   endif()
   # Set source file and execution character sets to UTF-8
-  add_definitions(/utf-8)
+  add_compile_options(/utf-8)
   # MSVC warning suppressions
-  add_definitions(
+  add_compile_options(
     /wd4065 # switch statement contains 'default' but no 'case' labels
     /wd4244 # 'conversion' conversion from 'type1' to 'type2', possible loss of data
     /wd4251 # 'identifier' : class 'type' needs to have dll-interface to be used by clients of class 'type2'
@@ -262,23 +267,16 @@
     /wd4996 # The compiler encountered a deprecated declaration.
   )
   # Allow big object
-  add_definitions(/bigobj)
+  add_compile_options(/bigobj)
   string(REPLACE "/" "\\" PROTOBUF_SOURCE_WIN32_PATH ${protobuf_SOURCE_DIR})
   string(REPLACE "/" "\\" PROTOBUF_BINARY_WIN32_PATH ${protobuf_BINARY_DIR})
   string(REPLACE "." ","  protobuf_RC_FILEVERSION "${protobuf_VERSION}")
-  configure_file(${protobuf_SOURCE_DIR}/cmake/extract_includes.bat.in extract_includes.bat)
 
   # Suppress linker warnings about files with no symbols defined.
-  set(CMAKE_STATIC_LINKER_FLAGS "${CMAKE_STATIC_LINKER_FLAGS} /ignore:4221")
+  string(APPEND CMAKE_STATIC_LINKER_FLAGS " /ignore:4221")
 
-  if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
-    # Configure Resource Compiler
-    enable_language(RC)
-    # use English language (0x409) in resource compiler
-    set(rc_flags "/l0x409")
-    # fix rc.exe invocations because of usage of add_definitions()
-    set(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> ${rc_flags} <DEFINES> /fo<OBJECT> <SOURCE>")
-  endif()
+  # use English language (0x409) in resource compiler
+  string(APPEND CMAKE_RC_FLAGS " -l0x409")
 
   # Generate the version.rc file used elsewhere.
   configure_file(${protobuf_SOURCE_DIR}/cmake/version.rc.in ${CMAKE_CURRENT_BINARY_DIR}/version.rc @ONLY)
@@ -304,28 +302,49 @@
   add_definitions(-DUNICODE -D_UNICODE)
 endif (protobuf_UNICODE)
 
-include(${protobuf_SOURCE_DIR}/cmake/libprotobuf-lite.cmake)
-include(${protobuf_SOURCE_DIR}/cmake/libprotobuf.cmake)
-if (protobuf_BUILD_LIBPROTOC)
-  include(${protobuf_SOURCE_DIR}/cmake/libprotoc.cmake)
-endif (protobuf_BUILD_LIBPROTOC)
-if (protobuf_BUILD_PROTOC_BINARIES)
-  include(${protobuf_SOURCE_DIR}/cmake/protoc.cmake)
-  if (NOT DEFINED protobuf_PROTOC_EXE)
-    set(protobuf_PROTOC_EXE protoc)
-  endif (NOT DEFINED protobuf_PROTOC_EXE)
-endif (protobuf_BUILD_PROTOC_BINARIES)
+if (protobuf_BUILD_PROTOBUF_BINARIES)
+  include(${protobuf_SOURCE_DIR}/cmake/libprotobuf-lite.cmake)
+  if (NOT DEFINED protobuf_LIB_PROTOBUF_LITE)
+    set(protobuf_LIB_PROTOBUF_LITE libprotobuf-lite)
+  endif ()
+  include(${protobuf_SOURCE_DIR}/cmake/libprotobuf.cmake)
+  if (NOT DEFINED protobuf_LIB_PROTOBUF)
+    set(protobuf_LIB_PROTOBUF libprotobuf)
+  endif ()
+  if (protobuf_BUILD_LIBPROTOC)
+    include(${protobuf_SOURCE_DIR}/cmake/libprotoc.cmake)
+    if (NOT DEFINED protobuf_LIB_PROTOC)
+      set(protobuf_LIB_PROTOC libprotoc)
+    endif ()
+  endif ()
+  if (protobuf_BUILD_PROTOC_BINARIES)
+    include(${protobuf_SOURCE_DIR}/cmake/protoc.cmake)
+    if (NOT DEFINED protobuf_PROTOC_EXE)
+      set(protobuf_PROTOC_EXE protoc)
+    endif ()
+  endif ()
+else ()
+  find_package(protobuf)
+  if (protobuf_FOUND)
+    set(protobuf_PROTOC_EXE protobuf::protoc)
+    set(protobuf_LIB_PROTOC protobuf::libprotoc)
+    set(protobuf_LIB_PROTOBUF protobuf::libprotobuf)
+    set(protobuf_LIB_PROTOBUF_LITE protobuf::libprotobuf-lite)
+    message(STATUS "CMake installation of Protobuf found.")
+  endif ()
+endif ()
 
-# Ensure we have a protoc executable if we need one
+# Ensure we have a protoc executable and protobuf libraries if we need one
 if (protobuf_BUILD_TESTS OR protobuf_BUILD_CONFORMANCE OR protobuf_BUILD_EXAMPLES)
   if (NOT DEFINED protobuf_PROTOC_EXE)
-    find_program(protobuf_PROTOC_EXE protoc)
-    if (NOT protobuf_PROTOC_EXE)
-      message(FATAL "Build requires 'protoc' but binary not found and not building protoc.")
-    endif ()
+    find_program(protobuf_PROTOC_EXE protoc REQUIRED)
+    message(STATUS "Found system ${protobuf_PROTOC_EXE}.")
   endif ()
   if(protobuf_VERBOSE)
     message(STATUS "Using protoc : ${protobuf_PROTOC_EXE}")
+    message(STATUS "Using libprotobuf : ${protobuf_LIB_PROTOBUF}")
+    message(STATUS "Using libprotobuf-lite : ${protobuf_LIB_PROTOBUF_LITE}")
+    message(STATUS "Using libprotoc : ${protobuf_LIB_PROTOC}")
   endif(protobuf_VERBOSE)
 endif ()
 
diff --git a/Makefile.am b/Makefile.am
index d35bcaf..acc72c2 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -131,11 +131,13 @@
   csharp/src/Google.Protobuf.Test/GeneratedMessageTest.Proto2.cs             \
   csharp/src/Google.Protobuf.Test/Google.Protobuf.Test.csproj                \
   csharp/src/Google.Protobuf.Test/IssuesTest.cs                              \
+  csharp/src/Google.Protobuf.Test/JsonFormatterSettingsTest.cs               \
   csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs                       \
   csharp/src/Google.Protobuf.Test/JsonParserTest.cs                          \
   csharp/src/Google.Protobuf.Test/JsonTokenizerTest.cs                       \
   csharp/src/Google.Protobuf.Test/LegacyGeneratedCodeTest.cs                 \
   csharp/src/Google.Protobuf.Test/MessageParsingHelpers.cs                   \
+  csharp/src/Google.Protobuf.Test/ParsingPrimitivesTest.cs                   \
   csharp/src/Google.Protobuf.Test/Proto3OptionalTest.cs                      \
   csharp/src/Google.Protobuf.Test/ReadOnlySequenceFactory.cs                 \
   csharp/src/Google.Protobuf.Test/RefStructCompatibilityTest.cs              \
@@ -174,6 +176,7 @@
   csharp/src/Google.Protobuf.Test/WellKnownTypes/FieldMaskTest.cs            \
   csharp/src/Google.Protobuf.Test/WellKnownTypes/TimestampTest.cs            \
   csharp/src/Google.Protobuf.Test/WellKnownTypes/WrappersTest.cs             \
+  csharp/src/Google.Protobuf.Test/WritingPrimitivesTest.cs                   \
   csharp/src/Google.Protobuf.Test/UnknownFieldSetTest.cs                     \
   csharp/src/Google.Protobuf.Test/testprotos.pb                              \
   csharp/src/Google.Protobuf.sln                                             \
@@ -958,6 +961,8 @@
   php/tests/EncodeDecodeTest.php                                      \
   php/tests/force_c_ext.php                                           \
   php/tests/gdb_test.sh                                               \
+  php/tests/generated_previous/GPBMetadata/ProtoPrevious/TestPreviouslyUnreservedMessage.php \
+  php/tests/generated_previous/Previous/readonly.php                  \
   php/tests/GeneratedClassTest.php                                    \
   php/tests/GeneratedPhpdocTest.php                                   \
   php/tests/GeneratedServiceTest.php                                  \
@@ -967,6 +972,7 @@
   php/tests/multirequest.php                                          \
   php/tests/multirequest.sh                                           \
   php/tests/PhpImplementationTest.php                                 \
+  php/tests/PreviouslyGeneratedClassTest.php                          \
   php/tests/proto/empty/echo.proto                                    \
   php/tests/proto/test.proto                                          \
   php/tests/proto/test_descriptors.proto                              \
@@ -985,6 +991,7 @@
   php/tests/proto/test_service.proto                                  \
   php/tests/proto/test_service_namespace.proto                        \
   php/tests/proto/test_wrapper_type_setters.proto                     \
+  php/tests/proto_previous/test_previously_unreserved_message.proto   \
   php/tests/test_base.php                                             \
   php/tests/test_util.php                                             \
   php/tests/valgrind.supp                                             \
@@ -1214,7 +1221,6 @@
   cmake/README.md                                   \
   cmake/conformance.cmake                           \
   cmake/examples.cmake                              \
-  cmake/extract_includes.bat.in                     \
   cmake/install.cmake                               \
   cmake/libprotobuf-lite.cmake                      \
   cmake/libprotobuf.cmake                           \
diff --git a/Protobuf-C++.podspec b/Protobuf-C++.podspec
index c8a516b..858a861 100644
--- a/Protobuf-C++.podspec
+++ b/Protobuf-C++.podspec
@@ -1,6 +1,6 @@
 Pod::Spec.new do |s|
   s.name     = 'Protobuf-C++'
-  s.version  = '3.21.2'
+  s.version  = '3.21.4'
   s.summary  = 'Protocol Buffers v3 runtime library for C++.'
   s.homepage = 'https://github.com/google/protobuf'
   s.license  = 'BSD-3-Clause'
diff --git a/Protobuf.podspec b/Protobuf.podspec
index 85f1dc6..e41f39a 100644
--- a/Protobuf.podspec
+++ b/Protobuf.podspec
@@ -5,7 +5,7 @@
 # dependent projects use the :git notation to refer to the library.
 Pod::Spec.new do |s|
   s.name     = 'Protobuf'
-  s.version  = '3.21.2'
+  s.version  = '3.21.4'
   s.summary  = 'Protocol Buffers v.3 runtime library for Objective-C.'
   s.homepage = 'https://github.com/protocolbuffers/protobuf'
   s.license  = 'BSD-3-Clause'
diff --git a/README.md b/README.md
index c2e6575..ae63158 100644
--- a/README.md
+++ b/README.md
@@ -82,3 +82,10 @@
 web at:
 
 https://developers.google.com/protocol-buffers/
+
+Developer Community
+-------------------
+
+To be alerted to upcoming changes in Protocol Buffers and connect with protobuf developers and users,
+[join the Google Group](https://groups.google.com/g/protobuf).
+
diff --git a/cmake/README.md b/cmake/README.md
index ce3e680..4a4259e 100644
--- a/cmake/README.md
+++ b/cmake/README.md
@@ -1,15 +1,19 @@
-This directory contains *CMake* files that can be used to build protobuf
-with *MSVC* on *Windows*. You can build the project from *Command Prompt*
-and using an *Visual Studio* IDE.
+This directory contains *CMake* files that can be used to build protobuf.
 
-You need to have [CMake](http://www.cmake.org), [Visual Studio](https://www.visualstudio.com)
-and optionally [Git](http://git-scm.com) installed on your computer before proceeding.
+You need to have [CMake](http://www.cmake.org) and
+[Git](http://git-scm.com) installed on your computer before proceeding.
 
-Most of the instructions will be given to the *Сommand Prompt*, but the same
-actions can be performed using appropriate GUI tools.
+Most of the instructions will be given using CMake's command-line interface, but
+the same actions can be performed using appropriate GUI tools.
 
-Environment Setup
-=================
+# Windows Builds
+
+On Windows, you can build the project from *Command Prompt* and using an
+*Visual Studio* IDE.  You will also need to have
+[Visual Studio](https://www.visualstudio.com) installed on your computer before
+proceeding.
+
+## Environment Setup
 
 Open the appropriate *Command Prompt* from the *Start* menu.
 
@@ -42,8 +46,7 @@
 
 Good. Now you are ready to continue.
 
-Getting Sources
-===============
+## Getting Sources
 
 You can get the latest stable source packages from the release page:
 
@@ -76,8 +79,7 @@
 
 Good. Now you are ready for *CMake* configuration.
 
-CMake Configuration
-===================
+## CMake Configuration
 
 *CMake* supports a lot of different
 [generators](http://www.cmake.org/cmake/help/latest/manual/cmake-generators.7.html)
@@ -137,8 +139,7 @@
 
 It will generate *Visual Studio* solution file *protobuf.sln* in current directory.
 
-Unit Tests
-----------
+### Unit Tests
 
 Unit tests are being built along with the rest of protobuf. The unit tests require Google Mock (now a part of Google Test).
 
@@ -178,8 +179,7 @@
      -Dprotobuf_BUILD_TESTS=OFF ^
      C:\Path\to\src\protobuf
 
-Compiling
-=========
+## Compiling
 
 The standard way to compile a *CMake* project is `cmake --build <directory>`.
 
@@ -206,8 +206,7 @@
 
 And wait for the compilation to finish.
 
-Testing
-=======
+## Testing
 
 To run unit-tests, first you must compile protobuf as described above.
 Then run:
@@ -259,8 +258,7 @@
 
 If all tests are passed, safely continue.
 
-Installing
-==========
+## Installing
 
 To install protobuf to the *install* folder you've specified in the configuration step, you need to build the `install` target:
 
@@ -292,8 +290,7 @@
 debug build of libprotobufd.lib with "d" postfix.  Similarly, release builds should link against
 release libprotobuf.lib library.
 
-DLLs vs. static linking
-=======================
+## DLLs vs. static linking
 
 Static linking is now the default for the Protocol Buffer libraries.  Due to
 issues with Win32's use of a separate heap for each DLL, as well as binary
@@ -318,8 +315,7 @@
 public interface, and that you statically link protocol buffers into your
 library.
 
-ZLib support
-============
+## ZLib support
 
 If you want to include GzipInputStream and GzipOutputStream
 (google/protobuf/io/gzip_stream.h) in libprotobuf, you will need to do a few
@@ -369,8 +365,7 @@
 
 Build and testing protobuf as usual.
 
-Notes on Compiler Warnings
-==========================
+## Notes on Compiler Warnings
 
 The following warnings have been disabled while building the protobuf libraries
 and compiler.  You may have to disable some of them in your own project as
@@ -397,3 +392,23 @@
 nevertheless.  So, we disable it.  Unfortunately, this warning will also be
 produced when compiling code which merely uses protocol buffers, meaning you
 may have to disable it in your code too.
+
+# Linux Builds
+
+Building with CMake works very similarly on Linux.  Instead of Visual Studio,
+you will need to have gcc or clang installed to handle the C++ builds.  CMake
+will generate Makefiles by default, but can also be configured to use Ninja.  To
+build Protobuf, you will need to run (from the source directory):
+
+     cmake .
+     cmake --build . --parallel 10
+
+Protobuf can be tested and installed with CMake:
+
+     ctest --verbose
+     sudo cmake --install .
+
+or directly with the generated Makefiles:
+
+     make VERBOSE=1 test
+     sudo make install
diff --git a/cmake/conformance.cmake b/cmake/conformance.cmake
index d6c435a..338b275 100644
--- a/cmake/conformance.cmake
+++ b/cmake/conformance.cmake
@@ -24,6 +24,9 @@
   ${protobuf_SOURCE_DIR}/conformance/conformance.pb.cc
   ${protobuf_SOURCE_DIR}/conformance/conformance_test.cc
   ${protobuf_SOURCE_DIR}/conformance/conformance_test_runner.cc
+  ${protobuf_SOURCE_DIR}/conformance/conformance_test_main.cc
+  ${protobuf_SOURCE_DIR}/conformance/text_format_conformance_suite.cc
+  ${protobuf_SOURCE_DIR}/conformance/text_format_conformance_suite.h
   ${protobuf_SOURCE_DIR}/conformance/third_party/jsoncpp/json.h
   ${protobuf_SOURCE_DIR}/conformance/third_party/jsoncpp/jsoncpp.cpp
   ${protobuf_SOURCE_DIR}/src/google/protobuf/test_messages_proto2.pb.cc
@@ -45,5 +48,13 @@
   conformance_cpp
   PUBLIC ${protobuf_SOURCE_DIR}/conformance)
 
-target_link_libraries(conformance_test_runner libprotobuf)
-target_link_libraries(conformance_cpp libprotobuf)
+target_link_libraries(conformance_test_runner ${protobuf_LIB_PROTOBUF})
+target_link_libraries(conformance_cpp ${protobuf_LIB_PROTOBUF})
+
+add_test(NAME conformance_cpp_test
+  COMMAND ${CMAKE_CURRENT_BINARY_DIR}/conformance_test_runner
+    --failure_list ${protobuf_SOURCE_DIR}/conformance/failure_list_cpp.txt
+    --text_format_failure_list ${protobuf_SOURCE_DIR}/conformance/text_format_failure_list_cpp.txt
+    --output_dir ${protobuf_TEST_XML_OUTDIR}
+    ${CMAKE_CURRENT_BINARY_DIR}/conformance_cpp
+  DEPENDS conformance_test_runner conformance_cpp)
diff --git a/cmake/examples.cmake b/cmake/examples.cmake
index 3b83d2b..07cfad2 100644
--- a/cmake/examples.cmake
+++ b/cmake/examples.cmake
@@ -30,7 +30,7 @@
 # Add examples as an external project.
 # sub_directory cannot be used because the find_package(protobuf) call would cause failures with redefined targets.
 add_examples_build(examples "-Dprotobuf_DIR:PATH=${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_CMAKEDIR}")
-add_dependencies(examples libprotobuf protoc)
+add_dependencies(examples ${protobuf_LIB_PROTOBUF} ${protobuf_PROTOC_EXE})
 
 option(protobuf_BUILD_EXAMPLES_MULTITEST "Build Examples in multiple configurations. Useful for testing." OFF)
 mark_as_advanced(protobuf_BUILD_EXAMPLES_MULTITEST)
@@ -42,7 +42,7 @@
     "-Dprotobuf_DIR:PATH=${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_CMAKEDIR}"
     "-Dprotobuf_MODULE_COMPATIBLE:BOOL=TRUE"
   )
-  add_dependencies(examples-legacy libprotobuf protoc)
+  add_dependencies(examples-legacy ${protobuf_LIB_PROTOBUF} ${protobuf_PROTOC_EXE})
 
   #Build using the installed library.
   add_examples_build(examples-installed
diff --git a/cmake/extract_includes.bat.in b/cmake/extract_includes.bat.in
deleted file mode 100644
index 1292829..0000000
--- a/cmake/extract_includes.bat.in
+++ /dev/null
@@ -1,144 +0,0 @@
-mkdir include
-mkdir include\google
-mkdir include\google\protobuf
-mkdir include\google\protobuf\compiler
-mkdir include\google\protobuf\compiler\cpp
-mkdir include\google\protobuf\compiler\csharp
-mkdir include\google\protobuf\compiler\java
-mkdir include\google\protobuf\compiler\objectivec
-mkdir include\google\protobuf\compiler\php
-mkdir include\google\protobuf\compiler\python
-mkdir include\google\protobuf\compiler\ruby
-mkdir include\google\protobuf\io
-mkdir include\google\protobuf\stubs
-mkdir include\google\protobuf\util
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\any.h" include\google\protobuf\any.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\any.pb.h" include\google\protobuf\any.pb.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\api.pb.h" include\google\protobuf\api.pb.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\arena.h" include\google\protobuf\arena.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\arena_impl.h" include\google\protobuf\arena_impl.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\arenastring.h" include\google\protobuf\arenastring.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\arenaz_sampler.h" include\google\protobuf\arenaz_sampler.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\code_generator.h" include\google\protobuf\compiler\code_generator.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\command_line_interface.h" include\google\protobuf\compiler\command_line_interface.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\cpp\file.h" include\google\protobuf\compiler\cpp\file.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\cpp\cpp_generator.h" include\google\protobuf\compiler\cpp\cpp_generator.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\cpp\generator.h" include\google\protobuf\compiler\cpp\generator.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\cpp\helpers.h" include\google\protobuf\compiler\cpp\helpers.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\cpp\names.h" include\google\protobuf\compiler\cpp\names.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\csharp\csharp_doc_comment.h" include\google\protobuf\compiler\csharp\csharp_doc_comment.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\csharp\csharp_generator.h" include\google\protobuf\compiler\csharp\csharp_generator.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\csharp\csharp_names.h" include\google\protobuf\compiler\csharp\csharp_names.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\csharp\csharp_options.h" include\google\protobuf\compiler\csharp\csharp_options.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\importer.h" include\google\protobuf\compiler\importer.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\java\generator.h" include\google\protobuf\compiler\java\generator.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\java\java_generator.h" include\google\protobuf\compiler\java\java_generator.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\java\kotlin_generator.h" include\google\protobuf\compiler\java\kotlin_generator.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\java\names.h" include\google\protobuf\compiler\java\names.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\objectivec\objectivec_generator.h" include\google\protobuf\compiler\objectivec\objectivec_generator.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\objectivec\objectivec_helpers.h" include\google\protobuf\compiler\objectivec\objectivec_helpers.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\parser.h" include\google\protobuf\compiler\parser.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\php\php_generator.h" include\google\protobuf\compiler\php\php_generator.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\plugin.h" include\google\protobuf\compiler\plugin.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\plugin.pb.h" include\google\protobuf\compiler\plugin.pb.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\python\generator.h" include\google\protobuf\compiler\python\generator.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\python\pyi_generator.h" include\google\protobuf\compiler\python\pyi_generator.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\python\python_generator.h" include\google\protobuf\compiler\python\python_generator.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\ruby\ruby_generator.h" include\google\protobuf\compiler\ruby\ruby_generator.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\descriptor.h" include\google\protobuf\descriptor.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\descriptor.pb.h" include\google\protobuf\descriptor.pb.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\descriptor_database.h" include\google\protobuf\descriptor_database.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\duration.pb.h" include\google\protobuf\duration.pb.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\dynamic_message.h" include\google\protobuf\dynamic_message.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\empty.pb.h" include\google\protobuf\empty.pb.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\endian.h" include\google\protobuf\endian.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\explicitly_constructed.h" include\google\protobuf\explicitly_constructed.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\extension_set.h" include\google\protobuf\extension_set.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\extension_set_inl.h" include\google\protobuf\extension_set_inl.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\field_access_listener.h" include\google\protobuf\field_access_listener.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\field_mask.pb.h" include\google\protobuf\field_mask.pb.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\generated_enum_reflection.h" include\google\protobuf\generated_enum_reflection.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\generated_enum_util.h" include\google\protobuf\generated_enum_util.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\generated_message_bases.h" include\google\protobuf\generated_message_bases.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\generated_message_reflection.h" include\google\protobuf\generated_message_reflection.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\generated_message_tctable_decl.h" include\google\protobuf\generated_message_tctable_decl.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\generated_message_tctable_impl.h" include\google\protobuf\generated_message_tctable_impl.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\generated_message_util.h" include\google\protobuf\generated_message_util.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\has_bits.h" include\google\protobuf\has_bits.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\implicit_weak_message.h" include\google\protobuf\implicit_weak_message.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\inlined_string_field.h" include\google\protobuf\inlined_string_field.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\io\coded_stream.h" include\google\protobuf\io\coded_stream.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\io\gzip_stream.h" include\google\protobuf\io\gzip_stream.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\io\io_win32.h" include\google\protobuf\io\io_win32.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\io\printer.h" include\google\protobuf\io\printer.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\io\strtod.h" include\google\protobuf\io\strtod.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\io\tokenizer.h" include\google\protobuf\io\tokenizer.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\io\zero_copy_stream.h" include\google\protobuf\io\zero_copy_stream.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\io\zero_copy_stream_impl.h" include\google\protobuf\io\zero_copy_stream_impl.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\io\zero_copy_stream_impl_lite.h" include\google\protobuf\io\zero_copy_stream_impl_lite.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\map.h" include\google\protobuf\map.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\map_entry.h" include\google\protobuf\map_entry.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\map_entry_lite.h" include\google\protobuf\map_entry_lite.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\map_field.h" include\google\protobuf\map_field.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\map_field_inl.h" include\google\protobuf\map_field_inl.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\map_field_lite.h" include\google\protobuf\map_field_lite.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\map_type_handler.h" include\google\protobuf\map_type_handler.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\message.h" include\google\protobuf\message.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\message_lite.h" include\google\protobuf\message_lite.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\metadata.h" include\google\protobuf\metadata.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\metadata_lite.h" include\google\protobuf\metadata_lite.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\parse_context.h" include\google\protobuf\parse_context.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\port.h" include\google\protobuf\port.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\port_def.inc" include\google\protobuf\port_def.inc
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\port_undef.inc" include\google\protobuf\port_undef.inc
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\reflection.h" include\google\protobuf\reflection.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\reflection_ops.h" include\google\protobuf\reflection_ops.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\repeated_field.h" include\google\protobuf\repeated_field.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\repeated_ptr_field.h" include\google\protobuf\repeated_ptr_field.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\service.h" include\google\protobuf\service.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\source_context.pb.h" include\google\protobuf\source_context.pb.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\struct.pb.h" include\google\protobuf\struct.pb.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\stubs\bytestream.h" include\google\protobuf\stubs\bytestream.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\stubs\callback.h" include\google\protobuf\stubs\callback.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\stubs\casts.h" include\google\protobuf\stubs\casts.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\stubs\common.h" include\google\protobuf\stubs\common.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\stubs\hash.h" include\google\protobuf\stubs\hash.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\stubs\logging.h" include\google\protobuf\stubs\logging.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\stubs\macros.h" include\google\protobuf\stubs\macros.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\stubs\map_util.h" include\google\protobuf\stubs\map_util.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\stubs\mutex.h" include\google\protobuf\stubs\mutex.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\stubs\once.h" include\google\protobuf\stubs\once.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\stubs\platform_macros.h" include\google\protobuf\stubs\platform_macros.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\stubs\port.h" include\google\protobuf\stubs\port.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\stubs\status.h" include\google\protobuf\stubs\status.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\stubs\stl_util.h" include\google\protobuf\stubs\stl_util.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\stubs\stringpiece.h" include\google\protobuf\stubs\stringpiece.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\stubs\strutil.h" include\google\protobuf\stubs\strutil.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\stubs\template_util.h" include\google\protobuf\stubs\template_util.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\text_format.h" include\google\protobuf\text_format.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\timestamp.pb.h" include\google\protobuf\timestamp.pb.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\type.pb.h" include\google\protobuf\type.pb.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\unknown_field_set.h" include\google\protobuf\unknown_field_set.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\util\delimited_message_util.h" include\google\protobuf\util\delimited_message_util.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\util\field_comparator.h" include\google\protobuf\util\field_comparator.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\util\field_mask_util.h" include\google\protobuf\util\field_mask_util.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\util\json_util.h" include\google\protobuf\util\json_util.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\util\message_differencer.h" include\google\protobuf\util\message_differencer.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\util\time_util.h" include\google\protobuf\util\time_util.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\util\type_resolver.h" include\google\protobuf\util\type_resolver.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\util\type_resolver_util.h" include\google\protobuf\util\type_resolver_util.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\wire_format.h" include\google\protobuf\wire_format.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\wire_format_lite.h" include\google\protobuf\wire_format_lite.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\wrappers.pb.h" include\google\protobuf\wrappers.pb.h
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\any.proto" include\google\protobuf\any.proto
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\api.proto" include\google\protobuf\api.proto
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\plugin.proto" include\google\protobuf\compiler\plugin.proto
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\descriptor.proto" include\google\protobuf\descriptor.proto
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\duration.proto" include\google\protobuf\duration.proto
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\empty.proto" include\google\protobuf\empty.proto
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\field_mask.proto" include\google\protobuf\field_mask.proto
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\source_context.proto" include\google\protobuf\source_context.proto
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\struct.proto" include\google\protobuf\struct.proto
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\timestamp.proto" include\google\protobuf\timestamp.proto
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\type.proto" include\google\protobuf\type.proto
-copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\wrappers.proto" include\google\protobuf\wrappers.proto
diff --git a/cmake/install.cmake b/cmake/install.cmake
index 2da8170..cf24e30 100644
--- a/cmake/install.cmake
+++ b/cmake/install.cmake
@@ -43,25 +43,23 @@
 
 install(FILES ${CMAKE_CURRENT_BINARY_DIR}/protobuf.pc ${CMAKE_CURRENT_BINARY_DIR}/protobuf-lite.pc DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
 
-file(STRINGS ${protobuf_SOURCE_DIR}/cmake/extract_includes.bat.in _extract_strings
-  REGEX "^copy")
-foreach(_extract_string ${_extract_strings})
-  string(REGEX REPLACE "^.* .+ include\\\\(.+)$" "\\1"
-    _header ${_extract_string})
-  string(REPLACE "\\" "/" _header ${_header})
+include(${protobuf_SOURCE_DIR}/src/file_lists.cmake)
+set(protobuf_HEADERS
+  ${libprotobuf_hdrs}
+  ${libprotoc_hdrs}
+  ${wkt_protos_files}
+  ${descriptor_proto_proto_srcs}
+  ${plugin_proto_proto_srcs}
+)
+foreach(_header ${protobuf_HEADERS})
+  string(REPLACE "${protobuf_SOURCE_DIR}/src" "" _header ${_header})
   get_filename_component(_extract_from "${protobuf_SOURCE_DIR}/src/${_header}" ABSOLUTE)
   get_filename_component(_extract_name ${_header} NAME)
   get_filename_component(_extract_to "${CMAKE_INSTALL_INCLUDEDIR}/${_header}" DIRECTORY)
-  if(EXISTS "${_extract_from}")
-    install(FILES "${_extract_from}"
-      DESTINATION "${_extract_to}"
-      COMPONENT protobuf-headers
-      RENAME "${_extract_name}")
-  else()
-    message(AUTHOR_WARNING "The file \"${_extract_from}\" is listed in "
-      "\"${protobuf_SOURCE_DIR}/cmake/extract_includes.bat.in\" "
-      "but there not exists. The file will not be installed.")
-  endif()
+  install(FILES "${_extract_from}"
+    DESTINATION "${_extract_to}"
+    COMPONENT protobuf-headers
+    RENAME "${_extract_name}")
 endforeach()
 
 # Internal function for parsing auto tools scripts
diff --git a/cmake/tests.cmake b/cmake/tests.cmake
index be1d429..ce2a890 100644
--- a/cmake/tests.cmake
+++ b/cmake/tests.cmake
@@ -1,6 +1,7 @@
 option(protobuf_USE_EXTERNAL_GTEST "Use external Google Test (i.e. not the one in third_party/googletest)" OFF)
 
-option(protobuf_TEST_XML_OUTDIR "Output directory for XML logs from tests." "")
+option(protobuf_REMOVE_INSTALLED_HEADERS
+  "Remove local headers so that installed ones are used instead" OFF)
 
 option(protobuf_ABSOLUTE_TEST_PLUGIN_PATH
   "Using absolute test_plugin path in tests" ON)
@@ -77,10 +78,9 @@
 
 add_library(protobuf-lite-test-common STATIC
   ${lite_test_util_srcs} ${lite_test_proto_files})
-target_link_libraries(protobuf-lite-test-common libprotobuf-lite GTest::gmock)
+target_link_libraries(protobuf-lite-test-common ${protobuf_LIB_PROTOBUF_LITE} GTest::gmock)
 
 set(common_test_files
-  ${lite_test_util_srcs}
   ${test_util_hdrs}
   ${test_util_srcs}
   ${mock_code_generator_srcs}
@@ -89,7 +89,7 @@
 
 add_library(protobuf-test-common STATIC
   ${common_test_files} ${tests_proto_files})
-target_link_libraries(protobuf-test-common libprotobuf GTest::gmock)
+target_link_libraries(protobuf-test-common ${protobuf_LIB_PROTOBUF} GTest::gmock)
 
 set(tests_files
   ${protobuf_test_files}
@@ -130,7 +130,7 @@
     /wd4146 # unary minus operator applied to unsigned type, result still unsigned
   )
 endif()
-target_link_libraries(tests protobuf-lite-test-common protobuf-test-common libprotoc libprotobuf GTest::gmock_main)
+target_link_libraries(tests protobuf-lite-test-common protobuf-test-common ${protobuf_LIB_PROTOC} ${protobuf_LIB_PROTOBUF_LITE} GTest::gmock_main)
 
 set(test_plugin_files
   ${test_plugin_files}
@@ -139,19 +139,50 @@
 )
 
 add_executable(test_plugin ${test_plugin_files})
-target_link_libraries(test_plugin libprotoc libprotobuf GTest::gmock)
+target_link_libraries(test_plugin ${protobuf_LIB_PROTOC} ${protobuf_LIB_PROTOBUF} GTest::gmock)
 
 add_executable(lite-test ${protobuf_lite_test_files})
-target_link_libraries(lite-test protobuf-lite-test-common libprotobuf-lite GTest::gmock_main)
+target_link_libraries(lite-test protobuf-lite-test-common ${protobuf_LIB_PROTOBUF_LITE} GTest::gmock_main)
 
 add_test(NAME lite-test
   COMMAND lite-test ${protobuf_GTEST_ARGS})
 
 add_custom_target(check
   COMMAND tests
-  DEPENDS tests test_plugin
+  DEPENDS tests lite-test test_plugin
   WORKING_DIRECTORY ${protobuf_SOURCE_DIR})
 
 add_test(NAME check
-  COMMAND tests ${protobuf_GTEST_ARGS}
-  WORKING_DIRECTORY "${protobuf_SOURCE_DIR}")
+  COMMAND tests ${protobuf_GTEST_ARGS})
+
+# For test purposes, remove headers that should already be installed.  This
+# prevents accidental conflicts and also version skew (since local headers take
+# precedence over installed headers).
+add_custom_target(save-installed-headers)
+add_custom_target(remove-installed-headers)
+add_custom_target(restore-installed-headers)
+
+# Explicitly skip the bootstrapping headers as it's directly used in tests
+set(_installed_hdrs ${libprotobuf_hdrs} ${libprotoc_hdrs})
+list(REMOVE_ITEM _installed_hdrs
+  "${protobuf_SOURCE_DIR}/src/google/protobuf/descriptor.pb.h"
+  "${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/plugin.pb.h")
+
+foreach(_hdr ${_installed_hdrs})
+  string(REPLACE "${protobuf_SOURCE_DIR}/src" "" _file ${_hdr})
+  set(_tmp_file "${CMAKE_BINARY_DIR}/tmp-install-test/${_file}")
+  add_custom_command(TARGET remove-installed-headers PRE_BUILD
+                     COMMAND ${CMAKE_COMMAND} -E remove -f "${_hdr}")
+  add_custom_command(TARGET save-installed-headers PRE_BUILD
+                     COMMAND ${CMAKE_COMMAND} -E
+                        copy "${_hdr}" "${_tmp_file}" || true)
+  add_custom_command(TARGET restore-installed-headers PRE_BUILD
+                     COMMAND ${CMAKE_COMMAND} -E
+                        copy "${_tmp_file}" "${_hdr}")
+endforeach()
+
+add_dependencies(remove-installed-headers save-installed-headers)
+if(protobuf_REMOVE_INSTALLED_HEADERS)
+  add_dependencies(protobuf-lite-test-common remove-installed-headers)
+  add_dependencies(protobuf-test-common remove-installed-headers)
+endif()
diff --git a/configure.ac b/configure.ac
index c55a6a2..64ddb3d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -17,7 +17,7 @@
 # In the SVN trunk, the version should always be the next anticipated release
 # version with the "-pre" suffix.  (We used to use "-SNAPSHOT" but this pushed
 # the size of one file name in the dist tarfile over the 99-char limit.)
-AC_INIT([Protocol Buffers],[3.21.2],[protobuf@googlegroups.com],[protobuf])
+AC_INIT([Protocol Buffers],[3.21.4],[protobuf@googlegroups.com],[protobuf])
 
 AM_MAINTAINER_MODE([enable])
 
diff --git a/conformance/binary_json_conformance_suite.cc b/conformance/binary_json_conformance_suite.cc
index 9b1548d..3719a52 100644
--- a/conformance/binary_json_conformance_suite.cc
+++ b/conformance/binary_json_conformance_suite.cc
@@ -2724,6 +2724,12 @@
       "DurationNull", REQUIRED,
       R"({"optionalDuration": null})",
       "");
+  RunValidJsonTest("DurationNegativeSeconds", REQUIRED,
+                   R"({"optionalDuration": "-5s"})",
+                   "optional_duration: {seconds: -5 nanos: 0}");
+  RunValidJsonTest("DurationNegativeNanos", REQUIRED,
+                   R"({"optionalDuration": "-0.5s"})",
+                   "optional_duration: {seconds: 0 nanos: -500000000}");
 
   ExpectParseFailureForJson(
       "DurationMissingS", REQUIRED,
diff --git a/conformance/failure_list_php.txt b/conformance/failure_list_php.txt
index 667f80c..3232047a 100644
--- a/conformance/failure_list_php.txt
+++ b/conformance/failure_list_php.txt
@@ -8,6 +8,8 @@
 Recommended.Proto3.ProtobufInput.ValidDataOneofBinary.MESSAGE.Merge.ProtobufOutput
 Required.Proto2.JsonInput.StoresDefaultPrimitive.Validator
 Required.Proto3.JsonInput.DoubleFieldTooSmall
+Required.Proto3.JsonInput.DurationNegativeNanos.JsonOutput
+Required.Proto3.JsonInput.DurationNegativeNanos.ProtobufOutput
 Required.Proto3.JsonInput.FloatFieldTooLarge
 Required.Proto3.JsonInput.FloatFieldTooSmall
 Required.Proto3.JsonInput.Int32FieldNotInteger
diff --git a/conformance/failure_list_php_c.txt b/conformance/failure_list_php_c.txt
index 63c7e8a..52ca454 100644
--- a/conformance/failure_list_php_c.txt
+++ b/conformance/failure_list_php_c.txt
@@ -1,2 +1,5 @@
 Recommended.Proto2.JsonInput.FieldNameExtension.Validator
 Required.Proto2.JsonInput.StoresDefaultPrimitive.Validator
+Required.Proto3.JsonInput.DurationNegativeNanos.JsonOutput
+Required.Proto3.JsonInput.DurationNegativeNanos.ProtobufOutput
+Required.Proto3.JsonInput.DurationNegativeSeconds.JsonOutput
diff --git a/conformance/failure_list_ruby.txt b/conformance/failure_list_ruby.txt
index 4938202..ff230a2 100644
--- a/conformance/failure_list_ruby.txt
+++ b/conformance/failure_list_ruby.txt
@@ -56,3 +56,6 @@
 Recommended.Proto3.ProtobufInput.ValidDataRepeated.UINT32.UnpackedInput.UnpackedOutput.ProtobufOutput
 Recommended.Proto3.ProtobufInput.ValidDataRepeated.UINT64.PackedInput.UnpackedOutput.ProtobufOutput
 Recommended.Proto3.ProtobufInput.ValidDataRepeated.UINT64.UnpackedInput.UnpackedOutput.ProtobufOutput
+Required.Proto3.JsonInput.DurationNegativeNanos.JsonOutput
+Required.Proto3.JsonInput.DurationNegativeNanos.ProtobufOutput
+Required.Proto3.JsonInput.DurationNegativeSeconds.JsonOutput
diff --git a/csharp/Google.Protobuf.Tools.nuspec b/csharp/Google.Protobuf.Tools.nuspec
index ea05597..90033cf 100644
--- a/csharp/Google.Protobuf.Tools.nuspec
+++ b/csharp/Google.Protobuf.Tools.nuspec
@@ -5,7 +5,7 @@
     <title>Google Protocol Buffers tools</title>
     <summary>Tools for Protocol Buffers - Google's data interchange format.</summary>
     <description>See project site for more info.</description>
-    <version>3.21.2</version>
+    <version>3.21.4</version>
     <authors>Google Inc.</authors>
     <owners>protobuf-packages</owners>
     <licenseUrl>https://github.com/protocolbuffers/protobuf/blob/main/LICENSE</licenseUrl>
diff --git a/csharp/README.md b/csharp/README.md
index df48499..02abb7c 100644
--- a/csharp/README.md
+++ b/csharp/README.md
@@ -16,18 +16,19 @@
 Supported platforms
 ===================
 
-The runtime library is built as a portable class library, supporting:
+The runtime library is built as a class library, supporting targets of:
 
-- .NET 4.5
-- Windows 8
-- Windows Phone Silverlight 8
-- Windows Phone 8.1
-- .NET Core
-- .NET 5
+- .NET 4.5+ (`net45`)
+- .NET Standard 1.1 and 2.0 (`netstandard1.1` and `netstandard2.0`)
+- .NET 5+ (`net50`)
 
 You should be able to use Protocol Buffers in Visual Studio 2012 and
 all later versions. This includes all code generated by `protoc`,
-which only uses features from C# 3 and earlier.
+which only uses features from C# 3 and earlier. When compiling generated
+code with old compilers (before C# 7.2) you need to define the
+`GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE` symbol in your project
+so that the generated classes don't implement `IBufferMessage`, which uses
+`ref struct` types.
 
 Building
 ========
@@ -42,12 +43,6 @@
 have no impact when using the compiled code - they're only relevant
 when building the `Google.Protobuf` assembly.
 
-In order to run and debug the AddressBook example in the IDE, you must
-install the optional component, ".Net Core 1.0 - 1.1 development tools
-for Web" (as it's labelled in current versions of the VS2017
-installer), above and beyond the main .NET Core cross-platform
-development feature.
-
 Testing
 =======
 
@@ -57,20 +52,11 @@
 .NET 3.5
 ========
 
-We don't officially support .NET 3.5. However, there has been some effort
-to make enabling .NET 3.5 support relatively painless in case you require it.
-There's no guarantee that this will continue in the future, so rely on .NET
-3.5 support at your peril.
-
-To enable .NET 3.5 support, you must edit the `TargetFrameworks` elements of
-[src/Google.Protobuf/Google.Protobuf.csproj](src/Google.Protobuf/Google.Protobuf.csproj)
-(and [src/Google.Protobuf.Test/Google.Protobuf.Test.csproj](src/Google.Protobuf.Test/Google.Protobuf.Test.csproj)
-if you want to run the unit tests):
-
-Open the .csproj file in a text editor and simply add `net35` to the list of
-target frameworks, noting that the `TargetFrameworks` element appears twice in
-the file (once in the first `PropertyGroup` element, and again in the second
-`PropertyGroup` element, i.e., the one with the conditional).
+We don't support .NET 3.5. It *used* to be feasible to build this library
+targeting .NET 3.5, but a number of changes requiring newer runtime/framework
+features have been added over time. While it would no doubt be *possible* to
+rework the current implementation to allow most of the functionality to be built
+in .NET 3.5, this would create an undue maintenance burden.
 
 History of C# protobufs
 =======================
@@ -82,9 +68,9 @@
 
 The previous project differs from this project in a number of ways:
 
-- The old code only supported proto2; the new code only supports
+- The old code only supported proto2; the new code initially only supported
 proto3 (so no unknown fields, no required/optional distinction, no
-extensions)
+extensions); since then proto2 support has been added
 - The old code was based on immutable message types and builders for
 them
 - The old code did not support maps or `oneof`
diff --git a/csharp/protos/unittest_issues.proto b/csharp/protos/unittest_issues.proto
index f46c20e..45df7a1 100644
--- a/csharp/protos/unittest_issues.proto
+++ b/csharp/protos/unittest_issues.proto
@@ -169,4 +169,20 @@
     string x = 1;
     string y = 2;
   }
-}
\ No newline at end of file
+}
+
+// Issue 8810
+message DisambiguateCommonMembers {
+  int32 disambiguate_common_members = 1;
+  int32 types = 2;
+  int32 descriptor = 3;
+  int32 equals = 4;
+  int32 to_string = 5;
+  int32 get_hash_code = 6;
+  int32 write_to = 7;
+  int32 clone = 8;
+  int32 calculate_size = 9;
+  int32 merge_from = 10;
+  int32 on_construction = 11;
+  int32 parser = 12;
+}
diff --git a/csharp/src/Google.Protobuf.Test.TestProtos/MapUnittestProto3.cs b/csharp/src/Google.Protobuf.Test.TestProtos/MapUnittestProto3.cs
index 57e59a9..d3284a4 100644
--- a/csharp/src/Google.Protobuf.Test.TestProtos/MapUnittestProto3.cs
+++ b/csharp/src/Google.Protobuf.Test.TestProtos/MapUnittestProto3.cs
@@ -580,23 +580,23 @@
       if (other == null) {
         return;
       }
-      mapInt32Int32_.Add(other.mapInt32Int32_);
-      mapInt64Int64_.Add(other.mapInt64Int64_);
-      mapUint32Uint32_.Add(other.mapUint32Uint32_);
-      mapUint64Uint64_.Add(other.mapUint64Uint64_);
-      mapSint32Sint32_.Add(other.mapSint32Sint32_);
-      mapSint64Sint64_.Add(other.mapSint64Sint64_);
-      mapFixed32Fixed32_.Add(other.mapFixed32Fixed32_);
-      mapFixed64Fixed64_.Add(other.mapFixed64Fixed64_);
-      mapSfixed32Sfixed32_.Add(other.mapSfixed32Sfixed32_);
-      mapSfixed64Sfixed64_.Add(other.mapSfixed64Sfixed64_);
-      mapInt32Float_.Add(other.mapInt32Float_);
-      mapInt32Double_.Add(other.mapInt32Double_);
-      mapBoolBool_.Add(other.mapBoolBool_);
-      mapStringString_.Add(other.mapStringString_);
-      mapInt32Bytes_.Add(other.mapInt32Bytes_);
-      mapInt32Enum_.Add(other.mapInt32Enum_);
-      mapInt32ForeignMessage_.Add(other.mapInt32ForeignMessage_);
+      mapInt32Int32_.MergeFrom(other.mapInt32Int32_);
+      mapInt64Int64_.MergeFrom(other.mapInt64Int64_);
+      mapUint32Uint32_.MergeFrom(other.mapUint32Uint32_);
+      mapUint64Uint64_.MergeFrom(other.mapUint64Uint64_);
+      mapSint32Sint32_.MergeFrom(other.mapSint32Sint32_);
+      mapSint64Sint64_.MergeFrom(other.mapSint64Sint64_);
+      mapFixed32Fixed32_.MergeFrom(other.mapFixed32Fixed32_);
+      mapFixed64Fixed64_.MergeFrom(other.mapFixed64Fixed64_);
+      mapSfixed32Sfixed32_.MergeFrom(other.mapSfixed32Sfixed32_);
+      mapSfixed64Sfixed64_.MergeFrom(other.mapSfixed64Sfixed64_);
+      mapInt32Float_.MergeFrom(other.mapInt32Float_);
+      mapInt32Double_.MergeFrom(other.mapInt32Double_);
+      mapBoolBool_.MergeFrom(other.mapBoolBool_);
+      mapStringString_.MergeFrom(other.mapStringString_);
+      mapInt32Bytes_.MergeFrom(other.mapInt32Bytes_);
+      mapInt32Enum_.MergeFrom(other.mapInt32Enum_);
+      mapInt32ForeignMessage_.MergeFrom(other.mapInt32ForeignMessage_);
       _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
@@ -1100,7 +1100,7 @@
       if (other == null) {
         return;
       }
-      mapInt32Message_.Add(other.mapInt32Message_);
+      mapInt32Message_.MergeFrom(other.mapInt32Message_);
       _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
@@ -1298,8 +1298,8 @@
       if (other == null) {
         return;
       }
-      map1_.Add(other.map1_);
-      map2_.Add(other.map2_);
+      map1_.MergeFrom(other.map1_);
+      map2_.MergeFrom(other.map2_);
       _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
@@ -1723,21 +1723,21 @@
       if (other == null) {
         return;
       }
-      mapInt32Int32_.Add(other.mapInt32Int32_);
-      mapInt64Int64_.Add(other.mapInt64Int64_);
-      mapUint32Uint32_.Add(other.mapUint32Uint32_);
-      mapUint64Uint64_.Add(other.mapUint64Uint64_);
-      mapSint32Sint32_.Add(other.mapSint32Sint32_);
-      mapSint64Sint64_.Add(other.mapSint64Sint64_);
-      mapFixed32Fixed32_.Add(other.mapFixed32Fixed32_);
-      mapFixed64Fixed64_.Add(other.mapFixed64Fixed64_);
-      mapSfixed32Sfixed32_.Add(other.mapSfixed32Sfixed32_);
-      mapSfixed64Sfixed64_.Add(other.mapSfixed64Sfixed64_);
-      mapInt32Float_.Add(other.mapInt32Float_);
-      mapInt32Double_.Add(other.mapInt32Double_);
-      mapBoolBool_.Add(other.mapBoolBool_);
-      mapInt32Enum_.Add(other.mapInt32Enum_);
-      mapInt32ForeignMessage_.Add(other.mapInt32ForeignMessage_);
+      mapInt32Int32_.MergeFrom(other.mapInt32Int32_);
+      mapInt64Int64_.MergeFrom(other.mapInt64Int64_);
+      mapUint32Uint32_.MergeFrom(other.mapUint32Uint32_);
+      mapUint64Uint64_.MergeFrom(other.mapUint64Uint64_);
+      mapSint32Sint32_.MergeFrom(other.mapSint32Sint32_);
+      mapSint64Sint64_.MergeFrom(other.mapSint64Sint64_);
+      mapFixed32Fixed32_.MergeFrom(other.mapFixed32Fixed32_);
+      mapFixed64Fixed64_.MergeFrom(other.mapFixed64Fixed64_);
+      mapSfixed32Sfixed32_.MergeFrom(other.mapSfixed32Sfixed32_);
+      mapSfixed64Sfixed64_.MergeFrom(other.mapSfixed64Sfixed64_);
+      mapInt32Float_.MergeFrom(other.mapInt32Float_);
+      mapInt32Double_.MergeFrom(other.mapInt32Double_);
+      mapBoolBool_.MergeFrom(other.mapBoolBool_);
+      mapInt32Enum_.MergeFrom(other.mapInt32Enum_);
+      mapInt32ForeignMessage_.MergeFrom(other.mapInt32ForeignMessage_);
       _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
@@ -2031,7 +2031,7 @@
       if (other == null) {
         return;
       }
-      type_.Add(other.type_);
+      type_.MergeFrom(other.type_);
       _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
@@ -2224,7 +2224,7 @@
       if (other == null) {
         return;
       }
-      entry_.Add(other.entry_);
+      entry_.MergeFrom(other.entry_);
       _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
diff --git a/csharp/src/Google.Protobuf.Test.TestProtos/TestMessagesProto2.cs b/csharp/src/Google.Protobuf.Test.TestProtos/TestMessagesProto2.cs
index 08b1aaf..d573455 100644
--- a/csharp/src/Google.Protobuf.Test.TestProtos/TestMessagesProto2.cs
+++ b/csharp/src/Google.Protobuf.Test.TestProtos/TestMessagesProto2.cs
@@ -4344,25 +4344,25 @@
       unpackedDouble_.Add(other.unpackedDouble_);
       unpackedBool_.Add(other.unpackedBool_);
       unpackedNestedEnum_.Add(other.unpackedNestedEnum_);
-      mapInt32Int32_.Add(other.mapInt32Int32_);
-      mapInt64Int64_.Add(other.mapInt64Int64_);
-      mapUint32Uint32_.Add(other.mapUint32Uint32_);
-      mapUint64Uint64_.Add(other.mapUint64Uint64_);
-      mapSint32Sint32_.Add(other.mapSint32Sint32_);
-      mapSint64Sint64_.Add(other.mapSint64Sint64_);
-      mapFixed32Fixed32_.Add(other.mapFixed32Fixed32_);
-      mapFixed64Fixed64_.Add(other.mapFixed64Fixed64_);
-      mapSfixed32Sfixed32_.Add(other.mapSfixed32Sfixed32_);
-      mapSfixed64Sfixed64_.Add(other.mapSfixed64Sfixed64_);
-      mapInt32Float_.Add(other.mapInt32Float_);
-      mapInt32Double_.Add(other.mapInt32Double_);
-      mapBoolBool_.Add(other.mapBoolBool_);
-      mapStringString_.Add(other.mapStringString_);
-      mapStringBytes_.Add(other.mapStringBytes_);
-      mapStringNestedMessage_.Add(other.mapStringNestedMessage_);
-      mapStringForeignMessage_.Add(other.mapStringForeignMessage_);
-      mapStringNestedEnum_.Add(other.mapStringNestedEnum_);
-      mapStringForeignEnum_.Add(other.mapStringForeignEnum_);
+      mapInt32Int32_.MergeFrom(other.mapInt32Int32_);
+      mapInt64Int64_.MergeFrom(other.mapInt64Int64_);
+      mapUint32Uint32_.MergeFrom(other.mapUint32Uint32_);
+      mapUint64Uint64_.MergeFrom(other.mapUint64Uint64_);
+      mapSint32Sint32_.MergeFrom(other.mapSint32Sint32_);
+      mapSint64Sint64_.MergeFrom(other.mapSint64Sint64_);
+      mapFixed32Fixed32_.MergeFrom(other.mapFixed32Fixed32_);
+      mapFixed64Fixed64_.MergeFrom(other.mapFixed64Fixed64_);
+      mapSfixed32Sfixed32_.MergeFrom(other.mapSfixed32Sfixed32_);
+      mapSfixed64Sfixed64_.MergeFrom(other.mapSfixed64Sfixed64_);
+      mapInt32Float_.MergeFrom(other.mapInt32Float_);
+      mapInt32Double_.MergeFrom(other.mapInt32Double_);
+      mapBoolBool_.MergeFrom(other.mapBoolBool_);
+      mapStringString_.MergeFrom(other.mapStringString_);
+      mapStringBytes_.MergeFrom(other.mapStringBytes_);
+      mapStringNestedMessage_.MergeFrom(other.mapStringNestedMessage_);
+      mapStringForeignMessage_.MergeFrom(other.mapStringForeignMessage_);
+      mapStringNestedEnum_.MergeFrom(other.mapStringNestedEnum_);
+      mapStringForeignEnum_.MergeFrom(other.mapStringForeignEnum_);
       if (other.HasData) {
         if (!HasData) {
           Data = new global::ProtobufTestMessages.Proto2.TestAllTypesProto2.Types.Data();
diff --git a/csharp/src/Google.Protobuf.Test.TestProtos/TestMessagesProto3.cs b/csharp/src/Google.Protobuf.Test.TestProtos/TestMessagesProto3.cs
index 520216f..74e2a57 100644
--- a/csharp/src/Google.Protobuf.Test.TestProtos/TestMessagesProto3.cs
+++ b/csharp/src/Google.Protobuf.Test.TestProtos/TestMessagesProto3.cs
@@ -3753,25 +3753,25 @@
       unpackedDouble_.Add(other.unpackedDouble_);
       unpackedBool_.Add(other.unpackedBool_);
       unpackedNestedEnum_.Add(other.unpackedNestedEnum_);
-      mapInt32Int32_.Add(other.mapInt32Int32_);
-      mapInt64Int64_.Add(other.mapInt64Int64_);
-      mapUint32Uint32_.Add(other.mapUint32Uint32_);
-      mapUint64Uint64_.Add(other.mapUint64Uint64_);
-      mapSint32Sint32_.Add(other.mapSint32Sint32_);
-      mapSint64Sint64_.Add(other.mapSint64Sint64_);
-      mapFixed32Fixed32_.Add(other.mapFixed32Fixed32_);
-      mapFixed64Fixed64_.Add(other.mapFixed64Fixed64_);
-      mapSfixed32Sfixed32_.Add(other.mapSfixed32Sfixed32_);
-      mapSfixed64Sfixed64_.Add(other.mapSfixed64Sfixed64_);
-      mapInt32Float_.Add(other.mapInt32Float_);
-      mapInt32Double_.Add(other.mapInt32Double_);
-      mapBoolBool_.Add(other.mapBoolBool_);
-      mapStringString_.Add(other.mapStringString_);
-      mapStringBytes_.Add(other.mapStringBytes_);
-      mapStringNestedMessage_.Add(other.mapStringNestedMessage_);
-      mapStringForeignMessage_.Add(other.mapStringForeignMessage_);
-      mapStringNestedEnum_.Add(other.mapStringNestedEnum_);
-      mapStringForeignEnum_.Add(other.mapStringForeignEnum_);
+      mapInt32Int32_.MergeFrom(other.mapInt32Int32_);
+      mapInt64Int64_.MergeFrom(other.mapInt64Int64_);
+      mapUint32Uint32_.MergeFrom(other.mapUint32Uint32_);
+      mapUint64Uint64_.MergeFrom(other.mapUint64Uint64_);
+      mapSint32Sint32_.MergeFrom(other.mapSint32Sint32_);
+      mapSint64Sint64_.MergeFrom(other.mapSint64Sint64_);
+      mapFixed32Fixed32_.MergeFrom(other.mapFixed32Fixed32_);
+      mapFixed64Fixed64_.MergeFrom(other.mapFixed64Fixed64_);
+      mapSfixed32Sfixed32_.MergeFrom(other.mapSfixed32Sfixed32_);
+      mapSfixed64Sfixed64_.MergeFrom(other.mapSfixed64Sfixed64_);
+      mapInt32Float_.MergeFrom(other.mapInt32Float_);
+      mapInt32Double_.MergeFrom(other.mapInt32Double_);
+      mapBoolBool_.MergeFrom(other.mapBoolBool_);
+      mapStringString_.MergeFrom(other.mapStringString_);
+      mapStringBytes_.MergeFrom(other.mapStringBytes_);
+      mapStringNestedMessage_.MergeFrom(other.mapStringNestedMessage_);
+      mapStringForeignMessage_.MergeFrom(other.mapStringForeignMessage_);
+      mapStringNestedEnum_.MergeFrom(other.mapStringNestedEnum_);
+      mapStringForeignEnum_.MergeFrom(other.mapStringForeignEnum_);
       if (other.optionalBoolWrapper_ != null) {
         if (optionalBoolWrapper_ == null || other.OptionalBoolWrapper != false) {
           OptionalBoolWrapper = other.OptionalBoolWrapper;
diff --git a/csharp/src/Google.Protobuf.Test.TestProtos/Unittest.cs b/csharp/src/Google.Protobuf.Test.TestProtos/Unittest.cs
index c1f43ce..7f1aca1 100644
--- a/csharp/src/Google.Protobuf.Test.TestProtos/Unittest.cs
+++ b/csharp/src/Google.Protobuf.Test.TestProtos/Unittest.cs
@@ -24112,7 +24112,7 @@
       if (other == null) {
         return;
       }
-      foo_.Add(other.foo_);
+      foo_.MergeFrom(other.foo_);
       _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
@@ -30708,7 +30708,7 @@
         }
         OptionalGroup.MergeFrom(other.OptionalGroup);
       }
-      stringStringMap_.Add(other.stringStringMap_);
+      stringStringMap_.MergeFrom(other.stringStringMap_);
       switch (other.OneofFieldCase) {
         case OneofFieldOneofCase.OneofUint32:
           OneofUint32 = other.OneofUint32;
diff --git a/csharp/src/Google.Protobuf.Test.TestProtos/UnittestIssues.cs b/csharp/src/Google.Protobuf.Test.TestProtos/UnittestIssues.cs
index 3fe2dd6..d65a656 100644
--- a/csharp/src/Google.Protobuf.Test.TestProtos/UnittestIssues.cs
+++ b/csharp/src/Google.Protobuf.Test.TestProtos/UnittestIssues.cs
@@ -56,11 +56,17 @@
             "dWxhcl9maWVsZBgBIAEoCRIbCg5vcHRpb25hbF9maWVsZBgCIAEoCUgAiAEB",
             "QhEKD19vcHRpb25hbF9maWVsZCI5ChJPbmVvZldpdGhOb25lRmllbGQSCwoB",
             "eBgBIAEoCUgAEg4KBG5vbmUYAiABKAlIAEIGCgR0ZXN0IjUKEU9uZW9mV2l0",
-            "aE5vbmVOYW1lEgsKAXgYASABKAlIABILCgF5GAIgASgJSABCBgoEbm9uZSpV",
-            "CgxOZWdhdGl2ZUVudW0SFgoSTkVHQVRJVkVfRU5VTV9aRVJPEAASFgoJRml2",
-            "ZUJlbG93EPv//////////wESFQoITWludXNPbmUQ////////////ASouCg5E",
-            "ZXByZWNhdGVkRW51bRITCg9ERVBSRUNBVEVEX1pFUk8QABIHCgNvbmUQAUId",
-            "qgIaVW5pdFRlc3QuSXNzdWVzLlRlc3RQcm90b3NiBnByb3RvMw=="));
+            "aE5vbmVOYW1lEgsKAXgYASABKAlIABILCgF5GAIgASgJSABCBgoEbm9uZSKT",
+            "AgoZRGlzYW1iaWd1YXRlQ29tbW9uTWVtYmVycxIjChtkaXNhbWJpZ3VhdGVf",
+            "Y29tbW9uX21lbWJlcnMYASABKAUSDQoFdHlwZXMYAiABKAUSEgoKZGVzY3Jp",
+            "cHRvchgDIAEoBRIOCgZlcXVhbHMYBCABKAUSEQoJdG9fc3RyaW5nGAUgASgF",
+            "EhUKDWdldF9oYXNoX2NvZGUYBiABKAUSEAoId3JpdGVfdG8YByABKAUSDQoF",
+            "Y2xvbmUYCCABKAUSFgoOY2FsY3VsYXRlX3NpemUYCSABKAUSEgoKbWVyZ2Vf",
+            "ZnJvbRgKIAEoBRIXCg9vbl9jb25zdHJ1Y3Rpb24YCyABKAUSDgoGcGFyc2Vy",
+            "GAwgASgFKlUKDE5lZ2F0aXZlRW51bRIWChJORUdBVElWRV9FTlVNX1pFUk8Q",
+            "ABIWCglGaXZlQmVsb3cQ+///////////ARIVCghNaW51c09uZRD/////////",
+            "//8BKi4KDkRlcHJlY2F0ZWRFbnVtEhMKD0RFUFJFQ0FURURfWkVSTxAAEgcK",
+            "A29uZRABQh2qAhpVbml0VGVzdC5Jc3N1ZXMuVGVzdFByb3Rvc2IGcHJvdG8z"));
       descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
           new pbr::FileDescriptor[] { global::Google.Protobuf.WellKnownTypes.StructReflection.Descriptor, },
           new pbr::GeneratedClrTypeInfo(new[] {typeof(global::UnitTest.Issues.TestProtos.NegativeEnum), typeof(global::UnitTest.Issues.TestProtos.DeprecatedEnum), }, null, new pbr::GeneratedClrTypeInfo[] {
@@ -77,7 +83,8 @@
             new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.NullValueNotInOneof), global::UnitTest.Issues.TestProtos.NullValueNotInOneof.Parser, new[]{ "NullValue" }, null, null, null, null),
             new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.MixedRegularAndOptional), global::UnitTest.Issues.TestProtos.MixedRegularAndOptional.Parser, new[]{ "RegularField", "OptionalField" }, new[]{ "OptionalField" }, null, null, null),
             new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.OneofWithNoneField), global::UnitTest.Issues.TestProtos.OneofWithNoneField.Parser, new[]{ "X", "None" }, new[]{ "Test" }, null, null, null),
-            new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.OneofWithNoneName), global::UnitTest.Issues.TestProtos.OneofWithNoneName.Parser, new[]{ "X", "Y" }, new[]{ "None" }, null, null, null)
+            new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.OneofWithNoneName), global::UnitTest.Issues.TestProtos.OneofWithNoneName.Parser, new[]{ "X", "Y" }, new[]{ "None" }, null, null, null),
+            new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.DisambiguateCommonMembers), global::UnitTest.Issues.TestProtos.DisambiguateCommonMembers.Parser, new[]{ "DisambiguateCommonMembers_", "Types_", "Descriptor_", "Equals_", "ToString_", "GetHashCode_", "WriteTo_", "Clone_", "CalculateSize_", "MergeFrom_", "OnConstruction_", "Parser_" }, null, null, null, null)
           }));
     }
     #endregion
@@ -4347,6 +4354,605 @@
 
   }
 
+  /// <summary>
+  /// Issue 8810
+  /// </summary>
+  public sealed partial class DisambiguateCommonMembers : pb::IMessage<DisambiguateCommonMembers>
+  #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+      , pb::IBufferMessage
+  #endif
+  {
+    private static readonly pb::MessageParser<DisambiguateCommonMembers> _parser = new pb::MessageParser<DisambiguateCommonMembers>(() => new DisambiguateCommonMembers());
+    private pb::UnknownFieldSet _unknownFields;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+    public static pb::MessageParser<DisambiguateCommonMembers> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::UnitTest.Issues.TestProtos.UnittestIssuesReflection.Descriptor.MessageTypes[14]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+    public DisambiguateCommonMembers() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+    public DisambiguateCommonMembers(DisambiguateCommonMembers other) : this() {
+      disambiguateCommonMembers_ = other.disambiguateCommonMembers_;
+      types_ = other.types_;
+      descriptor_ = other.descriptor_;
+      equals_ = other.equals_;
+      toString_ = other.toString_;
+      getHashCode_ = other.getHashCode_;
+      writeTo_ = other.writeTo_;
+      clone_ = other.clone_;
+      calculateSize_ = other.calculateSize_;
+      mergeFrom_ = other.mergeFrom_;
+      onConstruction_ = other.onConstruction_;
+      parser_ = other.parser_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+    public DisambiguateCommonMembers Clone() {
+      return new DisambiguateCommonMembers(this);
+    }
+
+    /// <summary>Field number for the "disambiguate_common_members" field.</summary>
+    public const int DisambiguateCommonMembers_FieldNumber = 1;
+    private int disambiguateCommonMembers_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+    public int DisambiguateCommonMembers_ {
+      get { return disambiguateCommonMembers_; }
+      set {
+        disambiguateCommonMembers_ = value;
+      }
+    }
+
+    /// <summary>Field number for the "types" field.</summary>
+    public const int Types_FieldNumber = 2;
+    private int types_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+    public int Types_ {
+      get { return types_; }
+      set {
+        types_ = value;
+      }
+    }
+
+    /// <summary>Field number for the "descriptor" field.</summary>
+    public const int Descriptor_FieldNumber = 3;
+    private int descriptor_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+    public int Descriptor_ {
+      get { return descriptor_; }
+      set {
+        descriptor_ = value;
+      }
+    }
+
+    /// <summary>Field number for the "equals" field.</summary>
+    public const int Equals_FieldNumber = 4;
+    private int equals_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+    public int Equals_ {
+      get { return equals_; }
+      set {
+        equals_ = value;
+      }
+    }
+
+    /// <summary>Field number for the "to_string" field.</summary>
+    public const int ToString_FieldNumber = 5;
+    private int toString_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+    public int ToString_ {
+      get { return toString_; }
+      set {
+        toString_ = value;
+      }
+    }
+
+    /// <summary>Field number for the "get_hash_code" field.</summary>
+    public const int GetHashCode_FieldNumber = 6;
+    private int getHashCode_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+    public int GetHashCode_ {
+      get { return getHashCode_; }
+      set {
+        getHashCode_ = value;
+      }
+    }
+
+    /// <summary>Field number for the "write_to" field.</summary>
+    public const int WriteTo_FieldNumber = 7;
+    private int writeTo_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+    public int WriteTo_ {
+      get { return writeTo_; }
+      set {
+        writeTo_ = value;
+      }
+    }
+
+    /// <summary>Field number for the "clone" field.</summary>
+    public const int Clone_FieldNumber = 8;
+    private int clone_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+    public int Clone_ {
+      get { return clone_; }
+      set {
+        clone_ = value;
+      }
+    }
+
+    /// <summary>Field number for the "calculate_size" field.</summary>
+    public const int CalculateSize_FieldNumber = 9;
+    private int calculateSize_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+    public int CalculateSize_ {
+      get { return calculateSize_; }
+      set {
+        calculateSize_ = value;
+      }
+    }
+
+    /// <summary>Field number for the "merge_from" field.</summary>
+    public const int MergeFrom_FieldNumber = 10;
+    private int mergeFrom_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+    public int MergeFrom_ {
+      get { return mergeFrom_; }
+      set {
+        mergeFrom_ = value;
+      }
+    }
+
+    /// <summary>Field number for the "on_construction" field.</summary>
+    public const int OnConstruction_FieldNumber = 11;
+    private int onConstruction_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+    public int OnConstruction_ {
+      get { return onConstruction_; }
+      set {
+        onConstruction_ = value;
+      }
+    }
+
+    /// <summary>Field number for the "parser" field.</summary>
+    public const int Parser_FieldNumber = 12;
+    private int parser_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+    public int Parser_ {
+      get { return parser_; }
+      set {
+        parser_ = value;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+    public override bool Equals(object other) {
+      return Equals(other as DisambiguateCommonMembers);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+    public bool Equals(DisambiguateCommonMembers other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      if (DisambiguateCommonMembers_ != other.DisambiguateCommonMembers_) return false;
+      if (Types_ != other.Types_) return false;
+      if (Descriptor_ != other.Descriptor_) return false;
+      if (Equals_ != other.Equals_) return false;
+      if (ToString_ != other.ToString_) return false;
+      if (GetHashCode_ != other.GetHashCode_) return false;
+      if (WriteTo_ != other.WriteTo_) return false;
+      if (Clone_ != other.Clone_) return false;
+      if (CalculateSize_ != other.CalculateSize_) return false;
+      if (MergeFrom_ != other.MergeFrom_) return false;
+      if (OnConstruction_ != other.OnConstruction_) return false;
+      if (Parser_ != other.Parser_) return false;
+      return Equals(_unknownFields, other._unknownFields);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+    public override int GetHashCode() {
+      int hash = 1;
+      if (DisambiguateCommonMembers_ != 0) hash ^= DisambiguateCommonMembers_.GetHashCode();
+      if (Types_ != 0) hash ^= Types_.GetHashCode();
+      if (Descriptor_ != 0) hash ^= Descriptor_.GetHashCode();
+      if (Equals_ != 0) hash ^= Equals_.GetHashCode();
+      if (ToString_ != 0) hash ^= ToString_.GetHashCode();
+      if (GetHashCode_ != 0) hash ^= GetHashCode_.GetHashCode();
+      if (WriteTo_ != 0) hash ^= WriteTo_.GetHashCode();
+      if (Clone_ != 0) hash ^= Clone_.GetHashCode();
+      if (CalculateSize_ != 0) hash ^= CalculateSize_.GetHashCode();
+      if (MergeFrom_ != 0) hash ^= MergeFrom_.GetHashCode();
+      if (OnConstruction_ != 0) hash ^= OnConstruction_.GetHashCode();
+      if (Parser_ != 0) hash ^= Parser_.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+    public void WriteTo(pb::CodedOutputStream output) {
+    #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+      output.WriteRawMessage(this);
+    #else
+      if (DisambiguateCommonMembers_ != 0) {
+        output.WriteRawTag(8);
+        output.WriteInt32(DisambiguateCommonMembers_);
+      }
+      if (Types_ != 0) {
+        output.WriteRawTag(16);
+        output.WriteInt32(Types_);
+      }
+      if (Descriptor_ != 0) {
+        output.WriteRawTag(24);
+        output.WriteInt32(Descriptor_);
+      }
+      if (Equals_ != 0) {
+        output.WriteRawTag(32);
+        output.WriteInt32(Equals_);
+      }
+      if (ToString_ != 0) {
+        output.WriteRawTag(40);
+        output.WriteInt32(ToString_);
+      }
+      if (GetHashCode_ != 0) {
+        output.WriteRawTag(48);
+        output.WriteInt32(GetHashCode_);
+      }
+      if (WriteTo_ != 0) {
+        output.WriteRawTag(56);
+        output.WriteInt32(WriteTo_);
+      }
+      if (Clone_ != 0) {
+        output.WriteRawTag(64);
+        output.WriteInt32(Clone_);
+      }
+      if (CalculateSize_ != 0) {
+        output.WriteRawTag(72);
+        output.WriteInt32(CalculateSize_);
+      }
+      if (MergeFrom_ != 0) {
+        output.WriteRawTag(80);
+        output.WriteInt32(MergeFrom_);
+      }
+      if (OnConstruction_ != 0) {
+        output.WriteRawTag(88);
+        output.WriteInt32(OnConstruction_);
+      }
+      if (Parser_ != 0) {
+        output.WriteRawTag(96);
+        output.WriteInt32(Parser_);
+      }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
+    #endif
+    }
+
+    #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+    void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
+      if (DisambiguateCommonMembers_ != 0) {
+        output.WriteRawTag(8);
+        output.WriteInt32(DisambiguateCommonMembers_);
+      }
+      if (Types_ != 0) {
+        output.WriteRawTag(16);
+        output.WriteInt32(Types_);
+      }
+      if (Descriptor_ != 0) {
+        output.WriteRawTag(24);
+        output.WriteInt32(Descriptor_);
+      }
+      if (Equals_ != 0) {
+        output.WriteRawTag(32);
+        output.WriteInt32(Equals_);
+      }
+      if (ToString_ != 0) {
+        output.WriteRawTag(40);
+        output.WriteInt32(ToString_);
+      }
+      if (GetHashCode_ != 0) {
+        output.WriteRawTag(48);
+        output.WriteInt32(GetHashCode_);
+      }
+      if (WriteTo_ != 0) {
+        output.WriteRawTag(56);
+        output.WriteInt32(WriteTo_);
+      }
+      if (Clone_ != 0) {
+        output.WriteRawTag(64);
+        output.WriteInt32(Clone_);
+      }
+      if (CalculateSize_ != 0) {
+        output.WriteRawTag(72);
+        output.WriteInt32(CalculateSize_);
+      }
+      if (MergeFrom_ != 0) {
+        output.WriteRawTag(80);
+        output.WriteInt32(MergeFrom_);
+      }
+      if (OnConstruction_ != 0) {
+        output.WriteRawTag(88);
+        output.WriteInt32(OnConstruction_);
+      }
+      if (Parser_ != 0) {
+        output.WriteRawTag(96);
+        output.WriteInt32(Parser_);
+      }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(ref output);
+      }
+    }
+    #endif
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+    public int CalculateSize() {
+      int size = 0;
+      if (DisambiguateCommonMembers_ != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(DisambiguateCommonMembers_);
+      }
+      if (Types_ != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(Types_);
+      }
+      if (Descriptor_ != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(Descriptor_);
+      }
+      if (Equals_ != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(Equals_);
+      }
+      if (ToString_ != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(ToString_);
+      }
+      if (GetHashCode_ != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(GetHashCode_);
+      }
+      if (WriteTo_ != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(WriteTo_);
+      }
+      if (Clone_ != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(Clone_);
+      }
+      if (CalculateSize_ != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(CalculateSize_);
+      }
+      if (MergeFrom_ != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(MergeFrom_);
+      }
+      if (OnConstruction_ != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(OnConstruction_);
+      }
+      if (Parser_ != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(Parser_);
+      }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+    public void MergeFrom(DisambiguateCommonMembers other) {
+      if (other == null) {
+        return;
+      }
+      if (other.DisambiguateCommonMembers_ != 0) {
+        DisambiguateCommonMembers_ = other.DisambiguateCommonMembers_;
+      }
+      if (other.Types_ != 0) {
+        Types_ = other.Types_;
+      }
+      if (other.Descriptor_ != 0) {
+        Descriptor_ = other.Descriptor_;
+      }
+      if (other.Equals_ != 0) {
+        Equals_ = other.Equals_;
+      }
+      if (other.ToString_ != 0) {
+        ToString_ = other.ToString_;
+      }
+      if (other.GetHashCode_ != 0) {
+        GetHashCode_ = other.GetHashCode_;
+      }
+      if (other.WriteTo_ != 0) {
+        WriteTo_ = other.WriteTo_;
+      }
+      if (other.Clone_ != 0) {
+        Clone_ = other.Clone_;
+      }
+      if (other.CalculateSize_ != 0) {
+        CalculateSize_ = other.CalculateSize_;
+      }
+      if (other.MergeFrom_ != 0) {
+        MergeFrom_ = other.MergeFrom_;
+      }
+      if (other.OnConstruction_ != 0) {
+        OnConstruction_ = other.OnConstruction_;
+      }
+      if (other.Parser_ != 0) {
+        Parser_ = other.Parser_;
+      }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+    public void MergeFrom(pb::CodedInputStream input) {
+    #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+      input.ReadRawMessage(this);
+    #else
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+            break;
+          case 8: {
+            DisambiguateCommonMembers_ = input.ReadInt32();
+            break;
+          }
+          case 16: {
+            Types_ = input.ReadInt32();
+            break;
+          }
+          case 24: {
+            Descriptor_ = input.ReadInt32();
+            break;
+          }
+          case 32: {
+            Equals_ = input.ReadInt32();
+            break;
+          }
+          case 40: {
+            ToString_ = input.ReadInt32();
+            break;
+          }
+          case 48: {
+            GetHashCode_ = input.ReadInt32();
+            break;
+          }
+          case 56: {
+            WriteTo_ = input.ReadInt32();
+            break;
+          }
+          case 64: {
+            Clone_ = input.ReadInt32();
+            break;
+          }
+          case 72: {
+            CalculateSize_ = input.ReadInt32();
+            break;
+          }
+          case 80: {
+            MergeFrom_ = input.ReadInt32();
+            break;
+          }
+          case 88: {
+            OnConstruction_ = input.ReadInt32();
+            break;
+          }
+          case 96: {
+            Parser_ = input.ReadInt32();
+            break;
+          }
+        }
+      }
+    #endif
+    }
+
+    #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+    void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
+            break;
+          case 8: {
+            DisambiguateCommonMembers_ = input.ReadInt32();
+            break;
+          }
+          case 16: {
+            Types_ = input.ReadInt32();
+            break;
+          }
+          case 24: {
+            Descriptor_ = input.ReadInt32();
+            break;
+          }
+          case 32: {
+            Equals_ = input.ReadInt32();
+            break;
+          }
+          case 40: {
+            ToString_ = input.ReadInt32();
+            break;
+          }
+          case 48: {
+            GetHashCode_ = input.ReadInt32();
+            break;
+          }
+          case 56: {
+            WriteTo_ = input.ReadInt32();
+            break;
+          }
+          case 64: {
+            Clone_ = input.ReadInt32();
+            break;
+          }
+          case 72: {
+            CalculateSize_ = input.ReadInt32();
+            break;
+          }
+          case 80: {
+            MergeFrom_ = input.ReadInt32();
+            break;
+          }
+          case 88: {
+            OnConstruction_ = input.ReadInt32();
+            break;
+          }
+          case 96: {
+            Parser_ = input.ReadInt32();
+            break;
+          }
+        }
+      }
+    }
+    #endif
+
+  }
+
   #endregion
 
 }
diff --git a/csharp/src/Google.Protobuf.Test.TestProtos/UnittestWellKnownTypes.cs b/csharp/src/Google.Protobuf.Test.TestProtos/UnittestWellKnownTypes.cs
index 3ec8d35..50b9046 100644
--- a/csharp/src/Google.Protobuf.Test.TestProtos/UnittestWellKnownTypes.cs
+++ b/csharp/src/Google.Protobuf.Test.TestProtos/UnittestWellKnownTypes.cs
@@ -3258,24 +3258,24 @@
       if (other == null) {
         return;
       }
-      anyField_.Add(other.anyField_);
-      apiField_.Add(other.apiField_);
-      durationField_.Add(other.durationField_);
-      emptyField_.Add(other.emptyField_);
-      fieldMaskField_.Add(other.fieldMaskField_);
-      sourceContextField_.Add(other.sourceContextField_);
-      structField_.Add(other.structField_);
-      timestampField_.Add(other.timestampField_);
-      typeField_.Add(other.typeField_);
-      doubleField_.Add(other.doubleField_);
-      floatField_.Add(other.floatField_);
-      int64Field_.Add(other.int64Field_);
-      uint64Field_.Add(other.uint64Field_);
-      int32Field_.Add(other.int32Field_);
-      uint32Field_.Add(other.uint32Field_);
-      boolField_.Add(other.boolField_);
-      stringField_.Add(other.stringField_);
-      bytesField_.Add(other.bytesField_);
+      anyField_.MergeFrom(other.anyField_);
+      apiField_.MergeFrom(other.apiField_);
+      durationField_.MergeFrom(other.durationField_);
+      emptyField_.MergeFrom(other.emptyField_);
+      fieldMaskField_.MergeFrom(other.fieldMaskField_);
+      sourceContextField_.MergeFrom(other.sourceContextField_);
+      structField_.MergeFrom(other.structField_);
+      timestampField_.MergeFrom(other.timestampField_);
+      typeField_.MergeFrom(other.typeField_);
+      doubleField_.MergeFrom(other.doubleField_);
+      floatField_.MergeFrom(other.floatField_);
+      int64Field_.MergeFrom(other.int64Field_);
+      uint64Field_.MergeFrom(other.uint64Field_);
+      int32Field_.MergeFrom(other.int32Field_);
+      uint32Field_.MergeFrom(other.uint32Field_);
+      boolField_.MergeFrom(other.boolField_);
+      stringField_.MergeFrom(other.stringField_);
+      bytesField_.MergeFrom(other.bytesField_);
       _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
diff --git a/csharp/src/Google.Protobuf.Test/GeneratedMessageTest.cs b/csharp/src/Google.Protobuf.Test/GeneratedMessageTest.cs
index 8387291..17c5249 100644
--- a/csharp/src/Google.Protobuf.Test/GeneratedMessageTest.cs
+++ b/csharp/src/Google.Protobuf.Test/GeneratedMessageTest.cs
@@ -36,6 +36,7 @@
 using NUnit.Framework;
 using System.Linq;
 using Google.Protobuf.WellKnownTypes;
+using Google.Protobuf.Collections;
 
 namespace Google.Protobuf
 {
@@ -795,5 +796,44 @@
             EqualityTester.AssertInequality(message1, message2);
             EqualityTester.AssertEquality(message1, message3);
         }
+
+        [Test]
+        [TestCase(false)]
+        [TestCase(true)]
+        public void MapFieldMerging(bool direct)
+        {
+            var message1 = new TestMap
+            {
+                MapStringString =
+                {
+                    { "x1", "y1" },
+                    { "common", "message1" }
+                }
+            };
+            var message2 = new TestMap
+            {
+                MapStringString =
+                {
+                    { "x2", "y2" },
+                    { "common", "message2" }
+                }
+            };
+            if (direct)
+            {
+                message1.MergeFrom(message2);
+            }
+            else
+            {
+                message1.MergeFrom(message2.ToByteArray());
+            }
+
+            var expected = new MapField<string, string>
+            {
+                { "x1", "y1" },
+                { "x2", "y2" },
+                { "common", "message2" }
+            };
+            Assert.AreEqual(expected, message1.MapStringString);
+        }
     }
 }
\ No newline at end of file
diff --git a/csharp/src/Google.Protobuf.Test/JsonFormatterSettingsTest.cs b/csharp/src/Google.Protobuf.Test/JsonFormatterSettingsTest.cs
new file mode 100644
index 0000000..f7ea97c
--- /dev/null
+++ b/csharp/src/Google.Protobuf.Test/JsonFormatterSettingsTest.cs
@@ -0,0 +1,111 @@
+#region Copyright notice and license
+
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using Google.Protobuf.Reflection;
+using NUnit.Framework;
+
+// For WrapInQuotes
+
+namespace Google.Protobuf
+{
+    public class JsonFormatterSettingsTest
+    {
+        [Test]
+        public void WithIndentation()
+        {
+            var settings = JsonFormatter.Settings.Default.WithIndentation("\t");
+            Assert.AreEqual("\t", settings.Indentation);
+        }
+
+        [Test]
+        public void WithTypeRegistry()
+        {
+            var typeRegistry = TypeRegistry.Empty;
+            var settings = JsonFormatter.Settings.Default.WithTypeRegistry(typeRegistry);
+            Assert.AreEqual(typeRegistry, settings.TypeRegistry);
+        }
+
+        [Test]
+        public void WithFormatDefaultValues()
+        {
+            var settingsWith = JsonFormatter.Settings.Default.WithFormatDefaultValues(true);
+            Assert.AreEqual(true, settingsWith.FormatDefaultValues);
+
+            var settingsWithout = JsonFormatter.Settings.Default.WithFormatDefaultValues(false);
+            Assert.AreEqual(false, settingsWithout.FormatDefaultValues);
+        }
+
+        [Test]
+        public void WithFormatEnumsAsIntegers()
+        {
+            var settingsWith = JsonFormatter.Settings.Default.WithFormatEnumsAsIntegers(true);
+            Assert.AreEqual(true, settingsWith.FormatEnumsAsIntegers);
+
+            var settingsWithout = JsonFormatter.Settings.Default.WithFormatEnumsAsIntegers(false);
+            Assert.AreEqual(false, settingsWithout.FormatEnumsAsIntegers);
+        }
+
+        [Test]
+        public void WithMethodsPreserveExistingSettings()
+        {
+            var typeRegistry = TypeRegistry.Empty;
+            var baseSettings = JsonFormatter.Settings.Default
+                .WithIndentation("\t")
+                .WithFormatDefaultValues(true)
+                .WithFormatEnumsAsIntegers(true)
+                .WithTypeRegistry(typeRegistry)
+                .WithPreserveProtoFieldNames(true);
+
+            var settings1 = baseSettings.WithIndentation("\t");
+            var settings2 = baseSettings.WithFormatDefaultValues(true);
+            var settings3 = baseSettings.WithFormatEnumsAsIntegers(true);
+            var settings4 = baseSettings.WithTypeRegistry(typeRegistry);
+            var settings5 = baseSettings.WithPreserveProtoFieldNames(true);
+
+            AssertAreEqual(baseSettings, settings1);
+            AssertAreEqual(baseSettings, settings2);
+            AssertAreEqual(baseSettings, settings3);
+            AssertAreEqual(baseSettings, settings4);
+            AssertAreEqual(baseSettings, settings5);
+        }
+
+        private static void AssertAreEqual(JsonFormatter.Settings settings, JsonFormatter.Settings other)
+        {
+            Assert.AreEqual(settings.Indentation, other.Indentation);
+            Assert.AreEqual(settings.FormatDefaultValues, other.FormatDefaultValues);
+            Assert.AreEqual(settings.FormatEnumsAsIntegers, other.FormatEnumsAsIntegers);
+            Assert.AreEqual(settings.TypeRegistry, other.TypeRegistry);
+        }
+    }
+}
diff --git a/csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs b/csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs
index 714c78c..f4dfde2 100644
--- a/csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs
+++ b/csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs
@@ -675,6 +675,200 @@
         }
 
         [Test]
+        public void WriteValueWithIndentation_EmptyMessage()
+        {
+            var value = new TestEmptyMessage();
+
+            AssertWriteValue(value, "{}", JsonFormatter.Settings.Default.WithIndentation());
+        }
+
+        [Test]
+        public void WriteValueWithIndentation_NestedTestAllTypes()
+        {
+            var value = new NestedTestAllTypes
+            {
+                Payload = new TestAllTypes
+                {
+                    SingleBool = true,
+                    SingleInt32 = 100,
+                    SingleString = "multiple fields",
+                    RepeatedString = { "string1", "string2" },
+                },
+                Child = new NestedTestAllTypes
+                {
+                    Payload = new TestAllTypes
+                    {
+                        SingleString = "single field",
+                    },
+                },
+                RepeatedChild =
+                {
+                    new NestedTestAllTypes { Payload = new TestAllTypes { SingleString = "child 1", RepeatedString = { "string" } } },
+                    new NestedTestAllTypes { Payload = new TestAllTypes { SingleString = "child 2" } },
+                },
+            };
+
+            const string expectedJson = @"
+{
+  'child': {
+    'payload': {
+      'singleString': 'single field'
+    }
+  },
+  'payload': {
+    'singleInt32': 100,
+    'singleBool': true,
+    'singleString': 'multiple fields',
+    'repeatedString': [
+      'string1',
+      'string2'
+    ]
+  },
+  'repeatedChild': [
+    {
+      'payload': {
+        'singleString': 'child 1',
+        'repeatedString': [
+          'string'
+        ]
+      }
+    },
+    {
+      'payload': {
+        'singleString': 'child 2'
+      }
+    }
+  ]
+}";
+            AssertWriteValue(value, expectedJson, JsonFormatter.Settings.Default.WithIndentation());
+        }
+
+        [Test]
+        public void WriteValueWithIndentation_WellKnownTypes()
+        {
+            var value = new TestWellKnownTypes
+            {
+                StructField = new Struct
+                {
+                    Fields =
+                    {
+                        { "string", Value.ForString("foo") },
+                        { "numbers", Value.ForList(Value.ForNumber(1), Value.ForNumber(2), Value.ForNumber(3)) },
+                        { "emptyList", Value.ForList() },
+                        { "emptyStruct", Value.ForStruct(new Struct()) },
+                    },
+                },
+            };
+
+            const string expectedJson = @"
+{
+  'structField': {
+    'string': 'foo',
+    'numbers': [
+      1,
+      2,
+      3
+    ],
+    'emptyList': [],
+    'emptyStruct': {}
+  }
+}";
+            AssertWriteValue(value, expectedJson, JsonFormatter.Settings.Default.WithIndentation());
+        }
+
+        [Test]
+        public void WriteValueWithIndentation_StructSingleField()
+        {
+            var value = new Struct { Fields = { { "structField1", Value.ForString("structFieldValue1") } } };
+
+            const string expectedJson = @"
+{
+  'structField1': 'structFieldValue1'
+}";
+            AssertWriteValue(value, expectedJson, JsonFormatter.Settings.Default.WithIndentation());
+        }
+
+        [Test]
+        public void WriteValueWithIndentation_StructMultipleFields()
+        {
+            var value = new Struct
+            {
+                Fields =
+                {
+                    { "structField1", Value.ForString("structFieldValue1") },
+                    { "structField2", Value.ForString("structFieldValue2") },
+                    { "structField3", Value.ForString("structFieldValue3") },
+                },
+            };
+
+            const string expectedJson = @"
+{
+  'structField1': 'structFieldValue1',
+  'structField2': 'structFieldValue2',
+  'structField3': 'structFieldValue3'
+}";
+            AssertWriteValue(value, expectedJson, JsonFormatter.Settings.Default.WithIndentation());
+        }
+
+        [Test]
+        public void FormatWithIndentation_EmbeddedMessage()
+        {
+            var value = new TestAllTypes { SingleInt32 = 100, SingleInt64 = 3210987654321L };
+            var formatter = new JsonFormatter(JsonFormatter.Settings.Default.WithIndentation());
+            var valueJson = formatter.Format(value, indentationLevel: 1);
+
+            var actualJson = $@"
+{{
+  ""data"": {valueJson}
+}}";
+            const string expectedJson = @"
+{
+  'data': {
+    'singleInt32': 100,
+    'singleInt64': '3210987654321'
+  }
+}";
+            AssertJson(expectedJson, actualJson.Trim());
+        }
+
+        [Test]
+        public void WriteValueWithIndentation_Map()
+        {
+            var value = new TestMap
+            {
+                MapStringString =
+                {
+                    { "key1", "value1" },
+                    { "key2", "value2" },
+                },
+            };
+
+            const string expectedJson = @"
+{
+  'mapStringString': {
+    'key1': 'value1',
+    'key2': 'value2'
+  }
+}";
+
+            AssertWriteValue(value, expectedJson, JsonFormatter.Settings.Default.WithIndentation());
+        }
+
+        [Test]
+        public void WriteValueWithIndentation_List()
+        {
+            var value = new RepeatedField<int> { 1, 2, 3 };
+            AssertWriteValue(value, "[\n  1,\n  2,\n  3\n]", JsonFormatter.Settings.Default.WithIndentation());
+        }
+
+        [Test]
+        public void WriteValueWithIndentation_CustomIndentation()
+        {
+            var value = new RepeatedField<int> { 1, 2, 3 };
+            AssertWriteValue(value, "[\n\t1,\n\t2,\n\t3\n]", JsonFormatter.Settings.Default.WithIndentation("\t"));
+        }
+
+        [Test]
         public void Proto2_DefaultValuesWritten()
         {
             var value = new ProtobufTestMessages.Proto2.TestAllTypesProto2() { FieldName13 = 0 };
@@ -683,7 +877,7 @@
 
         private static void AssertWriteValue(object value, string expectedJson, JsonFormatter.Settings settings = null)
         {
-            var writer = new StringWriter();
+            var writer = new StringWriter { NewLine = "\n" };
             new JsonFormatter(settings ?? JsonFormatter.Settings.Default).WriteValue(writer, value);
             string actual = writer.ToString();
             AssertJson(expectedJson, actual);
@@ -691,13 +885,17 @@
 
         /// <summary>
         /// Checks that the actual JSON is the same as the expected JSON - but after replacing
-        /// all apostrophes in the expected JSON with double quotes. This basically makes the tests easier
-        /// to read.
+        /// all apostrophes in the expected JSON with double quotes, trimming leading whitespace and normalizing new lines.
+        /// This basically makes the tests easier to read.
         /// </summary>
+        /// <remarks>
+        /// Line endings are normalized because indented JSON strings are generated with system-specific line endings,
+        /// while line endings in the test cases are hard-coded, but may be converted during source checkout, depending
+        /// on git settings, causing unpredictability in the test results otherwise.</remarks>
         private static void AssertJson(string expectedJsonWithApostrophes, string actualJson)
         {
-            var expectedJson = expectedJsonWithApostrophes.Replace("'", "\"");
-            Assert.AreEqual(expectedJson, actualJson);
+            var expectedJson = expectedJsonWithApostrophes.Replace("'", "\"").Replace("\r\n", "\n").TrimStart();
+            Assert.AreEqual(expectedJson, actualJson.Replace("\r\n", "\n"));
         }
     }
 }
diff --git a/csharp/src/Google.Protobuf.Test/ParsingPrimitivesTest.cs b/csharp/src/Google.Protobuf.Test/ParsingPrimitivesTest.cs
new file mode 100644
index 0000000..4d0aa9e
--- /dev/null
+++ b/csharp/src/Google.Protobuf.Test/ParsingPrimitivesTest.cs
@@ -0,0 +1,63 @@
+#region Copyright notice and license
+// Protocol Buffers - Google's data interchange format
+// Copyright 2022 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#endregion
+
+using NUnit.Framework;
+using System;
+using System.Linq;
+
+namespace Google.Protobuf.Test;
+
+internal class ParsingPrimitivesTest
+{
+    // Note: test cases use integers rather than bytes as they're easier
+    // to specify in attributes.
+
+    [Test]
+    [TestCase("\ufffd", 255)]
+    [TestCase("A\ufffd", 65, 255)]
+    [TestCase("A\ufffd\ufffdB", 65, 255, 255, 66)]
+    // Overlong form of "space"
+    [TestCase("\ufffd\ufffd", 0xc0, 0xa0)]
+    public void ReadRawString_NonUtf8(string expectedText, params int[] bytes)
+    {
+        var context = CreateContext(bytes);
+        string text = ParsingPrimitives.ReadRawString(ref context.buffer, ref context.state, bytes.Length);
+        Assert.AreEqual(expectedText, text);
+    }
+
+    private static ParseContext CreateContext(int[] bytes)
+    {
+        byte[] actualBytes = bytes.Select(b => (byte) b).ToArray();
+        ParseContext.Initialize(actualBytes.AsSpan(), out var context);
+        return context;
+    }
+}
diff --git a/csharp/src/Google.Protobuf.Test/WritingPrimitivesTest.cs b/csharp/src/Google.Protobuf.Test/WritingPrimitivesTest.cs
new file mode 100644
index 0000000..069df34
--- /dev/null
+++ b/csharp/src/Google.Protobuf.Test/WritingPrimitivesTest.cs
@@ -0,0 +1,61 @@
+#region Copyright notice and license
+// Protocol Buffers - Google's data interchange format
+// Copyright 2022 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#endregion
+
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Google.Protobuf.Test;
+
+internal class WritingPrimitivesTest
+{
+    [Test]
+    public void WriteRawString_IllFormedUnicodeString()
+    {
+        // See https://codeblog.jonskeet.uk/2014/11/07/when-is-a-string-not-a-string/
+        char c1 = '\u0058';
+        char c2 = '\ud800';
+        char c3 = '\u0059';
+        string text = new string(new[] { c1, c2, c3 });
+        Span<byte> buffer = new byte[10];
+        WriteContext.Initialize(ref buffer, out var context);
+        WritingPrimitives.WriteString(ref context.buffer, ref context.state, text);
+
+        // The high surrogate is written out in a "raw" form, surrounded by the ASCII
+        // characters.
+        byte[] expectedBytes = { 0x5, 0x58, 0xef, 0xbf, 0xbd, 0x59 };
+        Assert.AreEqual(expectedBytes, buffer.Slice(0, context.state.position).ToArray());
+    }
+}
diff --git a/csharp/src/Google.Protobuf.Test/testprotos.pb b/csharp/src/Google.Protobuf.Test/testprotos.pb
index 5f41bc4..bdfe1cc 100644
--- a/csharp/src/Google.Protobuf.Test/testprotos.pb
+++ b/csharp/src/Google.Protobuf.Test/testprotos.pb
Binary files differ
diff --git a/csharp/src/Google.Protobuf/Collections/MapField.cs b/csharp/src/Google.Protobuf/Collections/MapField.cs
index f0124ee..09afb75 100644
--- a/csharp/src/Google.Protobuf/Collections/MapField.cs
+++ b/csharp/src/Google.Protobuf/Collections/MapField.cs
@@ -238,6 +238,21 @@
         }
 
         /// <summary>
+        /// Adds the specified entries to the map, replacing any existing entries with the same keys.
+        /// The keys and values are not automatically cloned.
+        /// </summary>
+        /// <remarks>This method primarily exists to be called from MergeFrom methods in generated classes for messages.</remarks>
+        /// <param name="entries">The entries to add to the map.</param>
+        public void MergeFrom(IDictionary<TKey, TValue> entries)
+        {
+            ProtoPreconditions.CheckNotNull(entries, nameof(entries));
+            foreach (var pair in entries)
+            {
+                this[pair.Key] = pair.Value;
+            }
+        }
+
+        /// <summary>
         /// Returns an enumerator that iterates through the collection.
         /// </summary>
         /// <returns>
diff --git a/csharp/src/Google.Protobuf/Google.Protobuf.csproj b/csharp/src/Google.Protobuf/Google.Protobuf.csproj
index 37d86ce..c95ecad 100644
--- a/csharp/src/Google.Protobuf/Google.Protobuf.csproj
+++ b/csharp/src/Google.Protobuf/Google.Protobuf.csproj
@@ -4,7 +4,7 @@
     <Description>C# runtime library for Protocol Buffers - Google's data interchange format.</Description>
     <Copyright>Copyright 2015, Google Inc.</Copyright>
     <AssemblyTitle>Google Protocol Buffers</AssemblyTitle>
-    <VersionPrefix>3.21.2</VersionPrefix>
+    <VersionPrefix>3.21.4</VersionPrefix>
     <LangVersion>10.0</LangVersion>
     <Authors>Google Inc.</Authors>
     <TargetFrameworks>netstandard1.1;netstandard2.0;net45;net50</TargetFrameworks>
diff --git a/csharp/src/Google.Protobuf/JsonFormatter.cs b/csharp/src/Google.Protobuf/JsonFormatter.cs
index 2ef10ee..4482c87 100644
--- a/csharp/src/Google.Protobuf/JsonFormatter.cs
+++ b/csharp/src/Google.Protobuf/JsonFormatter.cs
@@ -63,7 +63,12 @@
         internal const string AnyDiagnosticValueField = "@value";
         internal const string AnyWellKnownTypeValueField = "value";
         private const string NameValueSeparator = ": ";
-        private const string PropertySeparator = ", ";
+        private const string ValueSeparator = ", ";
+        private const string MultilineValueSeparator = ",";
+        private const char ObjectOpenBracket = '{';
+        private const char ObjectCloseBracket = '}';
+        private const char ListBracketOpen = '[';
+        private const char ListBracketClose = ']';
 
         /// <summary>
         /// Returns a formatter using the default settings.
@@ -140,11 +145,26 @@
         /// Formats the specified message as JSON.
         /// </summary>
         /// <param name="message">The message to format.</param>
+        /// <remarks>This method delegates to <c>Format(IMessage, int)</c> with <c>indentationLevel = 0</c>.</remarks>
         /// <returns>The formatted message.</returns>
-        public string Format(IMessage message)
+        public string Format(IMessage message) => Format(message, indentationLevel: 0);
+
+        /// <summary>
+        /// Formats the specified message as JSON.
+        /// </summary>
+        /// <param name="message">The message to format.</param>
+        /// <param name="indentationLevel">Indentation level to start at.</param>
+        /// <remarks>To keep consistent indentation when embedding a message inside another JSON string, set <see cref="indentationLevel"/>. E.g:
+        /// <code>
+        /// var response = $@"{{
+        ///   ""data"": { Format(message, indentationLevel: 1) }
+        /// }}"</code>
+        /// </remarks>
+        /// <returns>The formatted message.</returns>
+        public string Format(IMessage message, int indentationLevel)
         {
             var writer = new StringWriter();
-            Format(message, writer);
+            Format(message, writer, indentationLevel);
             return writer.ToString();
         }
 
@@ -153,19 +173,29 @@
         /// </summary>
         /// <param name="message">The message to format.</param>
         /// <param name="writer">The TextWriter to write the formatted message to.</param>
+        /// <remarks>This method delegates to <c>Format(IMessage, TextWriter, int)</c> with <c>indentationLevel = 0</c>.</remarks>
         /// <returns>The formatted message.</returns>
-        public void Format(IMessage message, TextWriter writer)
+        public void Format(IMessage message, TextWriter writer) => Format(message, writer, indentationLevel: 0);
+
+        /// <summary>
+        /// Formats the specified message as JSON. When <see cref="Settings.Indentation"/> is not null, start indenting at the specified <see cref="indentationLevel"/>.
+        /// </summary>
+        /// <param name="message">The message to format.</param>
+        /// <param name="writer">The TextWriter to write the formatted message to.</param>
+        /// <param name="indentationLevel">Indentation level to start at.</param>
+        /// <remarks>To keep consistent indentation when embedding a message inside another JSON string, set <see cref="indentationLevel"/>.</remarks>
+        public void Format(IMessage message, TextWriter writer, int indentationLevel)
         {
             ProtoPreconditions.CheckNotNull(message, nameof(message));
             ProtoPreconditions.CheckNotNull(writer, nameof(writer));
 
             if (message.Descriptor.IsWellKnownType)
             {
-                WriteWellKnownTypeValue(writer, message.Descriptor, message);
+                WriteWellKnownTypeValue(writer, message.Descriptor, message, indentationLevel);
             }
             else
             {
-                WriteMessage(writer, message);
+                WriteMessage(writer, message, indentationLevel);
             }
         }
 
@@ -192,7 +222,7 @@
             return diagnosticFormatter.Format(message);
         }
 
-        private void WriteMessage(TextWriter writer, IMessage message)
+        private void WriteMessage(TextWriter writer, IMessage message, int indentationLevel)
         {
             if (message == null)
             {
@@ -207,12 +237,13 @@
                     return;
                 }
             }
-            writer.Write("{ ");
-            bool writtenFields = WriteMessageFields(writer, message, false);
-            writer.Write(writtenFields ? " }" : "}");
+
+            WriteBracketOpen(writer, ObjectOpenBracket);
+            bool writtenFields = WriteMessageFields(writer, message, false, indentationLevel + 1);
+            WriteBracketClose(writer, ObjectCloseBracket, writtenFields, indentationLevel);
         }
 
-        private bool WriteMessageFields(TextWriter writer, IMessage message, bool assumeFirstFieldWritten)
+        private bool WriteMessageFields(TextWriter writer, IMessage message, bool assumeFirstFieldWritten, int indentationLevel)
         {
             var fields = message.Descriptor.Fields;
             bool first = !assumeFirstFieldWritten;
@@ -226,10 +257,8 @@
                     continue;
                 }
 
-                if (!first)
-                {
-                    writer.Write(PropertySeparator);
-                }
+                MaybeWriteValueSeparator(writer, first);
+                MaybeWriteValueWhitespace(writer, indentationLevel);
 
                 if (settings.PreserveProtoFieldNames)
                 {
@@ -240,13 +269,23 @@
                     WriteString(writer, accessor.Descriptor.JsonName);
                 }
                 writer.Write(NameValueSeparator);
-                WriteValue(writer, value);
+                WriteValue(writer, value, indentationLevel);
 
                 first = false;
             }
             return !first;
         }
 
+        private void MaybeWriteValueSeparator(TextWriter writer, bool first)
+        {
+            if (first)
+            {
+                return;
+            }
+
+            writer.Write(settings.Indentation == null ? ValueSeparator : MultilineValueSeparator);
+        }
+
         /// <summary>
         /// Determines whether or not a field value should be serialized according to the field,
         /// its value in the message, and the settings of this formatter.
@@ -342,7 +381,19 @@
         /// </summary>
         /// <param name="writer">The writer to write the value to. Must not be null.</param>
         /// <param name="value">The value to write. May be null.</param>
-        public void WriteValue(TextWriter writer, object value)
+        /// <remarks>Delegates to <c>WriteValue(TextWriter, object, int)</c> with <c>indentationLevel = 0</c>.</remarks>
+        public void WriteValue(TextWriter writer, object value) => WriteValue(writer, value, 0);
+
+        /// <summary>
+        /// Writes a single value to the given writer as JSON. Only types understood by
+        /// Protocol Buffers can be written in this way. This method is only exposed for
+        /// advanced use cases; most users should be using <see cref="Format(IMessage)"/>
+        /// or <see cref="Format(IMessage, TextWriter)"/>.
+        /// </summary>
+        /// <param name="writer">The writer to write the value to. Must not be null.</param>
+        /// <param name="value">The value to write. May be null.</param>
+        /// <param name="indentationLevel">The current indentationLevel. Not used when <see cref="Settings.Indentation"/> is null.</param>
+        public void WriteValue(TextWriter writer, object value, int indentationLevel)
         {
             if (value == null || value is NullValue)
             {
@@ -365,11 +416,11 @@
             }
             else if (value is IDictionary dictionary)
             {
-                WriteDictionary(writer, dictionary);
+                WriteDictionary(writer, dictionary, indentationLevel);
             }
             else if (value is IList list)
             {
-                WriteList(writer, list);
+                WriteList(writer, list, indentationLevel);
             }
             else if (value is int || value is uint)
             {
@@ -418,7 +469,7 @@
             }
             else if (value is IMessage message)
             {
-                Format(message, writer);
+                Format(message, writer, indentationLevel);
             }
             else
             {
@@ -432,7 +483,7 @@
         /// values are using the embedded well-known types, in order to allow for dynamic messages
         /// in the future.
         /// </summary>
-        private void WriteWellKnownTypeValue(TextWriter writer, MessageDescriptor descriptor, object value)
+        private void WriteWellKnownTypeValue(TextWriter writer, MessageDescriptor descriptor, object value, int indentationLevel)
         {
             // Currently, we can never actually get here, because null values are always handled by the caller. But if we *could*,
             // this would do the right thing.
@@ -472,26 +523,26 @@
             }
             if (descriptor.FullName == Struct.Descriptor.FullName)
             {
-                WriteStruct(writer, (IMessage)value);
+                WriteStruct(writer, (IMessage)value, indentationLevel);
                 return;
             }
             if (descriptor.FullName == ListValue.Descriptor.FullName)
             {
                 var fieldAccessor = descriptor.Fields[ListValue.ValuesFieldNumber].Accessor;
-                WriteList(writer, (IList)fieldAccessor.GetValue((IMessage)value));
+                WriteList(writer, (IList)fieldAccessor.GetValue((IMessage)value), indentationLevel);
                 return;
             }
             if (descriptor.FullName == Value.Descriptor.FullName)
             {
-                WriteStructFieldValue(writer, (IMessage)value);
+                WriteStructFieldValue(writer, (IMessage)value, indentationLevel);
                 return;
             }
             if (descriptor.FullName == Any.Descriptor.FullName)
             {
-                WriteAny(writer, (IMessage)value);
+                WriteAny(writer, (IMessage)value, indentationLevel);
                 return;
             }
-            WriteMessage(writer, (IMessage)value);
+            WriteMessage(writer, (IMessage)value, indentationLevel);
         }
 
         private void WriteTimestamp(TextWriter writer, IMessage value)
@@ -519,7 +570,7 @@
             writer.Write(FieldMask.ToJson(paths, DiagnosticOnly));
         }
 
-        private void WriteAny(TextWriter writer, IMessage value)
+        private void WriteAny(TextWriter writer, IMessage value, int indentationLevel)
         {
             if (DiagnosticOnly)
             {
@@ -536,23 +587,23 @@
                 throw new InvalidOperationException($"Type registry has no descriptor for type name '{typeName}'");
             }
             IMessage message = descriptor.Parser.ParseFrom(data);
-            writer.Write("{ ");
+            WriteBracketOpen(writer, ObjectOpenBracket);
             WriteString(writer, AnyTypeUrlField);
             writer.Write(NameValueSeparator);
             WriteString(writer, typeUrl);
 
             if (descriptor.IsWellKnownType)
             {
-                writer.Write(PropertySeparator);
+                writer.Write(ValueSeparator);
                 WriteString(writer, AnyWellKnownTypeValueField);
                 writer.Write(NameValueSeparator);
-                WriteWellKnownTypeValue(writer, descriptor, message);
+                WriteWellKnownTypeValue(writer, descriptor, message, indentationLevel);
             }
             else
             {
-                WriteMessageFields(writer, message, true);
+                WriteMessageFields(writer, message, true, indentationLevel);
             }
-            writer.Write(" }");
+            WriteBracketClose(writer, ObjectCloseBracket, true, indentationLevel);
         }
 
         private void WriteDiagnosticOnlyAny(TextWriter writer, IMessage value)
@@ -563,7 +614,7 @@
             WriteString(writer, AnyTypeUrlField);
             writer.Write(NameValueSeparator);
             WriteString(writer, typeUrl);
-            writer.Write(PropertySeparator);
+            writer.Write(ValueSeparator);
             WriteString(writer, AnyDiagnosticValueField);
             writer.Write(NameValueSeparator);
             writer.Write('"');
@@ -572,9 +623,9 @@
             writer.Write(" }");
         }
 
-        private void WriteStruct(TextWriter writer, IMessage message)
+        private void WriteStruct(TextWriter writer, IMessage message, int indentationLevel)
         {
-            writer.Write("{ ");
+            WriteBracketOpen(writer, ObjectOpenBracket);
             IDictionary fields = (IDictionary) message.Descriptor.Fields[Struct.FieldsFieldNumber].Accessor.GetValue(message);
             bool first = true;
             foreach (DictionaryEntry entry in fields)
@@ -586,19 +637,17 @@
                     throw new InvalidOperationException("Struct fields cannot have an empty key or a null value.");
                 }
 
-                if (!first)
-                {
-                    writer.Write(PropertySeparator);
-                }
+                MaybeWriteValueSeparator(writer, first);
+                MaybeWriteValueWhitespace(writer, indentationLevel + 1);
                 WriteString(writer, key);
                 writer.Write(NameValueSeparator);
-                WriteStructFieldValue(writer, value);
+                WriteStructFieldValue(writer, value, indentationLevel + 1);
                 first = false;
             }
-            writer.Write(first ? "}" : " }");
+            WriteBracketClose(writer, ObjectCloseBracket, !first, indentationLevel);
         }
 
-        private void WriteStructFieldValue(TextWriter writer, IMessage message)
+        private void WriteStructFieldValue(TextWriter writer, IMessage message, int indentationLevel)
         {
             var specifiedField = message.Descriptor.Oneofs[0].Accessor.GetCaseFieldDescriptor(message);
             if (specifiedField == null)
@@ -619,7 +668,7 @@
                 case Value.ListValueFieldNumber:
                     // Structs and ListValues are nested messages, and already well-known types.
                     var nestedMessage = (IMessage) specifiedField.Accessor.GetValue(message);
-                    WriteWellKnownTypeValue(writer, nestedMessage.Descriptor, nestedMessage);
+                    WriteWellKnownTypeValue(writer, nestedMessage.Descriptor, nestedMessage, indentationLevel);
                     return;
                 case Value.NullValueFieldNumber:
                     WriteNull(writer);
@@ -629,33 +678,30 @@
             }
         }
 
-        internal void WriteList(TextWriter writer, IList list)
+        internal void WriteList(TextWriter writer, IList list, int indentationLevel = 0)
         {
-            writer.Write("[ ");
+            WriteBracketOpen(writer, ListBracketOpen);
+
             bool first = true;
             foreach (var value in list)
             {
-                if (!first)
-                {
-                    writer.Write(PropertySeparator);
-                }
-                WriteValue(writer, value);
+                MaybeWriteValueSeparator(writer, first);
+                MaybeWriteValueWhitespace(writer, indentationLevel + 1);
+                WriteValue(writer, value, indentationLevel + 1);
                 first = false;
             }
-            writer.Write(first ? "]" : " ]");
+
+            WriteBracketClose(writer, ListBracketClose, !first, indentationLevel);
         }
 
-        internal void WriteDictionary(TextWriter writer, IDictionary dictionary)
+        internal void WriteDictionary(TextWriter writer, IDictionary dictionary, int indentationLevel = 0)
         {
-            writer.Write("{ ");
+            WriteBracketOpen(writer, ObjectOpenBracket);
+
             bool first = true;
             // This will box each pair. Could use IDictionaryEnumerator, but that's ugly in terms of disposal.
             foreach (DictionaryEntry pair in dictionary)
             {
-                if (!first)
-                {
-                    writer.Write(PropertySeparator);
-                }
                 string keyText;
                 if (pair.Key is string s)
                 {
@@ -677,12 +723,16 @@
                     }
                     throw new ArgumentException("Unhandled dictionary key type: " + pair.Key.GetType());
                 }
+
+                MaybeWriteValueSeparator(writer, first);
+                MaybeWriteValueWhitespace(writer, indentationLevel + 1);
                 WriteString(writer, keyText);
                 writer.Write(NameValueSeparator);
                 WriteValue(writer, pair.Value);
                 first = false;
             }
-            writer.Write(first ? "}" : " }");
+
+            WriteBracketClose(writer, ObjectCloseBracket, !first, indentationLevel);
         }
 
         /// <summary>
@@ -766,6 +816,49 @@
             writer.Write(Hex[(c >> 0) & 0xf]);
         }
 
+        private void WriteBracketOpen(TextWriter writer, char openChar)
+        {
+            writer.Write(openChar);
+            if (settings.Indentation == null)
+            {
+                writer.Write(' ');
+            }
+        }
+
+        private void WriteBracketClose(TextWriter writer, char closeChar, bool hasFields, int indentationLevel)
+        {
+            if (hasFields)
+            {
+                if (settings.Indentation != null)
+                {
+                    writer.WriteLine();
+                    WriteIndentation(writer, indentationLevel);
+                }
+                else
+                {
+                    writer.Write(" ");
+                }
+            }
+
+            writer.Write(closeChar);
+        }
+
+        private void MaybeWriteValueWhitespace(TextWriter writer, int indentationLevel)
+        {
+            if (settings.Indentation != null) {
+                writer.WriteLine();
+                WriteIndentation(writer, indentationLevel);
+            }
+        }
+
+        private void WriteIndentation(TextWriter writer, int indentationLevel)
+        {
+            for (int i = 0; i < indentationLevel; i++)
+            {
+                writer.Write(settings.Indentation);
+            }
+        }
+
         /// <summary>
         /// Settings controlling JSON formatting.
         /// </summary>
@@ -806,6 +899,10 @@
             /// </summary>
             public bool PreserveProtoFieldNames { get; }
 
+            /// <summary>
+            /// Indentation string, used for formatting. Setting null disables indentation.
+            /// </summary>
+            public string Indentation { get; }
 
             /// <summary>
             /// Creates a new <see cref="Settings"/> object with the specified formatting of default values
@@ -833,40 +930,54 @@
             /// <param name="typeRegistry">The <see cref="TypeRegistry"/> to use when formatting <see cref="Any"/> messages. TypeRegistry.Empty will be used if it is null.</param>
             /// <param name="formatEnumsAsIntegers"><c>true</c> to format the enums as integers; <c>false</c> to format enums as enum names.</param>
             /// <param name="preserveProtoFieldNames"><c>true</c> to preserve proto field names; <c>false</c> to convert them to lowerCamelCase.</param>
+            /// <param name="indentation">The indentation string to use for multi-line formatting. <c>null</c> to disable multi-line format.</param>
             private Settings(bool formatDefaultValues,
                             TypeRegistry typeRegistry,
                             bool formatEnumsAsIntegers,
-                            bool preserveProtoFieldNames)
+                            bool preserveProtoFieldNames,
+                            string indentation = null)
             {
                 FormatDefaultValues = formatDefaultValues;
                 TypeRegistry = typeRegistry ?? TypeRegistry.Empty;
                 FormatEnumsAsIntegers = formatEnumsAsIntegers;
                 PreserveProtoFieldNames = preserveProtoFieldNames;
+                Indentation = indentation;
             }
 
             /// <summary>
             /// Creates a new <see cref="Settings"/> object with the specified formatting of default values and the current settings.
             /// </summary>
             /// <param name="formatDefaultValues"><c>true</c> if default values (0, empty strings etc) should be formatted; <c>false</c> otherwise.</param>
-            public Settings WithFormatDefaultValues(bool formatDefaultValues) => new Settings(formatDefaultValues, TypeRegistry, FormatEnumsAsIntegers, PreserveProtoFieldNames);
+            public Settings WithFormatDefaultValues(bool formatDefaultValues) => new Settings(formatDefaultValues, TypeRegistry, FormatEnumsAsIntegers, PreserveProtoFieldNames, Indentation);
 
             /// <summary>
             /// Creates a new <see cref="Settings"/> object with the specified type registry and the current settings.
             /// </summary>
             /// <param name="typeRegistry">The <see cref="TypeRegistry"/> to use when formatting <see cref="Any"/> messages.</param>
-            public Settings WithTypeRegistry(TypeRegistry typeRegistry) => new Settings(FormatDefaultValues, typeRegistry, FormatEnumsAsIntegers, PreserveProtoFieldNames);
+            public Settings WithTypeRegistry(TypeRegistry typeRegistry) => new Settings(FormatDefaultValues, typeRegistry, FormatEnumsAsIntegers, PreserveProtoFieldNames, Indentation);
 
             /// <summary>
             /// Creates a new <see cref="Settings"/> object with the specified enums formatting option and the current settings.
             /// </summary>
             /// <param name="formatEnumsAsIntegers"><c>true</c> to format the enums as integers; <c>false</c> to format enums as enum names.</param>
-            public Settings WithFormatEnumsAsIntegers(bool formatEnumsAsIntegers) => new Settings(FormatDefaultValues, TypeRegistry, formatEnumsAsIntegers, PreserveProtoFieldNames);
+            public Settings WithFormatEnumsAsIntegers(bool formatEnumsAsIntegers) => new Settings(FormatDefaultValues, TypeRegistry, formatEnumsAsIntegers, PreserveProtoFieldNames, Indentation);
 
             /// <summary>
             /// Creates a new <see cref="Settings"/> object with the specified field name formatting option and the current settings.
             /// </summary>
             /// <param name="preserveProtoFieldNames"><c>true</c> to preserve proto field names; <c>false</c> to convert them to lowerCamelCase.</param>
-            public Settings WithPreserveProtoFieldNames(bool preserveProtoFieldNames) => new Settings(FormatDefaultValues, TypeRegistry, FormatEnumsAsIntegers, preserveProtoFieldNames);
+            public Settings WithPreserveProtoFieldNames(bool preserveProtoFieldNames) => new Settings(FormatDefaultValues, TypeRegistry, FormatEnumsAsIntegers, preserveProtoFieldNames, Indentation);
+
+            /// <summary>
+            /// Creates a new <see cref="Settings"/> object with the specified indentation and the current settings.
+            /// </summary>
+            /// <param name="indentation">The string to output for each level of indentation (nesting). The default is two spaces per level. Use null to disable indentation entirely.</param>
+            /// <remarks>A non-null value for <see cref="Indentation"/> will insert additional line-breaks to the JSON output.
+            /// Each line will contain either a single value, or braces. The default line-break is determined by <see cref="Environment.NewLine"/>,
+            /// which is <c>"\n"</c> on Unix platforms, and <c>"\r\n"</c> on Windows. If <see cref="JsonFormatter"/> seems to produce empty lines,
+            /// you need to pass a <see cref="TextWriter"/> that uses a <c>"\n"</c> newline. See <see cref="JsonFormatter.Format(Google.Protobuf.IMessage, TextWriter)"/>.
+            /// </remarks>
+            public Settings WithIndentation(string indentation = "  ") => new Settings(FormatDefaultValues, TypeRegistry, FormatEnumsAsIntegers, PreserveProtoFieldNames, indentation);
         }
 
         // Effectively a cache of mapping from enum values to the original name as specified in the proto file,
diff --git a/csharp/src/Google.Protobuf/MessageParser.cs b/csharp/src/Google.Protobuf/MessageParser.cs
index 66907d4..5710292 100644
--- a/csharp/src/Google.Protobuf/MessageParser.cs
+++ b/csharp/src/Google.Protobuf/MessageParser.cs
@@ -171,6 +171,10 @@
         /// <summary>
         /// Parses a message from the given JSON.
         /// </summary>
+        /// <remarks>This method always uses the default JSON parser; it is not affected by <see cref="WithDiscardUnknownFields(bool)"/>.
+        /// To ignore unknown fields when parsing JSON, create a <see cref="JsonParser"/> using a <see cref="JsonParser.Settings"/>
+        /// with <see cref="JsonParser.Settings.IgnoreUnknownFields"/> set to true and call <see cref="JsonParser.Parse{T}(string)"/> directly.
+        /// </remarks>
         /// <param name="json">The JSON to parse.</param>
         /// <returns>The parsed message.</returns>
         /// <exception cref="InvalidJsonException">The JSON does not comply with RFC 7159</exception>
@@ -203,6 +207,9 @@
         /// <summary>
         /// Creates a new message parser which optionally discards unknown fields when parsing.
         /// </summary>
+        /// <remarks>Note that this does not affect the behavior of <see cref="ParseJson(string)"/>
+        /// at all. To ignore unknown fields when parsing JSON, create a <see cref="JsonParser"/> using a <see cref="JsonParser.Settings"/>
+        /// with <see cref="JsonParser.Settings.IgnoreUnknownFields"/> set to true and call <see cref="JsonParser.Parse{T}(string)"/> directly.</remarks>
         /// <param name="discardUnknownFields">Whether or not to discard unknown fields when parsing.</param>
         /// <returns>A newly configured message parser.</returns>
         public MessageParser WithDiscardUnknownFields(bool discardUnknownFields) =>
diff --git a/csharp/src/Google.Protobuf/WellKnownTypes/Struct.cs b/csharp/src/Google.Protobuf/WellKnownTypes/Struct.cs
index 8c1eec5..aa25686 100644
--- a/csharp/src/Google.Protobuf/WellKnownTypes/Struct.cs
+++ b/csharp/src/Google.Protobuf/WellKnownTypes/Struct.cs
@@ -212,7 +212,7 @@
       if (other == null) {
         return;
       }
-      fields_.Add(other.fields_);
+      fields_.MergeFrom(other.fields_);
       _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
 
diff --git a/java/README.md b/java/README.md
index 7e71e66..5e3ded4 100644
--- a/java/README.md
+++ b/java/README.md
@@ -23,7 +23,7 @@
 <dependency>
   <groupId>com.google.protobuf</groupId>
   <artifactId>protobuf-java</artifactId>
-  <version>3.21.2</version>
+  <version>3.21.4</version>
 </dependency>
 ```
 
@@ -37,7 +37,7 @@
 <dependency>
   <groupId>com.google.protobuf</groupId>
   <artifactId>protobuf-java-util</artifactId>
-  <version>3.21.2</version>
+  <version>3.21.4</version>
 </dependency>
 ```
 
@@ -45,7 +45,7 @@
 
 If you are using Gradle, add the following to your `build.gradle` file's dependencies:
 ```
-    implementation 'com.google.protobuf:protobuf-java:3.21.2'
+    implementation 'com.google.protobuf:protobuf-java:3.21.4'
 ```
 Again, be sure to check that the version number matches (or is newer than) the version number of protoc that you are using.
 
diff --git a/java/bom/pom.xml b/java/bom/pom.xml
index b4c3465..4cccfb3 100644
--- a/java/bom/pom.xml
+++ b/java/bom/pom.xml
@@ -4,7 +4,7 @@
 
   <groupId>com.google.protobuf</groupId>
   <artifactId>protobuf-bom</artifactId>
-  <version>3.21.2</version>
+  <version>3.21.4</version>
   <packaging>pom</packaging>
 
   <name>Protocol Buffers [BOM]</name>
diff --git a/java/core/pom.xml b/java/core/pom.xml
index 845ff88..d5eacd6 100644
--- a/java/core/pom.xml
+++ b/java/core/pom.xml
@@ -4,7 +4,7 @@
   <parent>
     <groupId>com.google.protobuf</groupId>
     <artifactId>protobuf-parent</artifactId>
-    <version>3.21.2</version>
+    <version>3.21.4</version>
   </parent>
 
   <artifactId>protobuf-java</artifactId>
diff --git a/java/core/src/main/java/com/google/protobuf/BinaryWriter.java b/java/core/src/main/java/com/google/protobuf/BinaryWriter.java
index cf394e3..66cf51d 100644
--- a/java/core/src/main/java/com/google/protobuf/BinaryWriter.java
+++ b/java/core/src/main/java/com/google/protobuf/BinaryWriter.java
@@ -209,7 +209,7 @@
     }
   }
 
-  private final void writeInt32List_Internal(int fieldNumber, List<Integer> list, boolean packed)
+  private void writeInt32List_Internal(int fieldNumber, List<Integer> list, boolean packed)
       throws IOException {
     if (packed) {
       requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * MAX_VARINT64_SIZE));
@@ -227,7 +227,7 @@
     }
   }
 
-  private final void writeInt32List_Internal(int fieldNumber, IntArrayList list, boolean packed)
+  private void writeInt32List_Internal(int fieldNumber, IntArrayList list, boolean packed)
       throws IOException {
     if (packed) {
       requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * MAX_VARINT64_SIZE));
@@ -255,7 +255,7 @@
     }
   }
 
-  private final void writeFixed32List_Internal(int fieldNumber, List<Integer> list, boolean packed)
+  private void writeFixed32List_Internal(int fieldNumber, List<Integer> list, boolean packed)
       throws IOException {
     if (packed) {
       requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * FIXED32_SIZE));
@@ -273,7 +273,7 @@
     }
   }
 
-  private final void writeFixed32List_Internal(int fieldNumber, IntArrayList list, boolean packed)
+  private void writeFixed32List_Internal(int fieldNumber, IntArrayList list, boolean packed)
       throws IOException {
     if (packed) {
       requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * FIXED32_SIZE));
@@ -307,7 +307,7 @@
     }
   }
 
-  private final void writeUInt64List_Internal(int fieldNumber, List<Long> list, boolean packed)
+  private void writeUInt64List_Internal(int fieldNumber, List<Long> list, boolean packed)
       throws IOException {
     if (packed) {
       requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * MAX_VARINT64_SIZE));
@@ -325,7 +325,7 @@
     }
   }
 
-  private final void writeUInt64List_Internal(int fieldNumber, LongArrayList list, boolean packed)
+  private void writeUInt64List_Internal(int fieldNumber, LongArrayList list, boolean packed)
       throws IOException {
     if (packed) {
       requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * MAX_VARINT64_SIZE));
@@ -353,7 +353,7 @@
     }
   }
 
-  private final void writeFixed64List_Internal(int fieldNumber, List<Long> list, boolean packed)
+  private void writeFixed64List_Internal(int fieldNumber, List<Long> list, boolean packed)
       throws IOException {
     if (packed) {
       requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * FIXED64_SIZE));
@@ -371,7 +371,7 @@
     }
   }
 
-  private final void writeFixed64List_Internal(int fieldNumber, LongArrayList list, boolean packed)
+  private void writeFixed64List_Internal(int fieldNumber, LongArrayList list, boolean packed)
       throws IOException {
     if (packed) {
       requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * FIXED64_SIZE));
@@ -399,7 +399,7 @@
     }
   }
 
-  private final void writeFloatList_Internal(int fieldNumber, List<Float> list, boolean packed)
+  private void writeFloatList_Internal(int fieldNumber, List<Float> list, boolean packed)
       throws IOException {
     if (packed) {
       requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * FIXED32_SIZE));
@@ -417,7 +417,7 @@
     }
   }
 
-  private final void writeFloatList_Internal(int fieldNumber, FloatArrayList list, boolean packed)
+  private void writeFloatList_Internal(int fieldNumber, FloatArrayList list, boolean packed)
       throws IOException {
     if (packed) {
       requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * FIXED32_SIZE));
@@ -445,7 +445,7 @@
     }
   }
 
-  private final void writeDoubleList_Internal(int fieldNumber, List<Double> list, boolean packed)
+  private void writeDoubleList_Internal(int fieldNumber, List<Double> list, boolean packed)
       throws IOException {
     if (packed) {
       requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * FIXED64_SIZE));
@@ -463,7 +463,7 @@
     }
   }
 
-  private final void writeDoubleList_Internal(int fieldNumber, DoubleArrayList list, boolean packed)
+  private void writeDoubleList_Internal(int fieldNumber, DoubleArrayList list, boolean packed)
       throws IOException {
     if (packed) {
       requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * FIXED64_SIZE));
@@ -497,7 +497,7 @@
     }
   }
 
-  private final void writeBoolList_Internal(int fieldNumber, List<Boolean> list, boolean packed)
+  private void writeBoolList_Internal(int fieldNumber, List<Boolean> list, boolean packed)
       throws IOException {
     if (packed) {
       requireSpace((MAX_VARINT32_SIZE * 2) + list.size());
@@ -515,7 +515,7 @@
     }
   }
 
-  private final void writeBoolList_Internal(int fieldNumber, BooleanArrayList list, boolean packed)
+  private void writeBoolList_Internal(int fieldNumber, BooleanArrayList list, boolean packed)
       throws IOException {
     if (packed) {
       requireSpace((MAX_VARINT32_SIZE * 2) + list.size());
@@ -572,7 +572,7 @@
     }
   }
 
-  private final void writeUInt32List_Internal(int fieldNumber, List<Integer> list, boolean packed)
+  private void writeUInt32List_Internal(int fieldNumber, List<Integer> list, boolean packed)
       throws IOException {
     if (packed) {
       requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * MAX_VARINT32_SIZE));
@@ -590,7 +590,7 @@
     }
   }
 
-  private final void writeUInt32List_Internal(int fieldNumber, IntArrayList list, boolean packed)
+  private void writeUInt32List_Internal(int fieldNumber, IntArrayList list, boolean packed)
       throws IOException {
     if (packed) {
       requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * MAX_VARINT32_SIZE));
@@ -630,7 +630,7 @@
     }
   }
 
-  private final void writeSInt32List_Internal(int fieldNumber, List<Integer> list, boolean packed)
+  private void writeSInt32List_Internal(int fieldNumber, List<Integer> list, boolean packed)
       throws IOException {
     if (packed) {
       requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * MAX_VARINT32_SIZE));
@@ -648,7 +648,7 @@
     }
   }
 
-  private final void writeSInt32List_Internal(int fieldNumber, IntArrayList list, boolean packed)
+  private void writeSInt32List_Internal(int fieldNumber, IntArrayList list, boolean packed)
       throws IOException {
     if (packed) {
       requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * MAX_VARINT32_SIZE));
@@ -759,7 +759,7 @@
     }
   }
 
-  private final void writeSInt64List_Internal(int fieldNumber, List<Long> list, boolean packed)
+  private void writeSInt64List_Internal(int fieldNumber, List<Long> list, boolean packed)
       throws IOException {
     if (packed) {
       requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * MAX_VARINT64_SIZE));
@@ -777,7 +777,7 @@
     }
   }
 
-  private final void writeSInt64List_Internal(int fieldNumber, LongArrayList list, boolean packed)
+  private void writeSInt64List_Internal(int fieldNumber, LongArrayList list, boolean packed)
       throws IOException {
     if (packed) {
       requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * MAX_VARINT64_SIZE));
diff --git a/java/core/src/main/java/com/google/protobuf/Descriptors.java b/java/core/src/main/java/com/google/protobuf/Descriptors.java
index 8ba1f9b..91c9b19 100644
--- a/java/core/src/main/java/com/google/protobuf/Descriptors.java
+++ b/java/core/src/main/java/com/google/protobuf/Descriptors.java
@@ -1788,6 +1788,27 @@
       return Collections.unmodifiableList(Arrays.asList(values));
     }
 
+    /** Determines if the given field number is reserved. */
+    public boolean isReservedNumber(final int number) {
+      for (final EnumDescriptorProto.EnumReservedRange range : proto.getReservedRangeList()) {
+        if (range.getStart() <= number && number <= range.getEnd()) {
+          return true;
+        }
+      }
+      return false;
+    }
+
+    /** Determines if the given field name is reserved. */
+    public boolean isReservedName(final String name) {
+      checkNotNull(name);
+      for (final String reservedName : proto.getReservedNameList()) {
+        if (reservedName.equals(name)) {
+          return true;
+        }
+      }
+      return false;
+    }
+
     /**
      * Find an enum value by name.
      *
diff --git a/java/core/src/main/java/com/google/protobuf/GeneratedMessageLite.java b/java/core/src/main/java/com/google/protobuf/GeneratedMessageLite.java
index e212ab5..0c16000 100644
--- a/java/core/src/main/java/com/google/protobuf/GeneratedMessageLite.java
+++ b/java/core/src/main/java/com/google/protobuf/GeneratedMessageLite.java
@@ -137,7 +137,7 @@
   // any unnecessary intermediary allocations while reducing the generated code size.
 
   /** Lazily initializes unknown fields. */
-  private final void ensureUnknownFieldsInitialized() {
+  private void ensureUnknownFieldsInitialized() {
     if (unknownFields == UnknownFieldSetLite.getDefaultInstance()) {
       unknownFields = UnknownFieldSetLite.newInstance();
     }
diff --git a/java/core/src/test/java/com/google/protobuf/DescriptorsTest.java b/java/core/src/test/java/com/google/protobuf/DescriptorsTest.java
index 49a71dc..7988e7c 100644
--- a/java/core/src/test/java/com/google/protobuf/DescriptorsTest.java
+++ b/java/core/src/test/java/com/google/protobuf/DescriptorsTest.java
@@ -61,6 +61,7 @@
 import protobuf_unittest.UnittestProto.TestJsonName;
 import protobuf_unittest.UnittestProto.TestMultipleExtensionRanges;
 import protobuf_unittest.UnittestProto.TestRequired;
+import protobuf_unittest.UnittestProto.TestReservedEnumFields;
 import protobuf_unittest.UnittestProto.TestReservedFields;
 import protobuf_unittest.UnittestProto.TestService;
 import java.util.Collections;
@@ -797,6 +798,20 @@
   }
 
   @Test
+  public void testReservedEnumFields() {
+    EnumDescriptor d = TestReservedEnumFields.getDescriptor();
+    assertThat(d.isReservedNumber(2)).isTrue();
+    assertThat(d.isReservedNumber(8)).isFalse();
+    assertThat(d.isReservedNumber(9)).isTrue();
+    assertThat(d.isReservedNumber(10)).isTrue();
+    assertThat(d.isReservedNumber(11)).isTrue();
+    assertThat(d.isReservedNumber(12)).isFalse();
+    assertThat(d.isReservedName("foo")).isFalse();
+    assertThat(d.isReservedName("bar")).isTrue();
+    assertThat(d.isReservedName("baz")).isTrue();
+  }
+
+  @Test
   public void testToString() {
     assertThat(
             UnittestProto.TestAllTypes.getDescriptor()
diff --git a/java/kotlin-lite/pom.xml b/java/kotlin-lite/pom.xml
index 3fec5ec..2dc4309 100644
--- a/java/kotlin-lite/pom.xml
+++ b/java/kotlin-lite/pom.xml
@@ -4,7 +4,7 @@
   <parent>
     <groupId>com.google.protobuf</groupId>
     <artifactId>protobuf-parent</artifactId>
-    <version>3.21.2</version>
+    <version>3.21.4</version>
   </parent>
 
   <artifactId>protobuf-kotlin-lite</artifactId>
diff --git a/java/kotlin/pom.xml b/java/kotlin/pom.xml
index 43debaf..7a350f7 100644
--- a/java/kotlin/pom.xml
+++ b/java/kotlin/pom.xml
@@ -4,7 +4,7 @@
   <parent>
     <groupId>com.google.protobuf</groupId>
     <artifactId>protobuf-parent</artifactId>
-    <version>3.21.2</version>
+    <version>3.21.4</version>
   </parent>
 
   <artifactId>protobuf-kotlin</artifactId>
diff --git a/java/kotlin/src/test/kotlin/com/google/protobuf/Proto3Test.kt b/java/kotlin/src/test/kotlin/com/google/protobuf/Proto3Test.kt
index 1645cfb..8ce0094 100644
--- a/java/kotlin/src/test/kotlin/com/google/protobuf/Proto3Test.kt
+++ b/java/kotlin/src/test/kotlin/com/google/protobuf/Proto3Test.kt
@@ -66,6 +66,11 @@
       assertThat(optionalNestedMessage).isEqualTo(TestAllTypesKt.nestedMessage { bb = 118 })
       optionalNestedEnum = NestedEnum.BAZ
       assertThat(optionalNestedEnum).isEqualTo(NestedEnum.BAZ)
+      assertThat(optionalNestedEnumValue).isEqualTo(3)
+      optionalNestedEnumValue = 1
+      assertThat(optionalNestedEnumValue).isEqualTo(1)
+      assertThat(optionalNestedEnum).isEqualTo(NestedEnum.FOO)
+
       oneofUint32 = 601
       assertThat(oneofUint32).isEqualTo(601)
     }
diff --git a/java/lite.md b/java/lite.md
index 1ad0019..d4b4fc8 100644
--- a/java/lite.md
+++ b/java/lite.md
@@ -29,7 +29,7 @@
 <dependency>
   <groupId>com.google.protobuf</groupId>
   <artifactId>protobuf-javalite</artifactId>
-  <version>3.21.2</version>
+  <version>3.21.4</version>
 </dependency>
 ```
 
diff --git a/java/lite/pom.xml b/java/lite/pom.xml
index 7c635ec..20381c6 100644
--- a/java/lite/pom.xml
+++ b/java/lite/pom.xml
@@ -4,7 +4,7 @@
   <parent>
     <groupId>com.google.protobuf</groupId>
     <artifactId>protobuf-parent</artifactId>
-    <version>3.21.2</version>
+    <version>3.21.4</version>
   </parent>
 
   <artifactId>protobuf-javalite</artifactId>
diff --git a/java/pom.xml b/java/pom.xml
index 34475a4..99abcf8 100644
--- a/java/pom.xml
+++ b/java/pom.xml
@@ -4,7 +4,7 @@
 
   <groupId>com.google.protobuf</groupId>
   <artifactId>protobuf-parent</artifactId>
-  <version>3.21.2</version>
+  <version>3.21.4</version>
   <packaging>pom</packaging>
 
   <name>Protocol Buffers [Parent]</name>
diff --git a/java/protoc/pom.xml b/java/protoc/pom.xml
index aa6bc58..b2cd0d5 100644
--- a/java/protoc/pom.xml
+++ b/java/protoc/pom.xml
@@ -4,11 +4,11 @@
   <parent>
     <groupId>com.google</groupId>
     <artifactId>google</artifactId>
-    <version>1</version>
+    <version>5</version>
   </parent>
   <groupId>com.google.protobuf</groupId>
   <artifactId>protoc</artifactId>
-  <version>3.21.2</version>
+  <version>3.21.3</version>
   <packaging>pom</packaging>
   <name>Protobuf Compiler</name>
   <description>
diff --git a/java/util/pom.xml b/java/util/pom.xml
index 53f90ce..f634047 100644
--- a/java/util/pom.xml
+++ b/java/util/pom.xml
@@ -4,7 +4,7 @@
   <parent>
     <groupId>com.google.protobuf</groupId>
     <artifactId>protobuf-parent</artifactId>
-    <version>3.21.2</version>
+    <version>3.21.4</version>
   </parent>
 
   <artifactId>protobuf-java-util</artifactId>
diff --git a/kokoro/linux/bazel_distcheck/build.sh b/kokoro/linux/bazel_distcheck/build.sh
deleted file mode 100755
index a50b175..0000000
--- a/kokoro/linux/bazel_distcheck/build.sh
+++ /dev/null
@@ -1,61 +0,0 @@
-#!/bin/bash
-#
-# Build file to set up and run tests using bazel-build dist archive
-#
-# Note that the builds use WORKSPACE to fetch external sources, not
-# git submodules.
-
-set -eu
-
-use_bazel.sh 5.0.0 || true
-bazel version
-
-# Change to repo root
-cd $(dirname $0)/../../..
-
-# Get kokoro scripts from repo root by default.
-: ${SCRIPT_ROOT:=$(pwd)}
-source ${SCRIPT_ROOT}/kokoro/common/pyenv.sh
-
-# Build distribution archive
-echo "============================================================"
-echo -e "[[ $(date) ]] Building distribution archive...\n"
-${SCRIPT_ROOT}/kokoro/common/bazel_wrapper.sh build //pkg:dist_all_tar
-DIST_ARCHIVE=$(readlink $(bazel info bazel-bin)/pkg/dist_all_tar.tar.gz)
-bazel shutdown
-
-# Extract the dist archive.
-echo "============================================================"
-echo -e "[[ $(date) ]] Extracting distribution archive...\n"
-
-# Construct temp directory for running the dist build.
-# If you want to run locally and keep the build dir, create a directory
-# and pass it in the DIST_WORK_ROOT env var.
-if [[ -z ${DIST_WORK_ROOT:-} ]]; then
-  : ${DIST_WORK_ROOT:=$(mktemp -d)}
-  function dist_cleanup() {
-    (( $BASH_SUBSHELL == 0 )) && rm -rf ${DIST_WORK_ROOT}
-  }
-  trap dist_cleanup EXIT
-fi
-
-DIST_WORKSPACE=${DIST_WORK_ROOT}/protobuf
-mkdir -p ${DIST_WORKSPACE}
-tar -C ${DIST_WORKSPACE} --strip-components=1 -axf bazel-bin/pkg/dist_all_tar.tar.gz
-
-echo "============================================================"
-echo -e "[[ $(date) ]] Building extracted archive...\n"
-
-cd ${DIST_WORKSPACE}
-
-bazel_args=(
-  test
-  --keep_going
-  --test_output=errors
-  --
-  //...
-  -//objectivec/...  # only works on macOS
-  -//csharp/...      # release builds require external dependencies
-  @com_google_protobuf_examples//...
-)
-${SCRIPT_ROOT}/kokoro/common/bazel_wrapper.sh "${bazel_args[@]}"
diff --git a/kokoro/linux/bazel_distcheck/common.cfg b/kokoro/linux/bazel_distcheck/common.cfg
deleted file mode 100644
index 6b18488..0000000
--- a/kokoro/linux/bazel_distcheck/common.cfg
+++ /dev/null
@@ -1,9 +0,0 @@
-# Common config shared by presubmit and continuous.
-
-bazel_setting: {
-  project_id: "protobuf-build"
-  bes_backend_address: "buildeventservice.googleapis.com"
-  foundry_backend_address: "remotebuildexecution.googleapis.com"
-  upsalite_frontend_address: "https://source.cloud.google.com"
-  local_execution: true
-}
diff --git a/kokoro/linux/bazel_distcheck/continuous.cfg b/kokoro/linux/bazel_distcheck/continuous.cfg
deleted file mode 100644
index 4ea8b21..0000000
--- a/kokoro/linux/bazel_distcheck/continuous.cfg
+++ /dev/null
@@ -1,5 +0,0 @@
-# Config file for running tests in Kokoro
-
-# Location of the build script in repository
-build_file: "protobuf/kokoro/linux/bazel_distcheck/build.sh"
-timeout_mins: 15
diff --git a/kokoro/linux/bazel_distcheck/presubmit.cfg b/kokoro/linux/bazel_distcheck/presubmit.cfg
deleted file mode 100644
index 4ea8b21..0000000
--- a/kokoro/linux/bazel_distcheck/presubmit.cfg
+++ /dev/null
@@ -1,5 +0,0 @@
-# Config file for running tests in Kokoro
-
-# Location of the build script in repository
-build_file: "protobuf/kokoro/linux/bazel_distcheck/build.sh"
-timeout_mins: 15
diff --git a/kokoro/linux/cmake/build.sh b/kokoro/linux/cmake/build.sh
new file mode 100755
index 0000000..523253d
--- /dev/null
+++ b/kokoro/linux/cmake/build.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+#
+# Build file to set up and run tests using CMake
+
+set -eux
+
+# Change to repo root
+cd $(dirname $0)/../../..
+GIT_REPO_ROOT=`pwd`
+
+CONTAINER_IMAGE=gcr.io/protobuf-build/cmake/linux@sha256:79e6ed9d7f3f8e56167a3309a521e5b7e6a212bfb19855c65ee1cbb6f9099671
+
+# Update git submodules
+git submodule update --init --recursive
+
+tmpfile=$(mktemp -u)
+
+docker run \
+  --cidfile $tmpfile \
+  -v $GIT_REPO_ROOT:/workspace \
+  $CONTAINER_IMAGE \
+  /test.sh -Dprotobuf_BUILD_CONFORMANCE=ON
+
+# Save logs for Kokoro
+docker cp \
+  `cat $tmpfile`:/workspace/logs $KOKORO_ARTIFACTS_DIR
diff --git a/kokoro/linux/cmake/continuous.cfg b/kokoro/linux/cmake/continuous.cfg
new file mode 100644
index 0000000..f03bd39
--- /dev/null
+++ b/kokoro/linux/cmake/continuous.cfg
@@ -0,0 +1,11 @@
+# Config file for running tests in Kokoro
+
+# Location of the build script in repository
+build_file: "protobuf/kokoro/linux/cmake/build.sh"
+timeout_mins: 1440
+
+action {
+  define_artifacts {
+    regex: "**/sponge_log.*"
+  }
+}
diff --git a/kokoro/linux/cmake/presubmit.cfg b/kokoro/linux/cmake/presubmit.cfg
new file mode 100644
index 0000000..f03bd39
--- /dev/null
+++ b/kokoro/linux/cmake/presubmit.cfg
@@ -0,0 +1,11 @@
+# Config file for running tests in Kokoro
+
+# Location of the build script in repository
+build_file: "protobuf/kokoro/linux/cmake/build.sh"
+timeout_mins: 1440
+
+action {
+  define_artifacts {
+    regex: "**/sponge_log.*"
+  }
+}
diff --git a/kokoro/linux/cmake_distcheck/build.sh b/kokoro/linux/cmake_distcheck/build.sh
deleted file mode 100755
index 116e40b..0000000
--- a/kokoro/linux/cmake_distcheck/build.sh
+++ /dev/null
@@ -1,60 +0,0 @@
-#!/bin/bash
-#
-# Build file to set up and run tests based on distribution archive
-
-set -eux
-
-# Change to repo root
-cd $(dirname $0)/../../..
-
-#
-# Update git submodules
-#
-git submodule update --init --recursive
-
-#
-# Build distribution archive
-#
-# TODO: this should use Bazel-built dist archives.
-date ; ./autogen.sh
-date ; ./configure
-date ; make dist
-date
-
-DIST_ARCHIVE=( $(ls protobuf-*.tar.gz) )
-if (( ${#DIST_ARCHIVE[@]} != 1 )); then
-  echo >&2 "Distribution archive not found. ${#DIST_ARCHIVE[@]} matches:"
-  echo >&2 "${DIST_ARCHIVE[@]}"
-  exit 1
-fi
-
-#
-# Check for all expected files
-#
-kokoro/common/check_missing_dist_files.sh ${DIST_ARCHIVE}
-
-#
-# Extract to a temporary directory
-#
-if [[ -z ${DIST_WORK_ROOT:-} ]]; then
-  # If you want to preserve the extracted sources, set the DIST_WORK_ROOT
-  # environment variable to an existing directory that should be used.
-  DIST_WORK_ROOT=$(mktemp -d)
-  function cleanup_work_root() {
-    echo "Cleaning up temporary directory ${DIST_WORK_ROOT}..."
-    rm -rf ${DIST_WORK_ROOT}
-  }
-  trap cleanup_work_root EXIT
-fi
-
-tar -C ${DIST_WORK_ROOT} --strip-components=1 -axf ${DIST_ARCHIVE}
-
-#
-# Run tests using extracted sources
-#
-SOURCE_DIR=${DIST_WORK_ROOT} \
-CMAKE_GENERATOR=Ninja \
-CTEST_PARALLEL_LEVEL=$(nproc) \
-kokoro/common/cmake.sh
-
-echo "PASS"
diff --git a/kokoro/linux/cmake_distcheck/continuous.cfg b/kokoro/linux/cmake_distcheck/continuous.cfg
deleted file mode 100644
index 6ef4c89..0000000
--- a/kokoro/linux/cmake_distcheck/continuous.cfg
+++ /dev/null
@@ -1,5 +0,0 @@
-# Config file for running tests in Kokoro
-
-# Location of the build script in repository
-build_file: "protobuf/kokoro/linux/cmake_distcheck/build.sh"
-timeout_mins: 1440
diff --git a/kokoro/linux/cmake_distcheck/presubmit.cfg b/kokoro/linux/cmake_distcheck/presubmit.cfg
deleted file mode 100644
index 6ef4c89..0000000
--- a/kokoro/linux/cmake_distcheck/presubmit.cfg
+++ /dev/null
@@ -1,5 +0,0 @@
-# Config file for running tests in Kokoro
-
-# Location of the build script in repository
-build_file: "protobuf/kokoro/linux/cmake_distcheck/build.sh"
-timeout_mins: 1440
diff --git a/kokoro/linux/cmake_install/build.sh b/kokoro/linux/cmake_install/build.sh
new file mode 100755
index 0000000..7fdf267
--- /dev/null
+++ b/kokoro/linux/cmake_install/build.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+#
+# Build file to build, install, and test using CMake.
+
+set -eux
+
+# Change to repo root
+cd $(dirname $0)/../../..
+GIT_REPO_ROOT=`pwd`
+
+CONTAINER_IMAGE=gcr.io/protobuf-build/cmake/linux@sha256:79e6ed9d7f3f8e56167a3309a521e5b7e6a212bfb19855c65ee1cbb6f9099671
+
+# Update git submodules
+git submodule update --init --recursive
+
+tmpfile=$(mktemp -u)
+
+docker run \
+  --cidfile $tmpfile \
+  -v $GIT_REPO_ROOT:/workspace \
+  $CONTAINER_IMAGE \
+  "/install.sh && /test.sh \
+  -Dprotobuf_REMOVE_INSTALLED_HEADERS=ON \
+  -Dprotobuf_BUILD_PROTOBUF_BINARIES=OFF \
+  -Dprotobuf_BUILD_CONFORMANCE=ON"
+
+
+# Save logs for Kokoro
+docker cp \
+  `cat $tmpfile`:/workspace/logs $KOKORO_ARTIFACTS_DIR
diff --git a/kokoro/linux/cmake_install/continuous.cfg b/kokoro/linux/cmake_install/continuous.cfg
new file mode 100644
index 0000000..f1ae0b3
--- /dev/null
+++ b/kokoro/linux/cmake_install/continuous.cfg
@@ -0,0 +1,11 @@
+# Config file for running tests in Kokoro
+
+# Location of the build script in repository
+build_file: "protobuf/kokoro/linux/cmake_install/build.sh"
+timeout_mins: 1440
+
+action {
+  define_artifacts {
+    regex: "**/sponge_log.*"
+  }
+}
diff --git a/kokoro/linux/cmake_install/presubmit.cfg b/kokoro/linux/cmake_install/presubmit.cfg
new file mode 100644
index 0000000..f1ae0b3
--- /dev/null
+++ b/kokoro/linux/cmake_install/presubmit.cfg
@@ -0,0 +1,11 @@
+# Config file for running tests in Kokoro
+
+# Location of the build script in repository
+build_file: "protobuf/kokoro/linux/cmake_install/build.sh"
+timeout_mins: 1440
+
+action {
+  define_artifacts {
+    regex: "**/sponge_log.*"
+  }
+}
diff --git a/kokoro/linux/cmake_ninja/build.sh b/kokoro/linux/cmake_ninja/build.sh
new file mode 100755
index 0000000..21cc01e
--- /dev/null
+++ b/kokoro/linux/cmake_ninja/build.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+#
+# Build file to set up and run tests using CMake with the Ninja generator.
+
+set -eux
+
+# Change to repo root
+cd $(dirname $0)/../../..
+GIT_REPO_ROOT=`pwd`
+
+CONTAINER_IMAGE=gcr.io/protobuf-build/cmake/linux@sha256:79e6ed9d7f3f8e56167a3309a521e5b7e6a212bfb19855c65ee1cbb6f9099671
+
+# Update git submodules
+git submodule update --init --recursive
+
+tmpfile=$(mktemp -u)
+
+docker run \
+  --cidfile $tmpfile \
+  -v $GIT_REPO_ROOT:/workspace \
+  $CONTAINER_IMAGE \
+  /test.sh -G Ninja -Dprotobuf_BUILD_CONFORMANCE=ON
+
+# Save logs for Kokoro
+docker cp \
+  `cat $tmpfile`:/workspace/logs $KOKORO_ARTIFACTS_DIR
diff --git a/kokoro/linux/cmake_ninja/continuous.cfg b/kokoro/linux/cmake_ninja/continuous.cfg
new file mode 100644
index 0000000..144fc90
--- /dev/null
+++ b/kokoro/linux/cmake_ninja/continuous.cfg
@@ -0,0 +1,11 @@
+# Config file for running tests in Kokoro
+
+# Location of the build script in repository
+build_file: "protobuf/kokoro/linux/cmake_ninja/build.sh"
+timeout_mins: 1440
+
+action {
+  define_artifacts {
+    regex: "**/sponge_log.*"
+  }
+}
diff --git a/kokoro/linux/cmake_ninja/presubmit.cfg b/kokoro/linux/cmake_ninja/presubmit.cfg
new file mode 100644
index 0000000..144fc90
--- /dev/null
+++ b/kokoro/linux/cmake_ninja/presubmit.cfg
@@ -0,0 +1,11 @@
+# Config file for running tests in Kokoro
+
+# Location of the build script in repository
+build_file: "protobuf/kokoro/linux/cmake_ninja/build.sh"
+timeout_mins: 1440
+
+action {
+  define_artifacts {
+    regex: "**/sponge_log.*"
+  }
+}
diff --git a/kokoro/linux/cmake_shared/build.sh b/kokoro/linux/cmake_shared/build.sh
new file mode 100755
index 0000000..87dde41
--- /dev/null
+++ b/kokoro/linux/cmake_shared/build.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+#
+# Build file to set up and run tests via CMake using shared libraries
+
+set -eux
+
+# TODO(mkruskal) Implement this.
\ No newline at end of file
diff --git a/kokoro/linux/cmake_shared/continuous.cfg b/kokoro/linux/cmake_shared/continuous.cfg
new file mode 100644
index 0000000..f03bd39
--- /dev/null
+++ b/kokoro/linux/cmake_shared/continuous.cfg
@@ -0,0 +1,11 @@
+# Config file for running tests in Kokoro
+
+# Location of the build script in repository
+build_file: "protobuf/kokoro/linux/cmake/build.sh"
+timeout_mins: 1440
+
+action {
+  define_artifacts {
+    regex: "**/sponge_log.*"
+  }
+}
diff --git a/kokoro/linux/cmake_shared/presubmit.cfg b/kokoro/linux/cmake_shared/presubmit.cfg
new file mode 100644
index 0000000..f03bd39
--- /dev/null
+++ b/kokoro/linux/cmake_shared/presubmit.cfg
@@ -0,0 +1,11 @@
+# Config file for running tests in Kokoro
+
+# Location of the build script in repository
+build_file: "protobuf/kokoro/linux/cmake/build.sh"
+timeout_mins: 1440
+
+action {
+  define_artifacts {
+    regex: "**/sponge_log.*"
+  }
+}
diff --git a/kokoro/linux/cpp_distcheck/build.sh b/kokoro/linux/cpp_distcheck/build.sh
deleted file mode 100755
index a28843e..0000000
--- a/kokoro/linux/cpp_distcheck/build.sh
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/bin/bash
-#
-# Build file to set up and run tests
-
-set -ex  # exit immediately on error
-
-# Change to repo root
-cd $(dirname $0)/../../..
-
-./tests.sh cpp_distcheck
-
-# Run tests under release docker image.
-DOCKER_IMAGE_NAME=protobuf/protoc_$(sha1sum protoc-artifacts/Dockerfile | cut -f1 -d " ")
-until docker pull $DOCKER_IMAGE_NAME; do sleep 10; done
-
-docker run -v $(pwd):/var/local/protobuf --rm $DOCKER_IMAGE_NAME \
-  bash -l /var/local/protobuf/tests.sh cpp || FAILED="true"
-
-# This directory is owned by root. We need to delete it, because otherwise
-# Kokoro will attempt to rsync it and fail with a permission error.
-rm -rf src/core
-
-if [ "$FAILED" = "true" ]; then
-  exit 1
-fi
diff --git a/kokoro/linux/cpp_distcheck/continuous.cfg b/kokoro/linux/cpp_distcheck/continuous.cfg
deleted file mode 100644
index 4289f6a..0000000
--- a/kokoro/linux/cpp_distcheck/continuous.cfg
+++ /dev/null
@@ -1,5 +0,0 @@
-# Config file for running tests in Kokoro
-
-# Location of the build script in repository
-build_file: "protobuf/kokoro/linux/cpp_distcheck/build.sh"
-timeout_mins: 1440
diff --git a/kokoro/linux/cpp_distcheck/presubmit.cfg b/kokoro/linux/cpp_distcheck/presubmit.cfg
deleted file mode 100644
index 4289f6a..0000000
--- a/kokoro/linux/cpp_distcheck/presubmit.cfg
+++ /dev/null
@@ -1,5 +0,0 @@
-# Config file for running tests in Kokoro
-
-# Location of the build script in repository
-build_file: "protobuf/kokoro/linux/cpp_distcheck/build.sh"
-timeout_mins: 1440
diff --git a/kokoro/linux/dist_install/build.sh b/kokoro/linux/dist_install/build.sh
deleted file mode 100755
index c456ee8..0000000
--- a/kokoro/linux/dist_install/build.sh
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/bin/bash
-#
-# Build file to set up and run tests
-
-set -ex  # exit immediately on error
-
-# Change to repo root
-cd $(dirname $0)/../../..
-
-export DOCKERHUB_ORGANIZATION=protobuftesting
-export DOCKERFILE_DIR=kokoro/linux/dockerfile/test/java_stretch
-export DOCKER_RUN_SCRIPT=kokoro/linux/pull_request_in_docker.sh
-export OUTPUT_DIR=testoutput
-export TEST_SET="dist_install"
-./kokoro/linux/build_and_run_docker.sh
diff --git a/kokoro/linux/dist_install/continuous.cfg b/kokoro/linux/dist_install/continuous.cfg
deleted file mode 100644
index b1e0b20..0000000
--- a/kokoro/linux/dist_install/continuous.cfg
+++ /dev/null
@@ -1,5 +0,0 @@
-# Config file for running tests in Kokoro
-
-# Location of the build script in repository
-build_file: "protobuf/kokoro/linux/dist_install/build.sh"
-timeout_mins: 1440
diff --git a/kokoro/linux/dist_install/presubmit.cfg b/kokoro/linux/dist_install/presubmit.cfg
deleted file mode 100644
index b1e0b20..0000000
--- a/kokoro/linux/dist_install/presubmit.cfg
+++ /dev/null
@@ -1,5 +0,0 @@
-# Config file for running tests in Kokoro
-
-# Location of the build script in repository
-build_file: "protobuf/kokoro/linux/dist_install/build.sh"
-timeout_mins: 1440
diff --git a/kokoro/macos/cpp_distcheck/build.sh b/kokoro/macos/cpp_distcheck/build.sh
deleted file mode 100755
index d729b63..0000000
--- a/kokoro/macos/cpp_distcheck/build.sh
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/bash
-#
-# Build file to set up and run tests
-
-# Change to repo root
-cd $(dirname $0)/../../..
-
-# Prepare worker environment to run tests
-source kokoro/macos/prepare_build_macos_rc
-
-./tests.sh cpp_distcheck
diff --git a/kokoro/macos/cpp_distcheck/continuous.cfg b/kokoro/macos/cpp_distcheck/continuous.cfg
deleted file mode 100644
index 89441bc..0000000
--- a/kokoro/macos/cpp_distcheck/continuous.cfg
+++ /dev/null
@@ -1,5 +0,0 @@
-# Config file for running tests in Kokoro
-
-# Location of the build script in repository
-build_file: "protobuf/kokoro/macos/cpp_distcheck/build.sh"
-timeout_mins: 1440
diff --git a/kokoro/macos/cpp_distcheck/presubmit.cfg b/kokoro/macos/cpp_distcheck/presubmit.cfg
deleted file mode 100644
index 89441bc..0000000
--- a/kokoro/macos/cpp_distcheck/presubmit.cfg
+++ /dev/null
@@ -1,5 +0,0 @@
-# Config file for running tests in Kokoro
-
-# Location of the build script in repository
-build_file: "protobuf/kokoro/macos/cpp_distcheck/build.sh"
-timeout_mins: 1440
diff --git a/kokoro/macos/php7.0_mac/build.sh b/kokoro/macos/php7.0_mac/build.sh
deleted file mode 100755
index c6717e0..0000000
--- a/kokoro/macos/php7.0_mac/build.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/bash
-#
-# Build file to set up and run tests
-
-# Change to repo root
-cd $(dirname $0)/../../..
-
-# Prepare worker environment to run tests
-source kokoro/macos/prepare_build_macos_rc
-
-# TODO(mkruskal) Re-enable this once we can get a working PHP 7.0 installed.
-#./tests.sh php7.0_mac
diff --git a/kokoro/macos/php7.0_mac/continuous.cfg b/kokoro/macos/php7.0_mac/continuous.cfg
deleted file mode 100644
index c2c1811..0000000
--- a/kokoro/macos/php7.0_mac/continuous.cfg
+++ /dev/null
@@ -1,5 +0,0 @@
-# Config file for running tests in Kokoro
-
-# Location of the build script in repository
-build_file: "protobuf/kokoro/macos/php7.0_mac/build.sh"
-timeout_mins: 1440
diff --git a/kokoro/macos/php7.0_mac/presubmit.cfg b/kokoro/macos/php7.0_mac/presubmit.cfg
deleted file mode 100644
index c2c1811..0000000
--- a/kokoro/macos/php7.0_mac/presubmit.cfg
+++ /dev/null
@@ -1,5 +0,0 @@
-# Config file for running tests in Kokoro
-
-# Location of the build script in repository
-build_file: "protobuf/kokoro/macos/php7.0_mac/build.sh"
-timeout_mins: 1440
diff --git a/kokoro/macos/php7.3_mac/build.sh b/kokoro/macos/php7.3_mac/build.sh
deleted file mode 100755
index 2688ddb..0000000
--- a/kokoro/macos/php7.3_mac/build.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/bash
-#
-# Build file to set up and run tests
-
-# Change to repo root
-cd $(dirname $0)/../../..
-
-# Prepare worker environment to run tests
-source kokoro/macos/prepare_build_macos_rc
-
-# TODO(mkruskal) Re-enable this once we can get a working PHP 7.0 installed.
-#./tests.sh php7.3_mac
diff --git a/kokoro/macos/php74/build.sh b/kokoro/macos/php74/build.sh
new file mode 100755
index 0000000..ff39657
--- /dev/null
+++ b/kokoro/macos/php74/build.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Build file to set up and run tests
+
+# Change to repo root
+cd $(dirname $0)/../../..
+
+# Prepare worker environment to run tests
+source kokoro/macos/prepare_build_macos_rc
+
+# Install Dependencies
+brew install coreutils php@7.4
+
+# Configure path
+PHP_FOLDER=$(find $HOMEBREW_PREFIX -type d -regex ".*php.*/7.4.[0-9]*")
+test ! -z "$PHP_FOLDER"
+export PATH="$PHP_FOLDER/bin:$PATH"
+
+# Test
+./tests.sh php_mac
diff --git a/kokoro/macos/php7.3_mac/continuous.cfg b/kokoro/macos/php74/continuous.cfg
similarity index 65%
rename from kokoro/macos/php7.3_mac/continuous.cfg
rename to kokoro/macos/php74/continuous.cfg
index 9a71745..cf7e80b 100644
--- a/kokoro/macos/php7.3_mac/continuous.cfg
+++ b/kokoro/macos/php74/continuous.cfg
@@ -1,5 +1,5 @@
 # Config file for running tests in Kokoro
 
 # Location of the build script in repository
-build_file: "protobuf/kokoro/macos/php7.3_mac/build.sh"
+build_file: "protobuf/kokoro/macos/php74/build.sh"
 timeout_mins: 1440
diff --git a/kokoro/macos/php7.3_mac/continuous.cfg b/kokoro/macos/php74/presubmit.cfg
similarity index 65%
copy from kokoro/macos/php7.3_mac/continuous.cfg
copy to kokoro/macos/php74/presubmit.cfg
index 9a71745..cf7e80b 100644
--- a/kokoro/macos/php7.3_mac/continuous.cfg
+++ b/kokoro/macos/php74/presubmit.cfg
@@ -1,5 +1,5 @@
 # Config file for running tests in Kokoro
 
 # Location of the build script in repository
-build_file: "protobuf/kokoro/macos/php7.3_mac/build.sh"
+build_file: "protobuf/kokoro/macos/php74/build.sh"
 timeout_mins: 1440
diff --git a/kokoro/macos/php80/build.sh b/kokoro/macos/php80/build.sh
new file mode 100755
index 0000000..84e2c46
--- /dev/null
+++ b/kokoro/macos/php80/build.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Build file to set up and run tests
+
+# Change to repo root
+cd $(dirname $0)/../../..
+
+# Prepare worker environment to run tests
+source kokoro/macos/prepare_build_macos_rc
+
+# Install Dependencies
+brew install coreutils php@8.0
+
+# Configure path
+PHP_FOLDER=$(find $HOMEBREW_PREFIX -type d -regex ".*php.*/8.0.[0-9]*")
+test ! -z "$PHP_FOLDER"
+export PATH="$PHP_FOLDER/bin:$PATH"
+
+# Test
+./tests.sh php_mac
diff --git a/kokoro/macos/php7.3_mac/continuous.cfg b/kokoro/macos/php80/continuous.cfg
similarity index 65%
copy from kokoro/macos/php7.3_mac/continuous.cfg
copy to kokoro/macos/php80/continuous.cfg
index 9a71745..ded43e6 100644
--- a/kokoro/macos/php7.3_mac/continuous.cfg
+++ b/kokoro/macos/php80/continuous.cfg
@@ -1,5 +1,5 @@
 # Config file for running tests in Kokoro
 
 # Location of the build script in repository
-build_file: "protobuf/kokoro/macos/php7.3_mac/build.sh"
+build_file: "protobuf/kokoro/macos/php80/build.sh"
 timeout_mins: 1440
diff --git a/kokoro/macos/php7.3_mac/continuous.cfg b/kokoro/macos/php80/presubmit.cfg
similarity index 65%
copy from kokoro/macos/php7.3_mac/continuous.cfg
copy to kokoro/macos/php80/presubmit.cfg
index 9a71745..ded43e6 100644
--- a/kokoro/macos/php7.3_mac/continuous.cfg
+++ b/kokoro/macos/php80/presubmit.cfg
@@ -1,5 +1,5 @@
 # Config file for running tests in Kokoro
 
 # Location of the build script in repository
-build_file: "protobuf/kokoro/macos/php7.3_mac/build.sh"
+build_file: "protobuf/kokoro/macos/php80/build.sh"
 timeout_mins: 1440
diff --git a/kokoro/macos/prepare_build_macos_rc b/kokoro/macos/prepare_build_macos_rc
index 8e0a87e..7ec2832 100755
--- a/kokoro/macos/prepare_build_macos_rc
+++ b/kokoro/macos/prepare_build_macos_rc
@@ -4,20 +4,16 @@
 
 set -eux
 
+export HOMEBREW_PREFIX=$(brew --prefix)
+
 ##
 # Select Xcode version
-
-# Remember to update the Xcode version when Xcode_11.3.app is not available.
-# If xcode is not available, it will probably encounter the failure for
-# "autom4te: need GNU m4 1.4 or later: /usr/bin/m4"
-# go/kokoro/userdocs/macos/selecting_xcode.md for more information.
-export DEVELOPER_DIR=/Applications/Xcode_11.3.app/Contents/Developer
+export DEVELOPER_DIR=/Applications/Xcode_13.3.1.app/Contents/Developer
+sudo xcode-select -s "${DEVELOPER_DIR}"
 
 ##
-# Select C/C++ compilers
-
-export CC=gcc
-export CXX=g++
+# Use Python 2 by default (for googletest)
+pyenv global 2.7.18
 
 ##
 # Install Tox
@@ -27,14 +23,18 @@
 fi
 
 ##
-# Install RVM
-
+# Setup RVM
 if [[ "${KOKORO_INSTALL_RVM:-}" == "yes" ]] ; then
-  curl -sSL https://rvm.io/mpapis.asc | gpg --import -
-  curl -sSL https://rvm.io/pkuczynski.asc | gpg --import -
+    git config --global --add safe.directory $HOMEBREW_PREFIX/Library/Taps/homebrew/homebrew-cask
+    git config --global --add safe.directory $HOMEBREW_PREFIX/Library/Taps/homebrew/homebrew-core
+    git config --global --add safe.directory $HOMEBREW_PREFIX/Library/Taps/homebrew/homebrew-services
+    sudo chown -R $(whoami) $HOME/.rvm/
+fi
 
-  # Old OpenSSL versions cannot handle the SSL certificate used by
-  # https://get.rvm.io, so as a workaround we download RVM directly from
-  # GitHub. See this issue for details: https://github.com/rvm/rvm/issues/5133
-  curl -sSL https://raw.githubusercontent.com/rvm/rvm/master/binscripts/rvm-installer | bash -s master --ruby
+# "Install" valgrind if it doesn't exist
+##
+if [ ! -x "$(command -v valgrind)" ]; then
+    echo "#! /bin/bash" > valgrind
+    chmod ug+x valgrind
+    sudo mv valgrind /usr/local/bin/valgrind
 fi
diff --git a/kokoro/release/ruby/macos/build_artifacts.sh b/kokoro/release/ruby/macos/build_artifacts.sh
index c68b63c..a109d45 100755
--- a/kokoro/release/ruby/macos/build_artifacts.sh
+++ b/kokoro/release/ruby/macos/build_artifacts.sh
@@ -12,8 +12,5 @@
 # ruby environment
 bash kokoro/release/ruby/macos/ruby/ruby_build_environment.sh
 
-gem install rubygems-update
-update_rubygems
-
 # build artifacts
 bash kokoro/release/ruby/macos/ruby/ruby_build.sh
diff --git a/kokoro/release/ruby/macos/ruby/ruby_build.sh b/kokoro/release/ruby/macos/ruby/ruby_build.sh
index 55773b2..bbfc631 100755
--- a/kokoro/release/ruby/macos/ruby/ruby_build.sh
+++ b/kokoro/release/ruby/macos/ruby/ruby_build.sh
@@ -3,7 +3,6 @@
 set -ex
 
 # Build protoc
-use_bazel.sh 5.1.1
 bazel build //:protoc
 export PROTOC=$PWD/bazel-bin/protoc
 
diff --git a/kokoro/release/ruby/macos/ruby/ruby_build_environment.sh b/kokoro/release/ruby/macos/ruby/ruby_build_environment.sh
index 2a9cb16..ae973fc 100755
--- a/kokoro/release/ruby/macos/ruby/ruby_build_environment.sh
+++ b/kokoro/release/ruby/macos/ruby/ruby_build_environment.sh
@@ -4,13 +4,9 @@
 
 set +ex  # rvm script is very verbose and exits with errorcode
 
-curl -sSL https://rvm.io/mpapis.asc | gpg --import -
-curl -sSL https://rvm.io/pkuczynski.asc | gpg --import -
-
-# Old OpenSSL versions cannot handle the SSL certificate used by
-# https://get.rvm.io, so as a workaround we download RVM directly from
-# GitHub. See this issue for details: https://github.com/rvm/rvm/issues/5133
-curl -sSL https://raw.githubusercontent.com/rvm/rvm/master/binscripts/rvm-installer | bash -s master --ruby
+# Fix permissions
+sudo chown -R $(whoami) $HOME/.rvm/
+sudo chown -R $(whoami) /Library/Ruby/
 
 source $HOME/.rvm/scripts/rvm
 set -e  # rvm commands are very verbose
diff --git a/kokoro/windows/bazel/build.bat b/kokoro/windows/bazel/build.bat
new file mode 100644
index 0000000..52b83f4
--- /dev/null
+++ b/kokoro/windows/bazel/build.bat
@@ -0,0 +1,4 @@
+@rem enter repo root
+cd /d %~dp0\..\..\..
+
+@rem TODO(mkruskal) Implement tests
diff --git a/kokoro/macos/php7.3_mac/continuous.cfg b/kokoro/windows/bazel/continuous.cfg
similarity index 65%
copy from kokoro/macos/php7.3_mac/continuous.cfg
copy to kokoro/windows/bazel/continuous.cfg
index 9a71745..37e89e0 100644
--- a/kokoro/macos/php7.3_mac/continuous.cfg
+++ b/kokoro/windows/bazel/continuous.cfg
@@ -1,5 +1,5 @@
 # Config file for running tests in Kokoro
 
 # Location of the build script in repository
-build_file: "protobuf/kokoro/macos/php7.3_mac/build.sh"
+build_file: "protobuf/kokoro/windows/cmake/build.bat"
 timeout_mins: 1440
diff --git a/kokoro/macos/php7.3_mac/continuous.cfg b/kokoro/windows/bazel/presubmit.cfg
similarity index 65%
copy from kokoro/macos/php7.3_mac/continuous.cfg
copy to kokoro/windows/bazel/presubmit.cfg
index 9a71745..37e89e0 100644
--- a/kokoro/macos/php7.3_mac/continuous.cfg
+++ b/kokoro/windows/bazel/presubmit.cfg
@@ -1,5 +1,5 @@
 # Config file for running tests in Kokoro
 
 # Location of the build script in repository
-build_file: "protobuf/kokoro/macos/php7.3_mac/build.sh"
+build_file: "protobuf/kokoro/windows/cmake/build.bat"
 timeout_mins: 1440
diff --git a/kokoro/windows/cmake/build.bat b/kokoro/windows/cmake/build.bat
new file mode 100644
index 0000000..52b83f4
--- /dev/null
+++ b/kokoro/windows/cmake/build.bat
@@ -0,0 +1,4 @@
+@rem enter repo root
+cd /d %~dp0\..\..\..
+
+@rem TODO(mkruskal) Implement tests
diff --git a/kokoro/macos/php7.3_mac/continuous.cfg b/kokoro/windows/cmake/continuous.cfg
similarity index 65%
copy from kokoro/macos/php7.3_mac/continuous.cfg
copy to kokoro/windows/cmake/continuous.cfg
index 9a71745..37e89e0 100644
--- a/kokoro/macos/php7.3_mac/continuous.cfg
+++ b/kokoro/windows/cmake/continuous.cfg
@@ -1,5 +1,5 @@
 # Config file for running tests in Kokoro
 
 # Location of the build script in repository
-build_file: "protobuf/kokoro/macos/php7.3_mac/build.sh"
+build_file: "protobuf/kokoro/windows/cmake/build.bat"
 timeout_mins: 1440
diff --git a/kokoro/macos/php7.3_mac/continuous.cfg b/kokoro/windows/cmake/presubmit.cfg
similarity index 65%
copy from kokoro/macos/php7.3_mac/continuous.cfg
copy to kokoro/windows/cmake/presubmit.cfg
index 9a71745..37e89e0 100644
--- a/kokoro/macos/php7.3_mac/continuous.cfg
+++ b/kokoro/windows/cmake/presubmit.cfg
@@ -1,5 +1,5 @@
 # Config file for running tests in Kokoro
 
 # Location of the build script in repository
-build_file: "protobuf/kokoro/macos/php7.3_mac/build.sh"
+build_file: "protobuf/kokoro/windows/cmake/build.bat"
 timeout_mins: 1440
diff --git a/kokoro/windows/cmake_install/build.bat b/kokoro/windows/cmake_install/build.bat
new file mode 100644
index 0000000..52b83f4
--- /dev/null
+++ b/kokoro/windows/cmake_install/build.bat
@@ -0,0 +1,4 @@
+@rem enter repo root
+cd /d %~dp0\..\..\..
+
+@rem TODO(mkruskal) Implement tests
diff --git a/kokoro/macos/php7.3_mac/continuous.cfg b/kokoro/windows/cmake_install/continuous.cfg
similarity index 63%
copy from kokoro/macos/php7.3_mac/continuous.cfg
copy to kokoro/windows/cmake_install/continuous.cfg
index 9a71745..2efc0dc 100644
--- a/kokoro/macos/php7.3_mac/continuous.cfg
+++ b/kokoro/windows/cmake_install/continuous.cfg
@@ -1,5 +1,5 @@
 # Config file for running tests in Kokoro
 
 # Location of the build script in repository
-build_file: "protobuf/kokoro/macos/php7.3_mac/build.sh"
+build_file: "protobuf/kokoro/windows/cmake_install/build.bat"
 timeout_mins: 1440
diff --git a/kokoro/macos/php7.3_mac/presubmit.cfg b/kokoro/windows/cmake_install/presubmit.cfg
similarity index 63%
rename from kokoro/macos/php7.3_mac/presubmit.cfg
rename to kokoro/windows/cmake_install/presubmit.cfg
index 9a71745..2efc0dc 100644
--- a/kokoro/macos/php7.3_mac/presubmit.cfg
+++ b/kokoro/windows/cmake_install/presubmit.cfg
@@ -1,5 +1,5 @@
 # Config file for running tests in Kokoro
 
 # Location of the build script in repository
-build_file: "protobuf/kokoro/macos/php7.3_mac/build.sh"
+build_file: "protobuf/kokoro/windows/cmake_install/build.bat"
 timeout_mins: 1440
diff --git a/kokoro/windows/cmake_nmake/build.bat b/kokoro/windows/cmake_nmake/build.bat
new file mode 100644
index 0000000..52b83f4
--- /dev/null
+++ b/kokoro/windows/cmake_nmake/build.bat
@@ -0,0 +1,4 @@
+@rem enter repo root
+cd /d %~dp0\..\..\..
+
+@rem TODO(mkruskal) Implement tests
diff --git a/kokoro/macos/php7.3_mac/continuous.cfg b/kokoro/windows/cmake_nmake/continuous.cfg
similarity index 63%
copy from kokoro/macos/php7.3_mac/continuous.cfg
copy to kokoro/windows/cmake_nmake/continuous.cfg
index 9a71745..3c279fe 100644
--- a/kokoro/macos/php7.3_mac/continuous.cfg
+++ b/kokoro/windows/cmake_nmake/continuous.cfg
@@ -1,5 +1,5 @@
 # Config file for running tests in Kokoro
 
 # Location of the build script in repository
-build_file: "protobuf/kokoro/macos/php7.3_mac/build.sh"
+build_file: "protobuf/kokoro/windows/cmake_nmake/build.bat"
 timeout_mins: 1440
diff --git a/kokoro/macos/php7.3_mac/continuous.cfg b/kokoro/windows/cmake_nmake/presubmit.cfg
similarity index 63%
copy from kokoro/macos/php7.3_mac/continuous.cfg
copy to kokoro/windows/cmake_nmake/presubmit.cfg
index 9a71745..3c279fe 100644
--- a/kokoro/macos/php7.3_mac/continuous.cfg
+++ b/kokoro/windows/cmake_nmake/presubmit.cfg
@@ -1,5 +1,5 @@
 # Config file for running tests in Kokoro
 
 # Location of the build script in repository
-build_file: "protobuf/kokoro/macos/php7.3_mac/build.sh"
+build_file: "protobuf/kokoro/windows/cmake_nmake/build.bat"
 timeout_mins: 1440
diff --git a/kokoro/windows/cmake_shared/build.bat b/kokoro/windows/cmake_shared/build.bat
new file mode 100644
index 0000000..52b83f4
--- /dev/null
+++ b/kokoro/windows/cmake_shared/build.bat
@@ -0,0 +1,4 @@
+@rem enter repo root
+cd /d %~dp0\..\..\..
+
+@rem TODO(mkruskal) Implement tests
diff --git a/kokoro/macos/php7.3_mac/continuous.cfg b/kokoro/windows/cmake_shared/continuous.cfg
similarity index 65%
copy from kokoro/macos/php7.3_mac/continuous.cfg
copy to kokoro/windows/cmake_shared/continuous.cfg
index 9a71745..37e89e0 100644
--- a/kokoro/macos/php7.3_mac/continuous.cfg
+++ b/kokoro/windows/cmake_shared/continuous.cfg
@@ -1,5 +1,5 @@
 # Config file for running tests in Kokoro
 
 # Location of the build script in repository
-build_file: "protobuf/kokoro/macos/php7.3_mac/build.sh"
+build_file: "protobuf/kokoro/windows/cmake/build.bat"
 timeout_mins: 1440
diff --git a/kokoro/macos/php7.3_mac/continuous.cfg b/kokoro/windows/cmake_shared/presubmit.cfg
similarity index 65%
copy from kokoro/macos/php7.3_mac/continuous.cfg
copy to kokoro/windows/cmake_shared/presubmit.cfg
index 9a71745..37e89e0 100644
--- a/kokoro/macos/php7.3_mac/continuous.cfg
+++ b/kokoro/windows/cmake_shared/presubmit.cfg
@@ -1,5 +1,5 @@
 # Config file for running tests in Kokoro
 
 # Location of the build script in repository
-build_file: "protobuf/kokoro/macos/php7.3_mac/build.sh"
+build_file: "protobuf/kokoro/windows/cmake/build.bat"
 timeout_mins: 1440
diff --git a/objectivec/GPBApi.pbobjc.h b/objectivec/GPBApi.pbobjc.h
index 871e908..1848aa6 100644
--- a/objectivec/GPBApi.pbobjc.h
+++ b/objectivec/GPBApi.pbobjc.h
@@ -74,12 +74,12 @@
 
 /** The methods of this interface, in unspecified order. */
 @property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<GPBMethod*> *methodsArray;
-/** The number of items in @c methodsArray without causing the array to be created. */
+/** The number of items in @c methodsArray without causing the container to be created. */
 @property(nonatomic, readonly) NSUInteger methodsArray_Count;
 
 /** Any metadata attached to the interface. */
 @property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<GPBOption*> *optionsArray;
-/** The number of items in @c optionsArray without causing the array to be created. */
+/** The number of items in @c optionsArray without causing the container to be created. */
 @property(nonatomic, readonly) NSUInteger optionsArray_Count;
 
 /**
@@ -115,7 +115,7 @@
 
 /** Included interfaces. See [Mixin][]. */
 @property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<GPBMixin*> *mixinsArray;
-/** The number of items in @c mixinsArray without causing the array to be created. */
+/** The number of items in @c mixinsArray without causing the container to be created. */
 @property(nonatomic, readonly) NSUInteger mixinsArray_Count;
 
 /** The source syntax of the service. */
@@ -169,7 +169,7 @@
 
 /** Any metadata attached to the method. */
 @property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<GPBOption*> *optionsArray;
-/** The number of items in @c optionsArray without causing the array to be created. */
+/** The number of items in @c optionsArray without causing the container to be created. */
 @property(nonatomic, readonly) NSUInteger optionsArray_Count;
 
 /** The source syntax of this method. */
diff --git a/objectivec/GPBFieldMask.pbobjc.h b/objectivec/GPBFieldMask.pbobjc.h
index c4667b4..b7eccff 100644
--- a/objectivec/GPBFieldMask.pbobjc.h
+++ b/objectivec/GPBFieldMask.pbobjc.h
@@ -247,7 +247,7 @@
 
 /** The set of field mask paths. */
 @property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<NSString*> *pathsArray;
-/** The number of items in @c pathsArray without causing the array to be created. */
+/** The number of items in @c pathsArray without causing the container to be created. */
 @property(nonatomic, readonly) NSUInteger pathsArray_Count;
 
 @end
diff --git a/objectivec/GPBStruct.pbobjc.h b/objectivec/GPBStruct.pbobjc.h
index dd6ab28..ff4eefd 100644
--- a/objectivec/GPBStruct.pbobjc.h
+++ b/objectivec/GPBStruct.pbobjc.h
@@ -87,7 +87,7 @@
 
 /** Unordered map of dynamically typed values. */
 @property(nonatomic, readwrite, strong, null_resettable) NSMutableDictionary<NSString*, GPBValue*> *fields;
-/** The number of items in @c fields without causing the array to be created. */
+/** The number of items in @c fields without causing the container to be created. */
 @property(nonatomic, readonly) NSUInteger fields_Count;
 
 @end
@@ -178,7 +178,7 @@
 
 /** Repeated field of dynamically typed values. */
 @property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<GPBValue*> *valuesArray;
-/** The number of items in @c valuesArray without causing the array to be created. */
+/** The number of items in @c valuesArray without causing the container to be created. */
 @property(nonatomic, readonly) NSUInteger valuesArray_Count;
 
 @end
diff --git a/objectivec/GPBType.pbobjc.h b/objectivec/GPBType.pbobjc.h
index b023050..969219f 100644
--- a/objectivec/GPBType.pbobjc.h
+++ b/objectivec/GPBType.pbobjc.h
@@ -195,17 +195,17 @@
 
 /** The list of fields. */
 @property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<GPBField*> *fieldsArray;
-/** The number of items in @c fieldsArray without causing the array to be created. */
+/** The number of items in @c fieldsArray without causing the container to be created. */
 @property(nonatomic, readonly) NSUInteger fieldsArray_Count;
 
 /** The list of types appearing in `oneof` definitions in this type. */
 @property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<NSString*> *oneofsArray;
-/** The number of items in @c oneofsArray without causing the array to be created. */
+/** The number of items in @c oneofsArray without causing the container to be created. */
 @property(nonatomic, readonly) NSUInteger oneofsArray_Count;
 
 /** The protocol buffer options. */
 @property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<GPBOption*> *optionsArray;
-/** The number of items in @c optionsArray without causing the array to be created. */
+/** The number of items in @c optionsArray without causing the container to be created. */
 @property(nonatomic, readonly) NSUInteger optionsArray_Count;
 
 /** The source context. */
@@ -279,7 +279,7 @@
 
 /** The protocol buffer options. */
 @property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<GPBOption*> *optionsArray;
-/** The number of items in @c optionsArray without causing the array to be created. */
+/** The number of items in @c optionsArray without causing the container to be created. */
 @property(nonatomic, readonly) NSUInteger optionsArray_Count;
 
 /** The field JSON name. */
@@ -334,12 +334,12 @@
 
 /** Enum value definitions. */
 @property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<GPBEnumValue*> *enumvalueArray;
-/** The number of items in @c enumvalueArray without causing the array to be created. */
+/** The number of items in @c enumvalueArray without causing the container to be created. */
 @property(nonatomic, readonly) NSUInteger enumvalueArray_Count;
 
 /** Protocol buffer options. */
 @property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<GPBOption*> *optionsArray;
-/** The number of items in @c optionsArray without causing the array to be created. */
+/** The number of items in @c optionsArray without causing the container to be created. */
 @property(nonatomic, readonly) NSUInteger optionsArray_Count;
 
 /** The source context. */
@@ -385,7 +385,7 @@
 
 /** Protocol buffer options. */
 @property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<GPBOption*> *optionsArray;
-/** The number of items in @c optionsArray without causing the array to be created. */
+/** The number of items in @c optionsArray without causing the container to be created. */
 @property(nonatomic, readonly) NSUInteger optionsArray_Count;
 
 @end
diff --git a/objectivec/GPBUtilities.m b/objectivec/GPBUtilities.m
index b72d0e2..cd0910c 100644
--- a/objectivec/GPBUtilities.m
+++ b/objectivec/GPBUtilities.m
@@ -2248,7 +2248,7 @@
     NSCAssert([[self descriptor] oneofWithName:oneof.name] == oneof,
               @"OneofDescriptor %@ doesn't appear to be for %@ messages.",
               oneof.name, [self class]);
-    GPBFieldDescriptor *firstField = oneof->fields_[0];
+    GPBFieldDescriptor *firstField __unused = oneof->fields_[0];
     NSCAssert(firstField->description_->hasIndex == oneofHasIndex,
               @"Internal error, oneofHasIndex (%d) doesn't match (%d).",
               firstField->description_->hasIndex, oneofHasIndex);
diff --git a/objectivec/generate_well_known_types.sh b/objectivec/generate_well_known_types.sh
index 1b9de6e..e6c9b04 100755
--- a/objectivec/generate_well_known_types.sh
+++ b/objectivec/generate_well_known_types.sh
@@ -73,6 +73,7 @@
     if ! diff "${ObjCDir}/GPB${OBJC_NAME}${EXT}" "${TMP_DIR}/${DIR}/${OBJC_NAME}${EXT}" > /dev/null 2>&1 ; then
       if [[ "${CHECK_ONLY}" == 1 ]] ; then
         echo "ERROR: The WKTs need to be regenerated! Run $0"
+        diff -u "${ObjCDir}/GPB${OBJC_NAME}${EXT}" "${TMP_DIR}/${DIR}/${OBJC_NAME}${EXT}"
         exit 1
       fi
 
diff --git a/php/BUILD.bazel b/php/BUILD.bazel
index 3460959..dfb0976 100644
--- a/php/BUILD.bazel
+++ b/php/BUILD.bazel
@@ -12,7 +12,9 @@
         "src/Google/Protobuf/**/*.php",
         "tests/*.php",
         "tests/*.sh",
+        "tests/generated_previous/**/*.php",
         "tests/proto/**/*.proto",
+        "tests/proto_previous/*.proto",
     ]) + [
         "BUILD.bazel",
         "README.md",
diff --git a/php/composer.json b/php/composer.json
index 756704f..436142f 100644
--- a/php/composer.json
+++ b/php/composer.json
@@ -20,7 +20,10 @@
   "autoload-dev": {
     "psr-4": {
       "": "tmp"
-    }
+    },
+    "classmap": [
+      "tests/generated_previous"
+    ]
   },
   "scripts": {
     "test_c": "./generate_test_protos.sh && ./tests/compile_extension.sh && php -dextension=ext/google/protobuf/modules/protobuf.so vendor/bin/phpunit --bootstrap tests/force_c_ext.php tests",
diff --git a/php/ext/google/protobuf/def.c b/php/ext/google/protobuf/def.c
index dfb96f2..018dac1 100644
--- a/php/ext/google/protobuf/def.c
+++ b/php/ext/google/protobuf/def.c
@@ -162,7 +162,7 @@
     ZVAL_NULL(val);
   } else {
     char *classname =
-        GetPhpClassname(upb_EnumDef_File(m), upb_EnumDef_FullName(m));
+        GetPhpClassname(upb_EnumDef_File(m), upb_EnumDef_FullName(m), false);
     zend_string *str = zend_string_init(classname, strlen(classname), 0);
     zend_class_entry *ce = zend_lookup_class(str);  // May autoload the class.
 
@@ -499,19 +499,23 @@
 }
 
 static zend_class_entry *Descriptor_GetGeneratedClass(const upb_MessageDef *m) {
-  char *classname =
-      GetPhpClassname(upb_MessageDef_File(m), upb_MessageDef_FullName(m));
-  zend_string *str = zend_string_init(classname, strlen(classname), 0);
-  zend_class_entry *ce = zend_lookup_class(str);  // May autoload the class.
+  for (int i = 0; i < 2; ++i) {
+    char *classname =
+        GetPhpClassname(upb_MessageDef_File(m), upb_MessageDef_FullName(m), (bool)i);
+    zend_string *str = zend_string_init(classname, strlen(classname), 0);
+    zend_class_entry *ce = zend_lookup_class(str);  // May autoload the class.
 
-  zend_string_release (str);
+    zend_string_release (str);
+    free(classname);
 
-  if (!ce) {
-    zend_error(E_ERROR, "Couldn't load generated class %s", classname);
+    if (ce) {
+      return ce;
+    }
   }
 
-  free(classname);
-  return ce;
+  char *classname =
+    GetPhpClassname(upb_MessageDef_File(m), upb_MessageDef_FullName(m), false);
+  zend_error(E_ERROR, "Couldn't load generated class %s", classname);
 }
 
 void Descriptor_FromMessageDef(zval *val, const upb_MessageDef *m) {
diff --git a/php/ext/google/protobuf/names.c b/php/ext/google/protobuf/names.c
index 5d7b68a..e359d01 100644
--- a/php/ext/google/protobuf/names.c
+++ b/php/ext/google/protobuf/names.c
@@ -82,12 +82,15 @@
     "global",     "goto",        "insteadof",    "interface",    "isset",
     "list",       "match",       "namespace",    "new",          "object",
     "or",         "parent",      "print",        "private",      "protected",
-    "public",     "require",     "require_once", "return",       "self",
-    "static",     "switch",      "throw",        "trait",        "try",
-    "unset",      "use",         "var",          "while",        "xor",
-    "yield",      "int",         "float",        "bool",         "string",
-    "true",       "false",       "null",         "void",         "iterable",
-    NULL};
+    "public",     "readonly",    "require",      "require_once", "return",
+    "self",       "static",      "switch",       "throw",        "trait",
+    "try",        "unset",       "use",          "var",          "while",
+    "xor",        "yield",       "int",          "float",        "bool",
+    "string",     "true",        "false",        "null",         "void",
+    "iterable",   NULL};
+
+const char *const kPreviouslyUnreservedNames[] = {
+    "readonly", NULL};
 
 bool is_reserved_name(const char* name) {
   int i;
@@ -99,6 +102,15 @@
   return false;
 }
 
+bool is_previously_unreserved_name(const char* name) {
+  for (int i = 0; kPreviouslyUnreservedNames[i]; i++) {
+    if (strcmp(kPreviouslyUnreservedNames[i], name) == 0) {
+      return true;
+    }
+  }
+  return false;
+}
+
 static char nolocale_tolower(char ch) {
   if (ch >= 'A' && ch <= 'Z') {
     return ch - ('A' - 'a');
@@ -115,17 +127,22 @@
   }
 }
 
-static bool is_reserved(const char *segment, int length) {
-  bool result;
-  char* lower = calloc(1, length + 1);
-  memcpy(lower, segment, length);
-  int i = 0;
-  while(lower[i]) {
-    lower[i] = nolocale_tolower(lower[i]);
-    i++;
+static char *strdup_nolocale_lower(char *str, int length) {
+  char* lower = malloc(length + 1);
+  lower[length] = '\0';
+  for(int i = 0; i < length; ++i) {
+    lower[i] = nolocale_tolower(str[i]);
   }
-  lower[length] = 0;
+  return lower;
+}
+
+static bool is_reserved(const char *segment, int length, bool previous) {
+  bool result;
+  char* lower = strdup_nolocale_lower(segment, length);
   result = is_reserved_name(lower);
+  if (result && previous && is_previously_unreserved_name(lower)) {
+    result = false;
+  }
   free(lower);
   return result;
 }
@@ -133,11 +150,12 @@
 static void fill_prefix(const char *segment, int length,
                         const char *prefix_given,
                         const char *package_name,
-                        stringsink *classname) {
+                        stringsink *classname,
+                        bool previous) {
   if (prefix_given != NULL && strcmp(prefix_given, "") != 0) {
     stringsink_string(classname, prefix_given, strlen(prefix_given));
   } else {
-    if (is_reserved(segment, length)) {
+    if (is_reserved(segment, length, previous)) {
       if (package_name != NULL &&
           strcmp("google.protobuf", package_name) == 0) {
         stringsink_string(classname, "GPB", 3);
@@ -160,7 +178,7 @@
 }
 
 static void fill_namespace(const char *package, const char *php_namespace,
-                           stringsink *classname) {
+                           stringsink *classname, bool previous) {
   if (php_namespace != NULL) {
     if (strlen(php_namespace) != 0) {
       stringsink_string(classname, php_namespace, strlen(php_namespace));
@@ -174,7 +192,7 @@
       while (j < package_len && package[j] != '.') {
         j++;
       }
-      fill_prefix(package + i, j - i, "", package, classname);
+      fill_prefix(package + i, j - i, "", package, classname, previous);
       fill_segment(package + i, j - i, classname, true);
       stringsink_string(classname, "\\", 1);
       i = j + 1;
@@ -185,7 +203,8 @@
 static void fill_classname(const char *fullname,
                            const char *package,
                            const char *prefix,
-                           stringsink *classname) {
+                           stringsink *classname,
+                           bool previous) {
   int classname_start = 0;
   if (package != NULL) {
     size_t package_len = strlen(package);
@@ -199,7 +218,7 @@
     while (j < fullname_len && fullname[j] != '.') {
       j++;
     }
-    fill_prefix(fullname + i, j - i, prefix, package, classname);
+    fill_prefix(fullname + i, j - i, prefix, package, classname, previous);
     fill_segment(fullname + i, j - i, classname, false);
     if (j != fullname_len) {
       stringsink_string(classname, "\\", 1);
@@ -215,7 +234,7 @@
   return ret;
 }
 
-char *GetPhpClassname(const upb_FileDef *file, const char *fullname) {
+char *GetPhpClassname(const upb_FileDef *file, const char *fullname, bool previous) {
   // Prepend '.' to package name to make it absolute. In the 5 additional
   // bytes allocated, one for '.', one for trailing 0, and 3 for 'GPB' if
   // given message is google.protobuf.Empty.
@@ -234,8 +253,8 @@
   stringsink namesink;
   stringsink_init(&namesink);
 
-  fill_namespace(package, php_namespace, &namesink);
-  fill_classname(fullname, package, prefix, &namesink);
+  fill_namespace(package, php_namespace, &namesink, previous);
+  fill_classname(fullname, package, prefix, &namesink, previous);
   stringsink_string(&namesink, "\0", 1);
   ret = strdup(namesink.ptr);
   stringsink_uninit(&namesink);
@@ -243,3 +262,26 @@
   free(prefix);
   return ret;
 }
+
+bool IsPreviouslyUnreservedClassName(const char* fullname) {
+  const char *classname = strrchr(fullname, '\\');
+  if (classname) {
+    classname += 1;
+  } else {
+    classname = fullname;
+  }
+  if (strncmp(classname, "PB", 2) != 0) {
+    return false;
+  }
+  classname += 2;
+  int length = strlen(classname);
+  char* lower = strdup_nolocale_lower(classname, length);
+  for (int j = 0; kPreviouslyUnreservedNames[j]; j++) {
+    if (strcmp(kPreviouslyUnreservedNames[j], lower) == 0) {
+      free(lower);
+      return true;
+    }
+  }
+  free(lower);
+  return false;
+}
diff --git a/php/ext/google/protobuf/names.h b/php/ext/google/protobuf/names.h
index 86af799..cc42dc8 100644
--- a/php/ext/google/protobuf/names.h
+++ b/php/ext/google/protobuf/names.h
@@ -35,6 +35,7 @@
 
 // Translates a protobuf symbol name (eg. foo.bar.Baz) into a PHP class name
 // (eg. \Foo\Bar\Baz).
-char *GetPhpClassname(const upb_FileDef *file, const char *fullname);
+char *GetPhpClassname(const upb_FileDef *file, const char *fullname, bool previous);
+bool IsPreviouslyUnreservedClassName(const char* fullname);
 
 #endif  // PHP_PROTOBUF_NAMES_H_
diff --git a/php/ext/google/protobuf/package.xml b/php/ext/google/protobuf/package.xml
index bf3d90e..c291714 100644
--- a/php/ext/google/protobuf/package.xml
+++ b/php/ext/google/protobuf/package.xml
@@ -5,16 +5,16 @@
  <summary>Google's language-neutral, platform-neutral, extensible mechanism for serializing structured data.</summary>
  <description>https://developers.google.com/protocol-buffers/</description>
  <lead>
-  <name>Bo Yang</name>
-  <user>stanleycheung</user>
-  <email>protobuf-opensource@google.com</email>
+  <name>Protobuf Team</name>
+  <user>protobufpackages</user>
+  <email>protobuf-packages@google.com</email>
   <active>yes</active>
  </lead>
- <date>2022-06-23</date>
- <time>12:12:44</time>
+ <date>2022-07-25</date>
+ <time>13:21:48</time>
  <version>
-  <release>3.21.2</release>
-  <api>3.21.2</api>
+  <release>3.21.4</release>
+  <api>3.21.4</api>
  </version>
  <stability>
   <release>stable</release>
@@ -1358,5 +1358,35 @@
    <notes>
    </notes>
   </release>
+  <release>
+   <version>
+    <release>3.21.3</release>
+    <api>3.21.3</api>
+   </version>
+   <stability>
+    <release>stable</release>
+    <api>stable</api>
+   </stability>
+   <date>2022-07-21</date>
+   <time>10:19:47</time>
+   <license uri="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause</license>
+   <notes>
+   </notes>
+  </release>
+  <release>
+   <version>
+    <release>3.21.4</release>
+    <api>3.21.4</api>
+   </version>
+   <stability>
+    <release>stable</release>
+    <api>stable</api>
+   </stability>
+   <date>2022-07-25</date>
+   <time>13:21:48</time>
+   <license uri="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause</license>
+   <notes>
+   </notes>
+  </release>
  </changelog>
 </package>
diff --git a/php/ext/google/protobuf/protobuf.c b/php/ext/google/protobuf/protobuf.c
index c786b6e..a5aba23 100644
--- a/php/ext/google/protobuf/protobuf.c
+++ b/php/ext/google/protobuf/protobuf.c
@@ -242,13 +242,19 @@
 // -----------------------------------------------------------------------------
 
 void NameMap_AddMessage(const upb_MessageDef *m) {
-  char *k = GetPhpClassname(upb_MessageDef_File(m), upb_MessageDef_FullName(m));
-  zend_hash_str_add_ptr(&PROTOBUF_G(name_msg_cache), k, strlen(k), (void*)m);
-  free(k);
+  for (int i = 0; i < 2; ++i) {
+    char *k = GetPhpClassname(upb_MessageDef_File(m), upb_MessageDef_FullName(m), (bool)i);
+    zend_hash_str_add_ptr(&PROTOBUF_G(name_msg_cache), k, strlen(k), (void*)m);
+    if (!IsPreviouslyUnreservedClassName(k)) {
+      free(k);
+      return;
+    }
+    free(k);
+  }
 }
 
 void NameMap_AddEnum(const upb_EnumDef *e) {
-  char *k = GetPhpClassname(upb_EnumDef_File(e), upb_EnumDef_FullName(e));
+  char *k = GetPhpClassname(upb_EnumDef_File(e), upb_EnumDef_FullName(e), false);
   zend_hash_str_add_ptr(&PROTOBUF_G(name_enum_cache), k, strlen(k), (void*)e);
   free(k);
 }
diff --git a/php/ext/google/protobuf/protobuf.h b/php/ext/google/protobuf/protobuf.h
index c5dac32..e63e9e8 100644
--- a/php/ext/google/protobuf/protobuf.h
+++ b/php/ext/google/protobuf/protobuf.h
@@ -127,7 +127,7 @@
   ZEND_ARG_INFO(0, value)
 ZEND_END_ARG_INFO()
 
-#define PHP_PROTOBUF_VERSION "3.21.2"
+#define PHP_PROTOBUF_VERSION "3.21.4"
 
 // ptr -> PHP object cache. This is a weak map that caches lazily-created
 // wrapper objects around upb types:
diff --git a/php/src/Google/Protobuf/Internal/Descriptor.php b/php/src/Google/Protobuf/Internal/Descriptor.php
index a7f80d5..51a34d6 100644
--- a/php/src/Google/Protobuf/Internal/Descriptor.php
+++ b/php/src/Google/Protobuf/Internal/Descriptor.php
@@ -45,6 +45,7 @@
     private $enum_type = [];
     private $klass;
     private $legacy_klass;
+    private $previous_klass;
     private $options;
     private $oneof_decl = [];
 
@@ -162,6 +163,16 @@
         return $this->legacy_klass;
     }
 
+    public function setPreviouslyUnreservedClass($klass)
+    {
+        $this->previous_klass = $klass;
+    }
+
+    public function getPreviouslyUnreservedClass()
+    {
+        return $this->previous_klass;
+    }
+
     public function setOptions($options)
     {
         $this->options = $options;
@@ -179,6 +190,7 @@
         $message_name_without_package  = "";
         $classname = "";
         $legacy_classname = "";
+        $previous_classname = "";
         $fullname = "";
         GPBUtil::getFullClassName(
             $proto,
@@ -187,10 +199,12 @@
             $message_name_without_package,
             $classname,
             $legacy_classname,
-            $fullname);
+            $fullname,
+            $previous_classname);
         $desc->setFullName($fullname);
         $desc->setClass($classname);
         $desc->setLegacyClass($legacy_classname);
+        $desc->setPreviouslyUnreservedClass($previous_classname);
         $desc->setOptions($proto->getOptions());
 
         foreach ($proto->getField() as $field_proto) {
diff --git a/php/src/Google/Protobuf/Internal/DescriptorPool.php b/php/src/Google/Protobuf/Internal/DescriptorPool.php
index 1468a02..1be00e2 100644
--- a/php/src/Google/Protobuf/Internal/DescriptorPool.php
+++ b/php/src/Google/Protobuf/Internal/DescriptorPool.php
@@ -96,6 +96,7 @@
             $descriptor->getClass();
         $this->class_to_desc[$descriptor->getClass()] = $descriptor;
         $this->class_to_desc[$descriptor->getLegacyClass()] = $descriptor;
+        $this->class_to_desc[$descriptor->getPreviouslyUnreservedClass()] = $descriptor;
         foreach ($descriptor->getNestedType() as $nested_type) {
             $this->addDescriptor($nested_type);
         }
diff --git a/php/src/Google/Protobuf/Internal/EnumDescriptor.php b/php/src/Google/Protobuf/Internal/EnumDescriptor.php
index 7af4f84..383f53b 100644
--- a/php/src/Google/Protobuf/Internal/EnumDescriptor.php
+++ b/php/src/Google/Protobuf/Internal/EnumDescriptor.php
@@ -101,7 +101,8 @@
             $enum_name_without_package,
             $classname,
             $legacy_classname,
-            $fullname);
+            $fullname,
+            $unused_previous_classname);
         $desc->setFullName($fullname);
         $desc->setClass($classname);
         $desc->setLegacyClass($legacy_classname);
diff --git a/php/src/Google/Protobuf/Internal/GPBUtil.php b/php/src/Google/Protobuf/Internal/GPBUtil.php
index 4b15283..ffea900 100644
--- a/php/src/Google/Protobuf/Internal/GPBUtil.php
+++ b/php/src/Google/Protobuf/Internal/GPBUtil.php
@@ -285,11 +285,12 @@
             "include"=>0, "include_once"=>0, "instanceof"=>0, "insteadof"=>0,
             "interface"=>0, "isset"=>0, "list"=>0, "match"=>0, "namespace"=>0,
             "new"=>0, "or"=>0, "parent"=>0, "print"=>0, "private"=>0,
-            "protected"=>0,"public"=>0, "require"=>0, "require_once"=>0,
-            "return"=>0, "self"=>0, "static"=>0, "switch"=>0, "throw"=>0,
-            "trait"=>0, "try"=>0,"unset"=>0, "use"=>0, "var"=>0, "while"=>0,
-            "xor"=>0, "yield"=>0, "int"=>0, "float"=>0, "bool"=>0, "string"=>0,
-            "true"=>0, "false"=>0, "null"=>0, "void"=>0, "iterable"=>0
+            "protected"=>0,"public"=>0, "readonly" => 0,"require"=>0,
+            "require_once"=>0,"return"=>0, "self"=>0, "static"=>0, "switch"=>0,
+            "throw"=>0,"trait"=>0, "try"=>0,"unset"=>0, "use"=>0, "var"=>0,
+            "while"=>0,"xor"=>0, "yield"=>0, "int"=>0, "float"=>0, "bool"=>0,
+            "string"=>0,"true"=>0, "false"=>0, "null"=>0, "void"=>0,
+            "iterable"=>0
         );
 
         if (array_key_exists(strtolower($classname), $reserved_words)) {
@@ -303,6 +304,27 @@
         return "";
     }
 
+    private static function getPreviouslyUnreservedClassNamePrefix(
+        $classname,
+        $file_proto)
+    {
+        $previously_unreserved_words = array(
+            "readonly"=>0
+        );
+
+        if (array_key_exists(strtolower($classname), $previously_unreserved_words)) {
+            $option = $file_proto->getOptions();
+            $prefix = is_null($option) ? "" : $option->getPhpClassPrefix();
+            if ($prefix !== "") {
+                return $prefix;
+            }
+
+            return "";
+        }
+
+        return self::getClassNamePrefix($classname, $file_proto);
+    }
+
     public static function getLegacyClassNameWithoutPackage(
         $name,
         $file_proto)
@@ -322,6 +344,17 @@
         return implode('\\', $parts);
     }
 
+    private static function getPreviouslyUnreservedClassNameWithoutPackage(
+        $name,
+        $file_proto)
+    {
+        $parts = explode('.', $name);
+        foreach ($parts as $i => $part) {
+            $parts[$i] = static::getPreviouslyUnreservedClassNamePrefix($parts[$i], $file_proto) . $parts[$i];
+        }
+        return implode('\\', $parts);
+    }
+
     public static function getFullClassName(
         $proto,
         $containing,
@@ -329,7 +362,8 @@
         &$message_name_without_package,
         &$classname,
         &$legacy_classname,
-        &$fullname)
+        &$fullname,
+        &$previous_classname)
     {
         // Full name needs to start with '.'.
         $message_name_without_package = $proto->getName();
@@ -350,6 +384,9 @@
         $legacy_class_name_without_package =
             static::getLegacyClassNameWithoutPackage(
                 $message_name_without_package, $file_proto);
+        $previous_class_name_without_package =
+            static::getPreviouslyUnreservedClassNameWithoutPackage(
+                $message_name_without_package, $file_proto);
 
         $option = $file_proto->getOptions();
         if (!is_null($option) && $option->hasPhpNamespace()) {
@@ -358,10 +395,13 @@
                 $classname = $namespace . "\\" . $class_name_without_package;
                 $legacy_classname =
                     $namespace . "\\" . $legacy_class_name_without_package;
+                $previous_classname =
+                    $namespace . "\\" . $previous_class_name_without_package;
                 return;
             } else {
                 $classname = $class_name_without_package;
                 $legacy_classname = $legacy_class_name_without_package;
+                $previous_classname = $previous_class_name_without_package;
                 return;
             }
         }
@@ -369,6 +409,7 @@
         if ($package === "") {
             $classname = $class_name_without_package;
             $legacy_classname = $legacy_class_name_without_package;
+            $previous_classname = $previous_class_name_without_package;
         } else {
             $parts = array_map('ucwords', explode('.', $package));
             foreach ($parts as $i => $part) {
@@ -381,6 +422,11 @@
             $legacy_classname =
                 implode('\\', array_map('ucwords', explode('.', $package))).
                 "\\".$legacy_class_name_without_package;
+            $previous_classname =
+                implode('\\', array_map('ucwords', explode('.', $package))).
+                "\\".self::getPreviouslyUnreservedClassNamePrefix(
+                    $previous_class_name_without_package, $file_proto).
+                    $previous_class_name_without_package;
         }
     }
 
diff --git a/php/tests/GeneratedClassTest.php b/php/tests/GeneratedClassTest.php
index 8a89973..37c33df 100644
--- a/php/tests/GeneratedClassTest.php
+++ b/php/tests/GeneratedClassTest.php
@@ -334,6 +334,18 @@
         $this->legacyEnum(new TestLegacyMessage\NestedEnum);
     }
 
+    public function testLegacyReadOnlyMessage()
+    {
+        $this->assertTrue(class_exists('\Upper\READONLY'));
+        $this->assertTrue(class_exists('\Lower\readonly'));
+    }
+
+    public function testLegacyReadOnlyEnum()
+    {
+        $this->assertTrue(class_exists('\Upper_enum\READONLY'));
+        $this->assertTrue(class_exists('\Lower_enum\readonly'));
+    }
+
     private function legacyEnum(TestLegacyMessage_NestedEnum $enum)
     {
         // If we made it here without a PHP Fatal error, the typehint worked
@@ -943,6 +955,7 @@
         $m = new \Lower\PBprivate();
         $m = new \Lower\PBprotected();
         $m = new \Lower\PBpublic();
+        $m = new \Lower\PBreadonly();
         $m = new \Lower\PBrequire();
         $m = new \Lower\PBrequire_once();
         $m = new \Lower\PBreturn();
@@ -1023,6 +1036,7 @@
         $m = new \Upper\PBPRIVATE();
         $m = new \Upper\PBPROTECTED();
         $m = new \Upper\PBPUBLIC();
+        $m = new \Upper\PBREADONLY();
         $m = new \Upper\PBREQUIRE();
         $m = new \Upper\PBREQUIRE_ONCE();
         $m = new \Upper\PBRETURN();
@@ -1104,6 +1118,7 @@
         $m = new \Lower_enum\PBprotected();
         $m = new \Lower_enum\PBpublic();
         $m = new \Lower_enum\PBrequire();
+        $m = new \Lower_enum\PBreadonly();
         $m = new \Lower_enum\PBrequire_once();
         $m = new \Lower_enum\PBreturn();
         $m = new \Lower_enum\PBself();
@@ -1183,6 +1198,7 @@
         $m = new \Upper_enum\PBPRIVATE();
         $m = new \Upper_enum\PBPROTECTED();
         $m = new \Upper_enum\PBPUBLIC();
+        $m = new \Upper_enum\PBREADONLY();
         $m = new \Upper_enum\PBREQUIRE();
         $m = new \Upper_enum\PBREQUIRE_ONCE();
         $m = new \Upper_enum\PBRETURN();
@@ -1287,6 +1303,7 @@
         $m = \Lower_enum_value\NotAllowed::iterable;
         $m = \Lower_enum_value\NotAllowed::parent;
         $m = \Lower_enum_value\NotAllowed::self;
+        $m = \Lower_enum_value\NotAllowed::readonly;
 
         $m = \Upper_enum_value\NotAllowed::PBABSTRACT;
         $m = \Upper_enum_value\NotAllowed::PBAND;
@@ -1367,6 +1384,7 @@
         $m = \Upper_enum_value\NotAllowed::ITERABLE;
         $m = \Upper_enum_value\NotAllowed::PARENT;
         $m = \Upper_enum_value\NotAllowed::SELF;
+        $m = \Upper_enum_value\NotAllowed::READONLY;
 
         $this->assertTrue(true);
     }
diff --git a/php/tests/PreviouslyGeneratedClassTest.php b/php/tests/PreviouslyGeneratedClassTest.php
new file mode 100644
index 0000000..077c84f
--- /dev/null
+++ b/php/tests/PreviouslyGeneratedClassTest.php
@@ -0,0 +1,18 @@
+<?php
+
+require_once('test_base.php');
+require_once('test_util.php');
+
+class PreviouslyGeneratedClassTest extends TestBase
+{
+    #########################################################
+    # Test compatibility for previously unreserved words.
+    #########################################################
+
+    public function testPrefixForReservedWords()
+    {
+        $m = new \Previous\readonly();
+
+        $this->assertTrue(true);
+    }
+}
diff --git a/php/tests/generated_previous/GPBMetadata/ProtoPrevious/TestPreviouslyUnreservedMessage.php b/php/tests/generated_previous/GPBMetadata/ProtoPrevious/TestPreviouslyUnreservedMessage.php
new file mode 100644
index 0000000..d2120f9
--- /dev/null
+++ b/php/tests/generated_previous/GPBMetadata/ProtoPrevious/TestPreviouslyUnreservedMessage.php
@@ -0,0 +1,28 @@
+<?php
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: proto_previous/test_previously_unreserved_message.proto
+
+namespace GPBMetadata\ProtoPrevious;
+
+class TestPreviouslyUnreservedMessage
+{
+    public static $is_initialized = false;
+
+    public static function initOnce() {
+        $pool = \Google\Protobuf\Internal\DescriptorPool::getGeneratedPool();
+
+        if (static::$is_initialized == true) {
+          return;
+        }
+        $pool->internalAddGeneratedFile(
+            '
+W
+7proto_previous/test_previously_unreserved_message.protoprevious"
+
+readonlybproto3'
+        , true);
+
+        static::$is_initialized = true;
+    }
+}
+
diff --git a/php/tests/generated_previous/Previous/readonly.php b/php/tests/generated_previous/Previous/readonly.php
new file mode 100644
index 0000000..013f293
--- /dev/null
+++ b/php/tests/generated_previous/Previous/readonly.php
@@ -0,0 +1,31 @@
+<?php
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: proto_previous/test_previously_unreserved_message.proto
+
+namespace Previous;
+
+use Google\Protobuf\Internal\GPBType;
+use Google\Protobuf\Internal\RepeatedField;
+use Google\Protobuf\Internal\GPBUtil;
+
+/**
+ * Generated from protobuf message <code>previous.readonly</code>
+ */
+class readonly extends \Google\Protobuf\Internal\Message
+{
+
+    /**
+     * Constructor.
+     *
+     * @param array $data {
+     *     Optional. Data for populating the Message object.
+     *
+     * }
+     */
+    public function __construct($data = NULL) {
+        \GPBMetadata\ProtoPrevious\TestPreviouslyUnreservedMessage::initOnce();
+        parent::__construct($data);
+    }
+
+}
+
diff --git a/php/tests/proto/test_reserved_enum_lower.proto b/php/tests/proto/test_reserved_enum_lower.proto
index f8557d2..1f96ac6 100644
--- a/php/tests/proto/test_reserved_enum_lower.proto
+++ b/php/tests/proto/test_reserved_enum_lower.proto
@@ -57,6 +57,7 @@
 enum private { ZERO52 = 0; }
 enum protected { ZERO53 = 0; }
 enum public { ZERO54 = 0; }
+enum readonly { ZERO80 = 0; }
 enum require { ZERO55 = 0; }
 enum require_once { ZERO56 = 0; }
 enum return { ZERO57 = 0; }
diff --git a/php/tests/proto/test_reserved_enum_upper.proto b/php/tests/proto/test_reserved_enum_upper.proto
index 8d382ab..c5e7e99 100644
--- a/php/tests/proto/test_reserved_enum_upper.proto
+++ b/php/tests/proto/test_reserved_enum_upper.proto
@@ -57,6 +57,7 @@
 enum PRIVATE { ZERO52 = 0; }
 enum PROTECTED { ZERO53 = 0; }
 enum PUBLIC { ZERO54 = 0; }
+enum READONLY { ZERO80 = 0; }
 enum REQUIRE { ZERO55 = 0; }
 enum REQUIRE_ONCE { ZERO56 = 0; }
 enum RETURN { ZERO57 = 0; }
diff --git a/php/tests/proto/test_reserved_enum_value_lower.proto b/php/tests/proto/test_reserved_enum_value_lower.proto
index ca5a7c7..86c6877 100644
--- a/php/tests/proto/test_reserved_enum_value_lower.proto
+++ b/php/tests/proto/test_reserved_enum_value_lower.proto
@@ -58,6 +58,7 @@
   private = 51;
   protected = 52;
   public = 53;
+  readonly = 79;
   require = 54;
   require_once = 55;
   return = 56;
diff --git a/php/tests/proto/test_reserved_enum_value_upper.proto b/php/tests/proto/test_reserved_enum_value_upper.proto
index 6b4040d..ac0beda 100644
--- a/php/tests/proto/test_reserved_enum_value_upper.proto
+++ b/php/tests/proto/test_reserved_enum_value_upper.proto
@@ -58,6 +58,7 @@
   PRIVATE = 51;
   PROTECTED = 52;
   PUBLIC = 53;
+  READONLY = 79;
   REQUIRE = 54;
   REQUIRE_ONCE = 55;
   RETURN = 56;
diff --git a/php/tests/proto/test_reserved_message_lower.proto b/php/tests/proto/test_reserved_message_lower.proto
index 2390a87..551ed7a 100644
--- a/php/tests/proto/test_reserved_message_lower.proto
+++ b/php/tests/proto/test_reserved_message_lower.proto
@@ -57,6 +57,7 @@
 message private {}
 message protected {}
 message public {}
+message readonly {}
 message require {}
 message require_once {}
 message return {}
diff --git a/php/tests/proto/test_reserved_message_upper.proto b/php/tests/proto/test_reserved_message_upper.proto
index 9f55330..96995c9 100644
--- a/php/tests/proto/test_reserved_message_upper.proto
+++ b/php/tests/proto/test_reserved_message_upper.proto
@@ -57,6 +57,7 @@
 message PRIVATE {}
 message PROTECTED {}
 message PUBLIC {}
+message READONLY {}
 message REQUIRE {}
 message REQUIRE_ONCE {}
 message RETURN {}
diff --git a/php/tests/proto_previous/test_previously_unreserved_message.proto b/php/tests/proto_previous/test_previously_unreserved_message.proto
new file mode 100644
index 0000000..1b4b0b7
--- /dev/null
+++ b/php/tests/proto_previous/test_previously_unreserved_message.proto
@@ -0,0 +1,5 @@
+syntax = "proto3";
+
+package previous;
+
+message readonly {}
\ No newline at end of file
diff --git a/protobuf_deps.bzl b/protobuf_deps.bzl
index e952643..0d2611f 100644
--- a/protobuf_deps.bzl
+++ b/protobuf_deps.bzl
@@ -114,6 +114,6 @@
         _github_archive(
             name = "upb",
             repo = "https://github.com/protocolbuffers/upb",
-            commit = "04cb5af6b67c80db61f0aee76dcb6d233e51795c",
-            sha256 = "62d3519a7b65d6695e011f2733bfc5d7c6ab77f2bd83cdd2dca449da2e739c7f",
+            commit = "17b6451684ffcf6e77d10a5def9bf19af57eccd3",
+            sha256 = "655c30a01c8ab56680c154baded548c5df8f726305d3338d0885cbb1f700ec10",
         )
diff --git a/protobuf_version.bzl b/protobuf_version.bzl
index b6c35f3..edac503 100644
--- a/protobuf_version.bzl
+++ b/protobuf_version.bzl
@@ -1,3 +1,3 @@
-PROTOC_VERSION = '21.2'
-PROTOBUF_JAVA_VERSION = '3.21.2'
-PROTOBUF_PYTHON_VERSION = '4.21.2'
+PROTOC_VERSION = '21.4'
+PROTOBUF_JAVA_VERSION = '3.21.4'
+PROTOBUF_PYTHON_VERSION = '4.21.4'
diff --git a/protoc-artifacts/pom.xml b/protoc-artifacts/pom.xml
index bce8121..d816174 100644
--- a/protoc-artifacts/pom.xml
+++ b/protoc-artifacts/pom.xml
@@ -4,11 +4,11 @@
   <parent>
     <groupId>com.google</groupId>
     <artifactId>google</artifactId>
-    <version>1</version>
+    <version>5</version>
   </parent>
   <groupId>com.google.protobuf</groupId>
   <artifactId>protoc</artifactId>
-  <version>3.21.2</version>
+  <version>3.21.4</version>
   <packaging>pom</packaging>
   <name>Protobuf Compiler</name>
   <description>
diff --git a/python/google/protobuf/__init__.py b/python/google/protobuf/__init__.py
index 2c3e06c..b01d870 100644
--- a/python/google/protobuf/__init__.py
+++ b/python/google/protobuf/__init__.py
@@ -30,4 +30,4 @@
 
 # Copyright 2007 Google Inc. All Rights Reserved.
 
-__version__ = '4.21.2'
+__version__ = '4.21.4'
diff --git a/python/release.sh b/python/release.sh
index 15a70db..87fcf8c 100755
--- a/python/release.sh
+++ b/python/release.sh
@@ -19,11 +19,24 @@
   chmod +x test-venv/bin/protoc
 
   source test-venv/bin/activate
-  pip install -i ${PYPI} protobuf==${VERSION} --no-cache-dir
+  (pip install -i ${PYPI} protobuf==${VERSION} --no-cache-dir) || (retry_pip_install ${PYPI} ${VERSION})
   deactivate
   rm -fr test-venv
 }
 
+function retry_pip_install() {
+  local PYPI=$1
+  local VERSION=$2
+  
+  read -p "pip install failed, possibly due to delay between upload and availability on pip. Retry? [y/n]" -r
+  echo
+  if [[ ! $REPLY =~ ^[Yy]$ ]]; then
+    exit 1
+  fi
+
+  (pip install -i ${PYPI} protobuf==${VERSION} --no-cache-dir) || (retry_pip_install ${PYPI} ${VERSION})
+}
+
 
 [ $# -lt 1 ] && {
   echo "Usage: $0 VERSION ["
@@ -86,13 +99,16 @@
 python3 setup.py sdist
 twine upload --skip-existing -r testpypi -u protobuf-wheel-test dist/*
 
-# Test locally with different python versions.
+# Sleep to allow time for distribution to be available on pip.
+sleep 5m
+
+# Test locally.
 run_install_test ${TESTING_VERSION} python3 https://test.pypi.org/simple
 
 # Deploy egg/wheel packages to testing PyPI and test again.
 python3 setup.py clean build bdist_wheel
 twine upload --skip-existing -r testpypi -u protobuf-wheel-test dist/*
-
+sleep 5m
 run_install_test ${TESTING_VERSION} python3 https://test.pypi.org/simple
 
 echo "All install tests have passed using testing PyPI."
diff --git a/ruby/google-protobuf.gemspec b/ruby/google-protobuf.gemspec
index e7aa377..e43665d 100644
--- a/ruby/google-protobuf.gemspec
+++ b/ruby/google-protobuf.gemspec
@@ -1,6 +1,6 @@
 Gem::Specification.new do |s|
   s.name        = "google-protobuf"
-  s.version     = "3.21.2"
+  s.version     = "3.21.4"
   git_tag       = "v#{s.version.to_s.sub('.rc.', '-rc')}" # Converts X.Y.Z.rc.N to vX.Y.Z-rcN, used for the git tag
   s.licenses    = ["BSD-3-Clause"]
   s.summary     = "Protocol Buffers"
diff --git a/ruby/pom.xml b/ruby/pom.xml
index f1bdbf1..4264172 100644
--- a/ruby/pom.xml
+++ b/ruby/pom.xml
@@ -9,7 +9,7 @@
 
     <groupId>com.google.protobuf.jruby</groupId>
     <artifactId>protobuf-jruby</artifactId>
-    <version>3.21.2</version>
+    <version>3.21.4</version>
     <name>Protocol Buffer JRuby native extension</name>
     <description>
       Protocol Buffers are a way of encoding structured data in an efficient yet
@@ -76,7 +76,7 @@
         <dependency>
           <groupId>com.google.protobuf</groupId>
           <artifactId>protobuf-java-util</artifactId>
-          <version>3.21.2</version>
+          <version>3.21.4</version>
         </dependency>
         <dependency>
             <groupId>org.jruby</groupId>
diff --git a/src/Makefile.am b/src/Makefile.am
index 9875b1c..15f7aa4 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -18,7 +18,7 @@
 PTHREAD_DEF =
 endif
 
-PROTOBUF_VERSION = 32:2:0
+PROTOBUF_VERSION = 32:4:0
 
 if GCC
 # Turn on all warnings except for sign comparison (we ignore sign comparison
@@ -774,6 +774,7 @@
   google/protobuf/compiler/csharp/csharp_generator_unittest.cc \
   google/protobuf/compiler/importer_unittest.cc                \
   google/protobuf/compiler/java/doc_comment_unittest.cc        \
+  google/protobuf/compiler/java/message_serialization_unittest.cc \
   google/protobuf/compiler/java/plugin_unittest.cc             \
   google/protobuf/compiler/mock_code_generator.cc              \
   google/protobuf/compiler/mock_code_generator.h               \
diff --git a/src/file_lists.cmake b/src/file_lists.cmake
index 32f10b2..67092cc 100644
--- a/src/file_lists.cmake
+++ b/src/file_lists.cmake
@@ -771,6 +771,7 @@
   ${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/csharp/csharp_generator_unittest.cc
   ${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/importer_unittest.cc
   ${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/java/doc_comment_unittest.cc
+  ${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/java/message_serialization_unittest.cc
   ${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/java/plugin_unittest.cc
   ${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/objectivec/objectivec_helpers_unittest.cc
   ${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/parser_unittest.cc
diff --git a/src/google/protobuf/any.pb.h b/src/google/protobuf/any.pb.h
index 2368446..0a9ee26 100644
--- a/src/google/protobuf/any.pb.h
+++ b/src/google/protobuf/any.pb.h
@@ -13,7 +13,7 @@
 #error incompatible with your Protocol Buffer headers. Please update
 #error your headers.
 #endif
-#if 3021002 < PROTOBUF_MIN_PROTOC_VERSION
+#if 3021004 < PROTOBUF_MIN_PROTOC_VERSION
 #error This file was generated by an older version of protoc which is
 #error incompatible with your Protocol Buffer headers. Please
 #error regenerate this file with a newer version of protoc.
diff --git a/src/google/protobuf/api.pb.h b/src/google/protobuf/api.pb.h
index ac80560..4903294 100644
--- a/src/google/protobuf/api.pb.h
+++ b/src/google/protobuf/api.pb.h
@@ -13,7 +13,7 @@
 #error incompatible with your Protocol Buffer headers. Please update
 #error your headers.
 #endif
-#if 3021002 < PROTOBUF_MIN_PROTOC_VERSION
+#if 3021004 < PROTOBUF_MIN_PROTOC_VERSION
 #error This file was generated by an older version of protoc which is
 #error incompatible with your Protocol Buffer headers. Please
 #error regenerate this file with a newer version of protoc.
diff --git a/src/google/protobuf/arena_unittest.cc b/src/google/protobuf/arena_unittest.cc
index 0987c99..1466376 100644
--- a/src/google/protobuf/arena_unittest.cc
+++ b/src/google/protobuf/arena_unittest.cc
@@ -1419,7 +1419,7 @@
   ASSERT_GT(arena.SpaceAllocated(), first_block_size);
   auto second_block_size = (arena.SpaceAllocated() - first_block_size);
 
-  EXPECT_EQ(second_block_size, 2*first_block_size);
+  EXPECT_GE(second_block_size, 2*first_block_size);
 }
 
 TEST(ArenaTest, Alignment) {
diff --git a/src/google/protobuf/arenastring.cc b/src/google/protobuf/arenastring.cc
index 03b1438..8d7e6ff 100644
--- a/src/google/protobuf/arenastring.cc
+++ b/src/google/protobuf/arenastring.cc
@@ -50,7 +50,8 @@
 
 namespace  {
 
-// Enforce that allocated data aligns to at least 8 bytes, and that
+// TaggedStringPtr::Flags uses the lower 2 bits as tags.
+// Enforce that allocated data aligns to at least 4 bytes, and that
 // the alignment of the global const string value does as well.
 // The alignment guaranteed by `new std::string` depends on both:
 // - new align = __STDCPP_DEFAULT_NEW_ALIGNMENT__ / max_align_t
@@ -64,8 +65,8 @@
 #endif
 constexpr size_t kStringAlign = alignof(std::string);
 
-static_assert((kStringAlign > kNewAlign ? kStringAlign : kNewAlign) >= 8, "");
-static_assert(alignof(ExplicitlyConstructedArenaString) >= 8, "");
+static_assert((kStringAlign > kNewAlign ? kStringAlign : kNewAlign) >= 4, "");
+static_assert(alignof(ExplicitlyConstructedArenaString) >= 4, "");
 
 }  // namespace
 
diff --git a/src/google/protobuf/arenaz_sampler.cc b/src/google/protobuf/arenaz_sampler.cc
index 6ad3a36..ab524fb 100644
--- a/src/google/protobuf/arenaz_sampler.cc
+++ b/src/google/protobuf/arenaz_sampler.cc
@@ -72,7 +72,7 @@
   bytes_used.store(0, std::memory_order_relaxed);
   bytes_allocated.store(0, std::memory_order_relaxed);
   bytes_wasted.store(0, std::memory_order_relaxed);
-  max_bytes_allocated.store(0, std::memory_order_relaxed);
+  max_block_size.store(0, std::memory_order_relaxed);
   thread_ids.store(0, std::memory_order_relaxed);
   weight = stride;
   // The inliner makes hardcoded skip_count difficult (especially when combined
@@ -87,6 +87,9 @@
   info->bytes_used.fetch_add(used, std::memory_order_relaxed);
   info->bytes_allocated.fetch_add(allocated, std::memory_order_relaxed);
   info->bytes_wasted.fetch_add(wasted, std::memory_order_relaxed);
+  if (info->max_block_size.load(std::memory_order_relaxed) < allocated) {
+    info->max_block_size.store(allocated, std::memory_order_relaxed);
+  }
   const uint64_t tid = 1ULL << (GetCachedTID() % 63);
   info->thread_ids.fetch_or(tid, std::memory_order_relaxed);
 }
diff --git a/src/google/protobuf/arenaz_sampler.h b/src/google/protobuf/arenaz_sampler.h
index 4a04c6a..e9a4dec 100644
--- a/src/google/protobuf/arenaz_sampler.h
+++ b/src/google/protobuf/arenaz_sampler.h
@@ -69,8 +69,8 @@
   std::atomic<size_t> bytes_used;
   std::atomic<size_t> bytes_allocated;
   std::atomic<size_t> bytes_wasted;
-  // Records the largest size an arena ever had.
-  std::atomic<size_t> max_bytes_allocated;
+  // Records the largest block allocated for the arena.
+  std::atomic<size_t> max_block_size;
   // Bit `i` is set to 1 indicates that a thread with `tid % 63 = i` accessed
   // the underlying arena.  We use `% 63` as a rudimentary hash to ensure some
   // bit mixing for thread-ids; `% 64` would only grab the low bits and might
diff --git a/src/google/protobuf/arenaz_sampler_test.cc b/src/google/protobuf/arenaz_sampler_test.cc
index 7bac100..1b73938 100644
--- a/src/google/protobuf/arenaz_sampler_test.cc
+++ b/src/google/protobuf/arenaz_sampler_test.cc
@@ -89,21 +89,21 @@
   EXPECT_EQ(info.bytes_used.load(), 0);
   EXPECT_EQ(info.bytes_allocated.load(), 0);
   EXPECT_EQ(info.bytes_wasted.load(), 0);
-  EXPECT_EQ(info.max_bytes_allocated.load(), 0);
+  EXPECT_EQ(info.max_block_size.load(), 0);
   EXPECT_EQ(info.weight, kTestStride);
 
   info.num_allocations.store(1, std::memory_order_relaxed);
   info.bytes_used.store(1, std::memory_order_relaxed);
   info.bytes_allocated.store(1, std::memory_order_relaxed);
   info.bytes_wasted.store(1, std::memory_order_relaxed);
-  info.max_bytes_allocated.store(1, std::memory_order_relaxed);
+  info.max_block_size.store(1, std::memory_order_relaxed);
 
   info.PrepareForSampling(2 * kTestStride);
   EXPECT_EQ(info.num_allocations.load(), 0);
   EXPECT_EQ(info.bytes_used.load(), 0);
   EXPECT_EQ(info.bytes_allocated.load(), 0);
   EXPECT_EQ(info.bytes_wasted.load(), 0);
-  EXPECT_EQ(info.max_bytes_allocated.load(), 0);
+  EXPECT_EQ(info.max_block_size.load(), 0);
   EXPECT_EQ(info.weight, 2 * kTestStride);
 }
 
@@ -117,14 +117,29 @@
   EXPECT_EQ(info.bytes_used.load(), 100);
   EXPECT_EQ(info.bytes_allocated.load(), 128);
   EXPECT_EQ(info.bytes_wasted.load(), 0);
-  EXPECT_EQ(info.max_bytes_allocated.load(), 0);
+  EXPECT_EQ(info.max_block_size.load(), 128);
   RecordAllocateSlow(&info, /*requested=*/100, /*allocated=*/256,
                      /*wasted=*/28);
   EXPECT_EQ(info.num_allocations.load(), 2);
   EXPECT_EQ(info.bytes_used.load(), 200);
   EXPECT_EQ(info.bytes_allocated.load(), 384);
   EXPECT_EQ(info.bytes_wasted.load(), 28);
-  EXPECT_EQ(info.max_bytes_allocated.load(), 0);
+  EXPECT_EQ(info.max_block_size.load(), 256);
+}
+
+TEST(ThreadSafeArenaStatsTest, RecordAllocateSlowMaxBlockSizeTest) {
+  ThreadSafeArenaStats info;
+  constexpr int64_t kTestStride = 458;
+  MutexLock l(&info.init_mu);
+  info.PrepareForSampling(kTestStride);
+  RecordAllocateSlow(&info, /*requested=*/100, /*allocated=*/128, /*wasted=*/0);
+  EXPECT_EQ(info.max_block_size.load(), 128);
+  RecordAllocateSlow(&info, /*requested=*/100, /*allocated=*/256,
+                     /*wasted=*/28);
+  EXPECT_EQ(info.max_block_size.load(), 256);
+  RecordAllocateSlow(&info, /*requested=*/100, /*allocated=*/128,
+                     /*wasted=*/28);
+  EXPECT_EQ(info.max_block_size.load(), 256);
 }
 
 TEST(ThreadSafeArenazSamplerTest, SamplingCorrectness) {
diff --git a/src/google/protobuf/compiler/command_line_interface.cc b/src/google/protobuf/compiler/command_line_interface.cc
index 0a0c7e3..7491046 100644
--- a/src/google/protobuf/compiler/command_line_interface.cc
+++ b/src/google/protobuf/compiler/command_line_interface.cc
@@ -339,9 +339,12 @@
   void AddErrorOrWarning(const std::string& filename, int line, int column,
                          const std::string& message, const std::string& type,
                          std::ostream& out) {
-    // Print full path when running under MSVS
     std::string dfile;
-    if (format_ == CommandLineInterface::ERROR_FORMAT_MSVS &&
+    if (
+#ifndef PROTOBUF_OPENSOURCE
+        // Print full path when running under MSVS
+        format_ == CommandLineInterface::ERROR_FORMAT_MSVS &&
+#endif  // !PROTOBUF_OPENSOURCE
         tree_ != nullptr && tree_->VirtualFileToDiskFile(filename, &dfile)) {
       out << dfile;
     } else {
@@ -398,7 +401,6 @@
 
   // Get name of all output files.
   void GetOutputFilenames(std::vector<std::string>* output_filenames);
-
   // implements GeneratorContext --------------------------------------
   io::ZeroCopyOutputStream* Open(const std::string& filename) override;
   io::ZeroCopyOutputStream* OpenForAppend(const std::string& filename) override;
@@ -963,6 +965,7 @@
 
 int CommandLineInterface::Run(int argc, const char* const argv[]) {
   Clear();
+
   switch (ParseArguments(argc, argv)) {
     case PARSE_ARGUMENT_DONE_AND_EXIT:
       return 0;
@@ -1076,7 +1079,6 @@
     }
   }
 
-  // Write all output to disk.
   for (const auto& pair : output_directories) {
     const std::string& location = pair.first;
     GeneratorContextImpl* directory = pair.second.get();
@@ -1151,7 +1153,6 @@
         // Do not add a default case.
     }
   }
-
   return 0;
 }
 
diff --git a/src/google/protobuf/compiler/cpp/field.cc b/src/google/protobuf/compiler/cpp/field.cc
index cf4f14e..90d2084 100644
--- a/src/google/protobuf/compiler/cpp/field.cc
+++ b/src/google/protobuf/compiler/cpp/field.cc
@@ -330,7 +330,6 @@
   }
 }
 
-
 void SetCommonOneofFieldVariables(
     const FieldDescriptor* descriptor,
     std::map<std::string, std::string>* variables) {
diff --git a/src/google/protobuf/compiler/cpp/field.h b/src/google/protobuf/compiler/cpp/field.h
index 3903e79..3fcbda3 100644
--- a/src/google/protobuf/compiler/cpp/field.h
+++ b/src/google/protobuf/compiler/cpp/field.h
@@ -208,7 +208,6 @@
 
   virtual bool IsInlined() const { return false; }
 
-
   virtual ArenaDtorNeeds NeedsArenaDestructor() const {
     return ArenaDtorNeeds::kNone;
   }
diff --git a/src/google/protobuf/compiler/cpp/file.cc b/src/google/protobuf/compiler/cpp/file.cc
index 838e0ab..502d8c0 100644
--- a/src/google/protobuf/compiler/cpp/file.cc
+++ b/src/google/protobuf/compiler/cpp/file.cc
@@ -495,12 +495,10 @@
     generator->GenerateInitDefaultSplitInstance(printer);
     format(
         "} {}\n"
-        "  ~$1$() {}\n"
         "  union {\n"
-        "    $2$ _instance;\n"
+        "    $1$ _instance;\n"
         "  };\n"
         "};\n",
-        DefaultInstanceType(generator->descriptor_, options_, /*split=*/true),
         StrCat(generator->classname_, "::Impl_::Split"));
     // NO_DESTROY is not necessary for correctness. The empty destructor is
     // enough. However, the empty destructor fails to be elided in some
@@ -508,7 +506,7 @@
     // there just to improve performance and binary size in these builds.
     format(
         "PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT "
-        "PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 $1$ $2$;\n",
+        "PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const $1$ $2$;\n",
         DefaultInstanceType(generator->descriptor_, options_, /*split=*/true),
         DefaultInstanceName(generator->descriptor_, options_, /*split=*/true));
   }
@@ -999,7 +997,7 @@
       const Descriptor* class_desc = p.second;
       format(
           "struct $1$;\n"
-          "$dllexport_decl $extern $1$ $2$;\n",
+          "$dllexport_decl $extern const $1$ $2$;\n",
           DefaultInstanceType(class_desc, options, /*split=*/true),
           DefaultInstanceName(class_desc, options, /*split=*/true));
     }
diff --git a/src/google/protobuf/compiler/cpp/helpers.cc b/src/google/protobuf/compiler/cpp/helpers.cc
index 4939aa5..d182c86 100644
--- a/src/google/protobuf/compiler/cpp/helpers.cc
+++ b/src/google/protobuf/compiler/cpp/helpers.cc
@@ -176,7 +176,6 @@
 #endif  // !PROTOBUF_FUTURE_BREAKING_CHANGES
 };
 
-
 static std::unordered_set<std::string>* MakeKeywordsMap() {
   auto* result = new std::unordered_set<std::string>();
   for (const auto keyword : kKeywordList) {
@@ -525,7 +524,6 @@
   return result;
 }
 
-
 std::string FieldMemberName(const FieldDescriptor* field, bool split) {
   StringPiece prefix =
       IsMapEntryMessage(field->containing_type()) ? "" : "_impl_.";
@@ -876,8 +874,6 @@
 bool IsProfileDriven(const Options& options) {
   return options.access_info_map != nullptr;
 }
-
-
 bool IsStringInlined(const FieldDescriptor* descriptor,
                      const Options& options) {
   (void)descriptor;
diff --git a/src/google/protobuf/compiler/cpp/message.cc b/src/google/protobuf/compiler/cpp/message.cc
index 3ca5315..51a3e65 100644
--- a/src/google/protobuf/compiler/cpp/message.cc
+++ b/src/google/protobuf/compiler/cpp/message.cc
@@ -828,7 +828,6 @@
 
     // Generate type-specific accessor declarations.
     field_generators_.get(field).GenerateAccessorDeclarations(printer);
-
     format("\n");
   }
 
@@ -1238,41 +1237,41 @@
 
     Formatter::SaveState saver(&format);
     format.AddMap(vars);
-      // Generate has_$name$() or $name$_size().
-      if (field->is_repeated()) {
-        if (IsFieldStripped(field, options_)) {
-          format(
-              "inline int $classname$::$name$_size() const { "
-              "__builtin_trap(); }\n");
-        } else {
-          format(
-              "inline int $classname$::_internal_$name$_size() const {\n"
-              "  return $field$$1$.size();\n"
-              "}\n"
-              "inline int $classname$::$name$_size() const {\n"
-              "$annotate_size$"
-              "  return _internal_$name$_size();\n"
-              "}\n",
-              IsImplicitWeakField(field, options_, scc_analyzer_) &&
-                      field->message_type()
-                  ? ".weak"
-                  : "");
-        }
-      } else if (field->real_containing_oneof()) {
-        format.Set("field_name", UnderscoresToCamelCase(field->name(), true));
-        format.Set("oneof_name", field->containing_oneof()->name());
-        format.Set("oneof_index",
-                   StrCat(field->containing_oneof()->index()));
-        GenerateOneofMemberHasBits(field, format);
+
+    // Generate has_$name$() or $name$_size().
+    if (field->is_repeated()) {
+      if (IsFieldStripped(field, options_)) {
+        format(
+            "inline int $classname$::$name$_size() const { "
+            "__builtin_trap(); }\n");
       } else {
-        // Singular field.
-        GenerateSingularFieldHasBits(field, format);
+        format(
+            "inline int $classname$::_internal_$name$_size() const {\n"
+            "  return $field$$1$.size();\n"
+            "}\n"
+            "inline int $classname$::$name$_size() const {\n"
+            "$annotate_size$"
+            "  return _internal_$name$_size();\n"
+            "}\n",
+            IsImplicitWeakField(field, options_, scc_analyzer_) &&
+                    field->message_type()
+                ? ".weak"
+                : "");
       }
+    } else if (field->real_containing_oneof()) {
+      format.Set("field_name", UnderscoresToCamelCase(field->name(), true));
+      format.Set("oneof_name", field->containing_oneof()->name());
+      format.Set("oneof_index",
+                 StrCat(field->containing_oneof()->index()));
+      GenerateOneofMemberHasBits(field, format);
+    } else {
+      // Singular field.
+      GenerateSingularFieldHasBits(field, format);
+    }
 
       if (!IsCrossFileMaybeMap(field)) {
         GenerateFieldClear(field, true, format);
       }
-
     // Generate type-specific accessors.
     if (!IsFieldStripped(field, options_)) {
       field_generators_.get(field).GenerateInlineAccessorDefinitions(printer);
@@ -1760,7 +1759,7 @@
     format(
         "private:\n"
         "inline bool IsSplitMessageDefault() const {\n"
-        "  return $split$ == reinterpret_cast<Impl_::Split*>(&$1$);\n"
+        "  return $split$ == reinterpret_cast<const Impl_::Split*>(&$1$);\n"
         "}\n"
         "PROTOBUF_NOINLINE void PrepareSplitMessageForWrite();\n"
         "public:\n",
@@ -1928,6 +1927,8 @@
         "  typedef void InternalArenaConstructable_;\n"
         "  typedef void DestructorSkippable_;\n"
         "};\n"
+        "static_assert(std::is_trivially_copy_constructible<Split>::value);\n"
+        "static_assert(std::is_trivially_destructible<Split>::value);\n"
         "Split* _split_;\n");
   }
 
@@ -2421,8 +2422,15 @@
   }
   if (ShouldSplit(descriptor_, options_)) {
     put_sep();
-    format("decltype($split$){reinterpret_cast<Impl_::Split*>(&$1$)}",
-           DefaultInstanceName(descriptor_, options_, /*split=*/true));
+    // We can't assign the default split to this->split without the const_cast
+    // because the former is a const. The const_cast is safe because we don't
+    // intend to modify the default split through this pointer, and we also
+    // expect the default split to be in the rodata section which is protected
+    // from mutation.
+    format(
+        "decltype($split$){const_cast<Impl_::Split*>"
+        "(reinterpret_cast<const Impl_::Split*>(&$1$))}",
+        DefaultInstanceName(descriptor_, options_, /*split=*/true));
   }
   for (auto oneof : OneOfRange(descriptor_)) {
     put_sep();
@@ -2681,7 +2689,7 @@
   }
   if (ShouldSplit(descriptor_, options_)) {
     put_sep();
-    format("/*decltype($split$)*/&$1$._instance",
+    format("/*decltype($split$)*/const_cast<Impl_::Split*>(&$1$._instance)",
            DefaultInstanceName(descriptor_, options_, /*split=*/true));
   }
 
@@ -2866,8 +2874,10 @@
       }
       if (ShouldSplit(descriptor_, options_)) {
         put_sep();
-        format("decltype($split$){reinterpret_cast<Impl_::Split*>(&$1$)}",
-               DefaultInstanceName(descriptor_, options_, /*split=*/true));
+        format(
+            "decltype($split$){const_cast<Impl_::Split*>"
+            "(reinterpret_cast<const Impl_::Split*>(&$1$))}",
+            DefaultInstanceName(descriptor_, options_, /*split=*/true));
       }
       for (auto oneof : OneOfRange(descriptor_)) {
         put_sep();
diff --git a/src/google/protobuf/compiler/cpp/parse_function_generator.cc b/src/google/protobuf/compiler/cpp/parse_function_generator.cc
index 8045f48..30b0fd4 100644
--- a/src/google/protobuf/compiler/cpp/parse_function_generator.cc
+++ b/src/google/protobuf/compiler/cpp/parse_function_generator.cc
@@ -83,7 +83,8 @@
   return 2;
 }
 
-void PopulateFastFieldEntry(const TailCallTableInfo::FieldEntryInfo& entry,
+void PopulateFastFieldEntry(const Descriptor* descriptor,
+                            const TailCallTableInfo::FieldEntryInfo& entry,
                             const Options& options,
                             TailCallTableInfo::FastFieldInfo& info);
 
@@ -158,6 +159,7 @@
 }
 
 std::vector<TailCallTableInfo::FastFieldInfo> SplitFastFieldsForSize(
+    const Descriptor* descriptor,
     const std::vector<TailCallTableInfo::FieldEntryInfo>& field_entries,
     int table_size_log2, const Options& options,
     MessageSCCAnalyzer* scc_analyzer) {
@@ -200,7 +202,7 @@
     GOOGLE_CHECK(info.func_name.empty()) << info.func_name;
     info.field = field;
     info.coded_tag = tag;
-    PopulateFastFieldEntry(entry, options, info);
+    PopulateFastFieldEntry(descriptor, entry, options, info);
     // If this field does not have presence, then it can set an out-of-bounds
     // bit (tailcall parsing uses a uint64_t for hasbits, but only stores 32).
     info.hasbit_idx = HasHasbit(field) ? entry.hasbit_idx : 63;
@@ -412,8 +414,8 @@
   int num_fast_fields = -1;
   for (int try_size_log2 : {0, 1, 2, 3, 4, 5}) {
     size_t try_size = 1 << try_size_log2;
-    auto split_fields = SplitFastFieldsForSize(field_entries, try_size_log2,
-                                               options, scc_analyzer);
+    auto split_fields = SplitFastFieldsForSize(
+        descriptor, field_entries, try_size_log2, options, scc_analyzer);
     GOOGLE_CHECK_EQ(split_fields.size(), try_size);
     int try_num_fast_fields = 0;
     for (const auto& info : split_fields) {
@@ -1667,11 +1669,12 @@
 
 namespace {
 
-void PopulateFastFieldEntry(const TailCallTableInfo::FieldEntryInfo& entry,
+void PopulateFastFieldEntry(const Descriptor* descriptor,
+                            const TailCallTableInfo::FieldEntryInfo& entry,
                             const Options& options,
                             TailCallTableInfo::FastFieldInfo& info) {
   const FieldDescriptor* field = entry.field;
-  std::string name = "::_pbi::TcParser::Fast";
+  std::string name;
   uint8_t aux_idx = static_cast<uint8_t>(entry.aux_idx);
 
   switch (field->type()) {
@@ -1784,7 +1787,36 @@
   // Append the tag length. Fast parsing only handles 1- or 2-byte tags.
   name.append(TagSize(field->number()) == 1 ? "1" : "2");
 
-  info.func_name = std::move(name);
+  if (name == "V8S1") {
+    info.func_name = StrCat(
+        "::_pbi::TcParser::SingularVarintNoZag1<bool, offsetof(",  //
+        ClassName(descriptor),                                     //
+        ", ",                                                      //
+        FieldMemberName(field, /*split=*/false),                   //
+        "), ",                                                     //
+        HasHasbit(field) ? entry.hasbit_idx : 63,                  //
+        ">()");
+  } else if (name == "V32S1") {
+    info.func_name = StrCat(
+        "::_pbi::TcParser::SingularVarintNoZag1<uint32_t, offsetof(",  //
+        ClassName(descriptor),                                         //
+        ", ",                                                          //
+        FieldMemberName(field, /*split=*/false),                       //
+        "), ",                                                         //
+        HasHasbit(field) ? entry.hasbit_idx : 63,                      //
+        ">()");
+  } else if (name == "V64S1") {
+    info.func_name = StrCat(
+        "::_pbi::TcParser::SingularVarintNoZag1<uint64_t, offsetof(",  //
+        ClassName(descriptor),                                         //
+        ", ",                                                          //
+        FieldMemberName(field, /*split=*/false),                       //
+        "), ",                                                         //
+        HasHasbit(field) ? entry.hasbit_idx : 63,                      //
+        ">()");
+  } else {
+    info.func_name = StrCat("::_pbi::TcParser::Fast", name);
+  }
   info.aux_idx = aux_idx;
 }
 
diff --git a/src/google/protobuf/compiler/csharp/csharp_helpers.cc b/src/google/protobuf/compiler/csharp/csharp_helpers.cc
index d40c9d3..1b58b95 100644
--- a/src/google/protobuf/compiler/csharp/csharp_helpers.cc
+++ b/src/google/protobuf/compiler/csharp/csharp_helpers.cc
@@ -397,15 +397,30 @@
 }
 
 std::string GetPropertyName(const FieldDescriptor* descriptor) {
+  // Names of members declared or overridden in the message.
+  static const auto& reserved_member_names = *new std::unordered_set<std::string>({
+    "Types",
+    "Descriptor",
+    "Equals",
+    "ToString",
+    "GetHashCode",
+    "WriteTo",
+    "Clone",
+    "CalculateSize",
+    "MergeFrom",
+    "OnConstruction",
+    "Parser"
+    });
+
   // TODO(jtattermusch): consider introducing csharp_property_name field option
   std::string property_name = UnderscoresToPascalCase(GetFieldName(descriptor));
-  // Avoid either our own type name or reserved names. Note that not all names
-  // are reserved - a field called to_string, write_to etc would still cause a problem.
+  // Avoid either our own type name or reserved names.
   // There are various ways of ending up with naming collisions, but we try to avoid obvious
-  // ones.
+  // ones. In particular, we avoid the names of all the members we generate.
+  // Note that we *don't* add an underscore for MemberwiseClone or GetType. Those generate
+  // warnings, but not errors; changing the name now could be a breaking change.
   if (property_name == descriptor->containing_type()->name()
-      || property_name == "Types"
-      || property_name == "Descriptor") {
+      || reserved_member_names.find(property_name) != reserved_member_names.end()) {
     property_name += "_";
   }
   return property_name;
diff --git a/src/google/protobuf/compiler/csharp/csharp_map_field.cc b/src/google/protobuf/compiler/csharp/csharp_map_field.cc
index a13b995..9efd3d5 100644
--- a/src/google/protobuf/compiler/csharp/csharp_map_field.cc
+++ b/src/google/protobuf/compiler/csharp/csharp_map_field.cc
@@ -90,7 +90,7 @@
 void MapFieldGenerator::GenerateMergingCode(io::Printer* printer) {
   printer->Print(
       variables_,
-      "$name$_.Add(other.$name$_);\n");
+      "$name$_.MergeFrom(other.$name$_);\n");
 }
 
 void MapFieldGenerator::GenerateParsingCode(io::Printer* printer) {
diff --git a/src/google/protobuf/compiler/java/enum_field.cc b/src/google/protobuf/compiler/java/enum_field.cc
index b82d147..aa3a51f 100644
--- a/src/google/protobuf/compiler/java/enum_field.cc
+++ b/src/google/protobuf/compiler/java/enum_field.cc
@@ -281,6 +281,18 @@
                  "    $kt_dsl_builder$.${$set$capitalized_name$$}$(value)\n"
                  "  }\n");
 
+  if (SupportUnknownEnumValue(descriptor_->file())) {
+    printer->Print(
+        variables_,
+        "$kt_deprecation$ var $kt_name$Value: kotlin.Int\n"
+        "  @JvmName(\"${$get$kt_capitalized_name$Value$}$\")\n"
+        "  get() = $kt_dsl_builder$.${$get$capitalized_name$Value$}$()\n"
+        "  @JvmName(\"${$set$kt_capitalized_name$Value$}$\")\n"
+        "  set(value) {\n"
+        "    $kt_dsl_builder$.${$set$capitalized_name$Value$}$(value)\n"
+        "  }\n");
+  }
+
   WriteFieldAccessorDocComment(printer, descriptor_, CLEARER,
                                /* builder */ false, /* kdoc */ true);
   printer->Print(variables_,
diff --git a/src/google/protobuf/compiler/java/enum_field_lite.cc b/src/google/protobuf/compiler/java/enum_field_lite.cc
index caa215b..a256824 100644
--- a/src/google/protobuf/compiler/java/enum_field_lite.cc
+++ b/src/google/protobuf/compiler/java/enum_field_lite.cc
@@ -296,6 +296,18 @@
                  "    $kt_dsl_builder$.${$set$capitalized_name$$}$(value)\n"
                  "  }\n");
 
+  if (SupportUnknownEnumValue(descriptor_->file())) {
+    printer->Print(
+        variables_,
+        "$kt_deprecation$ var $kt_name$Value: kotlin.Int\n"
+        "  @JvmName(\"${$get$kt_capitalized_name$Value$}$\")\n"
+        "  get() = $kt_dsl_builder$.${$get$capitalized_name$Value$}$()\n"
+        "  @JvmName(\"${$set$kt_capitalized_name$Value$}$\")\n"
+        "  set(value) {\n"
+        "    $kt_dsl_builder$.${$set$capitalized_name$Value$}$(value)\n"
+        "  }\n");
+  }
+
   WriteFieldAccessorDocComment(printer, descriptor_, CLEARER,
                                /* builder */ false, /* kdoc */ true);
   printer->Print(variables_,
diff --git a/src/google/protobuf/compiler/java/generator.h b/src/google/protobuf/compiler/java/generator.h
index bbc7170..39d2445 100644
--- a/src/google/protobuf/compiler/java/generator.h
+++ b/src/google/protobuf/compiler/java/generator.h
@@ -63,7 +63,12 @@
 
   uint64_t GetSupportedFeatures() const override;
 
+  void set_opensource_runtime(bool opensource) {
+    opensource_runtime_ = opensource;
+  }
+
  private:
+  bool opensource_runtime_ = true;
   GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(JavaGenerator);
 };
 
diff --git a/src/google/protobuf/compiler/java/message_serialization.h b/src/google/protobuf/compiler/java/message_serialization.h
index 6145392..15dc515 100644
--- a/src/google/protobuf/compiler/java/message_serialization.h
+++ b/src/google/protobuf/compiler/java/message_serialization.h
@@ -32,6 +32,7 @@
 #define GOOGLE_PROTOBUF_COMPILER_JAVA_MESSAGE_SERIALIZATION_H__
 
 #include <algorithm>
+#include <cstddef>
 #include <vector>
 
 #include <google/protobuf/io/printer.h>
@@ -66,20 +67,31 @@
   std::sort(sorted_extensions.begin(), sorted_extensions.end(),
             ExtensionRangeOrdering());
 
+  std::size_t range_idx = 0;
+
   // Merge the fields and the extension ranges, both sorted by field number.
-  for (int i = 0, j = 0;
-       i < descriptor->field_count() || j < sorted_extensions.size();) {
-    if (i == descriptor->field_count()) {
-      GenerateSerializeExtensionRange(printer, sorted_extensions[j++]);
-    } else if (j == sorted_extensions.size()) {
-      field_generators.get(sorted_fields[i++])
-          .GenerateSerializationCode(printer);
-    } else if (sorted_fields[i]->number() < sorted_extensions[j]->start) {
-      field_generators.get(sorted_fields[i++])
-          .GenerateSerializationCode(printer);
-    } else {
-      GenerateSerializeExtensionRange(printer, sorted_extensions[j++]);
+  for (int i = 0; i < descriptor->field_count(); ++i) {
+    const FieldDescriptor* field = sorted_fields[i];
+
+    // Collapse all extension ranges up until the next field. This leads to
+    // shorter and more efficient codegen for messages containing a large
+    // number of extension ranges without fields in between them.
+    const Descriptor::ExtensionRange* range = nullptr;
+    while (range_idx < sorted_extensions.size() &&
+           sorted_extensions[range_idx]->end <= field->number()) {
+      range = sorted_extensions[range_idx++];
     }
+
+    if (range != nullptr) {
+      GenerateSerializeExtensionRange(printer, range);
+    }
+    field_generators.get(field).GenerateSerializationCode(printer);
+  }
+
+  // After serializing all fields, serialize any remaining extensions via a
+  // single writeUntil call.
+  if (range_idx < sorted_extensions.size()) {
+    GenerateSerializeExtensionRange(printer, sorted_extensions.back());
   }
 }
 
diff --git a/src/google/protobuf/compiler/java/message_serialization_unittest.cc b/src/google/protobuf/compiler/java/message_serialization_unittest.cc
new file mode 100644
index 0000000..b2e1fca
--- /dev/null
+++ b/src/google/protobuf/compiler/java/message_serialization_unittest.cc
@@ -0,0 +1,124 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <cstddef>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <google/protobuf/stubs/logging.h>
+#include <google/protobuf/stubs/common.h>
+#include <google/protobuf/testing/file.h>
+#include <google/protobuf/testing/file.h>
+#include <gmock/gmock.h>
+#include <google/protobuf/testing/googletest.h>
+#include <gtest/gtest.h>
+#include <google/protobuf/compiler/command_line_interface.h>
+#include <google/protobuf/compiler/java/generator.h>
+#include <google/protobuf/test_util2.h>
+
+namespace google {
+namespace protobuf {
+namespace compiler {
+namespace java {
+namespace {
+
+using ::testing::ElementsAre;
+
+// Generates Java code for the specified Java proto, returning the compiler's
+// exit status.
+int CompileJavaProto(std::string proto_file_name) {
+  JavaGenerator java_generator;
+
+  CommandLineInterface cli;
+  cli.RegisterGenerator("--java_out", &java_generator, /*help_text=*/"");
+
+  std::string proto_path = StrCat(
+      "--proto_path=",
+      TestUtil::GetTestDataPath("third_party/protobuf/compiler/java"));
+  std::string java_out = StrCat("--java_out=", TestTempDir());
+
+  const char* argv[] = {
+      "protoc",
+      proto_path.c_str(),
+      java_out.c_str(),
+      proto_file_name.c_str(),
+  };
+
+  // Open-source codebase does not support ABSL_ARRAYSIZE.
+  return cli.Run(sizeof(argv) / sizeof(*argv), argv);
+}
+
+TEST(MessageSerializationTest, CollapseAdjacentExtensionRanges) {
+  GOOGLE_CHECK_EQ(CompileJavaProto("message_serialization_unittest.proto"), 0);
+
+  std::string java_source;
+  GOOGLE_CHECK_OK(File::GetContents(
+      // Open-source codebase does not support file::JoinPath, so we manually
+      // concatenate instead.
+      StrCat(TestTempDir(),
+                   "/TestMessageWithManyExtensionRanges.java"),
+      &java_source, true));
+
+  // Open-source codebase does not support constexpr StringPiece.
+  static constexpr const char kWriteUntilCall[] = "extensionWriter.writeUntil(";
+
+  std::vector<std::string> range_ends;
+
+  // Open-source codebase does not have Split overload taking a single
+  // char delimiter.
+  //
+  // NOLINTNEXTLINE(abseil-faster-strsplit-delimiter)
+  for (const auto& line : Split(java_source, "\n")) {
+    // Extract end position from writeUntil call. (Open-source codebase does not
+    // support RE2.)
+    std::size_t write_until_pos = line.find(kWriteUntilCall);
+    if (write_until_pos == std::string::npos) {
+      continue;
+    }
+    write_until_pos += (sizeof(kWriteUntilCall) - 1);
+
+    std::size_t comma_pos = line.find(',', write_until_pos);
+    if (comma_pos == std::string::npos) {
+      continue;
+    }
+
+    range_ends.push_back(
+        std::string(line.substr(write_until_pos, comma_pos - write_until_pos)));
+  }
+
+  EXPECT_THAT(range_ends, ElementsAre("3", "13", "43"));
+}
+
+}  // namespace
+}  // namespace java
+}  // namespace compiler
+}  // namespace protobuf
+}  // namespace google
diff --git a/src/google/protobuf/compiler/java/message_serialization_unittest.proto b/src/google/protobuf/compiler/java/message_serialization_unittest.proto
new file mode 100644
index 0000000..9cfdf42
--- /dev/null
+++ b/src/google/protobuf/compiler/java/message_serialization_unittest.proto
@@ -0,0 +1,56 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto2";
+
+package protobuf_unittest;
+
+option java_multiple_files = true;
+option java_package = "";
+
+// Each batch of extension ranges not separated by a non-extension field should
+// be serialized using a single ExtensionWriter#writeUntil call.
+message TestMessageWithManyExtensionRanges {
+  // First extension range: ends at field number 3 (exclusive)
+  extensions 1 to 2;
+
+  optional int32 foo = 3;
+  optional int32 bar = 5;
+
+  // Second extension range: ends at field number 13 (exclusive)
+  extensions 6;
+  extensions 8;
+  extensions 10 to 12;
+
+  optional int32 baz = 23;
+
+  // Third extension range: ends at field number 43 (exclusive)
+  extensions 42;
+}
diff --git a/src/google/protobuf/compiler/main.cc b/src/google/protobuf/compiler/main.cc
index 3960946..15b6591 100644
--- a/src/google/protobuf/compiler/main.cc
+++ b/src/google/protobuf/compiler/main.cc
@@ -66,6 +66,10 @@
   cli.RegisterGenerator("--java_out", "--java_opt", &java_generator,
                         "Generate Java source file.");
 
+#ifdef GOOGLE_PROTOBUF_RUNTIME_INCLUDE_BASE
+  java_generator.set_opensource_runtime(true);
+#endif
+
   // Proto2 Kotlin
   java::KotlinGenerator kt_generator;
   cli.RegisterGenerator("--kotlin_out", "--kotlin_opt", &kt_generator,
@@ -76,6 +80,11 @@
   python::Generator py_generator;
   cli.RegisterGenerator("--python_out", "--python_opt", &py_generator,
                         "Generate Python source file.");
+
+#ifdef GOOGLE_PROTOBUF_RUNTIME_INCLUDE_BASE
+  py_generator.set_opensource_runtime(true);
+#endif
+
   // Python pyi
   python::PyiGenerator pyi_generator;
   cli.RegisterGenerator("--pyi_out", &pyi_generator,
diff --git a/src/google/protobuf/compiler/objectivec/objectivec_field.cc b/src/google/protobuf/compiler/objectivec/objectivec_field.cc
index 004ea19..a9e6517 100644
--- a/src/google/protobuf/compiler/objectivec/objectivec_field.cc
+++ b/src/google/protobuf/compiler/objectivec/objectivec_field.cc
@@ -388,7 +388,7 @@
       "$comments$"
       "$array_comment$"
       "@property(nonatomic, readwrite, strong, null_resettable) $array_property_type$ *$name$$storage_attribute$$deprecated_attribute$;\n"
-      "/** The number of items in @c $name$ without causing the array to be created. */\n"
+      "/** The number of items in @c $name$ without causing the container to be created. */\n"
       "@property(nonatomic, readonly) NSUInteger $name$_Count$deprecated_attribute$;\n");
   if (IsInitName(variables_.find("name")->second)) {
     // If property name starts with init we need to annotate it to get past ARC.
diff --git a/src/google/protobuf/compiler/php/php_generator.cc b/src/google/protobuf/compiler/php/php_generator.cc
index 7c9a3e3..6c824e3 100644
--- a/src/google/protobuf/compiler/php/php_generator.cc
+++ b/src/google/protobuf/compiler/php/php_generator.cc
@@ -48,29 +48,29 @@
 const std::string kDescriptorDirName = "Google/Protobuf/Internal";
 const std::string kDescriptorPackageName = "Google\\Protobuf\\Internal";
 const char* const kReservedNames[] = {
-    "abstract",     "and",          "array",      "as",         "break",
-    "callable",     "case",         "catch",      "class",      "clone",
-    "const",        "continue",     "declare",    "default",    "die",
-    "do",           "echo",         "else",       "elseif",     "empty",
-    "enddeclare",   "endfor",       "endforeach", "endif",      "endswitch",
-    "endwhile",     "eval",         "exit",       "extends",    "final",
-    "finally",      "fn",           "for",        "foreach",    "function",
-    "global",       "goto",         "if",         "implements", "include",
-    "include_once", "instanceof",   "insteadof",  "interface",  "isset",
-    "list",         "match",        "namespace",  "new",        "or",
-    "parent",       "print",        "private",    "protected",  "public",
-    "require",      "require_once", "return",     "self",       "static",
-    "switch",       "throw",        "trait",      "try",        "unset",
-    "use",          "var",          "while",      "xor",        "yield",
-    "int",          "float",        "bool",       "string",     "true",
-    "false",        "null",         "void",       "iterable"};
+    "abstract",     "and",        "array",        "as",         "break",
+    "callable",     "case",       "catch",        "class",      "clone",
+    "const",        "continue",   "declare",      "default",    "die",
+    "do",           "echo",       "else",         "elseif",     "empty",
+    "enddeclare",   "endfor",     "endforeach",   "endif",      "endswitch",
+    "endwhile",     "eval",       "exit",         "extends",    "final",
+    "finally",      "fn",         "for",          "foreach",    "function",
+    "global",       "goto",       "if",           "implements", "include",
+    "include_once", "instanceof", "insteadof",    "interface",  "isset",
+    "list",         "match",      "namespace",    "new",        "or",
+    "parent",       "print",      "private",      "protected",  "public",
+    "readonly",     "require",    "require_once", "return",     "self",
+    "static",       "switch",     "throw",        "trait",      "try",
+    "unset",        "use",        "var",          "while",      "xor",
+    "yield",        "int",        "float",        "bool",       "string",
+    "true",         "false",      "null",         "void",       "iterable"};
 const char* const kValidConstantNames[] = {
     "int",   "float", "bool", "string",   "true",
     "false", "null",  "void", "iterable", "parent",
-    "self"
+    "self", "readonly"
 };
-const int kReservedNamesSize = 79;
-const int kValidConstantNamesSize = 11;
+const int kReservedNamesSize = 80;
+const int kValidConstantNamesSize = 12;
 const int kFieldSetter = 1;
 const int kFieldGetter = 2;
 const int kFieldProperty = 3;
@@ -407,6 +407,29 @@
   return result + ".php";
 }
 
+template <typename DescriptorType>
+std::string LegacyGeneratedClassFileName(const DescriptorType* desc,
+                                         const Options& options) {
+  std::string result = LegacyFullClassName(desc, options);
+
+  for (int i = 0; i < result.size(); i++) {
+    if (result[i] == '\\') {
+      result[i] = '/';
+    }
+  }
+  return result + ".php";
+}
+
+template <typename DescriptorType>
+std::string LegacyReadOnlyGeneratedClassFileName(const DescriptorType* desc,
+                                                 const Options& options) {
+  std::string php_namespace = RootPhpNamespace(desc, options);
+  if (!php_namespace.empty()) {
+    return php_namespace + "/" + desc->name() + ".php";
+  }
+  return desc->name() + ".php";
+}
+
 std::string GeneratedServiceFileName(const ServiceDescriptor* service,
                                      const Options& options) {
   std::string result = FullClassName(service, options) + "Interface";
@@ -1252,6 +1275,70 @@
   printer.Print("}\n\n");
 }
 
+template <typename DescriptorType>
+void LegacyGenerateClassFile(const FileDescriptor* file,
+                             const DescriptorType* desc, const Options& options,
+                             GeneratorContext* generator_context) {
+  std::string filename = LegacyGeneratedClassFileName(desc, options);
+  std::unique_ptr<io::ZeroCopyOutputStream> output(
+      generator_context->Open(filename));
+  io::Printer printer(output.get(), '^');
+
+  GenerateHead(file, &printer);
+
+  std::string php_namespace = RootPhpNamespace(desc, options);
+  if (!php_namespace.empty()) {
+    printer.Print(
+        "namespace ^name^;\n\n",
+        "name", php_namespace);
+  }
+  std::string newname = FullClassName(desc, options);
+  printer.Print("if (false) {\n");
+  Indent(&printer);
+  printer.Print("/**\n");
+  printer.Print(" * This class is deprecated. Use ^new^ instead.\n",
+      "new", newname);
+  printer.Print(" * @deprecated\n");
+  printer.Print(" */\n");
+  printer.Print("class ^old^ {}\n",
+      "old", LegacyGeneratedClassName(desc));
+  Outdent(&printer);
+  printer.Print("}\n");
+  printer.Print("class_exists(^new^::class);\n",
+      "new", GeneratedClassNameImpl(desc));
+  printer.Print("@trigger_error('^old^ is deprecated and will be removed in "
+      "the next major release. Use ^fullname^ instead', E_USER_DEPRECATED);\n\n",
+      "old", LegacyFullClassName(desc, options),
+      "fullname", newname);
+}
+
+template <typename DescriptorType>
+void LegacyReadOnlyGenerateClassFile(const FileDescriptor* file,
+                             const DescriptorType* desc, const Options& options,
+                             GeneratorContext* generator_context) {
+  std::string filename = LegacyReadOnlyGeneratedClassFileName(desc, options);
+  std::unique_ptr<io::ZeroCopyOutputStream> output(
+      generator_context->Open(filename));
+  io::Printer printer(output.get(), '^');
+
+  GenerateHead(file, &printer);
+
+  std::string php_namespace = RootPhpNamespace(desc, options);
+  if (!php_namespace.empty()) {
+    printer.Print(
+        "namespace ^name^;\n\n",
+        "name", php_namespace);
+  }
+  std::string newname = FullClassName(desc, options);
+  printer.Print("class_exists(^new^::class); // autoload the new class, which "
+      "will also create an alias to the deprecated class\n",
+      "new", GeneratedClassNameImpl(desc));
+  printer.Print("@trigger_error(__NAMESPACE__ . '\\^old^ is deprecated and will be removed in "
+      "the next major release. Use ^fullname^ instead', E_USER_DEPRECATED);\n\n",
+      "old", desc->name(),
+      "fullname", newname);
+}
+
 void GenerateEnumFile(const FileDescriptor* file, const EnumDescriptor* en,
                       const Options& options,
                       GeneratorContext* generator_context) {
@@ -1372,6 +1459,19 @@
         "new", fullname,
         "old", LegacyFullClassName(en, options));
   }
+
+  // Write legacy file for backwards compatibility with "readonly" keywword
+  std::string lower = en->name();
+  std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
+  if (lower == "readonly") {
+    printer.Print(
+        "// Adding a class alias for backwards compatibility with the \"readonly\" keyword.\n");
+    printer.Print(
+        "class_alias(^new^::class, __NAMESPACE__ . '\\^old^');\n\n",
+        "new", fullname,
+        "old", en->name());
+    LegacyReadOnlyGenerateClassFile(file, en, options, generator_context);
+  }
 }
 
 void GenerateMessageFile(const FileDescriptor* file, const Descriptor* message,
@@ -1487,6 +1587,19 @@
         "old", LegacyFullClassName(message, options));
   }
 
+  // Write legacy file for backwards compatibility with "readonly" keywword
+  std::string lower = message->name();
+  std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
+  if (lower == "readonly") {
+    printer.Print(
+        "// Adding a class alias for backwards compatibility with the \"readonly\" keyword.\n");
+    printer.Print(
+        "class_alias(^new^::class, __NAMESPACE__ . '\\^old^');\n\n",
+        "new", fullname,
+        "old", message->name());
+    LegacyReadOnlyGenerateClassFile(file, message, options, generator_context);
+  }
+
   // Nested messages and enums.
   for (int i = 0; i < message->nested_type_count(); i++) {
     GenerateMessageFile(file, message->nested_type(i), options,
diff --git a/src/google/protobuf/compiler/plugin.pb.h b/src/google/protobuf/compiler/plugin.pb.h
index b73c9de..51152e6 100644
--- a/src/google/protobuf/compiler/plugin.pb.h
+++ b/src/google/protobuf/compiler/plugin.pb.h
@@ -13,7 +13,7 @@
 #error incompatible with your Protocol Buffer headers. Please update
 #error your headers.
 #endif
-#if 3021002 < PROTOBUF_MIN_PROTOC_VERSION
+#if 3021004 < PROTOBUF_MIN_PROTOC_VERSION
 #error This file was generated by an older version of protoc which is
 #error incompatible with your Protocol Buffer headers. Please
 #error regenerate this file with a newer version of protoc.
diff --git a/src/google/protobuf/compiler/python/generator.h b/src/google/protobuf/compiler/python/generator.h
index f1fecbc..0b8d56d 100644
--- a/src/google/protobuf/compiler/python/generator.h
+++ b/src/google/protobuf/compiler/python/generator.h
@@ -76,6 +76,10 @@
 
   uint64_t GetSupportedFeatures() const override;
 
+  void set_opensource_runtime(bool opensource) {
+    opensource_runtime_ = opensource;
+  }
+
  private:
   void PrintImports() const;
   void PrintFileDescriptor() const;
@@ -172,6 +176,8 @@
   mutable io::Printer* printer_;  // Set in Generate().  Under mutex_.
   mutable bool pure_python_workable_;
 
+  bool opensource_runtime_ = true;
+
   GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(Generator);
 };
 
diff --git a/src/google/protobuf/compiler/python/pyi_generator.cc b/src/google/protobuf/compiler/python/pyi_generator.cc
index 1ccc9a2..e954fd3 100644
--- a/src/google/protobuf/compiler/python/pyi_generator.cc
+++ b/src/google/protobuf/compiler/python/pyi_generator.cc
@@ -44,39 +44,22 @@
 namespace compiler {
 namespace python {
 
-template <typename DescriptorT>
-struct SortByName {
-  bool operator()(const DescriptorT* l, const DescriptorT* r) const {
-    return l->name() < r->name();
-  }
-};
-
 PyiGenerator::PyiGenerator() : file_(nullptr) {}
 
 PyiGenerator::~PyiGenerator() {}
 
-void PyiGenerator::PrintItemMap(
-    const std::map<std::string, std::string>& item_map) const {
-  for (const auto& entry : item_map) {
-    printer_->Print("$key$: $value$\n", "key", entry.first, "value",
-                    entry.second);
-  }
-}
-
 template <typename DescriptorT>
-std::string PyiGenerator::ModuleLevelName(
-    const DescriptorT& descriptor,
-    const std::map<std::string, std::string>& import_map) const {
+std::string PyiGenerator::ModuleLevelName(const DescriptorT& descriptor) const {
   std::string name = NamePrefixedWithNestedTypes(descriptor, ".");
   if (descriptor.file() != file_) {
     std::string module_alias;
     std::string filename = descriptor.file()->name();
-    if (import_map.find(filename) == import_map.end()) {
+    if (import_map_.find(filename) == import_map_.end()) {
       std::string module_name = ModuleName(descriptor.file()->name());
       std::vector<std::string> tokens = Split(module_name, ".");
       module_alias = "_" + tokens.back();
     } else {
-      module_alias = import_map.at(filename);
+      module_alias = import_map_.at(filename);
     }
     name = module_alias + "." + name;
   }
@@ -156,7 +139,6 @@
 
 void PyiGenerator::PrintImportForDescriptor(
     const FileDescriptor& desc,
-    std::map<std::string, std::string>* import_map,
     std::set<std::string>* seen_aliases) const {
   const std::string& filename = desc.name();
   std::string module_name = StrippedModuleName(filename);
@@ -176,21 +158,19 @@
   }
   printer_->Print("$statement$ as $alias$\n", "statement",
                   import_statement, "alias", alias);
-  (*import_map)[filename] = alias;
+  import_map_[filename] = alias;
   seen_aliases->insert(alias);
 }
 
-void PyiGenerator::PrintImports(
-    std::map<std::string, std::string>* item_map,
-    std::map<std::string, std::string>* import_map) const {
+void PyiGenerator::PrintImports() const {
   // Prints imported dependent _pb2 files.
   std::set<std::string> seen_aliases;
   for (int i = 0; i < file_->dependency_count(); ++i) {
     const FileDescriptor* dep = file_->dependency(i);
-    PrintImportForDescriptor(*dep, import_map, &seen_aliases);
+    PrintImportForDescriptor(*dep, &seen_aliases);
     for (int j = 0; j < dep->public_dependency_count(); ++j) {
       PrintImportForDescriptor(
-          *dep->public_dependency(j), import_map, &seen_aliases);
+          *dep->public_dependency(j), &seen_aliases);
     }
   }
 
@@ -254,7 +234,7 @@
   if (import_modules.has_union) {
     printer_->Print(", Union as _Union");
   }
-  printer_->Print("\n\n");
+  printer_->Print("\n");
 
   // Public imports
   for (int i = 0; i < file_->public_dependency_count(); ++i) {
@@ -272,17 +252,8 @@
                       module_name, "enum_class",
                       public_dep->enum_type(i)->name());
     }
-    // Enum values for public imports
-    for (int i = 0; i < public_dep->enum_type_count(); ++i) {
-      const EnumDescriptor* enum_descriptor = public_dep->enum_type(i);
-      for (int j = 0; j < enum_descriptor->value_count(); ++j) {
-        (*item_map)[enum_descriptor->value(j)->name()] =
-            ModuleLevelName(*enum_descriptor, *import_map);
-      }
-    }
-    // Top level extensions for public imports
-    AddExtensions(*public_dep, item_map);
   }
+printer_->Print("\n");
 }
 
 void PyiGenerator::PrintEnum(const EnumDescriptor& enum_descriptor) const {
@@ -293,20 +264,18 @@
       "enum_name", enum_name);
 }
 
-// Adds enum value to item map which will be ordered and printed later.
-void PyiGenerator::AddEnumValue(
-    const EnumDescriptor& enum_descriptor,
-    std::map<std::string, std::string>* item_map,
-    const std::map<std::string, std::string>& import_map) const {
+void PyiGenerator::PrintEnumValues(
+    const EnumDescriptor& enum_descriptor) const {
   // enum values
-  std::string module_enum_name = ModuleLevelName(enum_descriptor, import_map);
+  std::string module_enum_name = ModuleLevelName(enum_descriptor);
   for (int j = 0; j < enum_descriptor.value_count(); ++j) {
     const EnumValueDescriptor* value_descriptor = enum_descriptor.value(j);
-    (*item_map)[value_descriptor->name()] = module_enum_name;
+    printer_->Print("$name$: $module_enum_name$\n",
+                    "name", value_descriptor->name(),
+                    "module_enum_name", module_enum_name);
   }
 }
 
-// Prints top level enums
 void PyiGenerator::PrintTopLevelEnums() const {
   for (int i = 0; i < file_->enum_type_count(); ++i) {
     printer_->Print("\n");
@@ -314,25 +283,22 @@
   }
 }
 
-// Add top level extensions to item_map which will be ordered and
-// printed later.
 template <typename DescriptorT>
-void PyiGenerator::AddExtensions(
-    const DescriptorT& descriptor,
-    std::map<std::string, std::string>* item_map) const {
+void PyiGenerator::PrintExtensions(const DescriptorT& descriptor) const {
   for (int i = 0; i < descriptor.extension_count(); ++i) {
     const FieldDescriptor* extension_field = descriptor.extension(i);
     std::string constant_name = extension_field->name() + "_FIELD_NUMBER";
     ToUpper(&constant_name);
-    (*item_map)[constant_name] = "_ClassVar[int]";
-    (*item_map)[extension_field->name()] = "_descriptor.FieldDescriptor";
+    printer_->Print("$constant_name$: _ClassVar[int]\n",
+                    "constant_name", constant_name);
+    printer_->Print("$name$: _descriptor.FieldDescriptor\n",
+                    "name", extension_field->name());
   }
 }
 
 // Returns the string format of a field's cpp_type
 std::string PyiGenerator::GetFieldType(
-    const FieldDescriptor& field_des, const Descriptor& containing_des,
-    const std::map<std::string, std::string>& import_map) const {
+    const FieldDescriptor& field_des, const Descriptor& containing_des) const {
   switch (field_des.cpp_type()) {
     case FieldDescriptor::CPPTYPE_INT32:
     case FieldDescriptor::CPPTYPE_UINT32:
@@ -345,7 +311,7 @@
     case FieldDescriptor::CPPTYPE_BOOL:
       return "bool";
     case FieldDescriptor::CPPTYPE_ENUM:
-      return ModuleLevelName(*field_des.enum_type(), import_map);
+      return ModuleLevelName(*field_des.enum_type());
     case FieldDescriptor::CPPTYPE_STRING:
       if (field_des.type() == FieldDescriptor::TYPE_STRING) {
         return "str";
@@ -356,7 +322,7 @@
       // If the field is inside a nested message and the nested message has the
       // same name as a top-level message, then we need to prefix the field type
       // with the module name for disambiguation.
-      std::string name = ModuleLevelName(*field_des.message_type(), import_map);
+      std::string name = ModuleLevelName(*field_des.message_type());
       if ((containing_des.containing_type() != nullptr &&
            name == containing_des.name())) {
         std::string module = ModuleName(field_des.file()->name());
@@ -371,8 +337,7 @@
 }
 
 void PyiGenerator::PrintMessage(
-    const Descriptor& message_descriptor, bool is_nested,
-    const std::map<std::string, std::string>& import_map) const {
+    const Descriptor& message_descriptor, bool is_nested) const {
   if (!is_nested) {
     printer_->Print("\n");
   }
@@ -390,17 +355,11 @@
   printer_->Indent();
   printer_->Indent();
 
-  std::vector<const FieldDescriptor*> fields;
-  fields.reserve(message_descriptor.field_count());
-  for (int i = 0; i < message_descriptor.field_count(); ++i) {
-    fields.push_back(message_descriptor.field(i));
-  }
-  std::sort(fields.begin(), fields.end(), SortByName<FieldDescriptor>());
-
   // Prints slots
   printer_->Print("__slots__ = [", "class_name", class_name);
   bool first_item = true;
-  for (const auto& field_des : fields) {
+  for (int i = 0; i < message_descriptor.field_count(); ++i) {
+    const FieldDescriptor* field_des = message_descriptor.field(i);
     if (IsPythonKeyword(field_des->name())) {
       continue;
     }
@@ -413,48 +372,34 @@
   }
   printer_->Print("]\n");
 
-  std::map<std::string, std::string> item_map;
   // Prints Extensions for extendable messages
   if (message_descriptor.extension_range_count() > 0) {
-    item_map["Extensions"] = "_python_message._ExtensionDict";
+    printer_->Print("Extensions: _python_message._ExtensionDict\n");
   }
 
   // Prints nested enums
-  std::vector<const EnumDescriptor*> nested_enums;
-  nested_enums.reserve(message_descriptor.enum_type_count());
   for (int i = 0; i < message_descriptor.enum_type_count(); ++i) {
-    nested_enums.push_back(message_descriptor.enum_type(i));
-  }
-  std::sort(nested_enums.begin(), nested_enums.end(),
-            SortByName<EnumDescriptor>());
-
-  for (const auto& entry : nested_enums) {
-    PrintEnum(*entry);
-    // Adds enum value to item_map which will be ordered and printed later
-    AddEnumValue(*entry, &item_map, import_map);
+    PrintEnum(*message_descriptor.enum_type(i));
+    PrintEnumValues(*message_descriptor.enum_type(i));
   }
 
   // Prints nested messages
-  std::vector<const Descriptor*> nested_messages;
-  nested_messages.reserve(message_descriptor.nested_type_count());
   for (int i = 0; i < message_descriptor.nested_type_count(); ++i) {
-    nested_messages.push_back(message_descriptor.nested_type(i));
-  }
-  std::sort(nested_messages.begin(), nested_messages.end(),
-            SortByName<Descriptor>());
-
-  for (const auto& entry : nested_messages) {
-    PrintMessage(*entry, true, import_map);
+    PrintMessage(*message_descriptor.nested_type(i), true);
   }
 
-  // Adds extensions to item_map which will be ordered and printed later
-  AddExtensions(message_descriptor, &item_map);
+  PrintExtensions(message_descriptor);
 
-  // Adds field number and field descriptor to item_map
+  // Prints field number
   for (int i = 0; i < message_descriptor.field_count(); ++i) {
     const FieldDescriptor& field_des = *message_descriptor.field(i);
-    item_map[ToUpper(field_des.name()) + "_FIELD_NUMBER"] =
-        "_ClassVar[int]";
+    printer_->Print(
+      "$field_number_name$: _ClassVar[int]\n", "field_number_name",
+      ToUpper(field_des.name()) + "_FIELD_NUMBER");
+  }
+  // Prints field name and type
+  for (int i = 0; i < message_descriptor.field_count(); ++i) {
+    const FieldDescriptor& field_des = *message_descriptor.field(i);
     if (IsPythonKeyword(field_des.name())) {
       continue;
     }
@@ -465,27 +410,25 @@
       field_type = (value_des->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE
                         ? "_containers.MessageMap["
                         : "_containers.ScalarMap[");
-      field_type += GetFieldType(*key_des, message_descriptor, import_map);
+      field_type += GetFieldType(*key_des, message_descriptor);
       field_type += ", ";
-      field_type += GetFieldType(*value_des, message_descriptor, import_map);
+      field_type += GetFieldType(*value_des, message_descriptor);
     } else {
       if (field_des.is_repeated()) {
         field_type = (field_des.cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE
                           ? "_containers.RepeatedCompositeFieldContainer["
                           : "_containers.RepeatedScalarFieldContainer[");
       }
-      field_type += GetFieldType(field_des, message_descriptor, import_map);
+      field_type += GetFieldType(field_des, message_descriptor);
     }
 
     if (field_des.is_repeated()) {
       field_type += "]";
     }
-    item_map[field_des.name()] = field_type;
+    printer_->Print("$name$: $type$\n",
+                    "name", field_des.name(), "type", field_type);
   }
 
-  // Prints all items in item_map
-  PrintItemMap(item_map);
-
   // Prints __init__
   printer_->Print("def __init__(self");
   bool has_key_words = false;
@@ -513,9 +456,9 @@
       const Descriptor* map_entry = field_des->message_type();
       printer_->Print(
           "_Mapping[$key_type$, $value_type$]", "key_type",
-          GetFieldType(*map_entry->field(0), message_descriptor, import_map),
+          GetFieldType(*map_entry->field(0), message_descriptor),
           "value_type",
-          GetFieldType(*map_entry->field(1), message_descriptor, import_map));
+          GetFieldType(*map_entry->field(1), message_descriptor));
     } else {
       if (field_des->is_repeated()) {
         printer_->Print("_Iterable[");
@@ -523,15 +466,15 @@
       if (field_des->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
         printer_->Print(
             "_Union[$type_name$, _Mapping]", "type_name",
-            GetFieldType(*field_des, message_descriptor, import_map));
+            GetFieldType(*field_des, message_descriptor));
       } else {
         if (field_des->cpp_type() == FieldDescriptor::CPPTYPE_ENUM) {
           printer_->Print("_Union[$type_name$, str]", "type_name",
-                          ModuleLevelName(*field_des->enum_type(), import_map));
+                          ModuleLevelName(*field_des->enum_type()));
         } else {
           printer_->Print(
               "$type_name$", "type_name",
-              GetFieldType(*field_des, message_descriptor, import_map));
+              GetFieldType(*field_des, message_descriptor));
         }
       }
       if (field_des->is_repeated()) {
@@ -553,36 +496,21 @@
   printer_->Outdent();
 }
 
-void PyiGenerator::PrintMessages(
-    const std::map<std::string, std::string>& import_map) const {
+void PyiGenerator::PrintMessages() const {
   // Deterministically order the descriptors.
-  std::vector<const Descriptor*> messages;
-  messages.reserve(file_->message_type_count());
   for (int i = 0; i < file_->message_type_count(); ++i) {
-    messages.push_back(file_->message_type(i));
-  }
-  std::sort(messages.begin(), messages.end(), SortByName<Descriptor>());
-
-  for (const auto& entry : messages) {
-    PrintMessage(*entry, false, import_map);
+    PrintMessage(*file_->message_type(i), false);
   }
 }
 
 void PyiGenerator::PrintServices() const {
-  std::vector<const ServiceDescriptor*> services;
-  services.reserve(file_->service_count());
-  for (int i = 0; i < file_->service_count(); ++i) {
-    services.push_back(file_->service(i));
-  }
-  std::sort(services.begin(), services.end(), SortByName<ServiceDescriptor>());
-
   // Prints $Service$ and $Service$_Stub classes
-  for (const auto& entry : services) {
+  for (int i = 0; i < file_->service_count(); ++i) {
     printer_->Print("\n");
     printer_->Print(
         "class $service_name$(_service.service): ...\n\n"
         "class $service_name$_Stub($service_name$): ...\n",
-        "service_name", entry->name());
+        "service_name", file_->service(i)->name());
   }
 }
 
@@ -591,6 +519,7 @@
                             GeneratorContext* context,
                             std::string* error) const {
   MutexLock lock(&mutex_);
+  import_map_.clear();
   // Calculate file name.
   file_ = file;
   std::string filename =
@@ -601,29 +530,28 @@
   io::Printer printer(output.get(), '$');
   printer_ = &printer;
 
-  // item map will store "DESCRIPTOR", top level extensions, top level enum
-  // values. The items will be sorted and printed later.
-  std::map<std::string, std::string> item_map;
+  PrintImports();
+  printer_->Print("DESCRIPTOR: _descriptor.FileDescriptor\n");
 
-  // Adds "DESCRIPTOR" into item_map.
-  item_map["DESCRIPTOR"] = "_descriptor.FileDescriptor";
-
-  // import_map will be a mapping from filename to module alias, e.g.
-  // "google3/foo/bar.py" -> "_bar"
-  std::map<std::string, std::string> import_map;
-
-  PrintImports(&item_map, &import_map);
-  // Adds top level enum values to item_map.
-  for (int i = 0; i < file_->enum_type_count(); ++i) {
-    AddEnumValue(*file_->enum_type(i), &item_map, import_map);
+  // Prints extensions and enums from imports.
+  for (int i = 0; i < file_->public_dependency_count(); ++i) {
+    const FileDescriptor* public_dep = file_->public_dependency(i);
+    PrintExtensions(*public_dep);
+    for (int i = 0; i < public_dep->enum_type_count(); ++i) {
+      const EnumDescriptor* enum_descriptor = public_dep->enum_type(i);
+      PrintEnumValues(*enum_descriptor);
+    }
   }
-  // Adds top level extensions to item_map.
-  AddExtensions(*file_, &item_map);
-  // Prints item map
-  PrintItemMap(item_map);
 
-  PrintMessages(import_map);
   PrintTopLevelEnums();
+  // Prints top level enum values
+  for (int i = 0; i < file_->enum_type_count(); ++i) {
+    PrintEnumValues(*file_->enum_type(i));
+  }
+  // Prints top level Extensions
+  PrintExtensions(*file_);
+  PrintMessages();
+
   if (HasGenericServices(file)) {
     PrintServices();
   }
diff --git a/src/google/protobuf/compiler/python/pyi_generator.h b/src/google/protobuf/compiler/python/pyi_generator.h
index 9611ed4..40741e1 100644
--- a/src/google/protobuf/compiler/python/pyi_generator.h
+++ b/src/google/protobuf/compiler/python/pyi_generator.h
@@ -76,37 +76,29 @@
 
  private:
   void PrintImportForDescriptor(const FileDescriptor& desc,
-                                std::map<std::string, std::string>* import_map,
                                 std::set<std::string>* seen_aliases) const;
-  void PrintImports(std::map<std::string, std::string>* item_map,
-                    std::map<std::string, std::string>* import_map) const;
-  void PrintEnum(const EnumDescriptor& enum_descriptor) const;
-  void AddEnumValue(const EnumDescriptor& enum_descriptor,
-                    std::map<std::string, std::string>* item_map,
-                    const std::map<std::string, std::string>& import_map) const;
+  void PrintImports() const;
   void PrintTopLevelEnums() const;
+  void PrintEnum(const EnumDescriptor& enum_descriptor) const;
+  void PrintEnumValues(const EnumDescriptor& enum_descriptor) const;
   template <typename DescriptorT>
-  void AddExtensions(const DescriptorT& descriptor,
-                     std::map<std::string, std::string>* item_map) const;
-  void PrintMessages(
-      const std::map<std::string, std::string>& import_map) const;
-  void PrintMessage(const Descriptor& message_descriptor, bool is_nested,
-                    const std::map<std::string, std::string>& import_map) const;
+  void PrintExtensions(const DescriptorT& descriptor) const;
+  void PrintMessages() const;
+  void PrintMessage(const Descriptor& message_descriptor, bool is_nested) const;
   void PrintServices() const;
-  void PrintItemMap(const std::map<std::string, std::string>& item_map) const;
   std::string GetFieldType(
-      const FieldDescriptor& field_des, const Descriptor& containing_des,
-      const std::map<std::string, std::string>& import_map) const;
+      const FieldDescriptor& field_des, const Descriptor& containing_des) const;
   template <typename DescriptorT>
-  std::string ModuleLevelName(
-      const DescriptorT& descriptor,
-      const std::map<std::string, std::string>& import_map) const;
+  std::string ModuleLevelName(const DescriptorT& descriptor) const;
 
   // Very coarse-grained lock to ensure that Generate() is reentrant.
-  // Guards file_ and printer_.
+  // Guards file_, printer_, and import_map_.
   mutable Mutex mutex_;
   mutable const FileDescriptor* file_;  // Set in Generate().  Under mutex_.
   mutable io::Printer* printer_;        // Set in Generate().  Under mutex_.
+  // import_map will be a mapping from filename to module alias, e.g.
+  // "google3/foo/bar.py" -> "_bar"
+  mutable std::map<std::string, std::string> import_map_;
   GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(PyiGenerator);
 };
 
diff --git a/src/google/protobuf/descriptor.pb.h b/src/google/protobuf/descriptor.pb.h
index f6f5da5..439db9f 100644
--- a/src/google/protobuf/descriptor.pb.h
+++ b/src/google/protobuf/descriptor.pb.h
@@ -13,7 +13,7 @@
 #error incompatible with your Protocol Buffer headers. Please update
 #error your headers.
 #endif
-#if 3021002 < PROTOBUF_MIN_PROTOC_VERSION
+#if 3021004 < PROTOBUF_MIN_PROTOC_VERSION
 #error This file was generated by an older version of protoc which is
 #error incompatible with your Protocol Buffer headers. Please
 #error regenerate this file with a newer version of protoc.
diff --git a/src/google/protobuf/duration.pb.h b/src/google/protobuf/duration.pb.h
index 87729c4..1e4a3e1 100644
--- a/src/google/protobuf/duration.pb.h
+++ b/src/google/protobuf/duration.pb.h
@@ -13,7 +13,7 @@
 #error incompatible with your Protocol Buffer headers. Please update
 #error your headers.
 #endif
-#if 3021002 < PROTOBUF_MIN_PROTOC_VERSION
+#if 3021004 < PROTOBUF_MIN_PROTOC_VERSION
 #error This file was generated by an older version of protoc which is
 #error incompatible with your Protocol Buffer headers. Please
 #error regenerate this file with a newer version of protoc.
diff --git a/src/google/protobuf/empty.pb.h b/src/google/protobuf/empty.pb.h
index fdc398c..c5f528b 100644
--- a/src/google/protobuf/empty.pb.h
+++ b/src/google/protobuf/empty.pb.h
@@ -13,7 +13,7 @@
 #error incompatible with your Protocol Buffer headers. Please update
 #error your headers.
 #endif
-#if 3021002 < PROTOBUF_MIN_PROTOC_VERSION
+#if 3021004 < PROTOBUF_MIN_PROTOC_VERSION
 #error This file was generated by an older version of protoc which is
 #error incompatible with your Protocol Buffer headers. Please
 #error regenerate this file with a newer version of protoc.
diff --git a/src/google/protobuf/field_mask.pb.h b/src/google/protobuf/field_mask.pb.h
index fd75b88..01ecfac 100644
--- a/src/google/protobuf/field_mask.pb.h
+++ b/src/google/protobuf/field_mask.pb.h
@@ -13,7 +13,7 @@
 #error incompatible with your Protocol Buffer headers. Please update
 #error your headers.
 #endif
-#if 3021002 < PROTOBUF_MIN_PROTOC_VERSION
+#if 3021004 < PROTOBUF_MIN_PROTOC_VERSION
 #error This file was generated by an older version of protoc which is
 #error incompatible with your Protocol Buffer headers. Please
 #error regenerate this file with a newer version of protoc.
diff --git a/src/google/protobuf/generated_message_tctable_impl.h b/src/google/protobuf/generated_message_tctable_impl.h
index 67349b4..3fad18c 100644
--- a/src/google/protobuf/generated_message_tctable_impl.h
+++ b/src/google/protobuf/generated_message_tctable_impl.h
@@ -38,7 +38,6 @@
 #include <google/protobuf/port.h>
 #include <google/protobuf/extension_set.h>
 #include <google/protobuf/generated_message_tctable_decl.h>
-#include <google/protobuf/message_lite.h>
 #include <google/protobuf/metadata_lite.h>
 #include <google/protobuf/parse_context.h>
 #include <google/protobuf/wire_format_lite.h>
@@ -257,16 +256,6 @@
 // clang-format on
 }  // namespace field_layout
 
-// PROTOBUF_TC_PARAM_DECL are the parameters for tailcall functions, it is
-// defined in port_def.inc.
-//
-// Note that this is performance sensitive: changing the parameters will change
-// the registers used by the ABI calling convention, which subsequently affects
-// register selection logic inside the function.
-
-// PROTOBUF_TC_PARAM_PASS passes values to match PROTOBUF_TC_PARAM_DECL.
-#define PROTOBUF_TC_PARAM_PASS msg, ptr, ctx, table, hasbits, data
-
 #ifndef NDEBUG
 template <size_t align>
 void AlignFail(uintptr_t address) {
@@ -349,6 +338,28 @@
   static const char* FastZ64P1(PROTOBUF_TC_PARAM_DECL);
   static const char* FastZ64P2(PROTOBUF_TC_PARAM_DECL);
 
+  // Manually unrolled and specialized Varint parsing.
+  template <typename FieldType, int data_offset, int hasbit_idx>
+  static const char* SpecializedUnrolledVImpl1(PROTOBUF_TC_PARAM_DECL);
+
+  template <typename FieldType, int data_offset, int hasbit_idx>
+  static constexpr TailCallParseFunc SingularVarintNoZag1() {
+    if (data_offset < 100) {
+      return &SpecializedUnrolledVImpl1<FieldType, data_offset, hasbit_idx>;
+    } else if (sizeof(FieldType) == 1) {
+      return &FastV8S1;
+    } else if (sizeof(FieldType) == 4) {
+      return &FastV32S1;
+    } else if (sizeof(FieldType) == 8) {
+      return &FastV64S1;
+    } else {
+      static_assert(sizeof(FieldType) == 1 || sizeof(FieldType) == 4 ||
+                        sizeof(FieldType) == 8,
+                    "");
+      return nullptr;
+    }
+  }
+
   // Functions referenced by generated fast tables (closed enum):
   //   E: closed enum (N.B.: open enums use V32, above)
   //   r: enum range  v: enum validator (_IsValid function)
@@ -600,6 +611,135 @@
   static const char* MpMap(PROTOBUF_TC_PARAM_DECL);
 };
 
+template <typename FieldType, int data_offset, int hasbit_idx>
+const char* TcParser::SpecializedUnrolledVImpl1(PROTOBUF_TC_PARAM_DECL) {
+  using TagType = uint8_t;
+  // super-early success test...
+  if (PROTOBUF_PREDICT_TRUE(((data.data) & 0x80FF) == 0)) {
+    ptr += sizeof(TagType);  // Consume tag
+    if (hasbit_idx < 32) {
+      hasbits |= (uint64_t{1} << hasbit_idx);
+    }
+    uint8_t value = data.data >> 8;
+    RefAt<FieldType>(msg, data_offset) = value;
+    ptr += 1;
+    PROTOBUF_MUSTTAIL return ToTagDispatch(PROTOBUF_TC_PARAM_PASS);
+  }
+  if (PROTOBUF_PREDICT_FALSE(data.coded_tag<TagType>() != 0)) {
+    PROTOBUF_MUSTTAIL return MiniParse(PROTOBUF_TC_PARAM_PASS);
+  }
+  ptr += sizeof(TagType);  // Consume tag
+  if (hasbit_idx < 32) {
+    hasbits |= (uint64_t{1} << hasbit_idx);
+  }
+
+  // Few registers
+  auto* out = &RefAt<FieldType>(msg, data_offset);
+  uint64_t res = 0xFF & (data.data >> 8);
+  /* if (PROTOBUF_PREDICT_FALSE(res & 0x80)) */ {
+    res = RotRight7AndReplaceLowByte(res, ptr[1]);
+    if (PROTOBUF_PREDICT_FALSE(res & 0x80)) {
+      res = RotRight7AndReplaceLowByte(res, ptr[2]);
+      if (PROTOBUF_PREDICT_FALSE(res & 0x80)) {
+        res = RotRight7AndReplaceLowByte(res, ptr[3]);
+        if (PROTOBUF_PREDICT_FALSE(res & 0x80)) {
+          res = RotRight7AndReplaceLowByte(res, ptr[4]);
+          if (PROTOBUF_PREDICT_FALSE(res & 0x80)) {
+            res = RotRight7AndReplaceLowByte(res, ptr[5]);
+            if (PROTOBUF_PREDICT_FALSE(res & 0x80)) {
+              res = RotRight7AndReplaceLowByte(res, ptr[6]);
+              if (PROTOBUF_PREDICT_FALSE(res & 0x80)) {
+                res = RotRight7AndReplaceLowByte(res, ptr[7]);
+                if (PROTOBUF_PREDICT_FALSE(res & 0x80)) {
+                  res = RotRight7AndReplaceLowByte(res, ptr[8]);
+                  if (PROTOBUF_PREDICT_FALSE(res & 0x80)) {
+                    if (ptr[9] & 0xFE) return nullptr;
+                    res = RotateLeft(res, -7) & ~1;
+                    res += ptr[9] & 1;
+                    *out = RotateLeft(res, 63);
+                    ptr += 10;
+                    PROTOBUF_MUSTTAIL return ToTagDispatch(PROTOBUF_TC_PARAM_PASS);
+                  }
+                  *out = RotateLeft(res, 56);
+                  ptr += 9;
+                  PROTOBUF_MUSTTAIL return ToTagDispatch(PROTOBUF_TC_PARAM_PASS);
+                }
+                *out = RotateLeft(res, 49);
+                ptr += 8;
+                PROTOBUF_MUSTTAIL return ToTagDispatch(PROTOBUF_TC_PARAM_PASS);
+              }
+              *out = RotateLeft(res, 42);
+              ptr += 7;
+              PROTOBUF_MUSTTAIL return ToTagDispatch(PROTOBUF_TC_PARAM_PASS);
+            }
+            *out = RotateLeft(res, 35);
+            ptr += 6;
+            PROTOBUF_MUSTTAIL return ToTagDispatch(PROTOBUF_TC_PARAM_PASS);
+          }
+          *out = RotateLeft(res, 28);
+          ptr += 5;
+          PROTOBUF_MUSTTAIL return ToTagDispatch(PROTOBUF_TC_PARAM_PASS);
+        }
+        *out = RotateLeft(res, 21);
+        ptr += 4;
+        PROTOBUF_MUSTTAIL return ToTagDispatch(PROTOBUF_TC_PARAM_PASS);
+      }
+      *out = RotateLeft(res, 14);
+      ptr += 3;
+      PROTOBUF_MUSTTAIL return ToTagDispatch(PROTOBUF_TC_PARAM_PASS);
+    }
+    *out = RotateLeft(res, 7);
+    ptr += 2;
+    PROTOBUF_MUSTTAIL return ToTagDispatch(PROTOBUF_TC_PARAM_PASS);
+  }
+  *out = res;
+  ptr += 1;
+  PROTOBUF_MUSTTAIL return ToTagDispatch(PROTOBUF_TC_PARAM_PASS);
+}
+
+// Dispatch to the designated parse function
+inline PROTOBUF_ALWAYS_INLINE const char* TcParser::TagDispatch(
+    PROTOBUF_TC_PARAM_DECL) {
+  const auto coded_tag = UnalignedLoad<uint16_t>(ptr);
+  const size_t idx = coded_tag & table->fast_idx_mask;
+  PROTOBUF_ASSUME((idx & 7) == 0);
+  auto* fast_entry = table->fast_entry(idx >> 3);
+  data = fast_entry->bits;
+  data.data ^= coded_tag;
+  PROTOBUF_MUSTTAIL return fast_entry->target(PROTOBUF_TC_PARAM_PASS);
+}
+
+// We can only safely call from field to next field if the call is optimized
+// to a proper tail call. Otherwise we blow through stack. Clang and gcc
+// reliably do this optimization in opt mode, but do not perform this in debug
+// mode. Luckily the structure of the algorithm is such that it's always
+// possible to just return and use the enclosing parse loop as a trampoline.
+inline PROTOBUF_ALWAYS_INLINE const char* TcParser::ToTagDispatch(
+    PROTOBUF_TC_PARAM_DECL) {
+  constexpr bool always_return = !PROTOBUF_TAILCALL;
+  if (always_return || !ctx->DataAvailable(ptr)) {
+    PROTOBUF_MUSTTAIL return ToParseLoop(PROTOBUF_TC_PARAM_PASS);
+  }
+  PROTOBUF_MUSTTAIL return TagDispatch(PROTOBUF_TC_PARAM_PASS);
+}
+
+inline PROTOBUF_ALWAYS_INLINE const char* TcParser::ToParseLoop(
+    PROTOBUF_TC_PARAM_DECL) {
+  (void)data;
+  (void)ctx;
+  SyncHasbits(msg, hasbits, table);
+  return ptr;
+}
+
+inline PROTOBUF_ALWAYS_INLINE const char* TcParser::Error(
+    PROTOBUF_TC_PARAM_DECL) {
+  (void)data;
+  (void)ctx;
+  (void)ptr;
+  SyncHasbits(msg, hasbits, table);
+  return nullptr;
+}
+
 }  // namespace internal
 }  // namespace protobuf
 }  // namespace google
diff --git a/src/google/protobuf/generated_message_tctable_lite.cc b/src/google/protobuf/generated_message_tctable_lite.cc
index aef780f..f9fb6cc 100644
--- a/src/google/protobuf/generated_message_tctable_lite.cc
+++ b/src/google/protobuf/generated_message_tctable_lite.cc
@@ -30,6 +30,7 @@
 
 #include <cstdint>
 #include <numeric>
+#include <type_traits>
 
 #include <google/protobuf/extension_set.h>
 #include <google/protobuf/generated_message_tctable_decl.h>
@@ -84,56 +85,13 @@
     // TODO(b/64614992): remove this asm
     asm("" : "+r"(table));
 #endif
-    ptr = TagDispatch(msg, ptr, ctx, table - 1, 0, {});
+    ptr = TagDispatch(msg, ptr, ctx, {}, table - 1, 0);
     if (ptr == nullptr) break;
     if (ctx->LastTag() != 1) break;  // Ended on terminating tag
   }
   return ptr;
 }
 
-  // Dispatch to the designated parse function
-inline PROTOBUF_ALWAYS_INLINE const char* TcParser::TagDispatch(
-    PROTOBUF_TC_PARAM_DECL) {
-  const auto coded_tag = UnalignedLoad<uint16_t>(ptr);
-  const size_t idx = coded_tag & table->fast_idx_mask;
-  PROTOBUF_ASSUME((idx & 7) == 0);
-  auto* fast_entry = table->fast_entry(idx >> 3);
-  data = fast_entry->bits;
-  data.data ^= coded_tag;
-  PROTOBUF_MUSTTAIL return fast_entry->target(PROTOBUF_TC_PARAM_PASS);
-}
-
-// We can only safely call from field to next field if the call is optimized
-// to a proper tail call. Otherwise we blow through stack. Clang and gcc
-// reliably do this optimization in opt mode, but do not perform this in debug
-// mode. Luckily the structure of the algorithm is such that it's always
-// possible to just return and use the enclosing parse loop as a trampoline.
-inline PROTOBUF_ALWAYS_INLINE const char* TcParser::ToTagDispatch(
-    PROTOBUF_TC_PARAM_DECL) {
-  constexpr bool always_return = !PROTOBUF_TAILCALL;
-  if (always_return || !ctx->DataAvailable(ptr)) {
-    PROTOBUF_MUSTTAIL return ToParseLoop(PROTOBUF_TC_PARAM_PASS);
-  }
-  PROTOBUF_MUSTTAIL return TagDispatch(PROTOBUF_TC_PARAM_PASS);
-}
-
-inline PROTOBUF_ALWAYS_INLINE const char* TcParser::ToParseLoop(
-    PROTOBUF_TC_PARAM_DECL) {
-  (void)data;
-  (void)ctx;
-  SyncHasbits(msg, hasbits, table);
-  return ptr;
-}
-
-inline PROTOBUF_ALWAYS_INLINE const char* TcParser::Error(
-    PROTOBUF_TC_PARAM_DECL) {
-  (void)data;
-  (void)ctx;
-  (void)ptr;
-  SyncHasbits(msg, hasbits, table);
-  return nullptr;
-}
-
 // On the fast path, a (matching) 1-byte tag already has the decoded value.
 static uint32_t FastDecodeTag(uint8_t coded_tag) {
   return coded_tag;
@@ -875,8 +833,31 @@
 }
 
 const char* TcParser::FastV8S1(PROTOBUF_TC_PARAM_DECL) {
-  PROTOBUF_MUSTTAIL return SingularVarint<bool, uint8_t>(
-      PROTOBUF_TC_PARAM_PASS);
+  // Special case for a varint bool field with a tag of 1 byte:
+  // The coded_tag() field will actually contain the value too and we can check
+  // both at the same time.
+  auto coded_tag = data.coded_tag<uint16_t>();
+  if (PROTOBUF_PREDICT_TRUE(coded_tag == 0x0000 || coded_tag == 0x0100)) {
+    auto& field = RefAt<bool>(msg, data.offset());
+    // Note: we use `data.data` because Clang generates suboptimal code when
+    // using coded_tag.
+    // In x86_64 this uses the CH register to read the second byte out of
+    // `data`.
+    uint8_t value = data.data >> 8;
+    // The assume allows using a mov instead of test+setne.
+    PROTOBUF_ASSUME(value <= 1);
+    field = static_cast<bool>(value);
+
+    ptr += 2;  // Consume the tag and the value.
+    hasbits |= (uint64_t{1} << data.hasbit_idx());
+
+    PROTOBUF_MUSTTAIL return ToTagDispatch(PROTOBUF_TC_PARAM_PASS);
+  }
+
+  // If it didn't match above either the tag is wrong, or the value is encoded
+  // non-canonically.
+  // Jump to MiniParse as wrong tag is the most probable reason.
+  PROTOBUF_MUSTTAIL return MiniParse(PROTOBUF_TC_PARAM_PASS);
 }
 const char* TcParser::FastV8S2(PROTOBUF_TC_PARAM_DECL) {
   PROTOBUF_MUSTTAIL return SingularVarint<bool, uint16_t>(
diff --git a/src/google/protobuf/io/printer.h b/src/google/protobuf/io/printer.h
index e30bfa6..aa507cb 100644
--- a/src/google/protobuf/io/printer.h
+++ b/src/google/protobuf/io/printer.h
@@ -251,7 +251,8 @@
   template <typename... Args>
   void Print(const char* text, const Args&... args) {
     std::map<std::string, std::string> vars;
-    PrintInternal(&vars, text, args...);
+    FillMap(&vars, args...);
+    Print(vars, text);
   }
 
   // Indent text by two spaces.  After calling Indent(), two spaces will be
@@ -299,18 +300,13 @@
   void Annotate(const char* begin_varname, const char* end_varname,
                 const std::string& file_path, const std::vector<int>& path);
 
-  // Base case
-  void PrintInternal(std::map<std::string, std::string>* vars,
-                     const char* text) {
-    Print(*vars, text);
-  }
+  void FillMap(std::map<std::string, std::string>* vars) {}
 
   template <typename... Args>
-  void PrintInternal(std::map<std::string, std::string>* vars, const char* text,
-                     const char* key, const std::string& value,
-                     const Args&... args) {
+  void FillMap(std::map<std::string, std::string>* vars, const std::string& key,
+               const std::string& value, const Args&... args) {
     (*vars)[key] = value;
-    PrintInternal(vars, text, args...);
+    FillMap(vars, args...);
   }
 
   // Copy size worth of bytes from data to buffer_.
diff --git a/src/google/protobuf/map.h b/src/google/protobuf/map.h
index d0aa010..a3f79f1 100644
--- a/src/google/protobuf/map.h
+++ b/src/google/protobuf/map.h
@@ -333,6 +333,11 @@
 
 }  // namespace internal
 
+#ifdef PROTOBUF_FUTURE_MAP_PAIR_UPGRADE
+// This is the class for Map's internal value_type.
+template <typename Key, typename T>
+using MapPair = std::pair<const Key, T>;
+#else
 // This is the class for Map's internal value_type. Instead of using
 // std::pair as value_type, we use this class which provides us more control of
 // its process of construction and destruction.
@@ -363,6 +368,7 @@
   friend class Arena;
   friend class Map<Key, T>;
 };
+#endif
 
 // Map is an associative container type used to store protobuf map
 // fields.  Each Map instance may or may not use a different hash function, a
diff --git a/src/google/protobuf/map_test.cc b/src/google/protobuf/map_test.cc
index f7c024c..fd89524 100644
--- a/src/google/protobuf/map_test.cc
+++ b/src/google/protobuf/map_test.cc
@@ -84,3 +84,5 @@
 }  // namespace internal
 }  // namespace protobuf
 }  // namespace google
+
+#include <google/protobuf/port_undef.inc>
diff --git a/src/google/protobuf/map_test.inc b/src/google/protobuf/map_test.inc
index 041ac82..1d55dd5 100644
--- a/src/google/protobuf/map_test.inc
+++ b/src/google/protobuf/map_test.inc
@@ -810,6 +810,8 @@
       m, UnorderedElementsAre(Pair(1, "one"), Pair(2, "two"), Pair(42, "aaa")));
 }
 
+#ifndef PROTOBUF_FUTURE_MAP_PAIR_UPGRADE
+
 TEST_F(MapImplTest, EmplaceKeyOnly) {
   using ::testing::Pair;
   using ::testing::UnorderedElementsAre;
@@ -824,6 +826,43 @@
   EXPECT_THAT(m, UnorderedElementsAre(Pair(1, ""), Pair(42, "")));
 }
 
+#else
+
+TEST_F(MapImplTest, ValueTypeNoImplicitConversion) {
+  using vt = typename Map<const char*, int>::value_type;
+
+  EXPECT_FALSE((std::is_convertible<
+                vt, std::pair<std::string, std::vector<std::string>>>::value));
+}
+
+enum class ConstructorType {
+  kDefault,
+  kCopy,
+  kMove,
+};
+
+struct ConstructorTag {
+  ConstructorTag() : invoked_constructor(ConstructorType::kDefault) {}
+  ConstructorTag(const ConstructorTag&)
+      : invoked_constructor(ConstructorType::kCopy) {}
+  ConstructorTag(ConstructorTag&&)
+      : invoked_constructor(ConstructorType::kMove) {}
+
+  ConstructorType invoked_constructor;
+};
+
+TEST_F(MapImplTest, ValueTypeHasMoveConstructor) {
+  using vt = typename Map<ConstructorTag, ConstructorTag>::value_type;
+  ConstructorTag l, r;
+
+  vt pair(l, std::move(r));
+
+  EXPECT_EQ(pair.first.invoked_constructor, ConstructorType::kCopy);
+  EXPECT_EQ(pair.second.invoked_constructor, ConstructorType::kMove);
+}
+
+#endif  // !PROTOBUF_FUTURE_MAP_PAIR_UPGRADE
+
 struct CountedInstance {
   CountedInstance() { ++num_created; }
   CountedInstance(const CountedInstance&) : CountedInstance() {}
diff --git a/src/google/protobuf/message.cc b/src/google/protobuf/message.cc
index 724a622..1279164 100644
--- a/src/google/protobuf/message.cc
+++ b/src/google/protobuf/message.cc
@@ -214,7 +214,7 @@
 }
 
 namespace internal {
-void* CreateSplitMessageGeneric(Arena* arena, void* default_split,
+void* CreateSplitMessageGeneric(Arena* arena, const void* default_split,
                                 size_t size) {
   void* split =
       (arena == nullptr) ? ::operator new(size) : arena->AllocateAligned(size);
diff --git a/src/google/protobuf/message.h b/src/google/protobuf/message.h
index 7042a13..b61fafb 100644
--- a/src/google/protobuf/message.h
+++ b/src/google/protobuf/message.h
@@ -411,7 +411,8 @@
 
 namespace internal {
 // Creates and returns an allocation for a split message.
-void* CreateSplitMessageGeneric(Arena* arena, void* default_split, size_t size);
+void* CreateSplitMessageGeneric(Arena* arena, const void* default_split,
+                                size_t size);
 
 // Forward-declare interfaces used to implement RepeatedFieldRef.
 // These are protobuf internals that users shouldn't care about.
diff --git a/src/google/protobuf/message_lite.cc b/src/google/protobuf/message_lite.cc
index 27a8b38..2674b26 100644
--- a/src/google/protobuf/message_lite.cc
+++ b/src/google/protobuf/message_lite.cc
@@ -521,18 +521,14 @@
   *to = from;
 }
 
-// Non-inline implementations of InternalMetadata routines
-#if defined(NDEBUG) || defined(_MSC_VER)
-// for opt and MSVC builds, the destructor is defined in the header.
-#else
+// Non-inline implementations of InternalMetadata destructor
 // This is moved out of the header because the GOOGLE_DCHECK produces a lot of code.
-InternalMetadata::~InternalMetadata() {
+void InternalMetadata::CheckedDestruct() {
   if (HasMessageOwnedArenaTag()) {
     GOOGLE_DCHECK(!HasUnknownFieldsTag());
     delete reinterpret_cast<Arena*>(ptr_ - kMessageOwnedArenaTagMask);
   }
 }
-#endif
 
 // Non-inline variants of std::string specializations for
 // various InternalMetadata routines.
diff --git a/src/google/protobuf/message_unittest.inc b/src/google/protobuf/message_unittest.inc
index 44e3b31..a1b9202 100644
--- a/src/google/protobuf/message_unittest.inc
+++ b/src/google/protobuf/message_unittest.inc
@@ -1174,11 +1174,12 @@
             std::signbit(out_message.optional_double()));
 }
 
-std::string EncodeEnumValue(int number, int value, int non_canonical_bytes) {
-  uint8_t buf[100];
-  uint8_t* p = buf;
-
-  p = internal::WireFormatLite::WriteEnumToArray(number, value, p);
+// Adds `non_canonical_bytes` bytes to the varint representation at the tail of
+// the buffer.
+// `buf` points to the start of the buffer, `p` points to one-past-the-end.
+// Returns the new one-past-the-end pointer.
+uint8_t* AddNonCanonicalBytes(const uint8_t* buf, uint8_t* p,
+                              int non_canonical_bytes) {
   // varint can have a max of 10 bytes.
   while (non_canonical_bytes-- > 0 && p - buf < 10) {
     // Add a dummy byte at the end.
@@ -1186,7 +1187,15 @@
     p[0] = 0;
     ++p;
   }
+  return p;
+}
 
+std::string EncodeEnumValue(int number, int value, int non_canonical_bytes) {
+  uint8_t buf[100];
+  uint8_t* p = buf;
+
+  p = internal::WireFormatLite::WriteEnumToArray(number, value, p);
+  p = AddNonCanonicalBytes(buf, p, non_canonical_bytes);
   return std::string(buf, p);
 }
 
@@ -1201,6 +1210,16 @@
 
   const auto other_field = EncodeOtherField();
 
+  // Encode a boolean field for many different cases and verify that it can be
+  // parsed as expected.
+  // There are:
+  //  - optional/repeated/packed fields
+  //  - field tags that encode in 1/2/3 bytes
+  //  - canonical and non-canonical encodings of the varint
+  //  - last vs not last field
+  //  - label combinations to trigger different parsers: sequential, small
+  //  sequential, non-validated.
+
   constexpr int kInvalidValue = 0x900913;
   auto* ref = obj.GetReflection();
   auto* descriptor = obj.descriptor();
@@ -1226,6 +1245,8 @@
           auto encoded = EncodeEnumValue(field->number(), value_desc->number(),
                                          non_canonical_bytes);
           if (use_tail_field) {
+            // Make sure that fields after this one can be parsed too. ie test
+            // that the "next" jump is correct too.
             encoded += other_field;
           }
 
@@ -1263,5 +1284,64 @@
   }
 }
 
+std::string EncodeBoolValue(int number, bool value, int non_canonical_bytes) {
+  uint8_t buf[100];
+  uint8_t* p = buf;
+
+  p = internal::WireFormatLite::WriteBoolToArray(number, value, p);
+  p = AddNonCanonicalBytes(buf, p, non_canonical_bytes);
+  return std::string(buf, p);
+}
+
+TEST(MESSAGE_TEST_NAME, TestBoolParsers) {
+  UNITTEST::BoolParseTester obj;
+
+  const auto other_field = EncodeOtherField();
+
+  // Encode a boolean field for many different cases and verify that it can be
+  // parsed as expected.
+  // There are:
+  //  - optional/repeated/packed fields
+  //  - field tags that encode in 1/2/3 bytes
+  //  - canonical and non-canonical encodings of the varint
+  //  - last vs not last field
+
+  auto* ref = obj.GetReflection();
+  auto* descriptor = obj.descriptor();
+  for (bool use_tail_field : {false, true}) {
+    SCOPED_TRACE(use_tail_field);
+    for (int non_canonical_bytes = 0; non_canonical_bytes < 10;
+         ++non_canonical_bytes) {
+      SCOPED_TRACE(non_canonical_bytes);
+      for (int i = 0; i < descriptor->field_count(); ++i) {
+        const auto* field = descriptor->field(i);
+        if (field->name() == "other_field") continue;
+        SCOPED_TRACE(field->full_name());
+        for (bool value : {false, true}) {
+          SCOPED_TRACE(value);
+          auto encoded =
+              EncodeBoolValue(field->number(), value, non_canonical_bytes);
+          if (use_tail_field) {
+            // Make sure that fields after this one can be parsed too. ie test
+            // that the "next" jump is correct too.
+            encoded += other_field;
+          }
+
+          EXPECT_TRUE(obj.ParseFromString(encoded));
+          if (field->is_repeated()) {
+            ASSERT_EQ(ref->FieldSize(obj, field), 1);
+            EXPECT_EQ(ref->GetRepeatedBool(obj, field, 0), value);
+          } else {
+            EXPECT_TRUE(ref->HasField(obj, field));
+            EXPECT_EQ(ref->GetBool(obj, field), value);
+          }
+          auto& unknown = ref->GetUnknownFields(obj);
+          ASSERT_EQ(unknown.field_count(), 0);
+        }
+      }
+    }
+  }
+}
+
 }  // namespace protobuf
 }  // namespace google
diff --git a/src/google/protobuf/metadata_lite.h b/src/google/protobuf/metadata_lite.h
index af840e5..d015dc2 100644
--- a/src/google/protobuf/metadata_lite.h
+++ b/src/google/protobuf/metadata_lite.h
@@ -77,15 +77,19 @@
     GOOGLE_DCHECK(!is_message_owned || arena != nullptr);
   }
 
-#if defined(NDEBUG) || defined(_MSC_VER)
+  // To keep the ABI identical between debug and non-debug builds,
+  // the destructor is always defined here even though it may delegate
+  // to a non-inline private method.
+  // (see https://github.com/protocolbuffers/protobuf/issues/9947)
   ~InternalMetadata() {
+#if defined(NDEBUG) || defined(_MSC_VER)
     if (HasMessageOwnedArenaTag()) {
       delete reinterpret_cast<Arena*>(ptr_ - kMessageOwnedArenaTagMask);
     }
-  }
 #else
-  ~InternalMetadata();
+    CheckedDestruct();
 #endif
+  }
 
   template <typename T>
   void Delete() {
@@ -264,6 +268,9 @@
   PROTOBUF_NOINLINE void DoSwap(T* other) {
     mutable_unknown_fields<T>()->Swap(other);
   }
+
+  // Private helper with debug checks for ~InternalMetadata()
+  void CheckedDestruct();
 };
 
 // String Template specializations.
diff --git a/src/google/protobuf/parse_context.h b/src/google/protobuf/parse_context.h
index a7baf4d..c72e19f 100644
--- a/src/google/protobuf/parse_context.h
+++ b/src/google/protobuf/parse_context.h
@@ -617,6 +617,7 @@
 
 PROTOBUF_NODISCARD inline PROTOBUF_ALWAYS_INLINE uint64_t
 RotRight7AndReplaceLowByte(uint64_t res, const char& byte) {
+  // TODO(b/239808098): remove the inline assembly
 #if defined(__x86_64__) && defined(__GNUC__)
   // This will only use one register for `res`.
   // `byte` comes as a reference to allow the compiler to generate code like:
diff --git a/src/google/protobuf/port_def.inc b/src/google/protobuf/port_def.inc
index 2e861d2..ea3d682 100644
--- a/src/google/protobuf/port_def.inc
+++ b/src/google/protobuf/port_def.inc
@@ -154,6 +154,13 @@
 #  define PROTOBUF_GNUC_MIN(x, y) 0
 #endif
 
+#if defined(__clang__) && defined(__clang_major__) && defined(__clang_minor__)
+#define PROTOBUF_CLANG_MIN(x, y) \
+  (__clang_major__ > (x) || __clang_major__ == (x) && __clang_minor__ >= (y))
+#else
+#define PROTOBUF_CLANG_MIN(x, y) 0
+#endif
+
 // Portable check for MSVC minimum version:
 // https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros
 #if defined(_MSC_VER)
@@ -174,11 +181,15 @@
 // Future versions of protobuf will include breaking changes to some APIs.
 // This macro can be set to enable these API changes ahead of time, so that
 // user code can be updated before upgrading versions of protobuf.
-// PROTOBUF_FUTURE_FINAL is used on classes that are historically not marked as
-// final, but that may be marked final in future (breaking) releases.
 
 #ifdef PROTOBUF_FUTURE_BREAKING_CHANGES
-// Used on classes that are historically not marked as final.
+
+// Used to upgrade google::protobuf::MapPair<K, V> to std::pair<const K, V>.
+// Owner: mordberg@
+#define PROTOBUF_FUTURE_MAP_PAIR_UPGRADE 1
+
+// Used on classes that are historically not marked as final, but that may be
+// marked final in future (breaking) releases.
 // Owner: kfm@
 #define PROTOBUF_FUTURE_FINAL final
 
@@ -201,7 +212,7 @@
 #ifdef PROTOBUF_VERSION
 #error PROTOBUF_VERSION was previously defined
 #endif
-#define PROTOBUF_VERSION 3021002
+#define PROTOBUF_VERSION 3021004
 
 #ifdef PROTOBUF_MIN_HEADER_VERSION_FOR_PROTOC
 #error PROTOBUF_MIN_HEADER_VERSION_FOR_PROTOC was previously defined
@@ -666,30 +677,39 @@
 // This experiment is purely for the purpose of gathering data. All code guarded
 // by this flag is supposed to be removed after this experiment.
 #define PROTOBUF_MESSAGE_OWNED_ARENA_EXPERIMENT
+
 #ifdef PROTOBUF_CONSTINIT
 #error PROTOBUF_CONSTINIT was previously defined
 #endif
-#if defined(__cpp_constinit) && !defined(_MSC_VER)
-#define PROTOBUF_CONSTINIT constinit
-#define PROTOBUF_CONSTEXPR constexpr
+
+// Lexan sets both MSV_VER and clang, so handle it with the clang path.
+#if defined(_MSC_VER) && !defined(__clang__)
+// MSVC 17 currently seems to raise an error about constant-initialized pointers.
+# if PROTOBUF_MSC_VER_MIN(1930)
+#  define PROTOBUF_CONSTINIT
+#  define PROTOBUF_CONSTEXPR constexpr
+# endif
+#else
+# if defined(__cpp_constinit) && !defined(__CYGWIN__)
+#  define PROTOBUF_CONSTINIT constinit
+#  define PROTOBUF_CONSTEXPR constexpr
 // Some older Clang versions incorrectly raise an error about
 // constant-initializing weak default instance pointers. Versions 12.0 and
 // higher seem to work, except that XCode 12.5.1 shows the error even though it
 // uses Clang 12.0.5.
-// Clang-cl on Windows raises error also.
-#elif !defined(_MSC_VER) && __has_cpp_attribute(clang::require_constant_initialization) && \
-    ((defined(__APPLE__) && __clang_major__ >= 13) ||                \
-     (!defined(__APPLE__) && __clang_major__ >= 12))
-#define PROTOBUF_CONSTINIT [[clang::require_constant_initialization]]
-#define PROTOBUF_CONSTEXPR constexpr
-#elif PROTOBUF_GNUC_MIN(12, 2)
-#define PROTOBUF_CONSTINIT __constinit
-#define PROTOBUF_CONSTEXPR constexpr
-// MSVC 17 currently seems to raise an error about constant-initialized pointers.
-#elif defined(_MSC_VER) && _MSC_VER >= 1930
-#define PROTOBUF_CONSTINIT
-#define PROTOBUF_CONSTEXPR constexpr
-#else
+# elif !defined(__CYGWIN__) && \
+    __has_cpp_attribute(clang::require_constant_initialization) && \
+    ((defined(__APPLE__) && PROTOBUF_CLANG_MIN(13, 0)) ||             \
+     (!defined(__APPLE__) && PROTOBUF_CLANG_MIN(12, 0)))
+#  define PROTOBUF_CONSTINIT [[clang::require_constant_initialization]]
+#  define PROTOBUF_CONSTEXPR constexpr
+# elif PROTOBUF_GNUC_MIN(12, 2)
+#  define PROTOBUF_CONSTINIT __constinit
+#  define PROTOBUF_CONSTEXPR constexpr
+# endif
+#endif
+
+#ifndef PROTOBUF_CONSTINIT
 #define PROTOBUF_CONSTINIT
 #define PROTOBUF_CONSTEXPR
 #endif
@@ -812,11 +832,22 @@
 #define PROTOBUF_TAIL_CALL_TABLE_PARSER_ENABLED 1
 #endif
 
-#define PROTOBUF_TC_PARAM_DECL                                 \
-  ::google::protobuf::MessageLite *msg, const char *ptr,                 \
-      ::google::protobuf::internal::ParseContext *ctx,                   \
-      const ::google::protobuf::internal::TcParseTableBase *table,       \
-      uint64_t hasbits, ::google::protobuf::internal::TcFieldData data
+// Note that this is performance sensitive: changing the parameters will change
+// the registers used by the ABI calling convention, which subsequently affects
+// register selection logic inside the function.
+// Arguments `msg`, `ptr` and `ctx` are the 1st/2nd/3rd argument to match the
+// signature of ParseLoop.
+//
+// Note for x86_64: `data` must be the third or fourth argument for performance
+// reasons. In order to efficiently read the second byte of `data` we need it to
+// be passed in RDX or RCX.
+#define PROTOBUF_TC_PARAM_DECL                 \
+  ::google::protobuf::MessageLite *msg, const char *ptr, \
+      ::google::protobuf::internal::ParseContext *ctx,   \
+      ::google::protobuf::internal::TcFieldData data,    \
+      const ::google::protobuf::internal::TcParseTableBase *table, uint64_t hasbits
+// PROTOBUF_TC_PARAM_PASS passes values to match PROTOBUF_TC_PARAM_DECL.
+#define PROTOBUF_TC_PARAM_PASS msg, ptr, ctx, data, table, hasbits
 
 #ifdef PROTOBUF_UNUSED
 #error PROTOBUF_UNUSED was previously defined
@@ -938,6 +969,21 @@
 #pragma warning(disable: 4125)
 #endif
 
+#if PROTOBUF_ENABLE_DEBUG_LOGGING_MAY_LEAK_PII
+#define PROTOBUF_DEBUG true
+#else
+#define PROTOBUF_DEBUG false
+#endif
+
+// This `for` allows us to condition the `GOOGLE_LOG` on the define above, so that
+// code can write `PROTOBUF_DLOG(INFO) << ...;` and have it turned off when
+// debug logging is off.
+//
+// This is a `for`, not and `if`, to avoid it accidentally chaining with an
+// `else` below it.
+#define PROTOBUF_DLOG(x) \
+  for (bool b = PROTOBUF_DEBUG; b; b = false) GOOGLE_LOG(x)
+
 // We don't want code outside port_def doing complex testing, so
 // remove our portable condition test macros to nudge folks away from
 // using it themselves.
diff --git a/src/google/protobuf/port_undef.inc b/src/google/protobuf/port_undef.inc
index 4ad9d33..23eb789 100644
--- a/src/google/protobuf/port_undef.inc
+++ b/src/google/protobuf/port_undef.inc
@@ -40,6 +40,7 @@
 #undef PROTOBUF_BUILTIN_BSWAP64
 #undef PROTOBUF_BUILTIN_ATOMIC
 #undef PROTOBUF_GNUC_MIN
+#undef PROTOBUF_CLANG_MIN
 #undef PROTOBUF_MSC_VER_MIN
 #undef PROTOBUF_CPLUSPLUS_MIN
 #undef PROTOBUF_NAMESPACE
@@ -107,9 +108,12 @@
 #undef PROTOBUF_LOCKS_EXCLUDED
 #undef PROTOBUF_NO_THREAD_SAFETY_ANALYSIS
 #undef PROTOBUF_GUARDED_BY
+#undef PROTOBUF_DEBUG
+#undef PROTOBUF_DLOG
 
 #ifdef PROTOBUF_FUTURE_BREAKING_CHANGES
 #undef PROTOBUF_FUTURE_BREAKING_CHANGES
+#undef PROTOBUF_FUTURE_MAP_PAIR_UPGRADE
 #undef PROTOBUF_FUTURE_REMOVE_DEFAULT_FIELD_COMPARATOR
 #undef PROTOBUF_FUTURE_REMOVE_CLEARED_API
 #endif
diff --git a/src/google/protobuf/repeated_field.h b/src/google/protobuf/repeated_field.h
index e504274..b4e4416 100644
--- a/src/google/protobuf/repeated_field.h
+++ b/src/google/protobuf/repeated_field.h
@@ -233,12 +233,6 @@
   // copies data between each other.
   void Swap(RepeatedField* other);
 
-  // Swaps entire contents with "other". Should be called only if the caller can
-  // guarantee that both repeated fields are on the same arena or are on the
-  // heap. Swapping between different arenas is disallowed and caught by a
-  // GOOGLE_DCHECK (see API docs for details).
-  void UnsafeArenaSwap(RepeatedField* other);
-
   // Swaps two elements.
   void SwapElements(int index1, int index2);
 
@@ -321,6 +315,12 @@
                               : rep()->arena;
   }
 
+  // Swaps entire contents with "other". Should be called only if the caller can
+  // guarantee that both repeated fields are on the same arena or are on the
+  // heap. Swapping between different arenas is disallowed and caught by a
+  // GOOGLE_DCHECK (see API docs for details).
+  void UnsafeArenaSwap(RepeatedField* other);
+
   static constexpr int kInitialSize = 0;
   // A note on the representation here (see also comment below for
   // RepeatedPtrFieldBase's struct Rep):
diff --git a/src/google/protobuf/source_context.pb.h b/src/google/protobuf/source_context.pb.h
index fb2e29f..899cfb0 100644
--- a/src/google/protobuf/source_context.pb.h
+++ b/src/google/protobuf/source_context.pb.h
@@ -13,7 +13,7 @@
 #error incompatible with your Protocol Buffer headers. Please update
 #error your headers.
 #endif
-#if 3021002 < PROTOBUF_MIN_PROTOC_VERSION
+#if 3021004 < PROTOBUF_MIN_PROTOC_VERSION
 #error This file was generated by an older version of protoc which is
 #error incompatible with your Protocol Buffer headers. Please
 #error regenerate this file with a newer version of protoc.
diff --git a/src/google/protobuf/struct.pb.h b/src/google/protobuf/struct.pb.h
index 7a0805d..154ea45 100644
--- a/src/google/protobuf/struct.pb.h
+++ b/src/google/protobuf/struct.pb.h
@@ -13,7 +13,7 @@
 #error incompatible with your Protocol Buffer headers. Please update
 #error your headers.
 #endif
-#if 3021002 < PROTOBUF_MIN_PROTOC_VERSION
+#if 3021004 < PROTOBUF_MIN_PROTOC_VERSION
 #error This file was generated by an older version of protoc which is
 #error incompatible with your Protocol Buffer headers. Please
 #error regenerate this file with a newer version of protoc.
diff --git a/src/google/protobuf/stubs/common.h b/src/google/protobuf/stubs/common.h
index 9d90305..c4d2636 100644
--- a/src/google/protobuf/stubs/common.h
+++ b/src/google/protobuf/stubs/common.h
@@ -82,7 +82,7 @@
 
 // The current version, represented as a single integer to make comparison
 // easier:  major * 10^6 + minor * 10^3 + micro
-#define GOOGLE_PROTOBUF_VERSION 3021002
+#define GOOGLE_PROTOBUF_VERSION 3021004
 
 // A suffix string for alpha, beta or rc releases. Empty for stable releases.
 #define GOOGLE_PROTOBUF_VERSION_SUFFIX ""
diff --git a/src/google/protobuf/timestamp.pb.h b/src/google/protobuf/timestamp.pb.h
index 3b5a469..771216e 100644
--- a/src/google/protobuf/timestamp.pb.h
+++ b/src/google/protobuf/timestamp.pb.h
@@ -13,7 +13,7 @@
 #error incompatible with your Protocol Buffer headers. Please update
 #error your headers.
 #endif
-#if 3021002 < PROTOBUF_MIN_PROTOC_VERSION
+#if 3021004 < PROTOBUF_MIN_PROTOC_VERSION
 #error This file was generated by an older version of protoc which is
 #error incompatible with your Protocol Buffer headers. Please
 #error regenerate this file with a newer version of protoc.
diff --git a/src/google/protobuf/type.pb.h b/src/google/protobuf/type.pb.h
index bcf1a44..2555602 100644
--- a/src/google/protobuf/type.pb.h
+++ b/src/google/protobuf/type.pb.h
@@ -13,7 +13,7 @@
 #error incompatible with your Protocol Buffer headers. Please update
 #error your headers.
 #endif
-#if 3021002 < PROTOBUF_MIN_PROTOC_VERSION
+#if 3021004 < PROTOBUF_MIN_PROTOC_VERSION
 #error This file was generated by an older version of protoc which is
 #error incompatible with your Protocol Buffer headers. Please
 #error regenerate this file with a newer version of protoc.
diff --git a/src/google/protobuf/unittest.proto b/src/google/protobuf/unittest.proto
index 4d08b44..7176f09 100644
--- a/src/google/protobuf/unittest.proto
+++ b/src/google/protobuf/unittest.proto
@@ -221,6 +221,12 @@
   reserved "bar", "baz";
 }
 
+enum TestReservedEnumFields {
+  UNKNOWN = 0;
+  reserved 2, 15, 9 to 11;
+  reserved "bar", "baz";
+}
+
 message TestAllExtensions {
   extensions 1 to max;
 }
@@ -1521,3 +1527,20 @@
   optional int32 other_field = 99;
 };
 
+// This message contains different kind of bool fields to exercise the different
+// parsers in table-drived.
+message BoolParseTester {
+  optional bool optional_bool_lowfield = 1;
+  optional bool optional_bool_midfield = 1001;
+  optional bool optional_bool_hifield = 1000001;
+  repeated bool repeated_bool_lowfield = 2;
+  repeated bool repeated_bool_midfield = 1002;
+  repeated bool repeated_bool_hifield = 1000002;
+  repeated bool packed_bool_lowfield = 3 [packed = true];
+  repeated bool packed_bool_midfield = 1003 [packed = true];
+  repeated bool packed_bool_hifield = 1000003 [packed = true];
+
+  // An arbitrary field we can append to to break the runs of repeated fields.
+  optional int32 other_field = 99;
+};
+
diff --git a/src/google/protobuf/util/json_util.cc b/src/google/protobuf/util/json_util.cc
index 9c59d39..04cab9c 100644
--- a/src/google/protobuf/util/json_util.cc
+++ b/src/google/protobuf/util/json_util.cc
@@ -48,10 +48,12 @@
 #include <google/protobuf/util/zero_copy_sink.h>
 #include <google/protobuf/stubs/status_macros.h>
 
+
 // clang-format off
 #include <google/protobuf/port_def.inc>
 // clang-format on
 
+
 namespace google {
 namespace protobuf {
 namespace util {
@@ -62,6 +64,7 @@
                                 io::ZeroCopyInputStream* binary_input,
                                 io::ZeroCopyOutputStream* json_output,
                                 const JsonPrintOptions& options) {
+
   io::CodedInputStream in_stream(binary_input);
   google::protobuf::Type type;
   RETURN_IF_ERROR(resolver->ResolveMessageType(type_url, &type));
@@ -152,6 +155,7 @@
                                 io::ZeroCopyInputStream* json_input,
                                 io::ZeroCopyOutputStream* binary_output,
                                 const JsonParseOptions& options) {
+
   google::protobuf::Type type;
   RETURN_IF_ERROR(resolver->ResolveMessageType(type_url, &type));
   ZeroCopyStreamByteSink sink(binary_output);
@@ -219,6 +223,7 @@
 
 util::Status MessageToJsonString(const Message& message, std::string* output,
                                  const JsonOptions& options) {
+
   const DescriptorPool* pool = message.GetDescriptor()->file()->pool();
   TypeResolver* resolver =
       pool == DescriptorPool::generated_pool()
@@ -235,6 +240,7 @@
 
 util::Status JsonStringToMessage(StringPiece input, Message* message,
                                  const JsonParseOptions& options) {
+
   const DescriptorPool* pool = message->GetDescriptor()->file()->pool();
   TypeResolver* resolver =
       pool == DescriptorPool::generated_pool()
diff --git a/src/google/protobuf/util/json_util.h b/src/google/protobuf/util/json_util.h
index 8fbab5a..6a5a448 100644
--- a/src/google/protobuf/util/json_util.h
+++ b/src/google/protobuf/util/json_util.h
@@ -33,6 +33,7 @@
 #ifndef GOOGLE_PROTOBUF_UTIL_JSON_UTIL_H__
 #define GOOGLE_PROTOBUF_UTIL_JSON_UTIL_H__
 
+
 #include <google/protobuf/stubs/bytestream.h>
 #include <google/protobuf/stubs/status.h>
 #include <google/protobuf/stubs/strutil.h>
diff --git a/src/google/protobuf/util/json_util_test.cc b/src/google/protobuf/util/json_util_test.cc
index 1c72072..be0d39e 100644
--- a/src/google/protobuf/util/json_util_test.cc
+++ b/src/google/protobuf/util/json_util_test.cc
@@ -42,6 +42,7 @@
 #include <google/protobuf/struct.pb.h>
 #include <google/protobuf/timestamp.pb.h>
 #include <google/protobuf/wrappers.pb.h>
+#include <google/protobuf/unittest.pb.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <google/protobuf/stubs/status.h>
@@ -155,7 +156,7 @@
   util::StatusOr<Proto> ToProto(StringPiece json,
                                 JsonParseOptions options = {}) {
     Proto proto;
-    RETURN_IF_ERROR(JsonStringToMessage(json, &proto, options));
+    RETURN_IF_ERROR(ToProto(proto, json, options));
     return proto;
   }
 
@@ -270,13 +271,26 @@
           R"("defaultString":"hello","defaultBytes":"d29ybGQ=","defaultNestedEnum":"BAR",)"
           R"("defaultForeignEnum":"FOREIGN_BAR","defaultImportEnum":"IMPORT_BAR",)"
           R"("defaultStringPiece":"abc","defaultCord":"123"})"));
+
+  // The ESF parser actually gets this wrong, and serializes floats whose
+  // default value is non-finite as 0. We make sure to reproduce this bug.
+  EXPECT_THAT(
+      ToJson(protobuf_unittest::TestExtremeDefaultValues(), options),
+      IsOkAndHolds(
+          R"({"escapedBytes":"XDAwMFwwMDFcMDA3XDAxMFwwMTRcblxyXHRcMDEzXFxcJ1wiXDM3Ng==")"
+          R"(,"largeUint32":4294967295,"largeUint64":"18446744073709551615",)"
+          R"("smallInt32":-2147483647,"smallInt64":"-9223372036854775807")"
+          R"(,"reallySmallInt32":-2147483648,"reallySmallInt64":"-9223372036854775808",)"
+          R"("utf8String":"ሴ","zeroFloat":0,"oneFloat":1,"smallFloat":1.5,)"
+          R"("negativeOneFloat":-1,"negativeFloat":-1.5,"largeFloat":2e+08,)"
+          R"("smallNegativeFloat":-8e-28,"infDouble":0,"negInfDouble":0)"
+          R"(,"nanDouble":0,"infFloat":0,"negInfFloat":0,"nanFloat":0)"
+          R"(,"cppTrigraph":"? ? ?? ?? ??? ??/ ??-","stringWithZero":"hel\u0000lo")"
+          R"(,"bytesWithZero":"d29yXDAwMGxk","stringPieceWithZero":"ab\u0000c")"
+          R"(,"cordWithZero":"12\u00003","replacementString":"${unknown}"})"));
 }
 
 TEST_P(JsonTest, TestPreserveProtoFieldNames) {
-  if (GetParam() == Codec::kResolver) {
-    GTEST_SKIP();
-  }
-
   TestMessage m;
   m.mutable_message_value();
 
@@ -286,6 +300,22 @@
 
 }
 
+TEST_P(JsonTest, Camels) {
+  protobuf_unittest::TestCamelCaseFieldNames m;
+  m.set_stringfield("sTRINGfIELD");
+
+  EXPECT_THAT(ToJson(m), IsOkAndHolds(R"({"StringField":"sTRINGfIELD"})"));
+}
+
+TEST_P(JsonTest, EvilString) {
+  auto m = ToProto<TestMessage>(R"json(
+    {"string_value": ")json"
+                                "\n\r\b\f\1\2\3"
+                                "\"}");
+  ASSERT_OK(m);
+  EXPECT_EQ(m->string_value(), "\n\r\b\f\1\2\3");
+}
+
 TEST_P(JsonTest, TestAlwaysPrintEnumsAsInts) {
   TestMessage orig;
   orig.set_enum_value(proto3::BAR);
@@ -378,6 +408,7 @@
       "repeatedEnumValue": [1, "FOO"],
       "repeatedMessageValue": [
         {"value": 40},
+        {},
         {"value": 96}
       ]
     }
@@ -406,9 +437,10 @@
   EXPECT_THAT(m->repeated_string_value(), ElementsAre("foo", "bar ", ""));
   EXPECT_THAT(m->repeated_enum_value(), ElementsAre(proto3::BAR, proto3::FOO));
 
-  ASSERT_THAT(m->repeated_message_value(), SizeIs(2));
+  ASSERT_THAT(m->repeated_message_value(), SizeIs(3));
   EXPECT_EQ(m->repeated_message_value(0).value(), 40);
-  EXPECT_EQ(m->repeated_message_value(1).value(), 96);
+  EXPECT_EQ(m->repeated_message_value(1).value(), 0);
+  EXPECT_EQ(m->repeated_message_value(2).value(), 96);
 
   EXPECT_THAT(
       ToJson(*m),
@@ -419,7 +451,7 @@
           R"("messageValue":{"value":2048},"repeatedBoolValue":[true],"repeatedInt32Value":[0,-42])"
           R"(,"repeatedUint64Value":["1","2"],"repeatedDoubleValue":[1.5,-2],)"
           R"("repeatedStringValue":["foo","bar ",""],"repeatedEnumValue":["BAR","FOO"],)"
-          R"("repeatedMessageValue":[{"value":40},{"value":96}]})"));
+          R"("repeatedMessageValue":[{"value":40},{},{"value":96}]})"));
 }
 
 TEST_P(JsonTest, CurseOfAtob) {
@@ -434,6 +466,15 @@
                           false, true));
 }
 
+TEST_P(JsonTest, FloatPrecision) {
+  google::protobuf::Value v;
+  v.mutable_list_value()->add_values()->set_number_value(0.9900000095367432);
+  v.mutable_list_value()->add_values()->set_number_value(0.8799999952316284);
+
+  EXPECT_THAT(ToJson(v),
+              IsOkAndHolds("[0.99000000953674316,0.87999999523162842]"));
+}
+
 TEST_P(JsonTest, ParseLegacySingleRepeatedField) {
   auto m = ToProto<TestMessage>(R"json({
     "repeatedInt32Value": 1997,
@@ -470,8 +511,10 @@
 
 TEST_P(JsonTest, RepeatedMapKey) {
   EXPECT_THAT(ToProto<TestMap>(R"json({
-    "twiceKey": 0,
-    "twiceKey": 1
+    "string_map": {
+      "twiceKey": 0,
+      "twiceKey": 1
+    }
   })json"), StatusIs(util::StatusCode::kInvalidArgument));
 }
 
@@ -700,6 +743,55 @@
           R"("int32Value":5,"stringValue":"expected_value","messageValue":{"value":1}}}})"));
 }
 
+TEST_P(JsonTest, TestParsingBrokenAny) {
+  auto m = ToProto<TestAny>(R"json(
+    {
+      "value": {}
+    }
+  )json");
+  ASSERT_OK(m);
+  EXPECT_EQ(m->value().type_url(), "");
+  EXPECT_EQ(m->value().value(), "");
+
+  EXPECT_THAT(ToProto<TestAny>(R"json(
+    {
+      "value": {
+        "type_url": "garbage",
+        "value": "bW9yZSBnYXJiYWdl"
+      }
+    }
+  )json"),
+              StatusIs(util::StatusCode::kInvalidArgument));
+}
+
+TEST_P(JsonTest, TestFlatList) {
+  auto m = ToProto<TestMessage>(R"json(
+    {
+      "repeatedInt32Value": [[[5]], [6]]
+    }
+  )json");
+  ASSERT_OK(m);
+  EXPECT_THAT(m->repeated_int32_value(), ElementsAre(5, 6));
+
+  // The above flatteing behavior is suppressed for google::protobuf::ListValue.
+  auto m2 = ToProto<google::protobuf::Value>(R"json(
+    {
+      "repeatedInt32Value": [[[5]], [6]]
+    }
+  )json");
+  ASSERT_OK(m2);
+  auto fields = m2->struct_value().fields();
+  auto list = fields["repeatedInt32Value"].list_value();
+  EXPECT_EQ(list.values(0)
+                .list_value()
+                .values(0)
+                .list_value()
+                .values(0)
+                .number_value(),
+            5);
+  EXPECT_EQ(list.values(1).list_value().values(0).number_value(), 6);
+}
+
 TEST_P(JsonTest, ParseWrappers) {
   auto m = ToProto<TestWrapper>(R"json(
     {
@@ -757,6 +849,14 @@
   EXPECT_EQ(t.string_value(), "expected_value");
 }
 
+TEST_P(JsonTest, TestHugeBareString) {
+  auto m = ToProto<TestMessage>(R"json({
+    "int64Value": 6009652459062546621
+  })json");
+  ASSERT_OK(m);
+  EXPECT_EQ(m->int64_value(), 6009652459062546621);
+}
+
 TEST_P(JsonTest, TestParsingUnknownEnumsProto2) {
   StringPiece input = R"json({"ayuLmao": "UNKNOWN_VALUE"})json";
 
@@ -895,6 +995,39 @@
 
   EXPECT_EQ(m2->value().seconds(), 4);
   EXPECT_EQ(m2->value().nanos(), 5);
+
+  // Negative duration with zero seconds.
+  auto m3 = ToProto<proto3::TestDuration>(R"json(
+    {
+      "value": {"nanos": -5},
+    }
+  )json");
+  ASSERT_OK(m3);
+  EXPECT_EQ(m3->value().seconds(), 0);
+  EXPECT_EQ(m3->value().nanos(), -5);
+  EXPECT_THAT(ToJson(m3->value()), IsOkAndHolds("\"-0.000000005s\""));
+
+  // Negative duration with zero nanos.
+  auto m4 = ToProto<proto3::TestDuration>(R"json(
+    {
+      "value": {"seconds": -5},
+    }
+  )json");
+  ASSERT_OK(m4);
+  EXPECT_EQ(m4->value().seconds(), -5);
+  EXPECT_EQ(m4->value().nanos(), 0);
+  EXPECT_THAT(ToJson(m4->value()), IsOkAndHolds("\"-5s\""));
+
+  // Parse "0.5s" as a JSON string.
+  auto m5 = ToProto<proto3::TestDuration>(R"json(
+    {
+      "value": "0.5s",
+    }
+  )json");
+  ASSERT_OK(m5);
+  EXPECT_EQ(m5->value().seconds(), 0);
+  EXPECT_EQ(m5->value().nanos(), 500000000);
+  EXPECT_THAT(ToJson(m5->value()), IsOkAndHolds("\"0.500s\""));
 }
 
 // These tests are not exhaustive; tests in //third_party/protobuf/conformance
@@ -991,13 +1124,40 @@
 
   ASSERT_THAT(m2->repeated_value(), SizeIs(1));
   EXPECT_TRUE(m2->repeated_value(0).has_null_value());
+
+  m2->Clear();
+  m2->mutable_repeated_value();  // Materialize an empty singular Value.
+  m2->add_repeated_value();
+  m2->add_repeated_value()->set_string_value("solitude");
+  m2->add_repeated_value();
+  EXPECT_THAT(ToJson(*m2), IsOkAndHolds(R"({"repeatedValue":["solitude"]})"));
 }
 
-TEST_P(JsonTest, DISABLED_HtmlEscape) {
+TEST_P(JsonTest, ListList) {
+  auto m = ToProto<proto3::TestListValue>(R"json({
+    "repeated_value": [["ayy", "lmao"]]
+  })json");
+  ASSERT_OK(m);
+
+  EXPECT_EQ(m->repeated_value(0).values(0).string_value(), "ayy");
+  EXPECT_EQ(m->repeated_value(0).values(1).string_value(), "lmao");
+
+  m = ToProto<proto3::TestListValue>(R"json({
+    "repeated_value": [{
+      "values": ["ayy", "lmao"]
+    }]
+  })json");
+  ASSERT_OK(m);
+
+  EXPECT_EQ(m->repeated_value(0).values(0).string_value(), "ayy");
+  EXPECT_EQ(m->repeated_value(0).values(1).string_value(), "lmao");
+}
+
+TEST_P(JsonTest, HtmlEscape) {
   TestMessage m;
   m.set_string_value("</script>");
   EXPECT_THAT(ToJson(m),
-              IsOkAndHolds("{\"stringValue\":\"\\u003c/script\\u003e\"}"));
+              IsOkAndHolds(R"({"stringValue":"\u003c/script\u003e"})"));
 }
 
 }  // namespace
diff --git a/src/google/protobuf/util/message_differencer.h b/src/google/protobuf/util/message_differencer.h
index 4df3152..c8291c1 100644
--- a/src/google/protobuf/util/message_differencer.h
+++ b/src/google/protobuf/util/message_differencer.h
@@ -242,17 +242,17 @@
 
     // Reports that a field has been added into Message2.
     virtual void ReportAdded(const Message& message1, const Message& message2,
-                             const std::vector<SpecificField>& field_path) = 0;
+                             const std::vector<SpecificField>& field_path) {}
 
     // Reports that a field has been deleted from Message1.
     virtual void ReportDeleted(
         const Message& message1, const Message& message2,
-        const std::vector<SpecificField>& field_path) = 0;
+        const std::vector<SpecificField>& field_path) {}
 
     // Reports that the value of a field has been modified.
     virtual void ReportModified(
         const Message& message1, const Message& message2,
-        const std::vector<SpecificField>& field_path) = 0;
+        const std::vector<SpecificField>& field_path) {}
 
     // Reports that a repeated field has been moved to another location.  This
     // only applies when using TreatAsSet or TreatAsMap()  -- see below. Also
diff --git a/src/google/protobuf/wire_format_lite.h b/src/google/protobuf/wire_format_lite.h
index a7e64bf..8f38f7c 100644
--- a/src/google/protobuf/wire_format_lite.h
+++ b/src/google/protobuf/wire_format_lite.h
@@ -53,7 +53,6 @@
 #include <google/protobuf/message_lite.h>
 #include <google/protobuf/repeated_field.h>
 
-// Do UTF-8 validation on string type in Debug build only
 #ifndef NDEBUG
 #define GOOGLE_PROTOBUF_UTF8_VALIDATION_ENABLED
 #endif
diff --git a/src/google/protobuf/wrappers.pb.h b/src/google/protobuf/wrappers.pb.h
index 72304a5..f629e5e 100644
--- a/src/google/protobuf/wrappers.pb.h
+++ b/src/google/protobuf/wrappers.pb.h
@@ -13,7 +13,7 @@
 #error incompatible with your Protocol Buffer headers. Please update
 #error your headers.
 #endif
-#if 3021002 < PROTOBUF_MIN_PROTOC_VERSION
+#if 3021004 < PROTOBUF_MIN_PROTOC_VERSION
 #error This file was generated by an older version of protoc which is
 #error incompatible with your Protocol Buffer headers. Please
 #error regenerate this file with a newer version of protoc.
diff --git a/tests.sh b/tests.sh
index f2b481c..6508674 100755
--- a/tests.sh
+++ b/tests.sh
@@ -45,44 +45,6 @@
   PPROF_PATH=/usr/bin/google-pprof HEAPCHECK=strict ./protobuf-test
 }
 
-build_cpp_distcheck() {
-  grep -q -- "-Og" src/Makefile.am &&
-    echo "The -Og flag is incompatible with Clang versions older than 4.0." &&
-    exit 1
-
-  # Initialize any submodules.
-  git submodule update --init --recursive
-  ./autogen.sh
-  ./configure
-  make dist
-
-  # List all files that should be included in the distribution package.
-  git ls-files | grep "^\(java\|python\|objectivec\|csharp\|ruby\|php\|cmake\|examples\|src/google/protobuf/.*\.proto\)" |\
-    grep -v ".gitignore" | grep -v "java/lite/proguard.pgcfg" |\
-    grep -v "python/compatibility_tests" | grep -v "python/docs" | grep -v "python/.repo-metadata.json" |\
-    grep -v "python/protobuf_distutils" | grep -v "csharp/compatibility_tests" > dist.lst
-  # Unzip the dist tar file.
-  DIST=`ls *.tar.gz`
-  tar -xf $DIST
-  cd ${DIST//.tar.gz}
-  # Check if every file exists in the dist tar file.
-  FILES_MISSING=""
-  for FILE in $(<../dist.lst); do
-    [ -f "$FILE" ] || {
-      echo "$FILE is not found!"
-      FILES_MISSING="$FILE $FILES_MISSING"
-    }
-  done
-  cd ..
-  if [ ! -z "$FILES_MISSING" ]; then
-    echo "Missing files in EXTRA_DIST: $FILES_MISSING"
-    exit 1
-  fi
-
-  # Do the regular dist-check for C++.
-  make distcheck -j$(nproc)
-}
-
 build_dist_install() {
   # Create a symlink pointing to python2 and put it at the beginning of $PATH.
   # This is necessary because the googletest build system involves a Python
@@ -490,47 +452,8 @@
   test_php_c
 }
 
-build_php7.0_mac() {
+build_php_mac() {
   internal_build_cpp
-  # Install PHP
-  curl -s https://php-osx.liip.ch/install.sh | bash -s 7.0
-  PHP_FOLDER=`find /usr/local -type d -name "php5-7.0*"`  # The folder name may change upon time
-  test ! -z "$PHP_FOLDER"
-  export PATH="$PHP_FOLDER/bin:$PATH"
-
-  # Install Composer
-  wget https://getcomposer.org/download/2.0.13/composer.phar --progress=dot:mega -O /usr/local/bin/composer
-  chmod a+x /usr/local/bin/composer
-
-  # Install valgrind
-  echo "#! /bin/bash" > valgrind
-  chmod ug+x valgrind
-  sudo mv valgrind /usr/local/bin/valgrind
-
-  # Test
-  test_php_c
-}
-
-build_php7.3_mac() {
-  internal_build_cpp
-  # Install PHP
-  # We can't test PHP 7.4 with these binaries yet:
-  #   https://github.com/liip/php-osx/issues/276
-  curl -s https://php-osx.liip.ch/install.sh | bash -s 7.3
-  PHP_FOLDER=`find /usr/local -type d -name "php5-7.3*"`  # The folder name may change upon time
-  test ! -z "$PHP_FOLDER"
-  export PATH="$PHP_FOLDER/bin:$PATH"
-
-  # Install Composer
-  wget https://getcomposer.org/download/2.0.13/composer.phar --progress=dot:mega -O /usr/local/bin/composer
-  chmod a+x /usr/local/bin/composer
-
-  # Install valgrind
-  echo "#! /bin/bash" > valgrind
-  chmod ug+x valgrind
-  sudo mv valgrind /usr/local/bin/valgrind
-
-  # Test
   test_php_c
 }
 
@@ -578,7 +501,6 @@
 if [ "$#" -ne 1 ]; then
   echo "
 Usage: $0 { cpp |
-            cpp_distcheck |
             csharp |
             java_jdk7 |
             java_oracle7 |
diff --git a/version.json b/version.json
index e50d633..4244127 100644
--- a/version.json
+++ b/version.json
@@ -1,17 +1,17 @@
 {
     "main": {
-        "protoc_version": "21-dev",
+        "protoc_version": "22-dev",
         "lts": false,
-        "date": "2022-04-22",
+        "date": "2022-07-21",
         "languages": {
-            "cpp": "3.21-dev",
-            "csharp": "3.21-dev",
-            "java": "3.21-dev",
-            "javascript": "3.21-dev",
-            "objectivec": "3.21-dev",
-            "php": "3.21-dev",
-            "python": "4.21-dev",
-            "ruby": "3.21-dev"
+            "cpp": "3.22-dev",
+            "csharp": "3.22-dev",
+            "java": "3.22-dev",
+            "javascript": "3.22-dev",
+            "objectivec": "3.22-dev",
+            "php": "3.22-dev",
+            "python": "4.22-dev",
+            "ruby": "3.22-dev"
         }
     }
 }