Reorganize upb file structure

This change moves almost everything in the `upb/` directory up one level, so
that for example `upb/upb/generated_code_support.h` becomes just
`upb/generated_code_support.h`. The only exceptions I made to this were that I
left `upb/cmake` and `upb/BUILD` where they are, mostly because that avoids
conflict with other files and the current locations seem reasonable for now.

The `python/` directory is a little bit of a challenge because we had to merge
the existing directory there with `upb/python/`. I made `upb/python/BUILD` into
the BUILD file for the merged directory, and it effectively loads the contents
of the other BUILD file via `python/build_targets.bzl`, but I plan to clean
this up soon.

PiperOrigin-RevId: 568651768
diff --git a/upbc/BUILD b/upbc/BUILD
new file mode 100644
index 0000000..399a14e
--- /dev/null
+++ b/upbc/BUILD
@@ -0,0 +1,336 @@
+# Copyright (c) 2009-2021, Google LLC
+# All rights reserved.
+#
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file or at
+# https://developers.google.com/open-source/licenses/bsd
+
+load(
+    "//bazel:build_defs.bzl",
+    "UPB_DEFAULT_COPTS",
+    "UPB_DEFAULT_CPPOPTS",
+)
+load(
+    "//bazel:upb_minitable_proto_library.bzl",
+    "upb_minitable_proto_library",
+)
+load(
+    "//bazel:upb_proto_library.bzl",
+    "upb_proto_library",
+    "upb_proto_reflection_library",
+)
+load(
+    "//upbc:bootstrap_compiler.bzl",
+    "bootstrap_cc_binary",
+    "bootstrap_cc_library",
+    "bootstrap_upb_proto_library",
+)
+
+# begin:google_only
+# package(default_applicable_licenses = ["//upb:license"])
+# end:google_only
+
+licenses(["notice"])
+
+proto_library(
+    name = "code_generator_request",
+    srcs = ["code_generator_request.proto"],
+    visibility = ["//upb:friends"],
+    deps = ["//:compiler_plugin_proto"],
+)
+
+upb_proto_library(
+    name = "code_generator_request_upb_proto",
+    visibility = ["//upb:friends"],
+    deps = [":code_generator_request"],
+)
+
+upb_proto_reflection_library(
+    name = "code_generator_request_upb_proto_reflection",
+    visibility = ["//upb:friends"],
+    deps = [":code_generator_request"],
+)
+
+upb_minitable_proto_library(
+    name = "code_generator_request_upb_minitable_proto",
+    visibility = ["//upb:friends"],
+    deps = [":code_generator_request"],
+)
+
+bootstrap_upb_proto_library(
+    name = "plugin_upb_proto",
+    base_dir = "",
+    # TODO: Export 'net/proto2/proto/descriptor.upb.h' and remove "-layering_check".
+    features = ["-layering_check"],
+    google3_src_files = [
+        "net/proto2/compiler/proto/profile.proto",
+        "third_party/protobuf/compiler/plugin.proto",
+    ],
+    google3_src_rules = [
+        "//net/proto2/proto:descriptor_proto_source",
+        "//net/proto2/compiler/proto:profile.proto",
+        "//src/google/protobuf/compiler:plugin_proto_source",
+    ],
+    oss_src_files = ["google/protobuf/compiler/plugin.proto"],
+    oss_src_rules = [
+        "//:descriptor_proto_srcs",
+        "//src/google/protobuf/compiler:plugin_proto_src",
+    ],
+    oss_strip_prefix = "third_party/protobuf/github/bootstrap/src",
+    proto_lib_deps = ["//:compiler_plugin_proto"],
+    visibility = ["//upb:friends"],
+    deps = ["//upb:descriptor_upb_proto"],
+)
+
+upb_proto_reflection_library(
+    name = "plugin_upb_proto_reflection",
+    visibility = ["//upb:friends"],
+    deps = ["//:compiler_plugin_proto"],
+)
+
+bootstrap_cc_library(
+    name = "common",
+    srcs = [
+        "common.cc",
+    ],
+    hdrs = [
+        "common.h",
+    ],
+    bootstrap_deps = [
+        "//upb:reflection",
+    ],
+    copts = UPB_DEFAULT_CPPOPTS,
+    visibility = ["//protos_generator:__pkg__"],
+    deps = [
+        "//upb:mini_table",
+        "//upb:mini_table_internal",
+        "//upb:port",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+bootstrap_cc_library(
+    name = "file_layout",
+    srcs = [
+        "file_layout.cc",
+    ],
+    hdrs = [
+        "file_layout.h",
+    ],
+    bootstrap_deps = [
+        ":common",
+        "//upb:reflection",
+        "//upb:descriptor_upb_proto",
+    ],
+    copts = UPB_DEFAULT_CPPOPTS,
+    visibility = ["//protos_generator:__pkg__"],
+    deps = [
+        "//upb:base",
+        "//upb:mini_descriptor",
+        "//upb:mini_table",
+        "//upb:mini_table_internal",
+        "//upb:port",
+        "@com_google_absl//absl/container:flat_hash_map",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_library(
+    name = "keywords",
+    srcs = [
+        "keywords.cc",
+    ],
+    hdrs = [
+        "keywords.h",
+    ],
+    copts = UPB_DEFAULT_CPPOPTS,
+    visibility = ["//protos_generator:__pkg__"],
+)
+
+bootstrap_cc_library(
+    name = "plugin",
+    hdrs = [
+        "plugin.h",
+    ],
+    bootstrap_deps = [
+        ":plugin_upb_proto",
+        "//upb:descriptor_upb_proto",
+        "//upb:reflection",
+    ],
+    copts = UPB_DEFAULT_CPPOPTS,
+    visibility = ["//protos_generator:__pkg__"],
+    deps = [
+        "//upb:port",
+        "@com_google_absl//absl/container:flat_hash_set",
+        "@com_google_absl//absl/log:absl_check",
+        "@com_google_absl//absl/log:absl_log",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+bootstrap_cc_library(
+    name = "names",
+    srcs = [
+        "names.cc",
+    ],
+    hdrs = [
+        "names.h",
+    ],
+    bootstrap_deps = [
+        "//upb:reflection",
+    ],
+    copts = UPB_DEFAULT_CPPOPTS,
+    visibility = ["//protos_generator:__pkg__"],
+    deps = [
+        "//:protobuf",
+        "//src/google/protobuf/compiler:code_generator",
+        "@com_google_absl//absl/base:core_headers",
+        "@com_google_absl//absl/container:flat_hash_map",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_binary(
+    name = "libupbc.so",
+    srcs = ["upbc_so.c"],
+    copts = UPB_DEFAULT_COPTS + ["-DUPB_BUILD_API"],
+    linkshared = 1,
+    linkstatic = 1,
+    visibility = ["//visibility:public"],
+    deps = [
+        ":upbdev",
+        "//upb:port",
+    ],
+)
+
+cc_library(
+    name = "upbdev",
+    srcs = [
+        "code_generator_request.c",
+        "code_generator_request.h",
+        "get_used_fields.c",
+        "upbdev.c",
+    ],
+    hdrs = [
+        "get_used_fields.h",
+        "upbdev.h",
+    ],
+    copts = UPB_DEFAULT_COPTS,
+    visibility = ["//visibility:private"],
+    deps = [
+        ":code_generator_request_upb_proto",
+        ":code_generator_request_upb_proto_reflection",
+        ":plugin_upb_proto",
+        ":plugin_upb_proto_reflection",
+        "//upb:base",
+        "//upb:descriptor_upb_proto",
+        "//upb:json",
+        "//upb:mem",
+        "//upb:mini_descriptor",
+        "//upb:mini_table",
+        "//upb:port",
+        "//upb:reflection",
+        "//upb:reflection_internal",
+        "//upb:wire",
+    ],
+)
+
+bootstrap_cc_binary(
+    name = "protoc-gen-upb",
+    srcs = ["protoc-gen-upb.cc"],
+    bootstrap_deps = [
+        ":common",
+        ":file_layout",
+        ":names",
+        ":plugin",
+        ":plugin_upb_proto",
+        "//upb:descriptor_upb_proto",
+        "//upb:reflection",
+    ],
+    copts = UPB_DEFAULT_CPPOPTS,
+    visibility = ["//visibility:public"],
+    deps = [
+        "//upb:base",
+        "//upb:mem",
+        "//upb:mini_table_internal",
+        "//upb:port",
+        "//upb:wire_types",
+        "@com_google_absl//absl/container:flat_hash_map",
+        "@com_google_absl//absl/container:flat_hash_set",
+        "@com_google_absl//absl/log:absl_check",
+        "@com_google_absl//absl/log:absl_log",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+bootstrap_cc_binary(
+    name = "protoc-gen-upb_minitable",
+    srcs = ["protoc-gen-upb_minitable.cc"],
+    bootstrap_deps = [
+        ":common",
+        ":file_layout",
+        ":names",
+        ":plugin",
+        ":plugin_upb_proto",
+        "//upb:descriptor_upb_proto",
+        "//upb:reflection",
+    ],
+    copts = UPB_DEFAULT_CPPOPTS,
+    visibility = ["//visibility:public"],
+    deps = [
+        "//upb:base",
+        "//upb:mem",
+        "//upb:mini_table_internal",
+        "//upb:port",
+        "//upb:wire_types",
+        "@com_google_absl//absl/container:flat_hash_map",
+        "@com_google_absl//absl/container:flat_hash_set",
+        "@com_google_absl//absl/log:absl_check",
+        "@com_google_absl//absl/log:absl_log",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_binary(
+    name = "protoc-gen-upbdefs",
+    srcs = [
+        "protoc-gen-upbdefs.cc",
+    ],
+    copts = UPB_DEFAULT_CPPOPTS,
+    # To work around the following link error from ABSL:
+    # /usr/bin/x86_64-linux-gnu-ld: bazel-out/k8-opt-exec-2B5CBBC6-ST-c1776f9924ec/bin/external/com_google_absl/absl/time/libtime.a(duration.o): undefined reference to symbol 'floor@@GLIBC_2.2.5'
+    # /usr/bin/x86_64-linux-gnu-ld: /opt/manylinux/2014/x86_64/lib64/libm.so.6: error adding symbols: DSO missing from command line
+    # clang-14: error: linker command failed with exit code 1 (use -v to see invocation)
+    linkopts = ["-lm"],
+    visibility = ["//visibility:public"],
+    deps = [
+        ":common",
+        ":file_layout",
+        ":plugin",
+        "//upb:descriptor_upb_proto",
+        "//upb:reflection",
+        "//upb/util:def_to_proto",
+    ],
+)
+
+cc_binary(
+    name = "protoc-gen-upbdev",
+    srcs = [
+        "protoc-gen-upbdev.cc",
+        "subprocess.cc",
+        "subprocess.h",
+    ],
+    copts = UPB_DEFAULT_CPPOPTS,
+    target_compatible_with = select({
+        "@platforms//os:windows": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    }),
+    visibility = ["//visibility:public"],
+    deps = [
+        ":plugin_upb_proto",
+        ":upbdev",
+        "//upb:port",
+        "@com_google_absl//absl/log:absl_log",
+        "@com_google_absl//absl/strings",
+    ],
+)
diff --git a/upbc/bootstrap_compiler.bzl b/upbc/bootstrap_compiler.bzl
new file mode 100644
index 0000000..fcc1102
--- /dev/null
+++ b/upbc/bootstrap_compiler.bzl
@@ -0,0 +1,189 @@
+"""Macros that implement bootstrapping for the upb code generator."""
+
+load(
+    "//bazel:upb_proto_library.bzl",
+    "upb_proto_library",
+)
+load(
+    "//upb/cmake:build_defs.bzl",
+    "staleness_test",
+)
+
+_stages = ["_stage0", "_stage1", ""]
+_protoc = "//:protoc"
+_upbc_base = "//upbc:protoc-gen-"
+
+# begin:google_only
+# _is_google3 = True
+# _extra_proto_path = ""
+# end:google_only
+
+# begin:github_only
+_is_google3 = False
+_extra_proto_path = "-I$$(dirname $(location @com_google_protobuf//:descriptor_proto_srcs))/../.. "
+# end:github_only
+
+def _upbc(generator, stage):
+    return _upbc_base + generator + _stages[stage]
+
+def bootstrap_cc_library(name, visibility, deps, bootstrap_deps, **kwargs):
+    for stage in _stages:
+        stage_visibility = visibility if stage == "" else ["//upbc:__pkg__"]
+        native.cc_library(
+            name = name + stage,
+            deps = deps + [dep + stage for dep in bootstrap_deps],
+            visibility = stage_visibility,
+            **kwargs
+        )
+
+def bootstrap_cc_binary(name, deps, bootstrap_deps, **kwargs):
+    for stage in _stages:
+        native.cc_binary(
+            name = name + stage,
+            deps = deps + [dep + stage for dep in bootstrap_deps],
+            **kwargs
+        )
+
+def _generated_srcs_for_suffix(prefix, srcs, suffix):
+    return [prefix + "/" + src[:-len(".proto")] + suffix for src in srcs]
+
+def _generated_srcs_for_generator(prefix, srcs, generator):
+    ret = _generated_srcs_for_suffix(prefix, srcs, ".{}.h".format(generator))
+
+    if not (generator == "upb" and prefix.endswith("stage1")):
+        ret += _generated_srcs_for_suffix(prefix, srcs, ".{}.c".format(generator))
+    return ret
+
+def _generated_srcs(prefix, srcs):
+    return _generated_srcs_for_generator(prefix, srcs, "upb")
+
+def _stage0_proto_staleness_test(name, base_dir, src_files, src_rules, strip_prefix):
+    native.genrule(
+        name = name + "_generate_bootstrap",
+        srcs = src_rules,
+        outs = _generated_srcs("bootstrap_generated_sources/" + base_dir + "stage0", src_files),
+        tools = [_protoc, _upbc("upb", 0)],
+        cmd =
+            "$(location " + _protoc + ") " +
+            "-I$(GENDIR)/" + strip_prefix + " " + _extra_proto_path +
+            "--plugin=protoc-gen-upb=$(location " + _upbc("upb", 0) + ") " +
+            "--upb_out=bootstrap_upb:$(@D)/bootstrap_generated_sources/" + base_dir + "stage0 " +
+            " ".join(src_files),
+    )
+
+    staleness_test(
+        name = name + "_staleness_test",
+        outs = _generated_srcs(base_dir + "stage0", src_files),
+        generated_pattern = "bootstrap_generated_sources/%s",
+        target_files = native.glob([base_dir + "stage0/**"]),
+        # To avoid skew problems for descriptor.proto/pluging.proto between
+        # GitHub repos.  It's not critical that the checked-in protos are up to
+        # date for every change, they just needs to be complete enough to have
+        # everything needed by the code generator itself.
+        tags = ["manual"],
+    )
+
+def _generate_stage1_proto(name, base_dir, src_files, src_rules, generator, kwargs):
+    native.genrule(
+        name = "gen_{}_{}_stage1".format(name, generator),
+        srcs = src_rules,
+        outs = _generated_srcs_for_generator(base_dir + "stage1", src_files, generator),
+        cmd = "$(location " + _protoc + ") " +
+              "--plugin=protoc-gen-" + generator +
+              "=$(location " + _upbc(generator, 0) + ") " + _extra_proto_path +
+              "--" + generator + "_out=$(RULEDIR)/" + base_dir + "stage1 " +
+              " ".join(src_files),
+        visibility = ["//upbc:__pkg__"],
+        tools = [
+            _protoc,
+            _upbc(generator, 0),
+        ],
+        **kwargs
+    )
+
+def bootstrap_upb_proto_library(
+        name,
+        base_dir,
+        google3_src_files,
+        google3_src_rules,
+        oss_src_files,
+        oss_src_rules,
+        oss_strip_prefix,
+        proto_lib_deps,
+        visibility,
+        deps = [],
+        **kwargs):
+    """A version of upb_proto_library() that is augmented to allow for bootstrapping the compiler.
+
+    Args:
+        name: Name of this rule.  This name will resolve to a upb_proto_library().
+        base_dir: The directory that all generated files should be placed under.
+        google3_src_files: Google3 filenames of .proto files that should be built by this rule.
+          The names should be relative to the depot base.
+        google3_src_rules: Target names of the Blaze rules that will provide these filenames.
+        oss_src_files: OSS filenames of .proto files that should be built by this rule.
+        oss_src_rules: Target names of the Bazel rules that will provide these filenames.
+        oss_strip_prefix: Prefix that should be stripped from OSS file names.
+        proto_lib_deps: proto_library() rules that we will use to build the protos when we are
+          not bootstrapping.
+        visibility: Visibility list for the final upb_proto_library() rule.  Bootstrapping rules
+          will always be hidden, and will not honor the visibility parameter passed here.
+        deps: other bootstrap_upb_proto_library() rules that this one depends on.
+        **kwargs: Other arguments that will be passed through to cc_library(), genrule(), and
+          upb_proto_library().
+    """
+    _stage0_proto_staleness_test(name, base_dir, oss_src_files, oss_src_rules, oss_strip_prefix)
+
+    # stage0 uses checked-in protos, and has no MiniTable.
+    native.cc_library(
+        name = name + "_stage0",
+        srcs = _generated_srcs_for_suffix(base_dir + "stage0", oss_src_files, ".upb.c"),
+        hdrs = _generated_srcs_for_suffix(base_dir + "stage0", oss_src_files, ".upb.h"),
+        includes = [base_dir + "stage0"],
+        visibility = ["//upbc:__pkg__"],
+        # This macro signals to the runtime that it must use OSS APIs for descriptor.proto/plugin.proto.
+        defines = ["UPB_BOOTSTRAP_STAGE0"],
+        deps = [
+            "//upb:generated_code_support__only_for_generated_code_do_not_use__i_give_permission_to_break_me",
+            "//upb:mini_table",
+        ] + [dep + "_stage0" for dep in deps],
+        **kwargs
+    )
+
+    src_files = google3_src_files if _is_google3 else oss_src_files
+    src_rules = google3_src_rules if _is_google3 else oss_src_rules
+
+    # Generate stage1 protos (C API and MiniTables) using stage0 compiler.
+    _generate_stage1_proto(name, base_dir, src_files, src_rules, "upb", kwargs)
+    _generate_stage1_proto(name, base_dir, src_files, src_rules, "upb_minitable", kwargs)
+
+    native.cc_library(
+        name = name + "_minitable_stage1",
+        srcs = _generated_srcs_for_suffix(base_dir + "stage1", src_files, ".upb_minitable.c"),
+        hdrs = _generated_srcs_for_suffix(base_dir + "stage1", src_files, ".upb_minitable.h"),
+        includes = [base_dir + "stage1"],
+        visibility = ["//upbc:__pkg__"],
+        deps = [
+            "//upb:generated_code_support__only_for_generated_code_do_not_use__i_give_permission_to_break_me",
+        ] + [dep + "_stage1" for dep in deps],
+        **kwargs
+    )
+    native.cc_library(
+        name = name + "_stage1",
+        hdrs = _generated_srcs_for_suffix(base_dir + "stage1", src_files, ".upb.h"),
+        includes = [base_dir + "stage1"],
+        visibility = ["//upbc:__pkg__"],
+        deps = [
+            "//upb:generated_code_support__only_for_generated_code_do_not_use__i_give_permission_to_break_me",
+            ":" + name + "_minitable_stage1",
+        ] + [dep + "_stage1" for dep in deps],
+        **kwargs
+    )
+
+    # The final protos are generated via normal upb_proto_library().
+    upb_proto_library(
+        name = name,
+        deps = proto_lib_deps,
+        visibility = visibility,
+        **kwargs
+    )
diff --git a/upbc/code_generator_request.c b/upbc/code_generator_request.c
new file mode 100644
index 0000000..4ab5fcc
--- /dev/null
+++ b/upbc/code_generator_request.c
@@ -0,0 +1,262 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  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 LLC 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 "upbc/code_generator_request.h"
+
+#include <inttypes.h>
+
+#include "google/protobuf/compiler/plugin.upb.h"
+#include "upb/mini_descriptor/decode.h"
+#include "upb/reflection/def.h"
+
+// Must be last.
+#include "upb/port/def.inc"
+
+/******************************************************************************/
+
+// Kitchen sink storage for all of our state as we build the mini descriptors.
+
+typedef struct {
+  upb_Arena* arena;
+  upb_Status* status;
+  upb_DefPool* symtab;
+
+  upbc_CodeGeneratorRequest* out;
+
+  jmp_buf jmp;
+} upbc_State;
+
+static void upbc_State_Fini(upbc_State* s) {
+  if (s->symtab) upb_DefPool_Free(s->symtab);
+}
+
+UPB_NORETURN static void upbc_Error(upbc_State* s, const char* fn,
+                                    const char* msg) {
+  upb_Status_SetErrorFormat(s->status, "%s(): %s", fn, msg);
+  upbc_State_Fini(s);
+  UPB_LONGJMP(s->jmp, -1);
+}
+
+static void upbc_State_Init(upbc_State* s) {
+  s->symtab = upb_DefPool_New();
+  if (!s->symtab) upbc_Error(s, __func__, "could not allocate def pool");
+
+  s->out = upbc_CodeGeneratorRequest_new(s->arena);
+  if (!s->out) upbc_Error(s, __func__, "could not allocate request");
+}
+
+static upb_StringView upbc_State_StrDup(upbc_State* s, const char* str) {
+  upb_StringView from = upb_StringView_FromString(str);
+  char* to = upb_Arena_Malloc(s->arena, from.size);
+  if (!to) upbc_Error(s, __func__, "Out of memory");
+  memcpy(to, from.data, from.size);
+  return upb_StringView_FromDataAndSize(to, from.size);
+}
+
+static void upbc_State_AddMiniDescriptor(upbc_State* s, const char* name,
+                                         upb_StringView encoding) {
+  const upb_StringView key = upb_StringView_FromString(name);
+  upbc_CodeGeneratorRequest_UpbInfo* info =
+      upbc_CodeGeneratorRequest_UpbInfo_new(s->arena);
+  if (!info) upbc_Error(s, __func__, "Out of memory");
+  upbc_CodeGeneratorRequest_UpbInfo_set_mini_descriptor(info, encoding);
+  bool ok = upbc_CodeGeneratorRequest_upb_info_set(s->out, key, info, s->arena);
+  if (!ok) upbc_Error(s, __func__, "could not set mini descriptor in map");
+}
+
+/******************************************************************************/
+
+// Forward declaration.
+static void upbc_Scrape_Message(upbc_State*, const upb_MessageDef*);
+
+static void upbc_Scrape_Enum(upbc_State* s, const upb_EnumDef* e) {
+  upb_StringView desc;
+  bool ok = upb_EnumDef_MiniDescriptorEncode(e, s->arena, &desc);
+  if (!ok) upbc_Error(s, __func__, "could not encode enum");
+
+  upbc_State_AddMiniDescriptor(s, upb_EnumDef_FullName(e), desc);
+}
+
+static void upbc_Scrape_Extension(upbc_State* s, const upb_FieldDef* f) {
+  upb_StringView desc;
+  bool ok = upb_FieldDef_MiniDescriptorEncode(f, s->arena, &desc);
+  if (!ok) upbc_Error(s, __func__, "could not encode extension");
+
+  upbc_State_AddMiniDescriptor(s, upb_FieldDef_FullName(f), desc);
+}
+
+static void upbc_Scrape_FileEnums(upbc_State* s, const upb_FileDef* f) {
+  const size_t len = upb_FileDef_TopLevelEnumCount(f);
+
+  for (size_t i = 0; i < len; i++) {
+    upbc_Scrape_Enum(s, upb_FileDef_TopLevelEnum(f, i));
+  }
+}
+
+static void upbc_Scrape_FileExtensions(upbc_State* s, const upb_FileDef* f) {
+  const size_t len = upb_FileDef_TopLevelExtensionCount(f);
+
+  for (size_t i = 0; i < len; i++) {
+    upbc_Scrape_Extension(s, upb_FileDef_TopLevelExtension(f, i));
+  }
+}
+
+static void upbc_Scrape_FileMessages(upbc_State* s, const upb_FileDef* f) {
+  const size_t len = upb_FileDef_TopLevelMessageCount(f);
+
+  for (size_t i = 0; i < len; i++) {
+    upbc_Scrape_Message(s, upb_FileDef_TopLevelMessage(f, i));
+  }
+}
+
+static void upbc_Scrape_File(upbc_State* s, const upb_FileDef* f) {
+  upbc_Scrape_FileEnums(s, f);
+  upbc_Scrape_FileExtensions(s, f);
+  upbc_Scrape_FileMessages(s, f);
+}
+
+static void upbc_Scrape_Files(upbc_State* s) {
+  const google_protobuf_compiler_CodeGeneratorRequest* request =
+      upbc_CodeGeneratorRequest_request(s->out);
+
+  size_t len = 0;
+  const google_protobuf_FileDescriptorProto* const* files =
+      google_protobuf_compiler_CodeGeneratorRequest_proto_file(request, &len);
+
+  for (size_t i = 0; i < len; i++) {
+    const upb_FileDef* f = upb_DefPool_AddFile(s->symtab, files[i], s->status);
+    if (!f) upbc_Error(s, __func__, "could not add file to def pool");
+
+    upbc_Scrape_File(s, f);
+  }
+}
+
+static void upbc_Scrape_NestedEnums(upbc_State* s, const upb_MessageDef* m) {
+  const size_t len = upb_MessageDef_NestedEnumCount(m);
+
+  for (size_t i = 0; i < len; i++) {
+    upbc_Scrape_Enum(s, upb_MessageDef_NestedEnum(m, i));
+  }
+}
+
+static void upbc_Scrape_NestedExtensions(upbc_State* s,
+                                         const upb_MessageDef* m) {
+  const size_t len = upb_MessageDef_NestedExtensionCount(m);
+
+  for (size_t i = 0; i < len; i++) {
+    upbc_Scrape_Extension(s, upb_MessageDef_NestedExtension(m, i));
+  }
+}
+
+static void upbc_Scrape_NestedMessages(upbc_State* s, const upb_MessageDef* m) {
+  const size_t len = upb_MessageDef_NestedMessageCount(m);
+
+  for (size_t i = 0; i < len; i++) {
+    upbc_Scrape_Message(s, upb_MessageDef_NestedMessage(m, i));
+  }
+}
+
+static void upbc_Scrape_MessageSubs(upbc_State* s,
+                                    upbc_CodeGeneratorRequest_UpbInfo* info,
+                                    const upb_MessageDef* m) {
+  const upb_MiniTableField** fields =
+      malloc(upb_MessageDef_FieldCount(m) * sizeof(*fields));
+  const upb_MiniTable* mt = upb_MessageDef_MiniTable(m);
+  uint32_t counts = upb_MiniTable_GetSubList(mt, fields);
+  uint32_t msg_count = counts >> 16;
+  uint32_t enum_count = counts & 0xffff;
+
+  for (uint32_t i = 0; i < msg_count; i++) {
+    const upb_FieldDef* f =
+        upb_MessageDef_FindFieldByNumber(m, fields[i]->number);
+    if (!f) upbc_Error(s, __func__, "Missing f");
+    const upb_MessageDef* sub = upb_FieldDef_MessageSubDef(f);
+    if (!sub) upbc_Error(s, __func__, "Missing sub");
+    upb_StringView name = upbc_State_StrDup(s, upb_MessageDef_FullName(sub));
+    upbc_CodeGeneratorRequest_UpbInfo_add_sub_message(info, name, s->arena);
+  }
+
+  for (uint32_t i = 0; i < enum_count; i++) {
+    const upb_FieldDef* f =
+        upb_MessageDef_FindFieldByNumber(m, fields[msg_count + i]->number);
+    if (!f) upbc_Error(s, __func__, "Missing f (2)");
+    const upb_EnumDef* sub = upb_FieldDef_EnumSubDef(f);
+    if (!sub) upbc_Error(s, __func__, "Missing sub (2)");
+    upb_StringView name = upbc_State_StrDup(s, upb_EnumDef_FullName(sub));
+    upbc_CodeGeneratorRequest_UpbInfo_add_sub_enum(info, name, s->arena);
+  }
+
+  free(fields);
+}
+
+static void upbc_Scrape_Message(upbc_State* s, const upb_MessageDef* m) {
+  upb_StringView desc;
+  bool ok = upb_MessageDef_MiniDescriptorEncode(m, s->arena, &desc);
+  if (!ok) upbc_Error(s, __func__, "could not encode message");
+
+  upbc_CodeGeneratorRequest_UpbInfo* info =
+      upbc_CodeGeneratorRequest_UpbInfo_new(s->arena);
+  if (!info) upbc_Error(s, __func__, "Out of memory");
+  upbc_CodeGeneratorRequest_UpbInfo_set_mini_descriptor(info, desc);
+
+  upbc_Scrape_MessageSubs(s, info, m);
+
+  const upb_StringView key = upbc_State_StrDup(s, upb_MessageDef_FullName(m));
+  ok = upbc_CodeGeneratorRequest_upb_info_set(s->out, key, info, s->arena);
+  if (!ok) upbc_Error(s, __func__, "could not set mini descriptor in map");
+
+  upbc_Scrape_NestedEnums(s, m);
+  upbc_Scrape_NestedExtensions(s, m);
+  upbc_Scrape_NestedMessages(s, m);
+}
+
+static upbc_CodeGeneratorRequest* upbc_State_MakeCodeGeneratorRequest(
+    upbc_State* const s, google_protobuf_compiler_CodeGeneratorRequest* const request) {
+  if (UPB_SETJMP(s->jmp)) return NULL;
+  upbc_State_Init(s);
+
+  upbc_CodeGeneratorRequest_set_request(s->out, request);
+  upbc_Scrape_Files(s);
+  upbc_State_Fini(s);
+  return s->out;
+}
+
+upbc_CodeGeneratorRequest* upbc_MakeCodeGeneratorRequest(
+    google_protobuf_compiler_CodeGeneratorRequest* request, upb_Arena* arena,
+    upb_Status* status) {
+  upbc_State s = {
+      .arena = arena,
+      .status = status,
+      .symtab = NULL,
+      .out = NULL,
+  };
+
+  return upbc_State_MakeCodeGeneratorRequest(&s, request);
+}
diff --git a/upbc/code_generator_request.h b/upbc/code_generator_request.h
new file mode 100644
index 0000000..3218c08
--- /dev/null
+++ b/upbc/code_generator_request.h
@@ -0,0 +1,55 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  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 LLC 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.
+
+#ifndef UPBC_CODE_GENERATOR_REQUEST_H_
+#define UPBC_CODE_GENERATOR_REQUEST_H_
+
+#include "upb/mem/arena.h"
+#include "upb/reflection/def.h"
+#include "upbc/code_generator_request.upb.h"
+
+// Must be last.
+#include "upb/port/def.inc"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+upbc_CodeGeneratorRequest* upbc_MakeCodeGeneratorRequest(
+    struct google_protobuf_compiler_CodeGeneratorRequest* request, upb_Arena* a,
+    upb_Status* s);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#include "upb/port/undef.inc"
+
+#endif /* UPBC_CODE_GENERATOR_REQUEST_H_ */
diff --git a/upbc/code_generator_request.proto b/upbc/code_generator_request.proto
new file mode 100644
index 0000000..600faee
--- /dev/null
+++ b/upbc/code_generator_request.proto
@@ -0,0 +1,56 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  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 LLC 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 upbc;
+
+import "google/protobuf/compiler/plugin.proto";
+
+message CodeGeneratorRequest {
+  message UpbInfo {
+    optional string mini_descriptor = 1;
+
+    // An ordered list of fully qualified sub-message names whose upb_MiniTable
+    // should be passed to upb_MiniTable_Link().
+    repeated string sub_message = 3;
+
+    // An ordered list of fully qualified sub-enum names whose upb_MiniTableEnum
+    // should be passed to upb_MiniTable_Link().
+    repeated string sub_enum = 4;
+  }
+
+  // The pb sent by protoc to its plugins.
+  optional google.protobuf.compiler.CodeGeneratorRequest request = 1;
+
+  // upb-specific info for the messages/enums/extensions in the request, keyed
+  // by the fully qualified names.
+  map<string, UpbInfo> upb_info = 2;
+}
diff --git a/upbc/common.cc b/upbc/common.cc
new file mode 100644
index 0000000..b5bad20
--- /dev/null
+++ b/upbc/common.cc
@@ -0,0 +1,188 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  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 LLC 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 "upbc/common.h"
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <string>
+
+#include "absl/strings/ascii.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_replace.h"
+#include "absl/strings/string_view.h"
+#include "absl/strings/substitute.h"
+#include "upb/mini_table/field.h"
+#include "upb/mini_table/internal/field.h"
+#include "upb/reflection/def.hpp"
+
+// Must be last
+#include "upb/port/def.inc"
+
+namespace upbc {
+
+std::string StripExtension(absl::string_view fname) {
+  size_t lastdot = fname.find_last_of('.');
+  if (lastdot == std::string::npos) {
+    return std::string(fname);
+  }
+  return std::string(fname.substr(0, lastdot));
+}
+
+std::string ToCIdent(absl::string_view str) {
+  return absl::StrReplaceAll(str, {{".", "_"}, {"/", "_"}, {"-", "_"}});
+}
+
+std::string ToPreproc(absl::string_view str) {
+  return absl::AsciiStrToUpper(ToCIdent(str));
+}
+
+void EmitFileWarning(absl::string_view name, Output& output) {
+  output(
+      "/* This file was generated by upbc (the upb compiler) from the input\n"
+      " * file:\n"
+      " *\n"
+      " *     $0\n"
+      " *\n"
+      " * Do not edit -- your changes will be discarded when the file is\n"
+      " * regenerated. */\n\n",
+      name);
+}
+
+std::string MessageName(upb::MessageDefPtr descriptor) {
+  return ToCIdent(descriptor.full_name());
+}
+
+std::string FileLayoutName(upb::FileDefPtr file) {
+  return ToCIdent(file.name()) + "_upb_file_layout";
+}
+
+std::string CApiHeaderFilename(upb::FileDefPtr file) {
+  return StripExtension(file.name()) + ".upb.h";
+}
+
+std::string MiniTableHeaderFilename(upb::FileDefPtr file) {
+  return StripExtension(file.name()) + ".upb_minitable.h";
+}
+
+std::string MessageInit(absl::string_view full_name) {
+  return ToCIdent(full_name) + "_msg_init";
+}
+
+std::string EnumInit(upb::EnumDefPtr descriptor) {
+  return ToCIdent(descriptor.full_name()) + "_enum_init";
+}
+
+std::string FieldInitializer(upb::FieldDefPtr field,
+                             const upb_MiniTableField* field64,
+                             const upb_MiniTableField* field32) {
+  return absl::Substitute(
+      "{$0, $1, $2, $3, $4, $5}", field64->number,
+      ArchDependentSize(field32->offset, field64->offset),
+      ArchDependentSize(field32->presence, field64->presence),
+      field64->UPB_PRIVATE(submsg_index) == kUpb_NoSub
+          ? "kUpb_NoSub"
+          : absl::StrCat(field64->UPB_PRIVATE(submsg_index)).c_str(),
+      field64->UPB_PRIVATE(descriptortype), GetModeInit(field32, field64));
+}
+
+std::string ArchDependentSize(int64_t size32, int64_t size64) {
+  if (size32 == size64) return absl::StrCat(size32);
+  return absl::Substitute("UPB_SIZE($0, $1)", size32, size64);
+}
+
+// Returns the field mode as a string initializer.
+//
+// We could just emit this as a number (and we may yet go in that direction) but
+// for now emitting symbolic constants gives this better readability and
+// debuggability.
+std::string GetModeInit(const upb_MiniTableField* field32,
+                        const upb_MiniTableField* field64) {
+  std::string ret;
+  uint8_t mode32 = field32->mode;
+  switch (mode32 & kUpb_FieldMode_Mask) {
+    case kUpb_FieldMode_Map:
+      ret = "(int)kUpb_FieldMode_Map";
+      break;
+    case kUpb_FieldMode_Array:
+      ret = "(int)kUpb_FieldMode_Array";
+      break;
+    case kUpb_FieldMode_Scalar:
+      ret = "(int)kUpb_FieldMode_Scalar";
+      break;
+    default:
+      break;
+  }
+
+  if (mode32 & kUpb_LabelFlags_IsPacked) {
+    absl::StrAppend(&ret, " | (int)kUpb_LabelFlags_IsPacked");
+  }
+
+  if (mode32 & kUpb_LabelFlags_IsExtension) {
+    absl::StrAppend(&ret, " | (int)kUpb_LabelFlags_IsExtension");
+  }
+
+  if (mode32 & kUpb_LabelFlags_IsAlternate) {
+    absl::StrAppend(&ret, " | (int)kUpb_LabelFlags_IsAlternate");
+  }
+
+  absl::StrAppend(&ret, " | ((int)", GetFieldRep(field32, field64),
+                  " << kUpb_FieldRep_Shift)");
+  return ret;
+}
+
+std::string GetFieldRep(const upb_MiniTableField* field32,
+                        const upb_MiniTableField* field64) {
+  switch (_upb_MiniTableField_GetRep(field32)) {
+    case kUpb_FieldRep_1Byte:
+      return "kUpb_FieldRep_1Byte";
+      break;
+    case kUpb_FieldRep_4Byte: {
+      if (_upb_MiniTableField_GetRep(field64) == kUpb_FieldRep_4Byte) {
+        return "kUpb_FieldRep_4Byte";
+      } else {
+        assert(_upb_MiniTableField_GetRep(field64) == kUpb_FieldRep_8Byte);
+        return "UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte)";
+      }
+      break;
+    }
+    case kUpb_FieldRep_StringView:
+      return "kUpb_FieldRep_StringView";
+      break;
+    case kUpb_FieldRep_8Byte:
+      return "kUpb_FieldRep_8Byte";
+      break;
+  }
+  UPB_UNREACHABLE();
+}
+
+}  // namespace upbc
diff --git a/upbc/common.h b/upbc/common.h
new file mode 100644
index 0000000..e607714
--- /dev/null
+++ b/upbc/common.h
@@ -0,0 +1,102 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  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 LLC 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.
+
+#ifndef UPBC_COMMON_H
+#define UPBC_COMMON_H
+
+#include <vector>
+
+#include "absl/strings/str_replace.h"
+#include "absl/strings/substitute.h"
+#include "upb/reflection/def.hpp"
+
+namespace upbc {
+
+class Output {
+ public:
+  template <class... Arg>
+  void operator()(absl::string_view format, const Arg&... arg) {
+    Write(absl::Substitute(format, arg...));
+  }
+
+  absl::string_view output() const { return output_; }
+
+ private:
+  void Write(absl::string_view data) {
+    std::string stripped;
+    if (absl::StartsWith(data, "\n ")) {
+      size_t indent = data.substr(1).find_first_not_of(' ');
+      if (indent != absl::string_view::npos) {
+        // Remove indentation from all lines.
+        auto line_prefix = data.substr(0, indent + 1);
+        // The final line has an extra newline and is indented two less, eg.
+        //    R"cc(
+        //      UPB_INLINE $0 $1_$2(const $1 *msg) {
+        //        return $1_has_$2(msg) ? *UPB_PTR_AT(msg, $3, $0) : $4;
+        //      }
+        //    )cc",
+        std::string last_line_prefix = std::string(line_prefix);
+        last_line_prefix.resize(last_line_prefix.size() - 2);
+        data.remove_prefix(line_prefix.size());
+        stripped = absl::StrReplaceAll(
+            data, {{line_prefix, "\n"}, {last_line_prefix, "\n"}});
+        data = stripped;
+      }
+    }
+    absl::StrAppend(&output_, data);
+  }
+
+  std::string output_;
+};
+
+std::string StripExtension(absl::string_view fname);
+std::string ToCIdent(absl::string_view str);
+std::string ToPreproc(absl::string_view str);
+void EmitFileWarning(absl::string_view name, Output& output);
+std::string MessageName(upb::MessageDefPtr descriptor);
+std::string FileLayoutName(upb::FileDefPtr file);
+std::string MiniTableHeaderFilename(upb::FileDefPtr file);
+std::string CApiHeaderFilename(upb::FileDefPtr file);
+
+std::string MessageInit(absl::string_view full_name);
+std::string EnumInit(upb::EnumDefPtr descriptor);
+
+std::string FieldInitializer(upb::FieldDefPtr field,
+                             const upb_MiniTableField* field64,
+                             const upb_MiniTableField* field32);
+std::string ArchDependentSize(int64_t size32, int64_t size64);
+std::string GetModeInit(const upb_MiniTableField* field32,
+                        const upb_MiniTableField* field64);
+std::string GetFieldRep(const upb_MiniTableField* field32,
+                        const upb_MiniTableField* field64);
+
+}  // namespace upbc
+
+#endif  // UPBC_COMMON_H
diff --git a/upbc/file_layout.cc b/upbc/file_layout.cc
new file mode 100644
index 0000000..fab6120
--- /dev/null
+++ b/upbc/file_layout.cc
@@ -0,0 +1,144 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  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 LLC 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 "upbc/file_layout.h"
+
+#include <string>
+#include <unordered_set>
+
+#include "upb/mini_table/internal/extension.h"
+#include "upbc/common.h"
+
+namespace upbc {
+
+const char* kEnumsInit = "enums_layout";
+const char* kExtensionsInit = "extensions_layout";
+const char* kMessagesInit = "messages_layout";
+
+void AddEnums(upb::MessageDefPtr message, std::vector<upb::EnumDefPtr>* enums) {
+  enums->reserve(enums->size() + message.enum_type_count());
+  for (int i = 0; i < message.enum_type_count(); i++) {
+    enums->push_back(message.enum_type(i));
+  }
+  for (int i = 0; i < message.nested_message_count(); i++) {
+    AddEnums(message.nested_message(i), enums);
+  }
+}
+
+std::vector<upb::EnumDefPtr> SortedEnums(upb::FileDefPtr file) {
+  std::vector<upb::EnumDefPtr> enums;
+  enums.reserve(file.toplevel_enum_count());
+  for (int i = 0; i < file.toplevel_enum_count(); i++) {
+    enums.push_back(file.toplevel_enum(i));
+  }
+  for (int i = 0; i < file.toplevel_message_count(); i++) {
+    AddEnums(file.toplevel_message(i), &enums);
+  }
+  std::sort(enums.begin(), enums.end(),
+            [](upb::EnumDefPtr a, upb::EnumDefPtr b) {
+              return strcmp(a.full_name(), b.full_name()) < 0;
+            });
+  return enums;
+}
+
+std::vector<uint32_t> SortedUniqueEnumNumbers(upb::EnumDefPtr e) {
+  std::vector<uint32_t> values;
+  values.reserve(e.value_count());
+  for (int i = 0; i < e.value_count(); i++) {
+    values.push_back(static_cast<uint32_t>(e.value(i).number()));
+  }
+  std::sort(values.begin(), values.end());
+  auto last = std::unique(values.begin(), values.end());
+  values.erase(last, values.end());
+  return values;
+}
+
+void AddMessages(upb::MessageDefPtr message,
+                 std::vector<upb::MessageDefPtr>* messages) {
+  messages->push_back(message);
+  for (int i = 0; i < message.nested_message_count(); i++) {
+    AddMessages(message.nested_message(i), messages);
+  }
+}
+
+// Ordering must match upb/def.c!
+//
+// The ordering is significant because each upb_MessageDef* will point at the
+// corresponding upb_MiniTable and we just iterate through the list without
+// any search or lookup.
+std::vector<upb::MessageDefPtr> SortedMessages(upb::FileDefPtr file) {
+  std::vector<upb::MessageDefPtr> messages;
+  for (int i = 0; i < file.toplevel_message_count(); i++) {
+    AddMessages(file.toplevel_message(i), &messages);
+  }
+  return messages;
+}
+
+void AddExtensionsFromMessage(upb::MessageDefPtr message,
+                              std::vector<upb::FieldDefPtr>* exts) {
+  for (int i = 0; i < message.nested_extension_count(); i++) {
+    exts->push_back(message.nested_extension(i));
+  }
+  for (int i = 0; i < message.nested_message_count(); i++) {
+    AddExtensionsFromMessage(message.nested_message(i), exts);
+  }
+}
+
+// Ordering must match upb/def.c!
+//
+// The ordering is significant because each upb_FieldDef* will point at the
+// corresponding upb_MiniTableExtension and we just iterate through the list
+// without any search or lookup.
+std::vector<upb::FieldDefPtr> SortedExtensions(upb::FileDefPtr file) {
+  std::vector<upb::FieldDefPtr> ret;
+  ret.reserve(file.toplevel_extension_count());
+  for (int i = 0; i < file.toplevel_extension_count(); i++) {
+    ret.push_back(file.toplevel_extension(i));
+  }
+  for (int i = 0; i < file.toplevel_message_count(); i++) {
+    AddExtensionsFromMessage(file.toplevel_message(i), &ret);
+  }
+  return ret;
+}
+
+std::vector<upb::FieldDefPtr> FieldNumberOrder(upb::MessageDefPtr message) {
+  std::vector<upb::FieldDefPtr> fields;
+  fields.reserve(message.field_count());
+  for (int i = 0; i < message.field_count(); i++) {
+    fields.push_back(message.field(i));
+  }
+  std::sort(fields.begin(), fields.end(),
+            [](upb::FieldDefPtr a, upb::FieldDefPtr b) {
+              return a.number() < b.number();
+            });
+  return fields;
+}
+
+}  // namespace upbc
diff --git a/upbc/file_layout.h b/upbc/file_layout.h
new file mode 100644
index 0000000..9df43aa
--- /dev/null
+++ b/upbc/file_layout.h
@@ -0,0 +1,129 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  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 LLC 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.
+
+#ifndef UPBC_FILE_LAYOUT_H
+#define UPBC_FILE_LAYOUT_H
+
+#include <string>
+
+// begin:google_only
+// #ifndef UPB_BOOTSTRAP_STAGE0
+// #include "net/proto2/proto/descriptor.upb.h"
+// #else
+// #include "google/protobuf/descriptor.upb.h"
+// #endif
+// end:google_only
+
+// begin:github_only
+#include "google/protobuf/descriptor.upb.h"
+// end:github_only
+
+#include "absl/container/flat_hash_map.h"
+#include "upb/base/status.hpp"
+#include "upb/mini_descriptor/decode.h"
+#include "upb/reflection/def.h"
+#include "upb/reflection/def.hpp"
+
+// Must be last
+#include "upb/port/def.inc"
+
+namespace upbc {
+
+std::vector<upb::EnumDefPtr> SortedEnums(upb::FileDefPtr file);
+
+// Ordering must match upb/def.c!
+//
+// The ordering is significant because each upb_MessageDef* will point at the
+// corresponding upb_MiniTable and we just iterate through the list without
+// any search or lookup.
+std::vector<upb::MessageDefPtr> SortedMessages(upb::FileDefPtr file);
+
+// Ordering must match upb/def.c!
+//
+// The ordering is significant because each upb_FieldDef* will point at the
+// corresponding upb_MiniTableExtension and we just iterate through the list
+// without any search or lookup.
+std::vector<upb::FieldDefPtr> SortedExtensions(upb::FileDefPtr file);
+
+std::vector<upb::FieldDefPtr> FieldNumberOrder(upb::MessageDefPtr message);
+
+// DefPoolPair is a pair of DefPools: one for 32-bit and one for 64-bit.
+class DefPoolPair {
+ public:
+  DefPoolPair() {
+    pool32_._SetPlatform(kUpb_MiniTablePlatform_32Bit);
+    pool64_._SetPlatform(kUpb_MiniTablePlatform_64Bit);
+  }
+
+  upb::FileDefPtr AddFile(const UPB_DESC(FileDescriptorProto) * file_proto,
+                          upb::Status* status) {
+    upb::FileDefPtr file32 = pool32_.AddFile(file_proto, status);
+    upb::FileDefPtr file64 = pool64_.AddFile(file_proto, status);
+    if (!file32) return file32;
+    return file64;
+  }
+
+  const upb_MiniTable* GetMiniTable32(upb::MessageDefPtr m) const {
+    return pool32_.FindMessageByName(m.full_name()).mini_table();
+  }
+
+  const upb_MiniTable* GetMiniTable64(upb::MessageDefPtr m) const {
+    return pool64_.FindMessageByName(m.full_name()).mini_table();
+  }
+
+  const upb_MiniTableField* GetField32(upb::FieldDefPtr f) const {
+    return GetFieldFromPool(&pool32_, f);
+  }
+
+  const upb_MiniTableField* GetField64(upb::FieldDefPtr f) const {
+    return GetFieldFromPool(&pool64_, f);
+  }
+
+ private:
+  static const upb_MiniTableField* GetFieldFromPool(const upb::DefPool* pool,
+                                                    upb::FieldDefPtr f) {
+    if (f.is_extension()) {
+      return pool->FindExtensionByName(f.full_name()).mini_table();
+    } else {
+      return pool->FindMessageByName(f.containing_type().full_name())
+          .FindFieldByNumber(f.number())
+          .mini_table();
+    }
+  }
+
+  upb::DefPool pool32_;
+  upb::DefPool pool64_;
+};
+
+}  // namespace upbc
+
+#include "upb/port/undef.inc"
+
+#endif  // UPBC_FILE_LAYOUT_H
diff --git a/upbc/get_used_fields.c b/upbc/get_used_fields.c
new file mode 100644
index 0000000..796e501
--- /dev/null
+++ b/upbc/get_used_fields.c
@@ -0,0 +1,143 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  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 LLC 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 "upbc/get_used_fields.h"
+
+#include "google/protobuf/descriptor.upb.h"
+#include "google/protobuf/compiler/plugin.upb.h"
+#include "upb/reflection/def_pool.h"
+#include "upb/reflection/field_def.h"
+#include "upb/reflection/message.h"
+#include "upb/reflection/message_def.h"
+#include "upb/wire/decode.h"
+
+// Must be last.
+#include "upb/port/def.inc"
+
+#define upbdev_Err(...)           \
+  {                               \
+    fprintf(stderr, __VA_ARGS__); \
+    exit(1);                      \
+  }
+
+typedef struct {
+  char* buf;
+  size_t size;
+  size_t capacity;
+  upb_Arena* arena;
+} upbdev_StringBuf;
+
+void upbdev_StringBuf_Add(upbdev_StringBuf* buf, const char* sym) {
+  size_t len = strlen(sym);
+  size_t need = buf->size + len + (buf->size != 0);
+  if (need > buf->capacity) {
+    size_t new_cap = UPB_MAX(buf->capacity, 32);
+    while (need > new_cap) new_cap *= 2;
+    buf->buf = upb_Arena_Realloc(buf->arena, buf->buf, buf->capacity, new_cap);
+    buf->capacity = new_cap;
+  }
+  if (buf->size != 0) {
+    buf->buf[buf->size++] = '\n';  // Separator
+  }
+  memcpy(buf->buf + buf->size, sym, len);
+  buf->size = need;
+}
+
+void upbdev_VisitMessage(upbdev_StringBuf* buf, const upb_Message* msg,
+                         const upb_MessageDef* m) {
+  size_t iter = kUpb_Message_Begin;
+  const upb_FieldDef* f;
+  upb_MessageValue val;
+  while (upb_Message_Next(msg, m, NULL, &f, &val, &iter)) {
+    // This could be a duplicate, but we don't worry about it; we'll dedupe
+    // one level up.
+    upbdev_StringBuf_Add(buf, upb_FieldDef_FullName(f));
+
+    if (upb_FieldDef_CType(f) != kUpb_CType_Message) continue;
+    const upb_MessageDef* sub = upb_FieldDef_MessageSubDef(f);
+
+    if (upb_FieldDef_IsMap(f)) {
+      const upb_Map* map = val.map_val;
+      size_t iter = kUpb_Map_Begin;
+      upb_MessageValue map_key, map_val;
+      while (upb_Map_Next(map, &map_key, &map_val, &iter)) {
+        upbdev_VisitMessage(buf, map_val.msg_val, sub);
+      }
+    } else if (upb_FieldDef_IsRepeated(f)) {
+      const upb_Array* arr = val.array_val;
+      size_t n = upb_Array_Size(arr);
+      for (size_t i = 0; i < n; i++) {
+        upb_MessageValue val = upb_Array_Get(arr, i);
+        upbdev_VisitMessage(buf, val.msg_val, sub);
+      }
+    } else {
+      upbdev_VisitMessage(buf, val.msg_val, sub);
+    }
+  }
+}
+
+upb_StringView upbdev_GetUsedFields(const char* request, size_t request_size,
+                                    const char* payload, size_t payload_size,
+                                    const char* message_name,
+                                    upb_Arena* arena) {
+  upb_Arena* tmp_arena = upb_Arena_New();
+  google_protobuf_compiler_CodeGeneratorRequest* request_proto =
+      google_protobuf_compiler_CodeGeneratorRequest_parse(request, request_size,
+                                                 tmp_arena);
+  if (!request_proto) upbdev_Err("Couldn't parse request proto\n");
+
+  size_t len;
+  const google_protobuf_FileDescriptorProto* const* files =
+      google_protobuf_compiler_CodeGeneratorRequest_proto_file(request_proto, &len);
+
+  upb_DefPool* pool = upb_DefPool_New();
+  for (size_t i = 0; i < len; i++) {
+    const upb_FileDef* f = upb_DefPool_AddFile(pool, files[i], NULL);
+    if (!f) upbdev_Err("could not add file to def pool\n");
+  }
+
+  const upb_MessageDef* m = upb_DefPool_FindMessageByName(pool, message_name);
+  if (!m) upbdev_Err("Couldn't find message name\n");
+
+  const upb_MiniTable* mt = upb_MessageDef_MiniTable(m);
+  upb_Message* msg = upb_Message_New(mt, tmp_arena);
+  upb_DecodeStatus st =
+      upb_Decode(payload, payload_size, msg, mt, NULL, 0, tmp_arena);
+  if (st != kUpb_DecodeStatus_Ok) upbdev_Err("Error parsing payload: %d\n", st);
+
+  upbdev_StringBuf buf = {
+      .buf = NULL,
+      .size = 0,
+      .capacity = 0,
+      .arena = arena,
+  };
+  upbdev_VisitMessage(&buf, msg, m);
+  return upb_StringView_FromDataAndSize(buf.buf, buf.size);
+}
diff --git a/upbc/get_used_fields.h b/upbc/get_used_fields.h
new file mode 100644
index 0000000..ccf3208
--- /dev/null
+++ b/upbc/get_used_fields.h
@@ -0,0 +1,57 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  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 LLC 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.
+
+#ifndef UPBC_GET_USED_FIELDS
+#define UPBC_GET_USED_FIELDS
+
+#include "upb/base/status.h"
+#include "upb/base/string_view.h"
+#include "upb/mem/arena.h"
+
+// Must be last.
+#include "upb/port/def.inc"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Consume |buf|, deserialize it to a Code_Generator_Request proto, then
+// upbc_Code_Generator_Request, and return it as a JSON-encoded string.
+UPB_API upb_StringView upbdev_GetUsedFields(
+    const char* request, size_t request_size, const char* payload,
+    size_t payload_size, const char* message_name, upb_Arena* arena);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#include "upb/port/undef.inc"
+
+#endif  // UPBC_GET_USED_FIELDS
diff --git a/upbc/keywords.cc b/upbc/keywords.cc
new file mode 100644
index 0000000..3f51705
--- /dev/null
+++ b/upbc/keywords.cc
@@ -0,0 +1,152 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  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 LLC 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 "upbc/keywords.h"
+
+#include <string>
+#include <unordered_set>
+
+namespace upbc {
+
+static const char* const kKeywordList[] = {
+    //
+    "NULL",
+    "alignas",
+    "alignof",
+    "and",
+    "and_eq",
+    "asm",
+    "auto",
+    "bitand",
+    "bitor",
+    "bool",
+    "break",
+    "case",
+    "catch",
+    "char",
+    "class",
+    "compl",
+    "const",
+    "constexpr",
+    "const_cast",
+    "continue",
+    "decltype",
+    "default",
+    "delete",
+    "do",
+    "double",
+    "dynamic_cast",
+    "else",
+    "enum",
+    "explicit",
+    "export",
+    "extern",
+    "false",
+    "float",
+    "for",
+    "friend",
+    "goto",
+    "if",
+    "inline",
+    "int",
+    "long",
+    "mutable",
+    "namespace",
+    "new",
+    "noexcept",
+    "not",
+    "not_eq",
+    "nullptr",
+    "operator",
+    "or",
+    "or_eq",
+    "private",
+    "protected",
+    "public",
+    "register",
+    "reinterpret_cast",
+    "return",
+    "short",
+    "signed",
+    "sizeof",
+    "static",
+    "static_assert",
+    "static_cast",
+    "struct",
+    "switch",
+    "template",
+    "this",
+    "thread_local",
+    "throw",
+    "true",
+    "try",
+    "typedef",
+    "typeid",
+    "typename",
+    "union",
+    "unsigned",
+    "using",
+    "virtual",
+    "void",
+    "volatile",
+    "wchar_t",
+    "while",
+    "xor",
+    "xor_eq",
+    "char8_t",
+    "char16_t",
+    "char32_t",
+    "concept",
+    "consteval",
+    "constinit",
+    "co_await",
+    "co_return",
+    "co_yield",
+    "requires",
+};
+
+static std::unordered_set<std::string>* MakeKeywordsMap() {
+  auto* result = new std::unordered_set<std::string>();
+  for (const auto keyword : kKeywordList) {
+    result->emplace(keyword);
+  }
+  return result;
+}
+
+static std::unordered_set<std::string>& kKeywords = *MakeKeywordsMap();
+
+std::string ResolveKeywordConflict(const std::string& name) {
+  if (kKeywords.count(name) > 0) {
+    return name + "_";
+  }
+  return name;
+}
+
+}  // namespace upbc
diff --git a/upbc/keywords.h b/upbc/keywords.h
new file mode 100644
index 0000000..6638f99
--- /dev/null
+++ b/upbc/keywords.h
@@ -0,0 +1,43 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  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 LLC 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.
+
+#ifndef UPB_PROTOS_GENERATOR_KEYWORDS_H
+#define UPB_PROTOS_GENERATOR_KEYWORDS_H
+
+#include <string>
+
+namespace upbc {
+
+// Resolves proto field name conflict with C++ reserved keywords.
+std::string ResolveKeywordConflict(const std::string& name);
+
+}  // namespace upbc
+
+#endif  // UPB_PROTOS_GENERATOR_KEYWORDS_H
diff --git a/upbc/names.cc b/upbc/names.cc
new file mode 100644
index 0000000..63521b8
--- /dev/null
+++ b/upbc/names.cc
@@ -0,0 +1,132 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  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 LLC 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 "upbc/names.h"
+
+#include <string>
+
+#include "absl/strings/match.h"
+#include "absl/strings/string_view.h"
+#include "google/protobuf/descriptor.h"
+#include "upb/reflection/def.hpp"
+
+namespace upbc {
+
+namespace protobuf = ::google::protobuf;
+
+// Prefixes used by C code generator for field access.
+static constexpr absl::string_view kClearMethodPrefix = "clear_";
+static constexpr absl::string_view kSetMethodPrefix = "set_";
+static constexpr absl::string_view kHasMethodPrefix = "has_";
+static constexpr absl::string_view kDeleteMethodPrefix = "delete_";
+static constexpr absl::string_view kAddToRepeatedMethodPrefix = "add_";
+static constexpr absl::string_view kResizeArrayMethodPrefix = "resize_";
+
+ABSL_CONST_INIT const absl::string_view kRepeatedFieldArrayGetterPostfix =
+    "upb_array";
+ABSL_CONST_INIT const absl::string_view
+    kRepeatedFieldMutableArrayGetterPostfix = "mutable_upb_array";
+
+// List of generated accessor prefixes to check against.
+// Example:
+//     optional repeated string phase = 236;
+//     optional bool clear_phase = 237;
+static constexpr absl::string_view kAccessorPrefixes[] = {
+    kClearMethodPrefix,       kDeleteMethodPrefix, kAddToRepeatedMethodPrefix,
+    kResizeArrayMethodPrefix, kSetMethodPrefix,    kHasMethodPrefix};
+
+std::string ResolveFieldName(const protobuf::FieldDescriptor* field,
+                             const NameToFieldDescriptorMap& field_names) {
+  absl::string_view field_name = field->name();
+  for (const auto prefix : kAccessorPrefixes) {
+    // If field name starts with a prefix such as clear_ and the proto
+    // contains a field name with trailing end, depending on type of field
+    // (repeated, map, message) we have a conflict to resolve.
+    if (absl::StartsWith(field_name, prefix)) {
+      auto match = field_names.find(field_name.substr(prefix.size()));
+      if (match != field_names.end()) {
+        const auto* candidate = match->second;
+        if (candidate->is_repeated() || candidate->is_map() ||
+            (candidate->cpp_type() ==
+                 protobuf::FieldDescriptor::CPPTYPE_STRING &&
+             prefix == kClearMethodPrefix) ||
+            prefix == kSetMethodPrefix || prefix == kHasMethodPrefix) {
+          return absl::StrCat(field_name, "_");
+        }
+      }
+    }
+  }
+  return std::string(field_name);
+}
+
+// Returns field map by name to use for conflict checks.
+NameToFieldDescriptorMap CreateFieldNameMap(
+    const protobuf::Descriptor* message) {
+  NameToFieldDescriptorMap field_names;
+  for (int i = 0; i < message->field_count(); i++) {
+    const protobuf::FieldDescriptor* field = message->field(i);
+    field_names.emplace(field->name(), field);
+  }
+  return field_names;
+}
+
+NameToFieldDefMap CreateFieldNameMap(upb::MessageDefPtr message) {
+  NameToFieldDefMap field_names;
+  field_names.reserve(message.field_count());
+  for (const auto& field : message.fields()) {
+    field_names.emplace(field.name(), field);
+  }
+  return field_names;
+}
+
+std::string ResolveFieldName(upb::FieldDefPtr field,
+                             const NameToFieldDefMap& field_names) {
+  absl::string_view field_name(field.name());
+  for (absl::string_view prefix : kAccessorPrefixes) {
+    // If field name starts with a prefix such as clear_ and the proto
+    // contains a field name with trailing end, depending on type of field
+    // (repeated, map, message) we have a conflict to resolve.
+    if (absl::StartsWith(field_name, prefix)) {
+      auto match = field_names.find(field_name.substr(prefix.size()));
+      if (match != field_names.end()) {
+        const auto candidate = match->second;
+        if (candidate.IsSequence() || candidate.IsMap() ||
+            (candidate.ctype() == kUpb_CType_String &&
+             prefix == kClearMethodPrefix) ||
+            prefix == kSetMethodPrefix || prefix == kHasMethodPrefix) {
+          return absl::StrCat(field_name, "_");
+        }
+      }
+    }
+  }
+  return std::string(field_name);
+}
+
+}  // namespace upbc
diff --git a/upbc/names.h b/upbc/names.h
new file mode 100644
index 0000000..87b0a15
--- /dev/null
+++ b/upbc/names.h
@@ -0,0 +1,73 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  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 LLC 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.
+
+#ifndef UPB_PROTOS_GENERATOR_NAMES_H
+#define UPB_PROTOS_GENERATOR_NAMES_H
+
+#include <string>
+
+#include "absl/base/attributes.h"
+#include "absl/container/flat_hash_map.h"
+#include "absl/strings/string_view.h"
+#include "google/protobuf/descriptor.h"
+#include "upb/reflection/def.hpp"
+
+namespace upbc {
+
+using NameToFieldDescriptorMap =
+    absl::flat_hash_map<absl::string_view, const google::protobuf::FieldDescriptor*>;
+
+// Returns field name by resolving naming conflicts across
+// proto field names (such as clear_ prefixes).
+std::string ResolveFieldName(const google::protobuf::FieldDescriptor* field,
+                             const NameToFieldDescriptorMap& field_names);
+
+// Returns field map by name to use for conflict checks.
+NameToFieldDescriptorMap CreateFieldNameMap(const google::protobuf::Descriptor* message);
+
+using NameToFieldDefMap =
+    absl::flat_hash_map<absl::string_view, upb::FieldDefPtr>;
+
+// Returns field name by resolving naming conflicts across
+// proto field names (such as clear_ prefixes).
+std::string ResolveFieldName(upb::FieldDefPtr field,
+                             const NameToFieldDefMap& field_names);
+
+// Returns field map by name to use for conflict checks.
+NameToFieldDefMap CreateFieldNameMap(upb::MessageDefPtr message);
+
+// Private array getter name postfix for repeated fields.
+ABSL_CONST_INIT extern const absl::string_view kRepeatedFieldArrayGetterPostfix;
+ABSL_CONST_INIT extern const absl::string_view
+    kRepeatedFieldMutableArrayGetterPostfix;
+
+}  // namespace upbc
+
+#endif  // UPB_PROTOS_GENERATOR_NAMES_H
diff --git a/upbc/plugin.h b/upbc/plugin.h
new file mode 100644
index 0000000..465bda0
--- /dev/null
+++ b/upbc/plugin.h
@@ -0,0 +1,212 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  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 LLC 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.
+
+#ifndef UPB_UPBC_PLUGIN_H_
+#define UPB_UPBC_PLUGIN_H_
+
+#include <stdio.h>
+
+#include <string>
+#include <vector>
+#ifdef _WIN32
+#include <fcntl.h>
+#include <io.h>
+#endif
+
+// begin:google_only
+// #ifndef UPB_BOOTSTRAP_STAGE0
+// #include "net/proto2/proto/descriptor.upb.h"
+// #include "third_party/protobuf/compiler/plugin.upb.h"
+// #else
+// #include "google/protobuf/compiler/plugin.upb.h"
+// #include "google/protobuf/descriptor.upb.h"
+// #endif
+// end:google_only
+
+// begin:github_only
+#include "google/protobuf/compiler/plugin.upb.h"
+#include "google/protobuf/descriptor.upb.h"
+// end:github_only
+
+#include "absl/container/flat_hash_set.h"
+#include "absl/log/absl_log.h"
+#include "absl/strings/str_split.h"
+#include "absl/strings/string_view.h"
+#include "upb/reflection/def.hpp"
+
+// Must be last.
+#include "upb/port/def.inc"
+
+namespace upbc {
+
+inline std::vector<std::pair<std::string, std::string>> ParseGeneratorParameter(
+    const absl::string_view text) {
+  std::vector<std::pair<std::string, std::string>> ret;
+  for (absl::string_view sp : absl::StrSplit(text, ',', absl::SkipEmpty())) {
+    std::string::size_type equals_pos = sp.find_first_of('=');
+    std::pair<std::string, std::string> value;
+    if (equals_pos == std::string::npos) {
+      value.first = std::string(sp);
+    } else {
+      value.first = std::string(sp.substr(0, equals_pos));
+      value.second = std::string(sp.substr(equals_pos + 1));
+    }
+    ret.push_back(std::move(value));
+  }
+  return ret;
+}
+
+class Plugin {
+ public:
+  Plugin() { ReadRequest(); }
+  ~Plugin() { WriteResponse(); }
+
+  absl::string_view parameter() const {
+    return ToStringView(
+        UPB_DESC(compiler_CodeGeneratorRequest_parameter)(request_));
+  }
+
+  template <class T>
+  void GenerateFilesRaw(T&& func) {
+    absl::flat_hash_set<absl::string_view> files_to_generate;
+    size_t size;
+    const upb_StringView* file_to_generate = UPB_DESC(
+        compiler_CodeGeneratorRequest_file_to_generate)(request_, &size);
+    for (size_t i = 0; i < size; i++) {
+      files_to_generate.insert(
+          {file_to_generate[i].data, file_to_generate[i].size});
+    }
+
+    const UPB_DESC(FileDescriptorProto)* const* files =
+        UPB_DESC(compiler_CodeGeneratorRequest_proto_file)(request_, &size);
+    for (size_t i = 0; i < size; i++) {
+      upb::Status status;
+      absl::string_view name =
+          ToStringView(UPB_DESC(FileDescriptorProto_name)(files[i]));
+      func(files[i], files_to_generate.contains(name));
+    }
+  }
+
+  template <class T>
+  void GenerateFiles(T&& func) {
+    GenerateFilesRaw(
+        [this, &func](const UPB_DESC(FileDescriptorProto) * file_proto,
+                      bool generate) {
+          upb::Status status;
+          upb::FileDefPtr file = pool_.AddFile(file_proto, &status);
+          if (!file) {
+            absl::string_view name =
+                ToStringView(UPB_DESC(FileDescriptorProto_name)(file_proto));
+            ABSL_LOG(FATAL) << "Couldn't add file " << name
+                            << " to DefPool: " << status.error_message();
+          }
+          if (generate) func(file);
+        });
+  }
+
+  void SetError(absl::string_view error) {
+    char* data =
+        static_cast<char*>(upb_Arena_Malloc(arena_.ptr(), error.size()));
+    memcpy(data, error.data(), error.size());
+    UPB_DESC(compiler_CodeGeneratorResponse_set_error)
+    (response_, upb_StringView_FromDataAndSize(data, error.size()));
+  }
+
+  void AddOutputFile(absl::string_view filename, absl::string_view content) {
+    UPB_DESC(compiler_CodeGeneratorResponse_File)* file = UPB_DESC(
+        compiler_CodeGeneratorResponse_add_file)(response_, arena_.ptr());
+    UPB_DESC(compiler_CodeGeneratorResponse_File_set_name)
+    (file, StringDup(filename));
+    UPB_DESC(compiler_CodeGeneratorResponse_File_set_content)
+    (file, StringDup(content));
+  }
+
+ private:
+  upb::Arena arena_;
+  upb::DefPool pool_;
+  UPB_DESC(compiler_CodeGeneratorRequest) * request_;
+  UPB_DESC(compiler_CodeGeneratorResponse) * response_;
+
+  static absl::string_view ToStringView(upb_StringView sv) {
+    return absl::string_view(sv.data, sv.size);
+  }
+
+  upb_StringView StringDup(absl::string_view s) {
+    char* data =
+        reinterpret_cast<char*>(upb_Arena_Malloc(arena_.ptr(), s.size()));
+    memcpy(data, s.data(), s.size());
+    return upb_StringView_FromDataAndSize(data, s.size());
+  }
+
+  std::string ReadAllStdinBinary() {
+    std::string data;
+#ifdef _WIN32
+    _setmode(_fileno(stdin), _O_BINARY);
+    _setmode(_fileno(stdout), _O_BINARY);
+#endif
+    char buf[4096];
+    while (size_t len = fread(buf, 1, sizeof(buf), stdin)) {
+      data.append(buf, len);
+    }
+    return data;
+  }
+
+  void ReadRequest() {
+    std::string data = ReadAllStdinBinary();
+    request_ = UPB_DESC(compiler_CodeGeneratorRequest_parse)(
+        data.data(), data.size(), arena_.ptr());
+    if (!request_) {
+      ABSL_LOG(FATAL) << "Failed to parse CodeGeneratorRequest";
+    }
+    response_ = UPB_DESC(compiler_CodeGeneratorResponse_new)(arena_.ptr());
+    UPB_DESC(compiler_CodeGeneratorResponse_set_supported_features)
+    (response_,
+     UPB_DESC(compiler_CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL));
+  }
+
+  void WriteResponse() {
+    size_t size;
+    char* serialized = UPB_DESC(compiler_CodeGeneratorResponse_serialize)(
+        response_, arena_.ptr(), &size);
+    if (!serialized) {
+      ABSL_LOG(FATAL) << "Failed to serialize CodeGeneratorResponse";
+    }
+
+    if (fwrite(serialized, 1, size, stdout) != size) {
+      ABSL_LOG(FATAL) << "Failed to write response to stdout";
+    }
+  }
+};
+
+}  // namespace upbc
+
+#include "upb/port/undef.inc"
+
+#endif  // UPB_UPBC_PLUGIN_H_
diff --git a/upbc/protoc-gen-upb.cc b/upbc/protoc-gen-upb.cc
new file mode 100644
index 0000000..0fa716a
--- /dev/null
+++ b/upbc/protoc-gen-upb.cc
@@ -0,0 +1,1154 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  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 LLC 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 <algorithm>
+#include <cmath>
+#include <cstddef>
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <limits>
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "absl/container/flat_hash_map.h"
+#include "absl/container/flat_hash_set.h"
+#include "absl/log/absl_check.h"
+#include "absl/log/absl_log.h"
+#include "absl/strings/escaping.h"
+#include "absl/strings/str_replace.h"
+#include "absl/strings/string_view.h"
+#include "absl/strings/substitute.h"
+#include "upb/base/descriptor_constants.h"
+#include "upb/base/string_view.h"
+#include "upb/reflection/def.hpp"
+#include "upb/wire/types.h"
+#include "upbc/common.h"
+#include "upbc/file_layout.h"
+#include "upbc/names.h"
+#include "upbc/plugin.h"
+
+// Must be last.
+#include "upb/port/def.inc"
+
+namespace upbc {
+namespace {
+
+struct Options {
+  bool bootstrap = false;
+};
+
+std::string SourceFilename(upb::FileDefPtr file) {
+  return StripExtension(file.name()) + ".upb.c";
+}
+
+std::string MessageInitName(upb::MessageDefPtr descriptor) {
+  return absl::StrCat(MessageName(descriptor), "_msg_init");
+}
+
+std::string MessageMiniTableRef(upb::MessageDefPtr descriptor,
+                                const Options& options) {
+  if (options.bootstrap) {
+    return absl::StrCat(MessageInitName(descriptor), "()");
+  } else {
+    return absl::StrCat("&", MessageInitName(descriptor));
+  }
+}
+
+std::string EnumInitName(upb::EnumDefPtr descriptor) {
+  return ToCIdent(descriptor.full_name()) + "_enum_init";
+}
+
+std::string EnumMiniTableRef(upb::EnumDefPtr descriptor,
+                             const Options& options) {
+  if (options.bootstrap) {
+    return absl::StrCat(EnumInitName(descriptor), "()");
+  } else {
+    return absl::StrCat("&", EnumInitName(descriptor));
+  }
+}
+
+std::string ExtensionIdentBase(upb::FieldDefPtr ext) {
+  assert(ext.is_extension());
+  std::string ext_scope;
+  if (ext.extension_scope()) {
+    return MessageName(ext.extension_scope());
+  } else {
+    return ToCIdent(ext.file().package());
+  }
+}
+
+std::string ExtensionLayout(upb::FieldDefPtr ext) {
+  return absl::StrCat(ExtensionIdentBase(ext), "_", ext.name(), "_ext");
+}
+
+std::string EnumValueSymbol(upb::EnumValDefPtr value) {
+  return ToCIdent(value.full_name());
+}
+
+std::string CTypeInternal(upb::FieldDefPtr field, bool is_const) {
+  std::string maybe_const = is_const ? "const " : "";
+  switch (field.ctype()) {
+    case kUpb_CType_Message: {
+      std::string maybe_struct =
+          field.file() != field.message_type().file() ? "struct " : "";
+      return maybe_const + maybe_struct + MessageName(field.message_type()) +
+             "*";
+    }
+    case kUpb_CType_Bool:
+      return "bool";
+    case kUpb_CType_Float:
+      return "float";
+    case kUpb_CType_Int32:
+    case kUpb_CType_Enum:
+      return "int32_t";
+    case kUpb_CType_UInt32:
+      return "uint32_t";
+    case kUpb_CType_Double:
+      return "double";
+    case kUpb_CType_Int64:
+      return "int64_t";
+    case kUpb_CType_UInt64:
+      return "uint64_t";
+    case kUpb_CType_String:
+    case kUpb_CType_Bytes:
+      return "upb_StringView";
+    default:
+      abort();
+  }
+}
+
+std::string FloatToCLiteral(float value) {
+  if (value == std::numeric_limits<float>::infinity()) {
+    return "kUpb_FltInfinity";
+  } else if (value == -std::numeric_limits<float>::infinity()) {
+    return "-kUpb_FltInfinity";
+  } else if (std::isnan(value)) {
+    return "kUpb_NaN";
+  } else {
+    return absl::StrCat(value);
+  }
+}
+
+std::string DoubleToCLiteral(double value) {
+  if (value == std::numeric_limits<double>::infinity()) {
+    return "kUpb_Infinity";
+  } else if (value == -std::numeric_limits<double>::infinity()) {
+    return "-kUpb_Infinity";
+  } else if (std::isnan(value)) {
+    return "kUpb_NaN";
+  } else {
+    return absl::StrCat(value);
+  }
+}
+
+// Escape trigraphs by escaping question marks to \?
+std::string EscapeTrigraphs(absl::string_view to_escape) {
+  return absl::StrReplaceAll(to_escape, {{"?", "\\?"}});
+}
+
+std::string FieldDefault(upb::FieldDefPtr field) {
+  switch (field.ctype()) {
+    case kUpb_CType_Message:
+      return "NULL";
+    case kUpb_CType_Bytes:
+    case kUpb_CType_String: {
+      upb_StringView str = field.default_value().str_val;
+      return absl::Substitute("upb_StringView_FromString(\"$0\")",
+                              EscapeTrigraphs(absl::CEscape(
+                                  absl::string_view(str.data, str.size))));
+    }
+    case kUpb_CType_Int32:
+      return absl::Substitute("(int32_t)$0", field.default_value().int32_val);
+    case kUpb_CType_Int64:
+      if (field.default_value().int64_val == INT64_MIN) {
+        // Special-case to avoid:
+        //   integer literal is too large to be represented in a signed integer
+        //   type, interpreting as unsigned
+        //   [-Werror,-Wimplicitly-unsigned-literal]
+        //   int64_t default_val = (int64_t)-9223372036854775808ll;
+        //
+        // More info here: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52661
+        return "INT64_MIN";
+      } else {
+        return absl::Substitute("(int64_t)$0ll",
+                                field.default_value().int64_val);
+      }
+    case kUpb_CType_UInt32:
+      return absl::Substitute("(uint32_t)$0u",
+                              field.default_value().uint32_val);
+    case kUpb_CType_UInt64:
+      return absl::Substitute("(uint64_t)$0ull",
+                              field.default_value().uint64_val);
+    case kUpb_CType_Float:
+      return FloatToCLiteral(field.default_value().float_val);
+    case kUpb_CType_Double:
+      return DoubleToCLiteral(field.default_value().double_val);
+    case kUpb_CType_Bool:
+      return field.default_value().bool_val ? "true" : "false";
+    case kUpb_CType_Enum:
+      // Use a number instead of a symbolic name so that we don't require
+      // this enum's header to be included.
+      return absl::StrCat(field.default_value().int32_val);
+  }
+  ABSL_ASSERT(false);
+  return "XXX";
+}
+
+std::string CType(upb::FieldDefPtr field) {
+  return CTypeInternal(field, false);
+}
+
+std::string CTypeConst(upb::FieldDefPtr field) {
+  return CTypeInternal(field, true);
+}
+
+std::string MapKeyCType(upb::FieldDefPtr map_field) {
+  return CType(map_field.message_type().map_key());
+}
+
+std::string MapValueCType(upb::FieldDefPtr map_field) {
+  return CType(map_field.message_type().map_value());
+}
+
+std::string MapKeySize(upb::FieldDefPtr map_field, absl::string_view expr) {
+  return map_field.message_type().map_key().ctype() == kUpb_CType_String
+             ? "0"
+             : absl::StrCat("sizeof(", expr, ")");
+}
+
+std::string MapValueSize(upb::FieldDefPtr map_field, absl::string_view expr) {
+  return map_field.message_type().map_value().ctype() == kUpb_CType_String
+             ? "0"
+             : absl::StrCat("sizeof(", expr, ")");
+}
+
+std::string FieldInitializer(const DefPoolPair& pools, upb::FieldDefPtr field,
+                             const Options& options);
+
+void DumpEnumValues(upb::EnumDefPtr desc, Output& output) {
+  std::vector<upb::EnumValDefPtr> values;
+  values.reserve(desc.value_count());
+  for (int i = 0; i < desc.value_count(); i++) {
+    values.push_back(desc.value(i));
+  }
+  std::sort(values.begin(), values.end(),
+            [](upb::EnumValDefPtr a, upb::EnumValDefPtr b) {
+              return a.number() < b.number();
+            });
+
+  for (size_t i = 0; i < values.size(); i++) {
+    auto value = values[i];
+    output("  $0 = $1", EnumValueSymbol(value), value.number());
+    if (i != values.size() - 1) {
+      output(",");
+    }
+    output("\n");
+  }
+}
+
+std::string GetFieldRep(const DefPoolPair& pools, upb::FieldDefPtr field) {
+  return upbc::GetFieldRep(pools.GetField32(field), pools.GetField64(field));
+}
+
+void GenerateExtensionInHeader(const DefPoolPair& pools, upb::FieldDefPtr ext,
+                               Output& output) {
+  output(
+      R"cc(
+        UPB_INLINE bool $0_has_$1(const struct $2* msg) {
+          return _upb_Message_HasExtensionField(msg, &$3);
+        }
+      )cc",
+      ExtensionIdentBase(ext), ext.name(), MessageName(ext.containing_type()),
+      ExtensionLayout(ext));
+
+  output(
+      R"cc(
+        UPB_INLINE void $0_clear_$1(struct $2* msg) {
+          _upb_Message_ClearExtensionField(msg, &$3);
+        }
+      )cc",
+      ExtensionIdentBase(ext), ext.name(), MessageName(ext.containing_type()),
+      ExtensionLayout(ext));
+
+  if (ext.IsSequence()) {
+    // TODO: We need generated accessors for repeated extensions.
+  } else {
+    output(
+        R"cc(
+          UPB_INLINE $0 $1_$2(const struct $3* msg) {
+            const upb_MiniTableExtension* ext = &$4;
+            UPB_ASSUME(!upb_IsRepeatedOrMap(&ext->field));
+            UPB_ASSUME(_upb_MiniTableField_GetRep(&ext->field) == $5);
+            $0 default_val = $6;
+            $0 ret;
+            _upb_Message_GetExtensionField(msg, ext, &default_val, &ret);
+            return ret;
+          }
+        )cc",
+        CTypeConst(ext), ExtensionIdentBase(ext), ext.name(),
+        MessageName(ext.containing_type()), ExtensionLayout(ext),
+        GetFieldRep(pools, ext), FieldDefault(ext));
+    output(
+        R"cc(
+          UPB_INLINE void $1_set_$2(struct $3* msg, $0 val, upb_Arena* arena) {
+            const upb_MiniTableExtension* ext = &$4;
+            UPB_ASSUME(!upb_IsRepeatedOrMap(&ext->field));
+            UPB_ASSUME(_upb_MiniTableField_GetRep(&ext->field) == $5);
+            bool ok = _upb_Message_SetExtensionField(msg, ext, &val, arena);
+            UPB_ASSERT(ok);
+          }
+        )cc",
+        CTypeConst(ext), ExtensionIdentBase(ext), ext.name(),
+        MessageName(ext.containing_type()), ExtensionLayout(ext),
+        GetFieldRep(pools, ext));
+  }
+}
+
+void GenerateMessageFunctionsInHeader(upb::MessageDefPtr message,
+                                      const Options& options, Output& output) {
+  // TODO: The generated code here does not check the return values
+  // from upb_Encode(). How can we even fix this without breaking other things?
+  output(
+      R"cc(
+        UPB_INLINE $0* $0_new(upb_Arena* arena) {
+          return ($0*)_upb_Message_New($1, arena);
+        }
+        UPB_INLINE $0* $0_parse(const char* buf, size_t size, upb_Arena* arena) {
+          $0* ret = $0_new(arena);
+          if (!ret) return NULL;
+          if (upb_Decode(buf, size, ret, $1, NULL, 0, arena) != kUpb_DecodeStatus_Ok) {
+            return NULL;
+          }
+          return ret;
+        }
+        UPB_INLINE $0* $0_parse_ex(const char* buf, size_t size,
+                                   const upb_ExtensionRegistry* extreg,
+                                   int options, upb_Arena* arena) {
+          $0* ret = $0_new(arena);
+          if (!ret) return NULL;
+          if (upb_Decode(buf, size, ret, $1, extreg, options, arena) !=
+              kUpb_DecodeStatus_Ok) {
+            return NULL;
+          }
+          return ret;
+        }
+        UPB_INLINE char* $0_serialize(const $0* msg, upb_Arena* arena, size_t* len) {
+          char* ptr;
+          (void)upb_Encode(msg, $1, 0, arena, &ptr, len);
+          return ptr;
+        }
+        UPB_INLINE char* $0_serialize_ex(const $0* msg, int options,
+                                         upb_Arena* arena, size_t* len) {
+          char* ptr;
+          (void)upb_Encode(msg, $1, options, arena, &ptr, len);
+          return ptr;
+        }
+      )cc",
+      MessageName(message), MessageMiniTableRef(message, options));
+}
+
+void GenerateOneofInHeader(upb::OneofDefPtr oneof, const DefPoolPair& pools,
+                           absl::string_view msg_name, const Options& options,
+                           Output& output) {
+  std::string fullname = ToCIdent(oneof.full_name());
+  output("typedef enum {\n");
+  for (int j = 0; j < oneof.field_count(); j++) {
+    upb::FieldDefPtr field = oneof.field(j);
+    output("  $0_$1 = $2,\n", fullname, field.name(), field.number());
+  }
+  output(
+      "  $0_NOT_SET = 0\n"
+      "} $0_oneofcases;\n",
+      fullname);
+  output(
+      R"cc(
+        UPB_INLINE $0_oneofcases $1_$2_case(const $1* msg) {
+          const upb_MiniTableField field = $3;
+          return ($0_oneofcases)upb_Message_WhichOneofFieldNumber(msg, &field);
+        }
+      )cc",
+      fullname, msg_name, oneof.name(),
+      FieldInitializer(pools, oneof.field(0), options));
+}
+
+void GenerateHazzer(upb::FieldDefPtr field, const DefPoolPair& pools,
+                    absl::string_view msg_name,
+                    const NameToFieldDefMap& field_names,
+                    const Options& options, Output& output) {
+  std::string resolved_name = ResolveFieldName(field, field_names);
+  if (field.has_presence()) {
+    output(
+        R"cc(
+          UPB_INLINE bool $0_has_$1(const $0* msg) {
+            const upb_MiniTableField field = $2;
+            return _upb_Message_HasNonExtensionField(msg, &field);
+          }
+        )cc",
+        msg_name, resolved_name, FieldInitializer(pools, field, options));
+  } else if (field.IsMap()) {
+    // Do nothing.
+  } else if (field.IsSequence()) {
+    // TODO: remove.
+    output(
+        R"cc(
+          UPB_INLINE bool $0_has_$1(const $0* msg) {
+            size_t size;
+            $0_$1(msg, &size);
+            return size != 0;
+          }
+        )cc",
+        msg_name, resolved_name);
+  }
+}
+
+void GenerateClear(upb::FieldDefPtr field, const DefPoolPair& pools,
+                   absl::string_view msg_name,
+                   const NameToFieldDefMap& field_names, const Options& options,
+                   Output& output) {
+  if (field == field.containing_type().map_key() ||
+      field == field.containing_type().map_value()) {
+    // Cannot be cleared.
+    return;
+  }
+  std::string resolved_name = ResolveFieldName(field, field_names);
+  output(
+      R"cc(
+        UPB_INLINE void $0_clear_$1($0* msg) {
+          const upb_MiniTableField field = $2;
+          _upb_Message_ClearNonExtensionField(msg, &field);
+        }
+      )cc",
+      msg_name, resolved_name, FieldInitializer(pools, field, options));
+}
+
+void GenerateMapGetters(upb::FieldDefPtr field, const DefPoolPair& pools,
+                        absl::string_view msg_name,
+                        const NameToFieldDefMap& field_names,
+                        const Options& options, Output& output) {
+  std::string resolved_name = ResolveFieldName(field, field_names);
+  output(
+      R"cc(
+        UPB_INLINE size_t $0_$1_size(const $0* msg) {
+          const upb_MiniTableField field = $2;
+          const upb_Map* map = upb_Message_GetMap(msg, &field);
+          return map ? _upb_Map_Size(map) : 0;
+        }
+      )cc",
+      msg_name, resolved_name, FieldInitializer(pools, field, options));
+  output(
+      R"cc(
+        UPB_INLINE bool $0_$1_get(const $0* msg, $2 key, $3* val) {
+          const upb_MiniTableField field = $4;
+          const upb_Map* map = upb_Message_GetMap(msg, &field);
+          if (!map) return false;
+          return _upb_Map_Get(map, &key, $5, val, $6);
+        }
+      )cc",
+      msg_name, resolved_name, MapKeyCType(field), MapValueCType(field),
+      FieldInitializer(pools, field, options), MapKeySize(field, "key"),
+      MapValueSize(field, "*val"));
+  output(
+      R"cc(
+        UPB_INLINE $0 $1_$2_next(const $1* msg, size_t* iter) {
+          const upb_MiniTableField field = $3;
+          const upb_Map* map = upb_Message_GetMap(msg, &field);
+          if (!map) return NULL;
+          return ($0)_upb_map_next(map, iter);
+        }
+      )cc",
+      CTypeConst(field), msg_name, resolved_name,
+      FieldInitializer(pools, field, options));
+}
+
+void GenerateMapEntryGetters(upb::FieldDefPtr field, absl::string_view msg_name,
+                             Output& output) {
+  output(
+      R"cc(
+        UPB_INLINE $0 $1_$2(const $1* msg) {
+          $3 ret;
+          _upb_msg_map_$2(msg, &ret, $4);
+          return ret;
+        }
+      )cc",
+      CTypeConst(field), msg_name, field.name(), CType(field),
+      field.ctype() == kUpb_CType_String ? "0" : "sizeof(ret)");
+}
+
+void GenerateRepeatedGetters(upb::FieldDefPtr field, const DefPoolPair& pools,
+                             absl::string_view msg_name,
+                             const NameToFieldDefMap& field_names,
+                             const Options& options, Output& output) {
+  // Generate getter returning first item and size.
+  //
+  // Example:
+  //   UPB_INLINE const struct Bar* const* name(const Foo* msg, size_t* size)
+  output(
+      R"cc(
+        UPB_INLINE $0 const* $1_$2(const $1* msg, size_t* size) {
+          const upb_MiniTableField field = $3;
+          const upb_Array* arr = upb_Message_GetArray(msg, &field);
+          if (arr) {
+            if (size) *size = arr->size;
+            return ($0 const*)_upb_array_constptr(arr);
+          } else {
+            if (size) *size = 0;
+            return NULL;
+          }
+        }
+      )cc",
+      CTypeConst(field),                       // $0
+      msg_name,                                // $1
+      ResolveFieldName(field, field_names),    // $2
+      FieldInitializer(pools, field, options)  // #3
+  );
+  // Generate private getter returning array or NULL for immutable and upb_Array
+  // for mutable.
+  //
+  // Example:
+  //   UPB_INLINE const upb_Array* _name_upbarray(size_t* size)
+  //   UPB_INLINE upb_Array* _name_mutable_upbarray(size_t* size)
+  output(
+      R"cc(
+        UPB_INLINE const upb_Array* _$1_$2_$4(const $1* msg, size_t* size) {
+          const upb_MiniTableField field = $3;
+          const upb_Array* arr = upb_Message_GetArray(msg, &field);
+          if (size) {
+            *size = arr ? arr->size : 0;
+          }
+          return arr;
+        }
+        UPB_INLINE upb_Array* _$1_$2_$5(const $1* msg, size_t* size, upb_Arena* arena) {
+          const upb_MiniTableField field = $3;
+          upb_Array* arr = upb_Message_GetOrCreateMutableArray(
+              (upb_Message*)msg, &field, arena);
+          if (size) {
+            *size = arr ? arr->size : 0;
+          }
+          return arr;
+        }
+      )cc",
+      CTypeConst(field),                        // $0
+      msg_name,                                 // $1
+      ResolveFieldName(field, field_names),     // $2
+      FieldInitializer(pools, field, options),  // $3
+      kRepeatedFieldArrayGetterPostfix,         // $4
+      kRepeatedFieldMutableArrayGetterPostfix   // $5
+  );
+}
+
+void GenerateScalarGetters(upb::FieldDefPtr field, const DefPoolPair& pools,
+                           absl::string_view msg_name,
+                           const NameToFieldDefMap& field_names,
+                           const Options& Options, Output& output) {
+  std::string field_name = ResolveFieldName(field, field_names);
+  output(
+      R"cc(
+        UPB_INLINE $0 $1_$2(const $1* msg) {
+          $0 default_val = $3;
+          $0 ret;
+          const upb_MiniTableField field = $4;
+          _upb_Message_GetNonExtensionField(msg, &field, &default_val, &ret);
+          return ret;
+        }
+      )cc",
+      CTypeConst(field), msg_name, field_name, FieldDefault(field),
+      FieldInitializer(pools, field, Options));
+}
+
+void GenerateGetters(upb::FieldDefPtr field, const DefPoolPair& pools,
+                     absl::string_view msg_name,
+                     const NameToFieldDefMap& field_names,
+                     const Options& options, Output& output) {
+  if (field.IsMap()) {
+    GenerateMapGetters(field, pools, msg_name, field_names, options, output);
+  } else if (UPB_DESC(MessageOptions_map_entry)(
+                 field.containing_type().options())) {
+    GenerateMapEntryGetters(field, msg_name, output);
+  } else if (field.IsSequence()) {
+    GenerateRepeatedGetters(field, pools, msg_name, field_names, options,
+                            output);
+  } else {
+    GenerateScalarGetters(field, pools, msg_name, field_names, options, output);
+  }
+}
+
+void GenerateMapSetters(upb::FieldDefPtr field, const DefPoolPair& pools,
+                        absl::string_view msg_name,
+                        const NameToFieldDefMap& field_names,
+                        const Options& options, Output& output) {
+  std::string resolved_name = ResolveFieldName(field, field_names);
+  output(
+      R"cc(
+        UPB_INLINE void $0_$1_clear($0* msg) {
+          const upb_MiniTableField field = $2;
+          upb_Map* map = (upb_Map*)upb_Message_GetMap(msg, &field);
+          if (!map) return;
+          _upb_Map_Clear(map);
+        }
+      )cc",
+      msg_name, resolved_name, FieldInitializer(pools, field, options));
+  output(
+      R"cc(
+        UPB_INLINE bool $0_$1_set($0* msg, $2 key, $3 val, upb_Arena* a) {
+          const upb_MiniTableField field = $4;
+          upb_Map* map = _upb_Message_GetOrCreateMutableMap(msg, &field, $5, $6, a);
+          return _upb_Map_Insert(map, &key, $5, &val, $6, a) !=
+                 kUpb_MapInsertStatus_OutOfMemory;
+        }
+      )cc",
+      msg_name, resolved_name, MapKeyCType(field), MapValueCType(field),
+      FieldInitializer(pools, field, options), MapKeySize(field, "key"),
+      MapValueSize(field, "val"));
+  output(
+      R"cc(
+        UPB_INLINE bool $0_$1_delete($0* msg, $2 key) {
+          const upb_MiniTableField field = $3;
+          upb_Map* map = (upb_Map*)upb_Message_GetMap(msg, &field);
+          if (!map) return false;
+          return _upb_Map_Delete(map, &key, $4, NULL);
+        }
+      )cc",
+      msg_name, resolved_name, MapKeyCType(field),
+      FieldInitializer(pools, field, options), MapKeySize(field, "key"));
+  output(
+      R"cc(
+        UPB_INLINE $0 $1_$2_nextmutable($1* msg, size_t* iter) {
+          const upb_MiniTableField field = $3;
+          upb_Map* map = (upb_Map*)upb_Message_GetMap(msg, &field);
+          if (!map) return NULL;
+          return ($0)_upb_map_next(map, iter);
+        }
+      )cc",
+      CType(field), msg_name, resolved_name,
+      FieldInitializer(pools, field, options));
+}
+
+void GenerateRepeatedSetters(upb::FieldDefPtr field, const DefPoolPair& pools,
+                             absl::string_view msg_name,
+                             const NameToFieldDefMap& field_names,
+                             const Options& options, Output& output) {
+  std::string resolved_name = ResolveFieldName(field, field_names);
+  output(
+      R"cc(
+        UPB_INLINE $0* $1_mutable_$2($1* msg, size_t* size) {
+          upb_MiniTableField field = $3;
+          upb_Array* arr = upb_Message_GetMutableArray(msg, &field);
+          if (arr) {
+            if (size) *size = arr->size;
+            return ($0*)_upb_array_ptr(arr);
+          } else {
+            if (size) *size = 0;
+            return NULL;
+          }
+        }
+      )cc",
+      CType(field), msg_name, resolved_name,
+      FieldInitializer(pools, field, options));
+  output(
+      R"cc(
+        UPB_INLINE $0* $1_resize_$2($1* msg, size_t size, upb_Arena* arena) {
+          upb_MiniTableField field = $3;
+          return ($0*)upb_Message_ResizeArrayUninitialized(msg, &field, size, arena);
+        }
+      )cc",
+      CType(field), msg_name, resolved_name,
+      FieldInitializer(pools, field, options));
+  if (field.ctype() == kUpb_CType_Message) {
+    output(
+        R"cc(
+          UPB_INLINE struct $0* $1_add_$2($1* msg, upb_Arena* arena) {
+            upb_MiniTableField field = $4;
+            upb_Array* arr = upb_Message_GetOrCreateMutableArray(msg, &field, arena);
+            if (!arr || !_upb_Array_ResizeUninitialized(arr, arr->size + 1, arena)) {
+              return NULL;
+            }
+            struct $0* sub = (struct $0*)_upb_Message_New($3, arena);
+            if (!arr || !sub) return NULL;
+            _upb_Array_Set(arr, arr->size - 1, &sub, sizeof(sub));
+            return sub;
+          }
+        )cc",
+        MessageName(field.message_type()), msg_name, resolved_name,
+        MessageMiniTableRef(field.message_type(), options),
+        FieldInitializer(pools, field, options));
+  } else {
+    output(
+        R"cc(
+          UPB_INLINE bool $1_add_$2($1* msg, $0 val, upb_Arena* arena) {
+            upb_MiniTableField field = $3;
+            upb_Array* arr = upb_Message_GetOrCreateMutableArray(msg, &field, arena);
+            if (!arr || !_upb_Array_ResizeUninitialized(arr, arr->size + 1, arena)) {
+              return false;
+            }
+            _upb_Array_Set(arr, arr->size - 1, &val, sizeof(val));
+            return true;
+          }
+        )cc",
+        CType(field), msg_name, resolved_name,
+        FieldInitializer(pools, field, options));
+  }
+}
+
+void GenerateNonRepeatedSetters(upb::FieldDefPtr field,
+                                const DefPoolPair& pools,
+                                absl::string_view msg_name,
+                                const NameToFieldDefMap& field_names,
+                                const Options& options, Output& output) {
+  if (field == field.containing_type().map_key()) {
+    // Key cannot be mutated.
+    return;
+  }
+
+  std::string field_name = ResolveFieldName(field, field_names);
+
+  if (field == field.containing_type().map_value()) {
+    output(R"cc(
+             UPB_INLINE void $0_set_$1($0 *msg, $2 value) {
+               _upb_msg_map_set_value(msg, &value, $3);
+             }
+           )cc",
+           msg_name, field_name, CType(field),
+           field.ctype() == kUpb_CType_String ? "0"
+                                              : "sizeof(" + CType(field) + ")");
+  } else {
+    output(R"cc(
+             UPB_INLINE void $0_set_$1($0 *msg, $2 value) {
+               const upb_MiniTableField field = $3;
+               _upb_Message_SetNonExtensionField(msg, &field, &value);
+             }
+           )cc",
+           msg_name, field_name, CType(field),
+           FieldInitializer(pools, field, options));
+  }
+
+  // Message fields also have a Msg_mutable_foo() accessor that will create
+  // the sub-message if it doesn't already exist.
+  if (field.ctype() == kUpb_CType_Message &&
+      !UPB_DESC(MessageOptions_map_entry)(field.containing_type().options())) {
+    output(
+        R"cc(
+          UPB_INLINE struct $0* $1_mutable_$2($1* msg, upb_Arena* arena) {
+            struct $0* sub = (struct $0*)$1_$2(msg);
+            if (sub == NULL) {
+              sub = (struct $0*)_upb_Message_New($3, arena);
+              if (sub) $1_set_$2(msg, sub);
+            }
+            return sub;
+          }
+        )cc",
+        MessageName(field.message_type()), msg_name, field_name,
+        MessageMiniTableRef(field.message_type(), options));
+  }
+}
+
+void GenerateSetters(upb::FieldDefPtr field, const DefPoolPair& pools,
+                     absl::string_view msg_name,
+                     const NameToFieldDefMap& field_names,
+                     const Options& options, Output& output) {
+  if (field.IsMap()) {
+    GenerateMapSetters(field, pools, msg_name, field_names, options, output);
+  } else if (field.IsSequence()) {
+    GenerateRepeatedSetters(field, pools, msg_name, field_names, options,
+                            output);
+  } else {
+    GenerateNonRepeatedSetters(field, pools, msg_name, field_names, options,
+                               output);
+  }
+}
+
+void GenerateMessageInHeader(upb::MessageDefPtr message,
+                             const DefPoolPair& pools, const Options& options,
+                             Output& output) {
+  output("/* $0 */\n\n", message.full_name());
+  std::string msg_name = ToCIdent(message.full_name());
+  if (!UPB_DESC(MessageOptions_map_entry)(message.options())) {
+    GenerateMessageFunctionsInHeader(message, options, output);
+  }
+
+  for (int i = 0; i < message.real_oneof_count(); i++) {
+    GenerateOneofInHeader(message.oneof(i), pools, msg_name, options, output);
+  }
+
+  auto field_names = CreateFieldNameMap(message);
+  for (auto field : FieldNumberOrder(message)) {
+    GenerateClear(field, pools, msg_name, field_names, options, output);
+    GenerateGetters(field, pools, msg_name, field_names, options, output);
+    GenerateHazzer(field, pools, msg_name, field_names, options, output);
+  }
+
+  output("\n");
+
+  for (auto field : FieldNumberOrder(message)) {
+    GenerateSetters(field, pools, msg_name, field_names, options, output);
+  }
+
+  output("\n");
+}
+
+void ForwardDeclareMiniTableInit(upb::MessageDefPtr message,
+                                 const Options& options, Output& output) {
+  if (options.bootstrap) {
+    output("extern const upb_MiniTable* $0();\n", MessageInitName(message));
+  } else {
+    output("extern const upb_MiniTable $0;\n", MessageInitName(message));
+  }
+}
+
+std::vector<upb::MessageDefPtr> SortedForwardMessages(
+    const std::vector<upb::MessageDefPtr>& this_file_messages,
+    const std::vector<upb::FieldDefPtr>& this_file_exts) {
+  std::map<std::string, upb::MessageDefPtr> forward_messages;
+  for (auto message : this_file_messages) {
+    for (int i = 0; i < message.field_count(); i++) {
+      upb::FieldDefPtr field = message.field(i);
+      if (field.ctype() == kUpb_CType_Message &&
+          field.file() != field.message_type().file()) {
+        forward_messages[field.message_type().full_name()] =
+            field.message_type();
+      }
+    }
+  }
+  for (auto ext : this_file_exts) {
+    if (ext.file() != ext.containing_type().file()) {
+      forward_messages[ext.containing_type().full_name()] =
+          ext.containing_type();
+    }
+  }
+  std::vector<upb::MessageDefPtr> ret;
+  ret.reserve(forward_messages.size());
+  for (const auto& pair : forward_messages) {
+    ret.push_back(pair.second);
+  }
+  return ret;
+}
+
+void WriteHeader(const DefPoolPair& pools, upb::FileDefPtr file,
+                 const Options& options, Output& output) {
+  const std::vector<upb::MessageDefPtr> this_file_messages =
+      SortedMessages(file);
+  const std::vector<upb::FieldDefPtr> this_file_exts = SortedExtensions(file);
+  std::vector<upb::EnumDefPtr> this_file_enums = SortedEnums(file);
+  std::vector<upb::MessageDefPtr> forward_messages =
+      SortedForwardMessages(this_file_messages, this_file_exts);
+
+  EmitFileWarning(file.name(), output);
+  output(
+      "#ifndef $0_UPB_H_\n"
+      "#define $0_UPB_H_\n\n"
+      "#include \"upb/generated_code_support.h\"\n\n",
+      ToPreproc(file.name()));
+
+  for (int i = 0; i < file.public_dependency_count(); i++) {
+    if (i == 0) {
+      output("/* Public Imports. */\n");
+    }
+    output("#include \"$0\"\n", CApiHeaderFilename(file.public_dependency(i)));
+  }
+  if (file.public_dependency_count() > 0) {
+    output("\n");
+  }
+
+  if (!options.bootstrap) {
+    output("#include \"$0\"\n\n", MiniTableHeaderFilename(file));
+    for (int i = 0; i < file.dependency_count(); i++) {
+      output("#include \"$0\"\n", MiniTableHeaderFilename(file.dependency(i)));
+    }
+    if (file.dependency_count() > 0) {
+      output("\n");
+    }
+  }
+
+  output(
+      "// Must be last.\n"
+      "#include \"upb/port/def.inc\"\n"
+      "\n"
+      "#ifdef __cplusplus\n"
+      "extern \"C\" {\n"
+      "#endif\n"
+      "\n");
+
+  if (options.bootstrap) {
+    for (auto message : this_file_messages) {
+      output("extern const upb_MiniTable* $0();\n", MessageInitName(message));
+    }
+    for (auto message : forward_messages) {
+      output("extern const upb_MiniTable* $0();\n", MessageInitName(message));
+    }
+    for (auto enumdesc : this_file_enums) {
+      output("extern const upb_MiniTableEnum* $0();\n", EnumInit(enumdesc));
+    }
+    output("\n");
+  }
+
+  // Forward-declare types defined in this file.
+  for (auto message : this_file_messages) {
+    output("typedef struct $0 $0;\n", ToCIdent(message.full_name()));
+  }
+
+  // Forward-declare types not in this file, but used as submessages.
+  // Order by full name for consistent ordering.
+  for (auto msg : forward_messages) {
+    output("struct $0;\n", MessageName(msg));
+  }
+
+  if (!this_file_messages.empty()) {
+    output("\n");
+  }
+
+  for (auto enumdesc : this_file_enums) {
+    output("typedef enum {\n");
+    DumpEnumValues(enumdesc, output);
+    output("} $0;\n\n", ToCIdent(enumdesc.full_name()));
+  }
+
+  output("\n");
+
+  output("\n");
+  for (auto message : this_file_messages) {
+    GenerateMessageInHeader(message, pools, options, output);
+  }
+
+  for (auto ext : this_file_exts) {
+    GenerateExtensionInHeader(pools, ext, output);
+  }
+
+  if (absl::string_view(file.name()) == "google/protobuf/descriptor.proto" ||
+      absl::string_view(file.name()) == "net/proto2/proto/descriptor.proto") {
+    // This is gratuitously inefficient with how many times it rebuilds
+    // MessageLayout objects for the same message. But we only do this for one
+    // proto (descriptor.proto) so we don't worry about it.
+    upb::MessageDefPtr max32_message;
+    upb::MessageDefPtr max64_message;
+    size_t max32 = 0;
+    size_t max64 = 0;
+    for (const auto message : this_file_messages) {
+      if (absl::EndsWith(message.name(), "Options")) {
+        size_t size32 = pools.GetMiniTable32(message)->size;
+        size_t size64 = pools.GetMiniTable64(message)->size;
+        if (size32 > max32) {
+          max32 = size32;
+          max32_message = message;
+        }
+        if (size64 > max64) {
+          max64 = size64;
+          max64_message = message;
+        }
+      }
+    }
+
+    output("/* Max size 32 is $0 */\n", max32_message.full_name());
+    output("/* Max size 64 is $0 */\n", max64_message.full_name());
+    output("#define _UPB_MAXOPT_SIZE UPB_SIZE($0, $1)\n\n", max32, max64);
+  }
+
+  output(
+      "#ifdef __cplusplus\n"
+      "}  /* extern \"C\" */\n"
+      "#endif\n"
+      "\n"
+      "#include \"upb/port/undef.inc\"\n"
+      "\n"
+      "#endif  /* $0_UPB_H_ */\n",
+      ToPreproc(file.name()));
+}
+
+std::string FieldInitializer(upb::FieldDefPtr field,
+                             const upb_MiniTableField* field64,
+                             const upb_MiniTableField* field32,
+                             const Options& options) {
+  if (options.bootstrap) {
+    ABSL_CHECK(!field.is_extension());
+    return absl::Substitute(
+        "*upb_MiniTable_FindFieldByNumber($0, $1)",
+        MessageMiniTableRef(field.containing_type(), options), field.number());
+  } else {
+    return upbc::FieldInitializer(field, field64, field32);
+  }
+}
+
+std::string FieldInitializer(const DefPoolPair& pools, upb::FieldDefPtr field,
+                             const Options& options) {
+  return FieldInitializer(field, pools.GetField64(field),
+                          pools.GetField32(field), options);
+}
+
+void WriteMessageMiniDescriptorInitializer(upb::MessageDefPtr msg,
+                                           const Options& options,
+                                           Output& output) {
+  Output resolve_calls;
+  for (int i = 0; i < msg.field_count(); i++) {
+    upb::FieldDefPtr field = msg.field(i);
+    if (!field.message_type() && !field.enum_subdef()) continue;
+    if (field.message_type()) {
+      resolve_calls(
+          "upb_MiniTable_SetSubMessage(mini_table, "
+          "(upb_MiniTableField*)upb_MiniTable_FindFieldByNumber(mini_table, "
+          "$0), $1);\n  ",
+          field.number(), MessageMiniTableRef(field.message_type(), options));
+    } else if (field.enum_subdef() && field.enum_subdef().is_closed()) {
+      resolve_calls(
+          "upb_MiniTable_SetSubEnum(mini_table, "
+          "(upb_MiniTableField*)upb_MiniTable_FindFieldByNumber(mini_table, "
+          "$0), $1);\n  ",
+          field.number(), EnumMiniTableRef(field.enum_subdef(), options));
+    }
+  }
+
+  output(
+      R"cc(
+        const upb_MiniTable* $0() {
+          static upb_MiniTable* mini_table = NULL;
+          static const char* mini_descriptor = "$1";
+          if (mini_table) return mini_table;
+          mini_table =
+              upb_MiniTable_Build(mini_descriptor, strlen(mini_descriptor),
+                                  upb_BootstrapArena(), NULL);
+          $2return mini_table;
+        }
+      )cc",
+      MessageInitName(msg), msg.MiniDescriptorEncode(), resolve_calls.output());
+  output("\n");
+}
+
+void WriteEnumMiniDescriptorInitializer(upb::EnumDefPtr enum_def,
+                                        const Options& options,
+                                        Output& output) {
+  output(
+      R"cc(
+        const upb_MiniTableEnum* $0() {
+          static const upb_MiniTableEnum* mini_table = NULL;
+          static const char* mini_descriptor = "$1";
+          if (mini_table) return mini_table;
+          mini_table =
+              upb_MiniTableEnum_Build(mini_descriptor, strlen(mini_descriptor),
+                                      upb_BootstrapArena(), NULL);
+          return mini_table;
+        }
+      )cc",
+      EnumInitName(enum_def), enum_def.MiniDescriptorEncode());
+  output("\n");
+}
+
+void WriteMiniDescriptorSource(const DefPoolPair& pools, upb::FileDefPtr file,
+                               const Options& options, Output& output) {
+  output(
+      "#include <stddef.h>\n"
+      "#include \"upb/generated_code_support.h\"\n"
+      "#include \"$0\"\n\n",
+      CApiHeaderFilename(file));
+
+  for (int i = 0; i < file.dependency_count(); i++) {
+    output("#include \"$0\"\n", CApiHeaderFilename(file.dependency(i)));
+  }
+
+  output(
+      R"cc(
+        static upb_Arena* upb_BootstrapArena() {
+          static upb_Arena* arena = NULL;
+          if (!arena) arena = upb_Arena_New();
+          return arena;
+        }
+      )cc");
+
+  output("\n");
+
+  for (const auto msg : SortedMessages(file)) {
+    WriteMessageMiniDescriptorInitializer(msg, options, output);
+  }
+
+  for (const auto msg : SortedEnums(file)) {
+    WriteEnumMiniDescriptorInitializer(msg, options, output);
+  }
+}
+
+void GenerateFile(const DefPoolPair& pools, upb::FileDefPtr file,
+                  const Options& options, Plugin* plugin) {
+  Output h_output;
+  WriteHeader(pools, file, options, h_output);
+  plugin->AddOutputFile(CApiHeaderFilename(file), h_output.output());
+
+  if (options.bootstrap) {
+    Output c_output;
+    WriteMiniDescriptorSource(pools, file, options, c_output);
+    plugin->AddOutputFile(SourceFilename(file), c_output.output());
+  } else {
+    // TODO: remove once we can figure out how to make both Blaze
+    // and Bazel happy with header-only libraries.
+
+    // begin:github_only
+    plugin->AddOutputFile(SourceFilename(file), "\n");
+    // end:github_only
+  }
+}
+
+bool ParseOptions(Plugin* plugin, Options* options) {
+  for (const auto& pair : ParseGeneratorParameter(plugin->parameter())) {
+    if (pair.first == "bootstrap_upb") {
+      options->bootstrap = true;
+    } else {
+      plugin->SetError(absl::Substitute("Unknown parameter: $0", pair.first));
+      return false;
+    }
+  }
+
+  return true;
+}
+
+absl::string_view ToStringView(upb_StringView str) {
+  return absl::string_view(str.data, str.size);
+}
+
+}  // namespace
+
+}  // namespace upbc
+
+int main(int argc, char** argv) {
+  upbc::DefPoolPair pools;
+  upbc::Plugin plugin;
+  upbc::Options options;
+  if (!ParseOptions(&plugin, &options)) return 0;
+  plugin.GenerateFilesRaw([&](const UPB_DESC(FileDescriptorProto) * file_proto,
+                              bool generate) {
+    upb::Status status;
+    upb::FileDefPtr file = pools.AddFile(file_proto, &status);
+    if (!file) {
+      absl::string_view name =
+          upbc::ToStringView(UPB_DESC(FileDescriptorProto_name)(file_proto));
+      ABSL_LOG(FATAL) << "Couldn't add file " << name
+                      << " to DefPool: " << status.error_message();
+    }
+    if (generate) GenerateFile(pools, file, options, &plugin);
+  });
+  return 0;
+}
diff --git a/upbc/protoc-gen-upb_minitable.cc b/upbc/protoc-gen-upb_minitable.cc
new file mode 100644
index 0000000..745dc84
--- /dev/null
+++ b/upbc/protoc-gen-upb_minitable.cc
@@ -0,0 +1,709 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  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 LLC 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 <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "absl/log/absl_check.h"
+#include "absl/log/absl_log.h"
+#include "absl/strings/escaping.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_replace.h"
+#include "absl/strings/string_view.h"
+#include "absl/strings/substitute.h"
+#include "upb/base/descriptor_constants.h"
+#include "upb/base/string_view.h"
+#include "upb/reflection/def.hpp"
+#include "upb/wire/types.h"
+#include "upbc/common.h"
+#include "upbc/file_layout.h"
+#include "upbc/names.h"
+#include "upbc/plugin.h"
+
+// Must be last.
+#include "upb/port/def.inc"
+
+namespace upbc {
+namespace {
+
+// Returns fields in order of "hotness", eg. how frequently they appear in
+// serialized payloads. Ideally this will use a profile. When we don't have
+// that, we assume that fields with smaller numbers are used more frequently.
+inline std::vector<upb::FieldDefPtr> FieldHotnessOrder(
+    upb::MessageDefPtr message) {
+  std::vector<upb::FieldDefPtr> fields;
+  size_t field_count = message.field_count();
+  fields.reserve(field_count);
+  for (size_t i = 0; i < field_count; i++) {
+    fields.push_back(message.field(i));
+  }
+  std::sort(fields.begin(), fields.end(),
+            [](upb::FieldDefPtr a, upb::FieldDefPtr b) {
+              return std::make_pair(!a.is_required(), a.number()) <
+                     std::make_pair(!b.is_required(), b.number());
+            });
+  return fields;
+}
+
+std::string SourceFilename(upb::FileDefPtr file) {
+  return StripExtension(file.name()) + ".upb_minitable.c";
+}
+
+std::string MessageInitName(upb::MessageDefPtr descriptor) {
+  return absl::StrCat(MessageName(descriptor), "_msg_init");
+}
+
+std::string ExtensionIdentBase(upb::FieldDefPtr ext) {
+  assert(ext.is_extension());
+  std::string ext_scope;
+  if (ext.extension_scope()) {
+    return MessageName(ext.extension_scope());
+  } else {
+    return ToCIdent(ext.file().package());
+  }
+}
+
+std::string ExtensionLayout(upb::FieldDefPtr ext) {
+  return absl::StrCat(ExtensionIdentBase(ext), "_", ext.name(), "_ext");
+}
+
+const char* kEnumsInit = "enums_layout";
+const char* kExtensionsInit = "extensions_layout";
+const char* kMessagesInit = "messages_layout";
+
+void WriteHeader(const DefPoolPair& pools, upb::FileDefPtr file,
+                 Output& output) {
+  EmitFileWarning(file.name(), output);
+  output(
+      "#ifndef $0_UPB_MINITABLE_H_\n"
+      "#define $0_UPB_MINITABLE_H_\n\n"
+      "#include \"upb/generated_code_support.h\"\n",
+      ToPreproc(file.name()));
+
+  for (int i = 0; i < file.public_dependency_count(); i++) {
+    if (i == 0) {
+      output("/* Public Imports. */\n");
+    }
+    output("#include \"$0\"\n",
+           MiniTableHeaderFilename(file.public_dependency(i)));
+    if (i == file.public_dependency_count() - 1) {
+      output("\n");
+    }
+  }
+
+  output(
+      "\n"
+      "// Must be last.\n"
+      "#include \"upb/port/def.inc\"\n"
+      "\n"
+      "#ifdef __cplusplus\n"
+      "extern \"C\" {\n"
+      "#endif\n"
+      "\n");
+
+  const std::vector<upb::MessageDefPtr> this_file_messages =
+      SortedMessages(file);
+  const std::vector<upb::FieldDefPtr> this_file_exts = SortedExtensions(file);
+
+  for (auto message : this_file_messages) {
+    output("extern const upb_MiniTable $0;\n", MessageInitName(message));
+  }
+  for (auto ext : this_file_exts) {
+    output("extern const upb_MiniTableExtension $0;\n", ExtensionLayout(ext));
+  }
+
+  output("\n");
+
+  std::vector<upb::EnumDefPtr> this_file_enums = SortedEnums(file);
+
+  if (file.syntax() == kUpb_Syntax_Proto2) {
+    for (const auto enumdesc : this_file_enums) {
+      output("extern const upb_MiniTableEnum $0;\n", EnumInit(enumdesc));
+    }
+  }
+
+  output("extern const upb_MiniTableFile $0;\n\n", FileLayoutName(file));
+
+  output(
+      "#ifdef __cplusplus\n"
+      "}  /* extern \"C\" */\n"
+      "#endif\n"
+      "\n"
+      "#include \"upb/port/undef.inc\"\n"
+      "\n"
+      "#endif  /* $0_UPB_MINITABLE_H_ */\n",
+      ToPreproc(file.name()));
+}
+
+typedef std::pair<std::string, uint64_t> TableEntry;
+
+uint32_t GetWireTypeForField(upb::FieldDefPtr field) {
+  if (field.packed()) return kUpb_WireType_Delimited;
+  switch (field.type()) {
+    case kUpb_FieldType_Double:
+    case kUpb_FieldType_Fixed64:
+    case kUpb_FieldType_SFixed64:
+      return kUpb_WireType_64Bit;
+    case kUpb_FieldType_Float:
+    case kUpb_FieldType_Fixed32:
+    case kUpb_FieldType_SFixed32:
+      return kUpb_WireType_32Bit;
+    case kUpb_FieldType_Int64:
+    case kUpb_FieldType_UInt64:
+    case kUpb_FieldType_Int32:
+    case kUpb_FieldType_Bool:
+    case kUpb_FieldType_UInt32:
+    case kUpb_FieldType_Enum:
+    case kUpb_FieldType_SInt32:
+    case kUpb_FieldType_SInt64:
+      return kUpb_WireType_Varint;
+    case kUpb_FieldType_Group:
+      return kUpb_WireType_StartGroup;
+    case kUpb_FieldType_Message:
+    case kUpb_FieldType_String:
+    case kUpb_FieldType_Bytes:
+      return kUpb_WireType_Delimited;
+  }
+  UPB_UNREACHABLE();
+}
+
+uint32_t MakeTag(uint32_t field_number, uint32_t wire_type) {
+  return field_number << 3 | wire_type;
+}
+
+size_t WriteVarint32ToArray(uint64_t val, char* buf) {
+  size_t i = 0;
+  do {
+    uint8_t byte = val & 0x7fU;
+    val >>= 7;
+    if (val) byte |= 0x80U;
+    buf[i++] = byte;
+  } while (val);
+  return i;
+}
+
+uint64_t GetEncodedTag(upb::FieldDefPtr field) {
+  uint32_t wire_type = GetWireTypeForField(field);
+  uint32_t unencoded_tag = MakeTag(field.number(), wire_type);
+  char tag_bytes[10] = {0};
+  WriteVarint32ToArray(unencoded_tag, tag_bytes);
+  uint64_t encoded_tag = 0;
+  memcpy(&encoded_tag, tag_bytes, sizeof(encoded_tag));
+  // TODO: byte-swap for big endian.
+  return encoded_tag;
+}
+
+int GetTableSlot(upb::FieldDefPtr field) {
+  uint64_t tag = GetEncodedTag(field);
+  if (tag > 0x7fff) {
+    // Tag must fit within a two-byte varint.
+    return -1;
+  }
+  return (tag & 0xf8) >> 3;
+}
+
+bool TryFillTableEntry(const DefPoolPair& pools, upb::FieldDefPtr field,
+                       TableEntry& ent) {
+  const upb_MiniTable* mt = pools.GetMiniTable64(field.containing_type());
+  const upb_MiniTableField* mt_f =
+      upb_MiniTable_FindFieldByNumber(mt, field.number());
+  std::string type = "";
+  std::string cardinality = "";
+  switch (upb_MiniTableField_Type(mt_f)) {
+    case kUpb_FieldType_Bool:
+      type = "b1";
+      break;
+    case kUpb_FieldType_Enum:
+      if (upb_MiniTableField_IsClosedEnum(mt_f)) {
+        // We don't have the means to test proto2 enum fields for valid values.
+        return false;
+      }
+      [[fallthrough]];
+    case kUpb_FieldType_Int32:
+    case kUpb_FieldType_UInt32:
+      type = "v4";
+      break;
+    case kUpb_FieldType_Int64:
+    case kUpb_FieldType_UInt64:
+      type = "v8";
+      break;
+    case kUpb_FieldType_Fixed32:
+    case kUpb_FieldType_SFixed32:
+    case kUpb_FieldType_Float:
+      type = "f4";
+      break;
+    case kUpb_FieldType_Fixed64:
+    case kUpb_FieldType_SFixed64:
+    case kUpb_FieldType_Double:
+      type = "f8";
+      break;
+    case kUpb_FieldType_SInt32:
+      type = "z4";
+      break;
+    case kUpb_FieldType_SInt64:
+      type = "z8";
+      break;
+    case kUpb_FieldType_String:
+      type = "s";
+      break;
+    case kUpb_FieldType_Bytes:
+      type = "b";
+      break;
+    case kUpb_FieldType_Message:
+      type = "m";
+      break;
+    default:
+      return false;  // Not supported yet.
+  }
+
+  switch (upb_FieldMode_Get(mt_f)) {
+    case kUpb_FieldMode_Map:
+      return false;  // Not supported yet (ever?).
+    case kUpb_FieldMode_Array:
+      if (mt_f->mode & kUpb_LabelFlags_IsPacked) {
+        cardinality = "p";
+      } else {
+        cardinality = "r";
+      }
+      break;
+    case kUpb_FieldMode_Scalar:
+      if (mt_f->presence < 0) {
+        cardinality = "o";
+      } else {
+        cardinality = "s";
+      }
+      break;
+  }
+
+  uint64_t expected_tag = GetEncodedTag(field);
+
+  // Data is:
+  //
+  //                  48                32                16                 0
+  // |--------|--------|--------|--------|--------|--------|--------|--------|
+  // |   offset (16)   |case offset (16) |presence| submsg |  exp. tag (16)  |
+  // |--------|--------|--------|--------|--------|--------|--------|--------|
+  //
+  // - |presence| is either hasbit index or field number for oneofs.
+
+  uint64_t data = static_cast<uint64_t>(mt_f->offset) << 48 | expected_tag;
+
+  if (field.IsSequence()) {
+    // No hasbit/oneof-related fields.
+  }
+  if (field.real_containing_oneof()) {
+    uint64_t case_offset = ~mt_f->presence;
+    if (case_offset > 0xffff || field.number() > 0xff) return false;
+    data |= field.number() << 24;
+    data |= case_offset << 32;
+  } else {
+    uint64_t hasbit_index = 63;  // No hasbit (set a high, unused bit).
+    if (mt_f->presence) {
+      hasbit_index = mt_f->presence;
+      if (hasbit_index > 31) return false;
+    }
+    data |= hasbit_index << 24;
+  }
+
+  if (field.ctype() == kUpb_CType_Message) {
+    uint64_t idx = mt_f->UPB_PRIVATE(submsg_index);
+    if (idx > 255) return false;
+    data |= idx << 16;
+
+    std::string size_ceil = "max";
+    size_t size = SIZE_MAX;
+    if (field.message_type().file() == field.file()) {
+      // We can only be guaranteed the size of the sub-message if it is in the
+      // same file as us.  We could relax this to increase the speed of
+      // cross-file sub-message parsing if we are comfortable requiring that
+      // users compile all messages at the same time.
+      const upb_MiniTable* sub_mt = pools.GetMiniTable64(field.message_type());
+      size = sub_mt->size + 8;
+    }
+    std::vector<size_t> breaks = {64, 128, 192, 256};
+    for (auto brk : breaks) {
+      if (size <= brk) {
+        size_ceil = std::to_string(brk);
+        break;
+      }
+    }
+    ent.first = absl::Substitute("upb_p$0$1_$2bt_max$3b", cardinality, type,
+                                 expected_tag > 0xff ? "2" : "1", size_ceil);
+
+  } else {
+    ent.first = absl::Substitute("upb_p$0$1_$2bt", cardinality, type,
+                                 expected_tag > 0xff ? "2" : "1");
+  }
+  ent.second = data;
+  return true;
+}
+
+std::vector<TableEntry> FastDecodeTable(upb::MessageDefPtr message,
+                                        const DefPoolPair& pools) {
+  std::vector<TableEntry> table;
+  for (const auto field : FieldHotnessOrder(message)) {
+    TableEntry ent;
+    int slot = GetTableSlot(field);
+    // std::cerr << "table slot: " << field->number() << ": " << slot << "\n";
+    if (slot < 0) {
+      // Tag can't fit in the table.
+      continue;
+    }
+    if (!TryFillTableEntry(pools, field, ent)) {
+      // Unsupported field type or offset, hasbit index, etc. doesn't fit.
+      continue;
+    }
+    while ((size_t)slot >= table.size()) {
+      size_t size = std::max(static_cast<size_t>(1), table.size() * 2);
+      table.resize(size, TableEntry{"_upb_FastDecoder_DecodeGeneric", 0});
+    }
+    if (table[slot].first != "_upb_FastDecoder_DecodeGeneric") {
+      // A hotter field already filled this slot.
+      continue;
+    }
+    table[slot] = ent;
+  }
+  return table;
+}
+
+std::string ArchDependentSize(int64_t size32, int64_t size64) {
+  if (size32 == size64) return absl::StrCat(size32);
+  return absl::Substitute("UPB_SIZE($0, $1)", size32, size64);
+}
+
+std::string FieldInitializer(const DefPoolPair& pools, upb::FieldDefPtr field) {
+  return upbc::FieldInitializer(field, pools.GetField64(field),
+                                pools.GetField32(field));
+}
+
+// Writes a single field into a .upb.c source file.
+void WriteMessageField(upb::FieldDefPtr field,
+                       const upb_MiniTableField* field64,
+                       const upb_MiniTableField* field32, Output& output) {
+  output("  $0,\n", upbc::FieldInitializer(field, field64, field32));
+}
+
+std::string GetSub(upb::FieldDefPtr field) {
+  if (auto message_def = field.message_type()) {
+    return absl::Substitute("{.submsg = &$0}", MessageInitName(message_def));
+  }
+
+  if (auto enum_def = field.enum_subdef()) {
+    if (enum_def.is_closed()) {
+      return absl::Substitute("{.subenum = &$0}", EnumInit(enum_def));
+    }
+  }
+
+  return std::string("{.submsg = NULL}");
+}
+
+// Writes a single message into a .upb.c source file.
+void WriteMessage(upb::MessageDefPtr message, const DefPoolPair& pools,
+                  Output& output) {
+  std::string msg_name = ToCIdent(message.full_name());
+  std::string fields_array_ref = "NULL";
+  std::string submsgs_array_ref = "NULL";
+  std::string subenums_array_ref = "NULL";
+  const upb_MiniTable* mt_32 = pools.GetMiniTable32(message);
+  const upb_MiniTable* mt_64 = pools.GetMiniTable64(message);
+  std::map<int, std::string> subs;
+
+  for (int i = 0; i < mt_64->field_count; i++) {
+    const upb_MiniTableField* f = &mt_64->fields[i];
+    uint32_t index = f->UPB_PRIVATE(submsg_index);
+    if (index != kUpb_NoSub) {
+      auto pair =
+          subs.emplace(index, GetSub(message.FindFieldByNumber(f->number)));
+      ABSL_CHECK(pair.second);
+    }
+  }
+
+  if (!subs.empty()) {
+    std::string submsgs_array_name = msg_name + "_submsgs";
+    submsgs_array_ref = "&" + submsgs_array_name + "[0]";
+    output("static const upb_MiniTableSub $0[$1] = {\n", submsgs_array_name,
+           subs.size());
+
+    int i = 0;
+    for (const auto& pair : subs) {
+      ABSL_CHECK(pair.first == i++);
+      output("  $0,\n", pair.second);
+    }
+
+    output("};\n\n");
+  }
+
+  if (mt_64->field_count > 0) {
+    std::string fields_array_name = msg_name + "__fields";
+    fields_array_ref = "&" + fields_array_name + "[0]";
+    output("static const upb_MiniTableField $0[$1] = {\n", fields_array_name,
+           mt_64->field_count);
+    for (int i = 0; i < mt_64->field_count; i++) {
+      WriteMessageField(message.FindFieldByNumber(mt_64->fields[i].number),
+                        &mt_64->fields[i], &mt_32->fields[i], output);
+    }
+    output("};\n\n");
+  }
+
+  std::vector<TableEntry> table;
+  uint8_t table_mask = -1;
+
+  table = FastDecodeTable(message, pools);
+
+  if (table.size() > 1) {
+    assert((table.size() & (table.size() - 1)) == 0);
+    table_mask = (table.size() - 1) << 3;
+  }
+
+  std::string msgext = "kUpb_ExtMode_NonExtendable";
+
+  if (message.extension_range_count()) {
+    if (UPB_DESC(MessageOptions_message_set_wire_format)(message.options())) {
+      msgext = "kUpb_ExtMode_IsMessageSet";
+    } else {
+      msgext = "kUpb_ExtMode_Extendable";
+    }
+  }
+
+  output("const upb_MiniTable $0 = {\n", MessageInitName(message));
+  output("  $0,\n", submsgs_array_ref);
+  output("  $0,\n", fields_array_ref);
+  output("  $0, $1, $2, $3, UPB_FASTTABLE_MASK($4), $5,\n",
+         ArchDependentSize(mt_32->size, mt_64->size), mt_64->field_count,
+         msgext, mt_64->dense_below, table_mask, mt_64->required_count);
+  if (!table.empty()) {
+    output("  UPB_FASTTABLE_INIT({\n");
+    for (const auto& ent : table) {
+      output("    {0x$1, &$0},\n", ent.first,
+             absl::StrCat(absl::Hex(ent.second, absl::kZeroPad16)));
+    }
+    output("  })\n");
+  }
+  output("};\n\n");
+}
+
+void WriteEnum(upb::EnumDefPtr e, Output& output) {
+  std::string values_init = "{\n";
+  const upb_MiniTableEnum* mt = e.mini_table();
+  uint32_t value_count = (mt->mask_limit / 32) + mt->value_count;
+  for (uint32_t i = 0; i < value_count; i++) {
+    absl::StrAppend(&values_init, "                0x", absl::Hex(mt->data[i]),
+                    ",\n");
+  }
+  values_init += "    }";
+
+  output(
+      R"cc(
+        const upb_MiniTableEnum $0 = {
+            $1,
+            $2,
+            $3,
+        };
+      )cc",
+      EnumInit(e), mt->mask_limit, mt->value_count, values_init);
+  output("\n");
+}
+
+int WriteEnums(const DefPoolPair& pools, upb::FileDefPtr file, Output& output) {
+  if (file.syntax() != kUpb_Syntax_Proto2) return 0;
+
+  std::vector<upb::EnumDefPtr> this_file_enums = SortedEnums(file);
+
+  for (const auto e : this_file_enums) {
+    WriteEnum(e, output);
+  }
+
+  if (!this_file_enums.empty()) {
+    output("static const upb_MiniTableEnum *$0[$1] = {\n", kEnumsInit,
+           this_file_enums.size());
+    for (const auto e : this_file_enums) {
+      output("  &$0,\n", EnumInit(e));
+    }
+    output("};\n");
+    output("\n");
+  }
+
+  return this_file_enums.size();
+}
+
+int WriteMessages(const DefPoolPair& pools, upb::FileDefPtr file,
+                  Output& output) {
+  std::vector<upb::MessageDefPtr> file_messages = SortedMessages(file);
+
+  if (file_messages.empty()) return 0;
+
+  for (auto message : file_messages) {
+    WriteMessage(message, pools, output);
+  }
+
+  output("static const upb_MiniTable *$0[$1] = {\n", kMessagesInit,
+         file_messages.size());
+  for (auto message : file_messages) {
+    output("  &$0,\n", MessageInitName(message));
+  }
+  output("};\n");
+  output("\n");
+  return file_messages.size();
+}
+
+void WriteExtension(upb::FieldDefPtr ext, const DefPoolPair& pools,
+                    Output& output) {
+  output("$0,\n", FieldInitializer(pools, ext));
+  output("  &$0,\n", MessageInitName(ext.containing_type()));
+  output("  $0,\n", GetSub(ext));
+}
+
+int WriteExtensions(const DefPoolPair& pools, upb::FileDefPtr file,
+                    Output& output) {
+  auto exts = SortedExtensions(file);
+
+  if (exts.empty()) return 0;
+
+  // Order by full name for consistent ordering.
+  std::map<std::string, upb::MessageDefPtr> forward_messages;
+
+  for (auto ext : exts) {
+    forward_messages[ext.containing_type().full_name()] = ext.containing_type();
+    if (ext.message_type()) {
+      forward_messages[ext.message_type().full_name()] = ext.message_type();
+    }
+  }
+
+  for (auto ext : exts) {
+    output("const upb_MiniTableExtension $0 = {\n  ", ExtensionLayout(ext));
+    WriteExtension(ext, pools, output);
+    output("\n};\n");
+  }
+
+  output(
+      "\n"
+      "static const upb_MiniTableExtension *$0[$1] = {\n",
+      kExtensionsInit, exts.size());
+
+  for (auto ext : exts) {
+    output("  &$0,\n", ExtensionLayout(ext));
+  }
+
+  output(
+      "};\n"
+      "\n");
+  return exts.size();
+}
+
+void WriteMiniTableSource(const DefPoolPair& pools, upb::FileDefPtr file,
+                          Output& output) {
+  EmitFileWarning(file.name(), output);
+
+  output(
+      "#include <stddef.h>\n"
+      "#include \"upb/generated_code_support.h\"\n"
+      "#include \"$0\"\n",
+      MiniTableHeaderFilename(file));
+
+  for (int i = 0; i < file.dependency_count(); i++) {
+    output("#include \"$0\"\n", MiniTableHeaderFilename(file.dependency(i)));
+  }
+
+  output(
+      "\n"
+      "// Must be last.\n"
+      "#include \"upb/port/def.inc\"\n"
+      "\n");
+
+  int msg_count = WriteMessages(pools, file, output);
+  int ext_count = WriteExtensions(pools, file, output);
+  int enum_count = WriteEnums(pools, file, output);
+
+  output("const upb_MiniTableFile $0 = {\n", FileLayoutName(file));
+  output("  $0,\n", msg_count ? kMessagesInit : "NULL");
+  output("  $0,\n", enum_count ? kEnumsInit : "NULL");
+  output("  $0,\n", ext_count ? kExtensionsInit : "NULL");
+  output("  $0,\n", msg_count);
+  output("  $0,\n", enum_count);
+  output("  $0,\n", ext_count);
+  output("};\n\n");
+
+  output("#include \"upb/port/undef.inc\"\n");
+  output("\n");
+}
+
+void GenerateFile(const DefPoolPair& pools, upb::FileDefPtr file,
+                  Plugin* plugin) {
+  Output h_output;
+  WriteHeader(pools, file, h_output);
+  plugin->AddOutputFile(MiniTableHeaderFilename(file), h_output.output());
+
+  Output c_output;
+  WriteMiniTableSource(pools, file, c_output);
+  plugin->AddOutputFile(SourceFilename(file), c_output.output());
+}
+
+bool ParseOptions(Plugin* plugin) {
+  for (const auto& pair : ParseGeneratorParameter(plugin->parameter())) {
+    plugin->SetError(absl::Substitute("Unknown parameter: $0", pair.first));
+    return false;
+  }
+
+  return true;
+}
+
+absl::string_view ToStringView(upb_StringView str) {
+  return absl::string_view(str.data, str.size);
+}
+
+}  // namespace
+
+}  // namespace upbc
+
+int main(int argc, char** argv) {
+  upbc::DefPoolPair pools;
+  upbc::Plugin plugin;
+  if (!upbc::ParseOptions(&plugin)) return 0;
+  plugin.GenerateFilesRaw([&](const UPB_DESC(FileDescriptorProto) * file_proto,
+                              bool generate) {
+    upb::Status status;
+    upb::FileDefPtr file = pools.AddFile(file_proto, &status);
+    if (!file) {
+      absl::string_view name =
+          upbc::ToStringView(UPB_DESC(FileDescriptorProto_name)(file_proto));
+      ABSL_LOG(FATAL) << "Couldn't add file " << name
+                      << " to DefPool: " << status.error_message();
+    }
+    if (generate) upbc::GenerateFile(pools, file, &plugin);
+  });
+  return 0;
+}
diff --git a/upbc/protoc-gen-upbdefs.cc b/upbc/protoc-gen-upbdefs.cc
new file mode 100644
index 0000000..d2870b0
--- /dev/null
+++ b/upbc/protoc-gen-upbdefs.cc
@@ -0,0 +1,175 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  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 LLC 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 <memory>
+
+#include "google/protobuf/descriptor.upb.h"
+#include "upb/reflection/def.hpp"
+#include "upb/util/def_to_proto.h"
+#include "upbc/common.h"
+#include "upbc/file_layout.h"
+#include "upbc/plugin.h"
+
+namespace upbc {
+namespace {
+
+std::string DefInitSymbol(upb::FileDefPtr file) {
+  return ToCIdent(file.name()) + "_upbdefinit";
+}
+
+static std::string DefHeaderFilename(upb::FileDefPtr file) {
+  return StripExtension(file.name()) + ".upbdefs.h";
+}
+
+static std::string DefSourceFilename(upb::FileDefPtr file) {
+  return StripExtension(file.name()) + ".upbdefs.c";
+}
+
+void GenerateMessageDefAccessor(upb::MessageDefPtr d, Output& output) {
+  output("UPB_INLINE const upb_MessageDef *$0_getmsgdef(upb_DefPool *s) {\n",
+         ToCIdent(d.full_name()));
+  output("  _upb_DefPool_LoadDefInit(s, &$0);\n", DefInitSymbol(d.file()));
+  output("  return upb_DefPool_FindMessageByName(s, \"$0\");\n", d.full_name());
+  output("}\n");
+  output("\n");
+}
+
+void WriteDefHeader(upb::FileDefPtr file, Output& output) {
+  EmitFileWarning(file.name(), output);
+
+  output(
+      "#ifndef $0_UPBDEFS_H_\n"
+      "#define $0_UPBDEFS_H_\n\n"
+      "#include \"upb/reflection/def.h\"\n"
+      "#include \"upb/reflection/internal/def_pool.h\"\n"
+      "#include \"upb/port/def.inc\"\n"
+      "#ifdef __cplusplus\n"
+      "extern \"C\" {\n"
+      "#endif\n\n",
+      ToPreproc(file.name()));
+
+  output("#include \"upb/reflection/def.h\"\n");
+  output("\n");
+  output("#include \"upb/port/def.inc\"\n");
+  output("\n");
+
+  output("extern _upb_DefPool_Init $0;\n", DefInitSymbol(file));
+  output("\n");
+
+  for (auto msg : SortedMessages(file)) {
+    GenerateMessageDefAccessor(msg, output);
+  }
+
+  output(
+      "#ifdef __cplusplus\n"
+      "}  /* extern \"C\" */\n"
+      "#endif\n"
+      "\n"
+      "#include \"upb/port/undef.inc\"\n"
+      "\n"
+      "#endif  /* $0_UPBDEFS_H_ */\n",
+      ToPreproc(file.name()));
+}
+
+void WriteDefSource(upb::FileDefPtr file, Output& output) {
+  EmitFileWarning(file.name(), output);
+
+  output("#include \"upb/reflection/def.h\"\n");
+  output("#include \"$0\"\n", DefHeaderFilename(file));
+  output("#include \"$0\"\n", MiniTableHeaderFilename(file));
+  output("\n");
+
+  for (int i = 0; i < file.dependency_count(); i++) {
+    output("extern _upb_DefPool_Init $0;\n", DefInitSymbol(file.dependency(i)));
+  }
+
+  upb::Arena arena;
+  google_protobuf_FileDescriptorProto* file_proto =
+      upb_FileDef_ToProto(file.ptr(), arena.ptr());
+  size_t serialized_size;
+  const char* serialized = google_protobuf_FileDescriptorProto_serialize(
+      file_proto, arena.ptr(), &serialized_size);
+  absl::string_view file_data(serialized, serialized_size);
+
+  output("static const char descriptor[$0] = {", serialized_size);
+
+  // C90 only guarantees that strings can be up to 509 characters, and some
+  // implementations have limits here (for example, MSVC only allows 64k:
+  // https://docs.microsoft.com/en-us/cpp/error-messages/compiler-errors-1/fatal-error-c1091.
+  // So we always emit an array instead of a string.
+  for (size_t i = 0; i < serialized_size;) {
+    for (size_t j = 0; j < 25 && i < serialized_size; ++i, ++j) {
+      output("'$0', ", absl::CEscape(file_data.substr(i, 1)));
+    }
+    output("\n");
+  }
+  output("};\n\n");
+
+  output("static _upb_DefPool_Init *deps[$0] = {\n",
+         file.dependency_count() + 1);
+  for (int i = 0; i < file.dependency_count(); i++) {
+    output("  &$0,\n", DefInitSymbol(file.dependency(i)));
+  }
+  output("  NULL\n");
+  output("};\n");
+  output("\n");
+
+  output("_upb_DefPool_Init $0 = {\n", DefInitSymbol(file));
+  output("  deps,\n");
+  output("  &$0,\n", FileLayoutName(file));
+  output("  \"$0\",\n", file.name());
+  output("  UPB_STRINGVIEW_INIT(descriptor, $0)\n", file_data.size());
+  output("};\n");
+}
+
+void GenerateFile(upb::FileDefPtr file, Plugin* plugin) {
+  Output h_def_output;
+  WriteDefHeader(file, h_def_output);
+  plugin->AddOutputFile(DefHeaderFilename(file), h_def_output.output());
+
+  Output c_def_output;
+  WriteDefSource(file, c_def_output);
+  plugin->AddOutputFile(DefSourceFilename(file), c_def_output.output());
+}
+
+}  // namespace
+}  // namespace upbc
+
+int main(int argc, char** argv) {
+  upbc::Plugin plugin;
+  if (!plugin.parameter().empty()) {
+    plugin.SetError(
+        absl::StrCat("Expected no parameters, got: ", plugin.parameter()));
+    return 0;
+  }
+  plugin.GenerateFiles(
+      [&](upb::FileDefPtr file) { upbc::GenerateFile(file, &plugin); });
+  return 0;
+}
diff --git a/upbc/protoc-gen-upbdev.cc b/upbc/protoc-gen-upbdev.cc
new file mode 100644
index 0000000..3093169
--- /dev/null
+++ b/upbc/protoc-gen-upbdev.cc
@@ -0,0 +1,91 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  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 LLC 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 <iostream>
+#include <string>
+
+#include "google/protobuf/compiler/plugin.upb.h"
+#include "upbc/subprocess.h"
+#include "upbc/upbdev.h"
+
+static constexpr char kDefaultPlugin[] = "protoc_dart_plugin";
+
+int main() {
+  upb_Arena* a = upb_Arena_New();
+  upb_Status status;
+  upb_Status_Clear(&status);
+
+  // Read (binary) stdin into a string.
+  const std::string input = {std::istreambuf_iterator<char>(std::cin),
+                             std::istreambuf_iterator<char>()};
+
+  // Parse the request.
+  auto inner_request = google_protobuf_compiler_CodeGeneratorRequest_parse(
+      input.c_str(), input.size(), a);
+
+  // Check the request for a plugin name.
+  std::string plugin = kDefaultPlugin;
+  if (google_protobuf_compiler_CodeGeneratorRequest_has_parameter(inner_request)) {
+    auto param = google_protobuf_compiler_CodeGeneratorRequest_parameter(inner_request);
+    plugin = std::string(param.data, param.size);
+  }
+
+  // Wrap the request inside a upbc_CodeGeneratorRequest and JSON-encode it.
+  const upb_StringView sv =
+      upbdev_ProcessInput(input.data(), input.size(), a, &status);
+  if (!upb_Status_IsOk(&status)) {
+    std::cerr << status.msg << std::endl;
+    return -1;
+  }
+
+  // Launch the subprocess.
+  upbc::Subprocess subprocess;
+  subprocess.Start(plugin, upbc::Subprocess::SEARCH_PATH);
+
+  // Exchange JSON strings with the subprocess.
+  const std::string json_request = std::string(sv.data, sv.size);
+  std::string json_response, error;
+  const bool ok = subprocess.Communicate(json_request, &json_response, &error);
+  if (!ok) {
+    // Dump the JSON request to stderr if we can't launch the next plugin.
+    std::cerr << json_request << std::endl;
+    return -1;
+  }
+
+  // Decode, serialize, and write the JSON response.
+  upbdev_ProcessOutput(json_response.data(), json_response.size(), a, &status);
+  if (!upb_Status_IsOk(&status)) {
+    std::cerr << status.msg << std::endl;
+    return -1;
+  }
+
+  upb_Arena_Free(a);
+  return 0;
+}
diff --git a/upbc/stage0/google/protobuf/compiler/plugin.upb.c b/upbc/stage0/google/protobuf/compiler/plugin.upb.c
new file mode 100644
index 0000000..3a67715
--- /dev/null
+++ b/upbc/stage0/google/protobuf/compiler/plugin.upb.c
@@ -0,0 +1,66 @@
+#include <stddef.h>
+#include "upb/generated_code_support.h"
+#include "google/protobuf/compiler/plugin.upb.h"
+
+#include "google/protobuf/descriptor.upb.h"
+static upb_Arena* upb_BootstrapArena() {
+  static upb_Arena* arena = NULL;
+  if (!arena) arena = upb_Arena_New();
+  return arena;
+}
+
+const upb_MiniTable* google_protobuf_compiler_Version_msg_init() {
+  static upb_MiniTable* mini_table = NULL;
+  static const char* mini_descriptor = "$(((1";
+  if (mini_table) return mini_table;
+  mini_table =
+      upb_MiniTable_Build(mini_descriptor, strlen(mini_descriptor),
+                          upb_BootstrapArena(), NULL);
+  return mini_table;
+}
+
+const upb_MiniTable* google_protobuf_compiler_CodeGeneratorRequest_msg_init() {
+  static upb_MiniTable* mini_table = NULL;
+  static const char* mini_descriptor = "$E13kGaG";
+  if (mini_table) return mini_table;
+  mini_table =
+      upb_MiniTable_Build(mini_descriptor, strlen(mini_descriptor),
+                          upb_BootstrapArena(), NULL);
+  upb_MiniTable_SetSubMessage(mini_table, (upb_MiniTableField*)upb_MiniTable_FindFieldByNumber(mini_table, 15), google_protobuf_FileDescriptorProto_msg_init());
+  upb_MiniTable_SetSubMessage(mini_table, (upb_MiniTableField*)upb_MiniTable_FindFieldByNumber(mini_table, 17), google_protobuf_FileDescriptorProto_msg_init());
+  upb_MiniTable_SetSubMessage(mini_table, (upb_MiniTableField*)upb_MiniTable_FindFieldByNumber(mini_table, 3), google_protobuf_compiler_Version_msg_init());
+  return mini_table;
+}
+
+const upb_MiniTable* google_protobuf_compiler_CodeGeneratorResponse_msg_init() {
+  static upb_MiniTable* mini_table = NULL;
+  static const char* mini_descriptor = "$1,lG";
+  if (mini_table) return mini_table;
+  mini_table =
+      upb_MiniTable_Build(mini_descriptor, strlen(mini_descriptor),
+                          upb_BootstrapArena(), NULL);
+  upb_MiniTable_SetSubMessage(mini_table, (upb_MiniTableField*)upb_MiniTable_FindFieldByNumber(mini_table, 15), google_protobuf_compiler_CodeGeneratorResponse_File_msg_init());
+  return mini_table;
+}
+
+const upb_MiniTable* google_protobuf_compiler_CodeGeneratorResponse_File_msg_init() {
+  static upb_MiniTable* mini_table = NULL;
+  static const char* mini_descriptor = "$11l13";
+  if (mini_table) return mini_table;
+  mini_table =
+      upb_MiniTable_Build(mini_descriptor, strlen(mini_descriptor),
+                          upb_BootstrapArena(), NULL);
+  upb_MiniTable_SetSubMessage(mini_table, (upb_MiniTableField*)upb_MiniTable_FindFieldByNumber(mini_table, 16), google_protobuf_GeneratedCodeInfo_msg_init());
+  return mini_table;
+}
+
+const upb_MiniTableEnum* google_protobuf_compiler_CodeGeneratorResponse_Feature_enum_init() {
+  static const upb_MiniTableEnum* mini_table = NULL;
+  static const char* mini_descriptor = "!)";
+  if (mini_table) return mini_table;
+  mini_table =
+      upb_MiniTableEnum_Build(mini_descriptor, strlen(mini_descriptor),
+                              upb_BootstrapArena(), NULL);
+  return mini_table;
+}
+
diff --git a/upbc/stage0/google/protobuf/compiler/plugin.upb.h b/upbc/stage0/google/protobuf/compiler/plugin.upb.h
new file mode 100644
index 0000000..4227e51
--- /dev/null
+++ b/upbc/stage0/google/protobuf/compiler/plugin.upb.h
@@ -0,0 +1,692 @@
+/* This file was generated by upbc (the upb compiler) from the input
+ * file:
+ *
+ *     google/protobuf/compiler/plugin.proto
+ *
+ * Do not edit -- your changes will be discarded when the file is
+ * regenerated. */
+
+#ifndef GOOGLE_PROTOBUF_COMPILER_PLUGIN_PROTO_UPB_H_
+#define GOOGLE_PROTOBUF_COMPILER_PLUGIN_PROTO_UPB_H_
+
+#include "upb/generated_code_support.h"
+
+// Must be last.
+#include "upb/port/def.inc"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern const upb_MiniTable* google_protobuf_compiler_Version_msg_init();
+extern const upb_MiniTable* google_protobuf_compiler_CodeGeneratorRequest_msg_init();
+extern const upb_MiniTable* google_protobuf_compiler_CodeGeneratorResponse_msg_init();
+extern const upb_MiniTable* google_protobuf_compiler_CodeGeneratorResponse_File_msg_init();
+extern const upb_MiniTable* google_protobuf_FileDescriptorProto_msg_init();
+extern const upb_MiniTable* google_protobuf_GeneratedCodeInfo_msg_init();
+extern const upb_MiniTableEnum* google_protobuf_compiler_CodeGeneratorResponse_Feature_enum_init();
+
+typedef struct google_protobuf_compiler_Version google_protobuf_compiler_Version;
+typedef struct google_protobuf_compiler_CodeGeneratorRequest google_protobuf_compiler_CodeGeneratorRequest;
+typedef struct google_protobuf_compiler_CodeGeneratorResponse google_protobuf_compiler_CodeGeneratorResponse;
+typedef struct google_protobuf_compiler_CodeGeneratorResponse_File google_protobuf_compiler_CodeGeneratorResponse_File;
+struct google_protobuf_FileDescriptorProto;
+struct google_protobuf_GeneratedCodeInfo;
+
+typedef enum {
+  google_protobuf_compiler_CodeGeneratorResponse_FEATURE_NONE = 0,
+  google_protobuf_compiler_CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL = 1,
+  google_protobuf_compiler_CodeGeneratorResponse_FEATURE_SUPPORTS_EDITIONS = 2
+} google_protobuf_compiler_CodeGeneratorResponse_Feature;
+
+
+
+/* google.protobuf.compiler.Version */
+
+UPB_INLINE google_protobuf_compiler_Version* google_protobuf_compiler_Version_new(upb_Arena* arena) {
+  return (google_protobuf_compiler_Version*)_upb_Message_New(google_protobuf_compiler_Version_msg_init(), arena);
+}
+UPB_INLINE google_protobuf_compiler_Version* google_protobuf_compiler_Version_parse(const char* buf, size_t size, upb_Arena* arena) {
+  google_protobuf_compiler_Version* ret = google_protobuf_compiler_Version_new(arena);
+  if (!ret) return NULL;
+  if (upb_Decode(buf, size, ret, google_protobuf_compiler_Version_msg_init(), NULL, 0, arena) != kUpb_DecodeStatus_Ok) {
+    return NULL;
+  }
+  return ret;
+}
+UPB_INLINE google_protobuf_compiler_Version* google_protobuf_compiler_Version_parse_ex(const char* buf, size_t size,
+                           const upb_ExtensionRegistry* extreg,
+                           int options, upb_Arena* arena) {
+  google_protobuf_compiler_Version* ret = google_protobuf_compiler_Version_new(arena);
+  if (!ret) return NULL;
+  if (upb_Decode(buf, size, ret, google_protobuf_compiler_Version_msg_init(), extreg, options, arena) !=
+      kUpb_DecodeStatus_Ok) {
+    return NULL;
+  }
+  return ret;
+}
+UPB_INLINE char* google_protobuf_compiler_Version_serialize(const google_protobuf_compiler_Version* msg, upb_Arena* arena, size_t* len) {
+  char* ptr;
+  (void)upb_Encode(msg, google_protobuf_compiler_Version_msg_init(), 0, arena, &ptr, len);
+  return ptr;
+}
+UPB_INLINE char* google_protobuf_compiler_Version_serialize_ex(const google_protobuf_compiler_Version* msg, int options,
+                                 upb_Arena* arena, size_t* len) {
+  char* ptr;
+  (void)upb_Encode(msg, google_protobuf_compiler_Version_msg_init(), options, arena, &ptr, len);
+  return ptr;
+}
+UPB_INLINE void google_protobuf_compiler_Version_clear_major(google_protobuf_compiler_Version* msg) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_Version_msg_init(), 1);
+  _upb_Message_ClearNonExtensionField(msg, &field);
+}
+UPB_INLINE int32_t google_protobuf_compiler_Version_major(const google_protobuf_compiler_Version* msg) {
+  int32_t default_val = (int32_t)0;
+  int32_t ret;
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_Version_msg_init(), 1);
+  _upb_Message_GetNonExtensionField(msg, &field, &default_val, &ret);
+  return ret;
+}
+UPB_INLINE bool google_protobuf_compiler_Version_has_major(const google_protobuf_compiler_Version* msg) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_Version_msg_init(), 1);
+  return _upb_Message_HasNonExtensionField(msg, &field);
+}
+UPB_INLINE void google_protobuf_compiler_Version_clear_minor(google_protobuf_compiler_Version* msg) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_Version_msg_init(), 2);
+  _upb_Message_ClearNonExtensionField(msg, &field);
+}
+UPB_INLINE int32_t google_protobuf_compiler_Version_minor(const google_protobuf_compiler_Version* msg) {
+  int32_t default_val = (int32_t)0;
+  int32_t ret;
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_Version_msg_init(), 2);
+  _upb_Message_GetNonExtensionField(msg, &field, &default_val, &ret);
+  return ret;
+}
+UPB_INLINE bool google_protobuf_compiler_Version_has_minor(const google_protobuf_compiler_Version* msg) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_Version_msg_init(), 2);
+  return _upb_Message_HasNonExtensionField(msg, &field);
+}
+UPB_INLINE void google_protobuf_compiler_Version_clear_patch(google_protobuf_compiler_Version* msg) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_Version_msg_init(), 3);
+  _upb_Message_ClearNonExtensionField(msg, &field);
+}
+UPB_INLINE int32_t google_protobuf_compiler_Version_patch(const google_protobuf_compiler_Version* msg) {
+  int32_t default_val = (int32_t)0;
+  int32_t ret;
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_Version_msg_init(), 3);
+  _upb_Message_GetNonExtensionField(msg, &field, &default_val, &ret);
+  return ret;
+}
+UPB_INLINE bool google_protobuf_compiler_Version_has_patch(const google_protobuf_compiler_Version* msg) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_Version_msg_init(), 3);
+  return _upb_Message_HasNonExtensionField(msg, &field);
+}
+UPB_INLINE void google_protobuf_compiler_Version_clear_suffix(google_protobuf_compiler_Version* msg) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_Version_msg_init(), 4);
+  _upb_Message_ClearNonExtensionField(msg, &field);
+}
+UPB_INLINE upb_StringView google_protobuf_compiler_Version_suffix(const google_protobuf_compiler_Version* msg) {
+  upb_StringView default_val = upb_StringView_FromString("");
+  upb_StringView ret;
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_Version_msg_init(), 4);
+  _upb_Message_GetNonExtensionField(msg, &field, &default_val, &ret);
+  return ret;
+}
+UPB_INLINE bool google_protobuf_compiler_Version_has_suffix(const google_protobuf_compiler_Version* msg) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_Version_msg_init(), 4);
+  return _upb_Message_HasNonExtensionField(msg, &field);
+}
+
+UPB_INLINE void google_protobuf_compiler_Version_set_major(google_protobuf_compiler_Version *msg, int32_t value) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_Version_msg_init(), 1);
+  _upb_Message_SetNonExtensionField(msg, &field, &value);
+}
+UPB_INLINE void google_protobuf_compiler_Version_set_minor(google_protobuf_compiler_Version *msg, int32_t value) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_Version_msg_init(), 2);
+  _upb_Message_SetNonExtensionField(msg, &field, &value);
+}
+UPB_INLINE void google_protobuf_compiler_Version_set_patch(google_protobuf_compiler_Version *msg, int32_t value) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_Version_msg_init(), 3);
+  _upb_Message_SetNonExtensionField(msg, &field, &value);
+}
+UPB_INLINE void google_protobuf_compiler_Version_set_suffix(google_protobuf_compiler_Version *msg, upb_StringView value) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_Version_msg_init(), 4);
+  _upb_Message_SetNonExtensionField(msg, &field, &value);
+}
+
+/* google.protobuf.compiler.CodeGeneratorRequest */
+
+UPB_INLINE google_protobuf_compiler_CodeGeneratorRequest* google_protobuf_compiler_CodeGeneratorRequest_new(upb_Arena* arena) {
+  return (google_protobuf_compiler_CodeGeneratorRequest*)_upb_Message_New(google_protobuf_compiler_CodeGeneratorRequest_msg_init(), arena);
+}
+UPB_INLINE google_protobuf_compiler_CodeGeneratorRequest* google_protobuf_compiler_CodeGeneratorRequest_parse(const char* buf, size_t size, upb_Arena* arena) {
+  google_protobuf_compiler_CodeGeneratorRequest* ret = google_protobuf_compiler_CodeGeneratorRequest_new(arena);
+  if (!ret) return NULL;
+  if (upb_Decode(buf, size, ret, google_protobuf_compiler_CodeGeneratorRequest_msg_init(), NULL, 0, arena) != kUpb_DecodeStatus_Ok) {
+    return NULL;
+  }
+  return ret;
+}
+UPB_INLINE google_protobuf_compiler_CodeGeneratorRequest* google_protobuf_compiler_CodeGeneratorRequest_parse_ex(const char* buf, size_t size,
+                           const upb_ExtensionRegistry* extreg,
+                           int options, upb_Arena* arena) {
+  google_protobuf_compiler_CodeGeneratorRequest* ret = google_protobuf_compiler_CodeGeneratorRequest_new(arena);
+  if (!ret) return NULL;
+  if (upb_Decode(buf, size, ret, google_protobuf_compiler_CodeGeneratorRequest_msg_init(), extreg, options, arena) !=
+      kUpb_DecodeStatus_Ok) {
+    return NULL;
+  }
+  return ret;
+}
+UPB_INLINE char* google_protobuf_compiler_CodeGeneratorRequest_serialize(const google_protobuf_compiler_CodeGeneratorRequest* msg, upb_Arena* arena, size_t* len) {
+  char* ptr;
+  (void)upb_Encode(msg, google_protobuf_compiler_CodeGeneratorRequest_msg_init(), 0, arena, &ptr, len);
+  return ptr;
+}
+UPB_INLINE char* google_protobuf_compiler_CodeGeneratorRequest_serialize_ex(const google_protobuf_compiler_CodeGeneratorRequest* msg, int options,
+                                 upb_Arena* arena, size_t* len) {
+  char* ptr;
+  (void)upb_Encode(msg, google_protobuf_compiler_CodeGeneratorRequest_msg_init(), options, arena, &ptr, len);
+  return ptr;
+}
+UPB_INLINE void google_protobuf_compiler_CodeGeneratorRequest_clear_file_to_generate(google_protobuf_compiler_CodeGeneratorRequest* msg) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorRequest_msg_init(), 1);
+  _upb_Message_ClearNonExtensionField(msg, &field);
+}
+UPB_INLINE upb_StringView const* google_protobuf_compiler_CodeGeneratorRequest_file_to_generate(const google_protobuf_compiler_CodeGeneratorRequest* msg, size_t* size) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorRequest_msg_init(), 1);
+  const upb_Array* arr = upb_Message_GetArray(msg, &field);
+  if (arr) {
+    if (size) *size = arr->size;
+    return (upb_StringView const*)_upb_array_constptr(arr);
+  } else {
+    if (size) *size = 0;
+    return NULL;
+  }
+}
+UPB_INLINE const upb_Array* _google_protobuf_compiler_CodeGeneratorRequest_file_to_generate_upb_array(const google_protobuf_compiler_CodeGeneratorRequest* msg, size_t* size) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorRequest_msg_init(), 1);
+  const upb_Array* arr = upb_Message_GetArray(msg, &field);
+  if (size) {
+    *size = arr ? arr->size : 0;
+  }
+  return arr;
+}
+UPB_INLINE upb_Array* _google_protobuf_compiler_CodeGeneratorRequest_file_to_generate_mutable_upb_array(const google_protobuf_compiler_CodeGeneratorRequest* msg, size_t* size, upb_Arena* arena) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorRequest_msg_init(), 1);
+  upb_Array* arr = upb_Message_GetOrCreateMutableArray(
+      (upb_Message*)msg, &field, arena);
+  if (size) {
+    *size = arr ? arr->size : 0;
+  }
+  return arr;
+}
+UPB_INLINE bool google_protobuf_compiler_CodeGeneratorRequest_has_file_to_generate(const google_protobuf_compiler_CodeGeneratorRequest* msg) {
+  size_t size;
+  google_protobuf_compiler_CodeGeneratorRequest_file_to_generate(msg, &size);
+  return size != 0;
+}
+UPB_INLINE void google_protobuf_compiler_CodeGeneratorRequest_clear_parameter(google_protobuf_compiler_CodeGeneratorRequest* msg) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorRequest_msg_init(), 2);
+  _upb_Message_ClearNonExtensionField(msg, &field);
+}
+UPB_INLINE upb_StringView google_protobuf_compiler_CodeGeneratorRequest_parameter(const google_protobuf_compiler_CodeGeneratorRequest* msg) {
+  upb_StringView default_val = upb_StringView_FromString("");
+  upb_StringView ret;
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorRequest_msg_init(), 2);
+  _upb_Message_GetNonExtensionField(msg, &field, &default_val, &ret);
+  return ret;
+}
+UPB_INLINE bool google_protobuf_compiler_CodeGeneratorRequest_has_parameter(const google_protobuf_compiler_CodeGeneratorRequest* msg) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorRequest_msg_init(), 2);
+  return _upb_Message_HasNonExtensionField(msg, &field);
+}
+UPB_INLINE void google_protobuf_compiler_CodeGeneratorRequest_clear_compiler_version(google_protobuf_compiler_CodeGeneratorRequest* msg) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorRequest_msg_init(), 3);
+  _upb_Message_ClearNonExtensionField(msg, &field);
+}
+UPB_INLINE const google_protobuf_compiler_Version* google_protobuf_compiler_CodeGeneratorRequest_compiler_version(const google_protobuf_compiler_CodeGeneratorRequest* msg) {
+  const google_protobuf_compiler_Version* default_val = NULL;
+  const google_protobuf_compiler_Version* ret;
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorRequest_msg_init(), 3);
+  _upb_Message_GetNonExtensionField(msg, &field, &default_val, &ret);
+  return ret;
+}
+UPB_INLINE bool google_protobuf_compiler_CodeGeneratorRequest_has_compiler_version(const google_protobuf_compiler_CodeGeneratorRequest* msg) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorRequest_msg_init(), 3);
+  return _upb_Message_HasNonExtensionField(msg, &field);
+}
+UPB_INLINE void google_protobuf_compiler_CodeGeneratorRequest_clear_proto_file(google_protobuf_compiler_CodeGeneratorRequest* msg) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorRequest_msg_init(), 15);
+  _upb_Message_ClearNonExtensionField(msg, &field);
+}
+UPB_INLINE const struct google_protobuf_FileDescriptorProto* const* google_protobuf_compiler_CodeGeneratorRequest_proto_file(const google_protobuf_compiler_CodeGeneratorRequest* msg, size_t* size) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorRequest_msg_init(), 15);
+  const upb_Array* arr = upb_Message_GetArray(msg, &field);
+  if (arr) {
+    if (size) *size = arr->size;
+    return (const struct google_protobuf_FileDescriptorProto* const*)_upb_array_constptr(arr);
+  } else {
+    if (size) *size = 0;
+    return NULL;
+  }
+}
+UPB_INLINE const upb_Array* _google_protobuf_compiler_CodeGeneratorRequest_proto_file_upb_array(const google_protobuf_compiler_CodeGeneratorRequest* msg, size_t* size) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorRequest_msg_init(), 15);
+  const upb_Array* arr = upb_Message_GetArray(msg, &field);
+  if (size) {
+    *size = arr ? arr->size : 0;
+  }
+  return arr;
+}
+UPB_INLINE upb_Array* _google_protobuf_compiler_CodeGeneratorRequest_proto_file_mutable_upb_array(const google_protobuf_compiler_CodeGeneratorRequest* msg, size_t* size, upb_Arena* arena) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorRequest_msg_init(), 15);
+  upb_Array* arr = upb_Message_GetOrCreateMutableArray(
+      (upb_Message*)msg, &field, arena);
+  if (size) {
+    *size = arr ? arr->size : 0;
+  }
+  return arr;
+}
+UPB_INLINE bool google_protobuf_compiler_CodeGeneratorRequest_has_proto_file(const google_protobuf_compiler_CodeGeneratorRequest* msg) {
+  size_t size;
+  google_protobuf_compiler_CodeGeneratorRequest_proto_file(msg, &size);
+  return size != 0;
+}
+UPB_INLINE void google_protobuf_compiler_CodeGeneratorRequest_clear_source_file_descriptors(google_protobuf_compiler_CodeGeneratorRequest* msg) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorRequest_msg_init(), 17);
+  _upb_Message_ClearNonExtensionField(msg, &field);
+}
+UPB_INLINE const struct google_protobuf_FileDescriptorProto* const* google_protobuf_compiler_CodeGeneratorRequest_source_file_descriptors(const google_protobuf_compiler_CodeGeneratorRequest* msg, size_t* size) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorRequest_msg_init(), 17);
+  const upb_Array* arr = upb_Message_GetArray(msg, &field);
+  if (arr) {
+    if (size) *size = arr->size;
+    return (const struct google_protobuf_FileDescriptorProto* const*)_upb_array_constptr(arr);
+  } else {
+    if (size) *size = 0;
+    return NULL;
+  }
+}
+UPB_INLINE const upb_Array* _google_protobuf_compiler_CodeGeneratorRequest_source_file_descriptors_upb_array(const google_protobuf_compiler_CodeGeneratorRequest* msg, size_t* size) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorRequest_msg_init(), 17);
+  const upb_Array* arr = upb_Message_GetArray(msg, &field);
+  if (size) {
+    *size = arr ? arr->size : 0;
+  }
+  return arr;
+}
+UPB_INLINE upb_Array* _google_protobuf_compiler_CodeGeneratorRequest_source_file_descriptors_mutable_upb_array(const google_protobuf_compiler_CodeGeneratorRequest* msg, size_t* size, upb_Arena* arena) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorRequest_msg_init(), 17);
+  upb_Array* arr = upb_Message_GetOrCreateMutableArray(
+      (upb_Message*)msg, &field, arena);
+  if (size) {
+    *size = arr ? arr->size : 0;
+  }
+  return arr;
+}
+UPB_INLINE bool google_protobuf_compiler_CodeGeneratorRequest_has_source_file_descriptors(const google_protobuf_compiler_CodeGeneratorRequest* msg) {
+  size_t size;
+  google_protobuf_compiler_CodeGeneratorRequest_source_file_descriptors(msg, &size);
+  return size != 0;
+}
+
+UPB_INLINE upb_StringView* google_protobuf_compiler_CodeGeneratorRequest_mutable_file_to_generate(google_protobuf_compiler_CodeGeneratorRequest* msg, size_t* size) {
+  upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorRequest_msg_init(), 1);
+  upb_Array* arr = upb_Message_GetMutableArray(msg, &field);
+  if (arr) {
+    if (size) *size = arr->size;
+    return (upb_StringView*)_upb_array_ptr(arr);
+  } else {
+    if (size) *size = 0;
+    return NULL;
+  }
+}
+UPB_INLINE upb_StringView* google_protobuf_compiler_CodeGeneratorRequest_resize_file_to_generate(google_protobuf_compiler_CodeGeneratorRequest* msg, size_t size, upb_Arena* arena) {
+  upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorRequest_msg_init(), 1);
+  return (upb_StringView*)upb_Message_ResizeArrayUninitialized(msg, &field, size, arena);
+}
+UPB_INLINE bool google_protobuf_compiler_CodeGeneratorRequest_add_file_to_generate(google_protobuf_compiler_CodeGeneratorRequest* msg, upb_StringView val, upb_Arena* arena) {
+  upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorRequest_msg_init(), 1);
+  upb_Array* arr = upb_Message_GetOrCreateMutableArray(msg, &field, arena);
+  if (!arr || !_upb_Array_ResizeUninitialized(arr, arr->size + 1, arena)) {
+    return false;
+  }
+  _upb_Array_Set(arr, arr->size - 1, &val, sizeof(val));
+  return true;
+}
+UPB_INLINE void google_protobuf_compiler_CodeGeneratorRequest_set_parameter(google_protobuf_compiler_CodeGeneratorRequest *msg, upb_StringView value) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorRequest_msg_init(), 2);
+  _upb_Message_SetNonExtensionField(msg, &field, &value);
+}
+UPB_INLINE void google_protobuf_compiler_CodeGeneratorRequest_set_compiler_version(google_protobuf_compiler_CodeGeneratorRequest *msg, google_protobuf_compiler_Version* value) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorRequest_msg_init(), 3);
+  _upb_Message_SetNonExtensionField(msg, &field, &value);
+}
+UPB_INLINE struct google_protobuf_compiler_Version* google_protobuf_compiler_CodeGeneratorRequest_mutable_compiler_version(google_protobuf_compiler_CodeGeneratorRequest* msg, upb_Arena* arena) {
+  struct google_protobuf_compiler_Version* sub = (struct google_protobuf_compiler_Version*)google_protobuf_compiler_CodeGeneratorRequest_compiler_version(msg);
+  if (sub == NULL) {
+    sub = (struct google_protobuf_compiler_Version*)_upb_Message_New(google_protobuf_compiler_Version_msg_init(), arena);
+    if (sub) google_protobuf_compiler_CodeGeneratorRequest_set_compiler_version(msg, sub);
+  }
+  return sub;
+}
+UPB_INLINE struct google_protobuf_FileDescriptorProto** google_protobuf_compiler_CodeGeneratorRequest_mutable_proto_file(google_protobuf_compiler_CodeGeneratorRequest* msg, size_t* size) {
+  upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorRequest_msg_init(), 15);
+  upb_Array* arr = upb_Message_GetMutableArray(msg, &field);
+  if (arr) {
+    if (size) *size = arr->size;
+    return (struct google_protobuf_FileDescriptorProto**)_upb_array_ptr(arr);
+  } else {
+    if (size) *size = 0;
+    return NULL;
+  }
+}
+UPB_INLINE struct google_protobuf_FileDescriptorProto** google_protobuf_compiler_CodeGeneratorRequest_resize_proto_file(google_protobuf_compiler_CodeGeneratorRequest* msg, size_t size, upb_Arena* arena) {
+  upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorRequest_msg_init(), 15);
+  return (struct google_protobuf_FileDescriptorProto**)upb_Message_ResizeArrayUninitialized(msg, &field, size, arena);
+}
+UPB_INLINE struct google_protobuf_FileDescriptorProto* google_protobuf_compiler_CodeGeneratorRequest_add_proto_file(google_protobuf_compiler_CodeGeneratorRequest* msg, upb_Arena* arena) {
+  upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorRequest_msg_init(), 15);
+  upb_Array* arr = upb_Message_GetOrCreateMutableArray(msg, &field, arena);
+  if (!arr || !_upb_Array_ResizeUninitialized(arr, arr->size + 1, arena)) {
+    return NULL;
+  }
+  struct google_protobuf_FileDescriptorProto* sub = (struct google_protobuf_FileDescriptorProto*)_upb_Message_New(google_protobuf_FileDescriptorProto_msg_init(), arena);
+  if (!arr || !sub) return NULL;
+  _upb_Array_Set(arr, arr->size - 1, &sub, sizeof(sub));
+  return sub;
+}
+UPB_INLINE struct google_protobuf_FileDescriptorProto** google_protobuf_compiler_CodeGeneratorRequest_mutable_source_file_descriptors(google_protobuf_compiler_CodeGeneratorRequest* msg, size_t* size) {
+  upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorRequest_msg_init(), 17);
+  upb_Array* arr = upb_Message_GetMutableArray(msg, &field);
+  if (arr) {
+    if (size) *size = arr->size;
+    return (struct google_protobuf_FileDescriptorProto**)_upb_array_ptr(arr);
+  } else {
+    if (size) *size = 0;
+    return NULL;
+  }
+}
+UPB_INLINE struct google_protobuf_FileDescriptorProto** google_protobuf_compiler_CodeGeneratorRequest_resize_source_file_descriptors(google_protobuf_compiler_CodeGeneratorRequest* msg, size_t size, upb_Arena* arena) {
+  upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorRequest_msg_init(), 17);
+  return (struct google_protobuf_FileDescriptorProto**)upb_Message_ResizeArrayUninitialized(msg, &field, size, arena);
+}
+UPB_INLINE struct google_protobuf_FileDescriptorProto* google_protobuf_compiler_CodeGeneratorRequest_add_source_file_descriptors(google_protobuf_compiler_CodeGeneratorRequest* msg, upb_Arena* arena) {
+  upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorRequest_msg_init(), 17);
+  upb_Array* arr = upb_Message_GetOrCreateMutableArray(msg, &field, arena);
+  if (!arr || !_upb_Array_ResizeUninitialized(arr, arr->size + 1, arena)) {
+    return NULL;
+  }
+  struct google_protobuf_FileDescriptorProto* sub = (struct google_protobuf_FileDescriptorProto*)_upb_Message_New(google_protobuf_FileDescriptorProto_msg_init(), arena);
+  if (!arr || !sub) return NULL;
+  _upb_Array_Set(arr, arr->size - 1, &sub, sizeof(sub));
+  return sub;
+}
+
+/* google.protobuf.compiler.CodeGeneratorResponse */
+
+UPB_INLINE google_protobuf_compiler_CodeGeneratorResponse* google_protobuf_compiler_CodeGeneratorResponse_new(upb_Arena* arena) {
+  return (google_protobuf_compiler_CodeGeneratorResponse*)_upb_Message_New(google_protobuf_compiler_CodeGeneratorResponse_msg_init(), arena);
+}
+UPB_INLINE google_protobuf_compiler_CodeGeneratorResponse* google_protobuf_compiler_CodeGeneratorResponse_parse(const char* buf, size_t size, upb_Arena* arena) {
+  google_protobuf_compiler_CodeGeneratorResponse* ret = google_protobuf_compiler_CodeGeneratorResponse_new(arena);
+  if (!ret) return NULL;
+  if (upb_Decode(buf, size, ret, google_protobuf_compiler_CodeGeneratorResponse_msg_init(), NULL, 0, arena) != kUpb_DecodeStatus_Ok) {
+    return NULL;
+  }
+  return ret;
+}
+UPB_INLINE google_protobuf_compiler_CodeGeneratorResponse* google_protobuf_compiler_CodeGeneratorResponse_parse_ex(const char* buf, size_t size,
+                           const upb_ExtensionRegistry* extreg,
+                           int options, upb_Arena* arena) {
+  google_protobuf_compiler_CodeGeneratorResponse* ret = google_protobuf_compiler_CodeGeneratorResponse_new(arena);
+  if (!ret) return NULL;
+  if (upb_Decode(buf, size, ret, google_protobuf_compiler_CodeGeneratorResponse_msg_init(), extreg, options, arena) !=
+      kUpb_DecodeStatus_Ok) {
+    return NULL;
+  }
+  return ret;
+}
+UPB_INLINE char* google_protobuf_compiler_CodeGeneratorResponse_serialize(const google_protobuf_compiler_CodeGeneratorResponse* msg, upb_Arena* arena, size_t* len) {
+  char* ptr;
+  (void)upb_Encode(msg, google_protobuf_compiler_CodeGeneratorResponse_msg_init(), 0, arena, &ptr, len);
+  return ptr;
+}
+UPB_INLINE char* google_protobuf_compiler_CodeGeneratorResponse_serialize_ex(const google_protobuf_compiler_CodeGeneratorResponse* msg, int options,
+                                 upb_Arena* arena, size_t* len) {
+  char* ptr;
+  (void)upb_Encode(msg, google_protobuf_compiler_CodeGeneratorResponse_msg_init(), options, arena, &ptr, len);
+  return ptr;
+}
+UPB_INLINE void google_protobuf_compiler_CodeGeneratorResponse_clear_error(google_protobuf_compiler_CodeGeneratorResponse* msg) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorResponse_msg_init(), 1);
+  _upb_Message_ClearNonExtensionField(msg, &field);
+}
+UPB_INLINE upb_StringView google_protobuf_compiler_CodeGeneratorResponse_error(const google_protobuf_compiler_CodeGeneratorResponse* msg) {
+  upb_StringView default_val = upb_StringView_FromString("");
+  upb_StringView ret;
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorResponse_msg_init(), 1);
+  _upb_Message_GetNonExtensionField(msg, &field, &default_val, &ret);
+  return ret;
+}
+UPB_INLINE bool google_protobuf_compiler_CodeGeneratorResponse_has_error(const google_protobuf_compiler_CodeGeneratorResponse* msg) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorResponse_msg_init(), 1);
+  return _upb_Message_HasNonExtensionField(msg, &field);
+}
+UPB_INLINE void google_protobuf_compiler_CodeGeneratorResponse_clear_supported_features(google_protobuf_compiler_CodeGeneratorResponse* msg) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorResponse_msg_init(), 2);
+  _upb_Message_ClearNonExtensionField(msg, &field);
+}
+UPB_INLINE uint64_t google_protobuf_compiler_CodeGeneratorResponse_supported_features(const google_protobuf_compiler_CodeGeneratorResponse* msg) {
+  uint64_t default_val = (uint64_t)0ull;
+  uint64_t ret;
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorResponse_msg_init(), 2);
+  _upb_Message_GetNonExtensionField(msg, &field, &default_val, &ret);
+  return ret;
+}
+UPB_INLINE bool google_protobuf_compiler_CodeGeneratorResponse_has_supported_features(const google_protobuf_compiler_CodeGeneratorResponse* msg) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorResponse_msg_init(), 2);
+  return _upb_Message_HasNonExtensionField(msg, &field);
+}
+UPB_INLINE void google_protobuf_compiler_CodeGeneratorResponse_clear_file(google_protobuf_compiler_CodeGeneratorResponse* msg) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorResponse_msg_init(), 15);
+  _upb_Message_ClearNonExtensionField(msg, &field);
+}
+UPB_INLINE const google_protobuf_compiler_CodeGeneratorResponse_File* const* google_protobuf_compiler_CodeGeneratorResponse_file(const google_protobuf_compiler_CodeGeneratorResponse* msg, size_t* size) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorResponse_msg_init(), 15);
+  const upb_Array* arr = upb_Message_GetArray(msg, &field);
+  if (arr) {
+    if (size) *size = arr->size;
+    return (const google_protobuf_compiler_CodeGeneratorResponse_File* const*)_upb_array_constptr(arr);
+  } else {
+    if (size) *size = 0;
+    return NULL;
+  }
+}
+UPB_INLINE const upb_Array* _google_protobuf_compiler_CodeGeneratorResponse_file_upb_array(const google_protobuf_compiler_CodeGeneratorResponse* msg, size_t* size) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorResponse_msg_init(), 15);
+  const upb_Array* arr = upb_Message_GetArray(msg, &field);
+  if (size) {
+    *size = arr ? arr->size : 0;
+  }
+  return arr;
+}
+UPB_INLINE upb_Array* _google_protobuf_compiler_CodeGeneratorResponse_file_mutable_upb_array(const google_protobuf_compiler_CodeGeneratorResponse* msg, size_t* size, upb_Arena* arena) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorResponse_msg_init(), 15);
+  upb_Array* arr = upb_Message_GetOrCreateMutableArray(
+      (upb_Message*)msg, &field, arena);
+  if (size) {
+    *size = arr ? arr->size : 0;
+  }
+  return arr;
+}
+UPB_INLINE bool google_protobuf_compiler_CodeGeneratorResponse_has_file(const google_protobuf_compiler_CodeGeneratorResponse* msg) {
+  size_t size;
+  google_protobuf_compiler_CodeGeneratorResponse_file(msg, &size);
+  return size != 0;
+}
+
+UPB_INLINE void google_protobuf_compiler_CodeGeneratorResponse_set_error(google_protobuf_compiler_CodeGeneratorResponse *msg, upb_StringView value) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorResponse_msg_init(), 1);
+  _upb_Message_SetNonExtensionField(msg, &field, &value);
+}
+UPB_INLINE void google_protobuf_compiler_CodeGeneratorResponse_set_supported_features(google_protobuf_compiler_CodeGeneratorResponse *msg, uint64_t value) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorResponse_msg_init(), 2);
+  _upb_Message_SetNonExtensionField(msg, &field, &value);
+}
+UPB_INLINE google_protobuf_compiler_CodeGeneratorResponse_File** google_protobuf_compiler_CodeGeneratorResponse_mutable_file(google_protobuf_compiler_CodeGeneratorResponse* msg, size_t* size) {
+  upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorResponse_msg_init(), 15);
+  upb_Array* arr = upb_Message_GetMutableArray(msg, &field);
+  if (arr) {
+    if (size) *size = arr->size;
+    return (google_protobuf_compiler_CodeGeneratorResponse_File**)_upb_array_ptr(arr);
+  } else {
+    if (size) *size = 0;
+    return NULL;
+  }
+}
+UPB_INLINE google_protobuf_compiler_CodeGeneratorResponse_File** google_protobuf_compiler_CodeGeneratorResponse_resize_file(google_protobuf_compiler_CodeGeneratorResponse* msg, size_t size, upb_Arena* arena) {
+  upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorResponse_msg_init(), 15);
+  return (google_protobuf_compiler_CodeGeneratorResponse_File**)upb_Message_ResizeArrayUninitialized(msg, &field, size, arena);
+}
+UPB_INLINE struct google_protobuf_compiler_CodeGeneratorResponse_File* google_protobuf_compiler_CodeGeneratorResponse_add_file(google_protobuf_compiler_CodeGeneratorResponse* msg, upb_Arena* arena) {
+  upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorResponse_msg_init(), 15);
+  upb_Array* arr = upb_Message_GetOrCreateMutableArray(msg, &field, arena);
+  if (!arr || !_upb_Array_ResizeUninitialized(arr, arr->size + 1, arena)) {
+    return NULL;
+  }
+  struct google_protobuf_compiler_CodeGeneratorResponse_File* sub = (struct google_protobuf_compiler_CodeGeneratorResponse_File*)_upb_Message_New(google_protobuf_compiler_CodeGeneratorResponse_File_msg_init(), arena);
+  if (!arr || !sub) return NULL;
+  _upb_Array_Set(arr, arr->size - 1, &sub, sizeof(sub));
+  return sub;
+}
+
+/* google.protobuf.compiler.CodeGeneratorResponse.File */
+
+UPB_INLINE google_protobuf_compiler_CodeGeneratorResponse_File* google_protobuf_compiler_CodeGeneratorResponse_File_new(upb_Arena* arena) {
+  return (google_protobuf_compiler_CodeGeneratorResponse_File*)_upb_Message_New(google_protobuf_compiler_CodeGeneratorResponse_File_msg_init(), arena);
+}
+UPB_INLINE google_protobuf_compiler_CodeGeneratorResponse_File* google_protobuf_compiler_CodeGeneratorResponse_File_parse(const char* buf, size_t size, upb_Arena* arena) {
+  google_protobuf_compiler_CodeGeneratorResponse_File* ret = google_protobuf_compiler_CodeGeneratorResponse_File_new(arena);
+  if (!ret) return NULL;
+  if (upb_Decode(buf, size, ret, google_protobuf_compiler_CodeGeneratorResponse_File_msg_init(), NULL, 0, arena) != kUpb_DecodeStatus_Ok) {
+    return NULL;
+  }
+  return ret;
+}
+UPB_INLINE google_protobuf_compiler_CodeGeneratorResponse_File* google_protobuf_compiler_CodeGeneratorResponse_File_parse_ex(const char* buf, size_t size,
+                           const upb_ExtensionRegistry* extreg,
+                           int options, upb_Arena* arena) {
+  google_protobuf_compiler_CodeGeneratorResponse_File* ret = google_protobuf_compiler_CodeGeneratorResponse_File_new(arena);
+  if (!ret) return NULL;
+  if (upb_Decode(buf, size, ret, google_protobuf_compiler_CodeGeneratorResponse_File_msg_init(), extreg, options, arena) !=
+      kUpb_DecodeStatus_Ok) {
+    return NULL;
+  }
+  return ret;
+}
+UPB_INLINE char* google_protobuf_compiler_CodeGeneratorResponse_File_serialize(const google_protobuf_compiler_CodeGeneratorResponse_File* msg, upb_Arena* arena, size_t* len) {
+  char* ptr;
+  (void)upb_Encode(msg, google_protobuf_compiler_CodeGeneratorResponse_File_msg_init(), 0, arena, &ptr, len);
+  return ptr;
+}
+UPB_INLINE char* google_protobuf_compiler_CodeGeneratorResponse_File_serialize_ex(const google_protobuf_compiler_CodeGeneratorResponse_File* msg, int options,
+                                 upb_Arena* arena, size_t* len) {
+  char* ptr;
+  (void)upb_Encode(msg, google_protobuf_compiler_CodeGeneratorResponse_File_msg_init(), options, arena, &ptr, len);
+  return ptr;
+}
+UPB_INLINE void google_protobuf_compiler_CodeGeneratorResponse_File_clear_name(google_protobuf_compiler_CodeGeneratorResponse_File* msg) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorResponse_File_msg_init(), 1);
+  _upb_Message_ClearNonExtensionField(msg, &field);
+}
+UPB_INLINE upb_StringView google_protobuf_compiler_CodeGeneratorResponse_File_name(const google_protobuf_compiler_CodeGeneratorResponse_File* msg) {
+  upb_StringView default_val = upb_StringView_FromString("");
+  upb_StringView ret;
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorResponse_File_msg_init(), 1);
+  _upb_Message_GetNonExtensionField(msg, &field, &default_val, &ret);
+  return ret;
+}
+UPB_INLINE bool google_protobuf_compiler_CodeGeneratorResponse_File_has_name(const google_protobuf_compiler_CodeGeneratorResponse_File* msg) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorResponse_File_msg_init(), 1);
+  return _upb_Message_HasNonExtensionField(msg, &field);
+}
+UPB_INLINE void google_protobuf_compiler_CodeGeneratorResponse_File_clear_insertion_point(google_protobuf_compiler_CodeGeneratorResponse_File* msg) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorResponse_File_msg_init(), 2);
+  _upb_Message_ClearNonExtensionField(msg, &field);
+}
+UPB_INLINE upb_StringView google_protobuf_compiler_CodeGeneratorResponse_File_insertion_point(const google_protobuf_compiler_CodeGeneratorResponse_File* msg) {
+  upb_StringView default_val = upb_StringView_FromString("");
+  upb_StringView ret;
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorResponse_File_msg_init(), 2);
+  _upb_Message_GetNonExtensionField(msg, &field, &default_val, &ret);
+  return ret;
+}
+UPB_INLINE bool google_protobuf_compiler_CodeGeneratorResponse_File_has_insertion_point(const google_protobuf_compiler_CodeGeneratorResponse_File* msg) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorResponse_File_msg_init(), 2);
+  return _upb_Message_HasNonExtensionField(msg, &field);
+}
+UPB_INLINE void google_protobuf_compiler_CodeGeneratorResponse_File_clear_content(google_protobuf_compiler_CodeGeneratorResponse_File* msg) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorResponse_File_msg_init(), 15);
+  _upb_Message_ClearNonExtensionField(msg, &field);
+}
+UPB_INLINE upb_StringView google_protobuf_compiler_CodeGeneratorResponse_File_content(const google_protobuf_compiler_CodeGeneratorResponse_File* msg) {
+  upb_StringView default_val = upb_StringView_FromString("");
+  upb_StringView ret;
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorResponse_File_msg_init(), 15);
+  _upb_Message_GetNonExtensionField(msg, &field, &default_val, &ret);
+  return ret;
+}
+UPB_INLINE bool google_protobuf_compiler_CodeGeneratorResponse_File_has_content(const google_protobuf_compiler_CodeGeneratorResponse_File* msg) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorResponse_File_msg_init(), 15);
+  return _upb_Message_HasNonExtensionField(msg, &field);
+}
+UPB_INLINE void google_protobuf_compiler_CodeGeneratorResponse_File_clear_generated_code_info(google_protobuf_compiler_CodeGeneratorResponse_File* msg) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorResponse_File_msg_init(), 16);
+  _upb_Message_ClearNonExtensionField(msg, &field);
+}
+UPB_INLINE const struct google_protobuf_GeneratedCodeInfo* google_protobuf_compiler_CodeGeneratorResponse_File_generated_code_info(const google_protobuf_compiler_CodeGeneratorResponse_File* msg) {
+  const struct google_protobuf_GeneratedCodeInfo* default_val = NULL;
+  const struct google_protobuf_GeneratedCodeInfo* ret;
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorResponse_File_msg_init(), 16);
+  _upb_Message_GetNonExtensionField(msg, &field, &default_val, &ret);
+  return ret;
+}
+UPB_INLINE bool google_protobuf_compiler_CodeGeneratorResponse_File_has_generated_code_info(const google_protobuf_compiler_CodeGeneratorResponse_File* msg) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorResponse_File_msg_init(), 16);
+  return _upb_Message_HasNonExtensionField(msg, &field);
+}
+
+UPB_INLINE void google_protobuf_compiler_CodeGeneratorResponse_File_set_name(google_protobuf_compiler_CodeGeneratorResponse_File *msg, upb_StringView value) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorResponse_File_msg_init(), 1);
+  _upb_Message_SetNonExtensionField(msg, &field, &value);
+}
+UPB_INLINE void google_protobuf_compiler_CodeGeneratorResponse_File_set_insertion_point(google_protobuf_compiler_CodeGeneratorResponse_File *msg, upb_StringView value) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorResponse_File_msg_init(), 2);
+  _upb_Message_SetNonExtensionField(msg, &field, &value);
+}
+UPB_INLINE void google_protobuf_compiler_CodeGeneratorResponse_File_set_content(google_protobuf_compiler_CodeGeneratorResponse_File *msg, upb_StringView value) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorResponse_File_msg_init(), 15);
+  _upb_Message_SetNonExtensionField(msg, &field, &value);
+}
+UPB_INLINE void google_protobuf_compiler_CodeGeneratorResponse_File_set_generated_code_info(google_protobuf_compiler_CodeGeneratorResponse_File *msg, struct google_protobuf_GeneratedCodeInfo* value) {
+  const upb_MiniTableField field = *upb_MiniTable_FindFieldByNumber(google_protobuf_compiler_CodeGeneratorResponse_File_msg_init(), 16);
+  _upb_Message_SetNonExtensionField(msg, &field, &value);
+}
+UPB_INLINE struct google_protobuf_GeneratedCodeInfo* google_protobuf_compiler_CodeGeneratorResponse_File_mutable_generated_code_info(google_protobuf_compiler_CodeGeneratorResponse_File* msg, upb_Arena* arena) {
+  struct google_protobuf_GeneratedCodeInfo* sub = (struct google_protobuf_GeneratedCodeInfo*)google_protobuf_compiler_CodeGeneratorResponse_File_generated_code_info(msg);
+  if (sub == NULL) {
+    sub = (struct google_protobuf_GeneratedCodeInfo*)_upb_Message_New(google_protobuf_GeneratedCodeInfo_msg_init(), arena);
+    if (sub) google_protobuf_compiler_CodeGeneratorResponse_File_set_generated_code_info(msg, sub);
+  }
+  return sub;
+}
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#include "upb/port/undef.inc"
+
+#endif  /* GOOGLE_PROTOBUF_COMPILER_PLUGIN_PROTO_UPB_H_ */
diff --git a/upbc/subprocess.cc b/upbc/subprocess.cc
new file mode 100644
index 0000000..e0c2604
--- /dev/null
+++ b/upbc/subprocess.cc
@@ -0,0 +1,465 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  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 LLC 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.
+
+// Shamelessly copied from the protobuf compiler's subprocess.cc
+// except this version passes strings instead of Messages.
+
+#include "upbc/subprocess.h"
+
+#include <algorithm>
+#include <cstring>
+#include <iostream>
+
+#ifndef _MSVC_LANG
+#include <errno.h>
+#include <signal.h>
+#include <sys/select.h>
+#include <sys/wait.h>
+#endif
+
+#include "absl/log/absl_log.h"
+#include "absl/strings/substitute.h"
+
+// Must be last.
+#include "upb/port/def.inc"
+
+namespace upbc {
+
+namespace {
+char* portable_strdup(const char* s) {
+  char* ns = (char*)malloc(strlen(s) + 1);
+  if (ns != nullptr) {
+    strcpy(ns, s);
+  }
+  return ns;
+}
+}  // namespace
+
+#ifdef _WIN32
+
+static void CloseHandleOrDie(HANDLE handle) {
+  if (!CloseHandle(handle)) {
+    ABSL_LOG(FATAL) << "CloseHandle: "
+                    << Subprocess::Win32ErrorMessage(GetLastError());
+  }
+}
+
+Subprocess::Subprocess()
+    : process_start_error_(ERROR_SUCCESS),
+      child_handle_(nullptr),
+      child_stdin_(nullptr),
+      child_stdout_(nullptr) {}
+
+Subprocess::~Subprocess() {
+  if (child_stdin_ != nullptr) {
+    CloseHandleOrDie(child_stdin_);
+  }
+  if (child_stdout_ != nullptr) {
+    CloseHandleOrDie(child_stdout_);
+  }
+}
+
+void Subprocess::Start(const std::string& program, SearchMode search_mode) {
+  // Create the pipes.
+  HANDLE stdin_pipe_read;
+  HANDLE stdin_pipe_write;
+  HANDLE stdout_pipe_read;
+  HANDLE stdout_pipe_write;
+
+  if (!CreatePipe(&stdin_pipe_read, &stdin_pipe_write, nullptr, 0)) {
+    ABSL_LOG(FATAL) << "CreatePipe: " << Win32ErrorMessage(GetLastError());
+  }
+  if (!CreatePipe(&stdout_pipe_read, &stdout_pipe_write, nullptr, 0)) {
+    ABSL_LOG(FATAL) << "CreatePipe: " << Win32ErrorMessage(GetLastError());
+  }
+
+  // Make child side of the pipes inheritable.
+  if (!SetHandleInformation(stdin_pipe_read, HANDLE_FLAG_INHERIT,
+                            HANDLE_FLAG_INHERIT)) {
+    ABSL_LOG(FATAL) << "SetHandleInformation: "
+                    << Win32ErrorMessage(GetLastError());
+  }
+  if (!SetHandleInformation(stdout_pipe_write, HANDLE_FLAG_INHERIT,
+                            HANDLE_FLAG_INHERIT)) {
+    ABSL_LOG(FATAL) << "SetHandleInformation: "
+                    << Win32ErrorMessage(GetLastError());
+  }
+
+  // Setup STARTUPINFO to redirect handles.
+  STARTUPINFOA startup_info;
+  ZeroMemory(&startup_info, sizeof(startup_info));
+  startup_info.cb = sizeof(startup_info);
+  startup_info.dwFlags = STARTF_USESTDHANDLES;
+  startup_info.hStdInput = stdin_pipe_read;
+  startup_info.hStdOutput = stdout_pipe_write;
+  startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
+
+  if (startup_info.hStdError == INVALID_HANDLE_VALUE) {
+    ABSL_LOG(FATAL) << "GetStdHandle: " << Win32ErrorMessage(GetLastError());
+  }
+
+  // Invoking cmd.exe allows for '.bat' files from the path as well as '.exe'.
+  // Using a malloc'ed string because CreateProcess() can mutate its second
+  // parameter.
+  char* command_line =
+      portable_strdup(("cmd.exe /c \"" + program + "\"").c_str());
+
+  // Create the process.
+  PROCESS_INFORMATION process_info;
+
+  if (CreateProcessA((search_mode == SEARCH_PATH) ? nullptr : program.c_str(),
+                     (search_mode == SEARCH_PATH) ? command_line : nullptr,
+                     nullptr,  // process security attributes
+                     nullptr,  // thread security attributes
+                     TRUE,     // inherit handles?
+                     0,        // obscure creation flags
+                     nullptr,  // environment (inherit from parent)
+                     nullptr,  // current directory (inherit from parent)
+                     &startup_info, &process_info)) {
+    child_handle_ = process_info.hProcess;
+    CloseHandleOrDie(process_info.hThread);
+    child_stdin_ = stdin_pipe_write;
+    child_stdout_ = stdout_pipe_read;
+  } else {
+    process_start_error_ = GetLastError();
+    CloseHandleOrDie(stdin_pipe_write);
+    CloseHandleOrDie(stdout_pipe_read);
+  }
+
+  CloseHandleOrDie(stdin_pipe_read);
+  CloseHandleOrDie(stdout_pipe_write);
+  free(command_line);
+}
+
+bool Subprocess::Communicate(const std::string& input_data,
+                             std::string* output_data, std::string* error) {
+  if (process_start_error_ != ERROR_SUCCESS) {
+    *error = Win32ErrorMessage(process_start_error_);
+    return false;
+  }
+
+  GOOGLE_CHECK(child_handle_ != nullptr) << "Must call Start() first.";
+
+  int input_pos = 0;
+
+  while (child_stdout_ != nullptr) {
+    HANDLE handles[2];
+    int handle_count = 0;
+
+    if (child_stdin_ != nullptr) {
+      handles[handle_count++] = child_stdin_;
+    }
+    if (child_stdout_ != nullptr) {
+      handles[handle_count++] = child_stdout_;
+    }
+
+    DWORD wait_result =
+        WaitForMultipleObjects(handle_count, handles, FALSE, INFINITE);
+
+    HANDLE signaled_handle = nullptr;
+    if (wait_result >= WAIT_OBJECT_0 &&
+        wait_result < WAIT_OBJECT_0 + handle_count) {
+      signaled_handle = handles[wait_result - WAIT_OBJECT_0];
+    } else if (wait_result == WAIT_FAILED) {
+      ABSL_LOG(FATAL) << "WaitForMultipleObjects: "
+                      << Win32ErrorMessage(GetLastError());
+    } else {
+      ABSL_LOG(FATAL) << "WaitForMultipleObjects: Unexpected return code: "
+                      << wait_result;
+    }
+
+    if (signaled_handle == child_stdin_) {
+      DWORD n;
+      if (!WriteFile(child_stdin_, input_data.data() + input_pos,
+                     input_data.size() - input_pos, &n, nullptr)) {
+        // Child closed pipe.  Presumably it will report an error later.
+        // Pretend we're done for now.
+        input_pos = input_data.size();
+      } else {
+        input_pos += n;
+      }
+
+      if (input_pos == input_data.size()) {
+        // We're done writing.  Close.
+        CloseHandleOrDie(child_stdin_);
+        child_stdin_ = nullptr;
+      }
+    } else if (signaled_handle == child_stdout_) {
+      char buffer[4096];
+      DWORD n;
+
+      if (!ReadFile(child_stdout_, buffer, sizeof(buffer), &n, nullptr)) {
+        // We're done reading.  Close.
+        CloseHandleOrDie(child_stdout_);
+        child_stdout_ = nullptr;
+      } else {
+        output_data->append(buffer, n);
+      }
+    }
+  }
+
+  if (child_stdin_ != nullptr) {
+    // Child did not finish reading input before it closed the output.
+    // Presumably it exited with an error.
+    CloseHandleOrDie(child_stdin_);
+    child_stdin_ = nullptr;
+  }
+
+  DWORD wait_result = WaitForSingleObject(child_handle_, INFINITE);
+
+  if (wait_result == WAIT_FAILED) {
+    ABSL_LOG(FATAL) << "WaitForSingleObject: "
+                    << Win32ErrorMessage(GetLastError());
+  } else if (wait_result != WAIT_OBJECT_0) {
+    ABSL_LOG(FATAL) << "WaitForSingleObject: Unexpected return code: "
+                    << wait_result;
+  }
+
+  DWORD exit_code;
+  if (!GetExitCodeProcess(child_handle_, &exit_code)) {
+    ABSL_LOG(FATAL) << "GetExitCodeProcess: "
+                    << Win32ErrorMessage(GetLastError());
+  }
+
+  CloseHandleOrDie(child_handle_);
+  child_handle_ = nullptr;
+
+  if (exit_code != 0) {
+    *error = absl::Substitute("Plugin failed with status code $0.", exit_code);
+    return false;
+  }
+
+  return true;
+}
+
+std::string Subprocess::Win32ErrorMessage(DWORD error_code) {
+  char* message;
+
+  // WTF?
+  FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
+                     FORMAT_MESSAGE_IGNORE_INSERTS,
+                 nullptr, error_code,
+                 MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
+                 (LPSTR)&message,  // NOT A BUG!
+                 0, nullptr);
+
+  std::string result = message;
+  LocalFree(message);
+  return result;
+}
+
+// ===================================================================
+
+#else  // _WIN32
+
+Subprocess::Subprocess()
+    : child_pid_(-1), child_stdin_(-1), child_stdout_(-1) {}
+
+Subprocess::~Subprocess() {
+  if (child_stdin_ != -1) {
+    close(child_stdin_);
+  }
+  if (child_stdout_ != -1) {
+    close(child_stdout_);
+  }
+}
+
+void Subprocess::Start(const std::string& program, SearchMode search_mode) {
+  // Note that we assume that there are no other threads, thus we don't have to
+  // do crazy stuff like using socket pairs or avoiding libc locks.
+
+  // [0] is read end, [1] is write end.
+  int stdin_pipe[2];
+  int stdout_pipe[2];
+
+  int p0 = pipe(stdin_pipe);
+  int p1 = pipe(stdout_pipe);
+  UPB_ASSERT(p0 != -1);
+  UPB_ASSERT(p1 != -1);
+
+  char* argv[2] = {portable_strdup(program.c_str()), nullptr};
+
+  child_pid_ = fork();
+  if (child_pid_ == -1) {
+    std::cerr << "fork: " << strerror(errno);
+  } else if (child_pid_ == 0) {
+    // We are the child.
+    dup2(stdin_pipe[0], STDIN_FILENO);
+    dup2(stdout_pipe[1], STDOUT_FILENO);
+
+    close(stdin_pipe[0]);
+    close(stdin_pipe[1]);
+    close(stdout_pipe[0]);
+    close(stdout_pipe[1]);
+
+    switch (search_mode) {
+      case SEARCH_PATH:
+        execvp(argv[0], argv);
+        break;
+      case EXACT_NAME:
+        execv(argv[0], argv);
+        break;
+    }
+
+    // Write directly to STDERR_FILENO to avoid stdio code paths that may do
+    // stuff that is unsafe here.
+    int ignored;
+    ignored = write(STDERR_FILENO, argv[0], strlen(argv[0]));
+    const char* message =
+        ": program not found or is not executable\n"
+        "Please specify a program using absolute path or make sure "
+        "the program is available in your PATH system variable\n";
+    ignored = write(STDERR_FILENO, message, strlen(message));
+    (void)ignored;
+
+    // Must use _exit() rather than exit() to avoid flushing output buffers
+    // that will also be flushed by the parent.
+    _exit(1);
+  } else {
+    free(argv[0]);
+
+    close(stdin_pipe[0]);
+    close(stdout_pipe[1]);
+
+    child_stdin_ = stdin_pipe[1];
+    child_stdout_ = stdout_pipe[0];
+  }
+}
+
+bool Subprocess::Communicate(const std::string& input_data,
+                             std::string* output_data, std::string* error) {
+  if (child_stdin_ == -1) {
+    std::cerr << "Must call Start() first." << std::endl;
+    UPB_ASSERT(child_stdin_ != -1);
+  }
+
+  // The "sighandler_t" typedef is GNU-specific, so define our own.
+  typedef void SignalHandler(int);
+
+  // Make sure SIGPIPE is disabled so that if the child dies it doesn't kill us.
+  SignalHandler* old_pipe_handler = signal(SIGPIPE, SIG_IGN);
+
+  int input_pos = 0;
+  int max_fd = std::max(child_stdin_, child_stdout_);
+
+  while (child_stdout_ != -1) {
+    fd_set read_fds;
+    fd_set write_fds;
+    FD_ZERO(&read_fds);
+    FD_ZERO(&write_fds);
+    if (child_stdout_ != -1) {
+      FD_SET(child_stdout_, &read_fds);
+    }
+    if (child_stdin_ != -1) {
+      FD_SET(child_stdin_, &write_fds);
+    }
+
+    if (select(max_fd + 1, &read_fds, &write_fds, nullptr, nullptr) < 0) {
+      if (errno == EINTR) {
+        // Interrupted by signal.  Try again.
+        continue;
+      } else {
+        std::cerr << "select: " << strerror(errno) << std::endl;
+        UPB_ASSERT(0);
+      }
+    }
+
+    if (child_stdin_ != -1 && FD_ISSET(child_stdin_, &write_fds)) {
+      int n = write(child_stdin_, input_data.data() + input_pos,
+                    input_data.size() - input_pos);
+      if (n < 0) {
+        // Child closed pipe.  Presumably it will report an error later.
+        // Pretend we're done for now.
+        input_pos = input_data.size();
+      } else {
+        input_pos += n;
+      }
+
+      if (input_pos == (int)input_data.size()) {
+        // We're done writing.  Close.
+        close(child_stdin_);
+        child_stdin_ = -1;
+      }
+    }
+
+    if (child_stdout_ != -1 && FD_ISSET(child_stdout_, &read_fds)) {
+      char buffer[4096];
+      int n = read(child_stdout_, buffer, sizeof(buffer));
+
+      if (n > 0) {
+        output_data->append(buffer, (size_t)n);
+      } else {
+        // We're done reading.  Close.
+        close(child_stdout_);
+        child_stdout_ = -1;
+      }
+    }
+  }
+
+  if (child_stdin_ != -1) {
+    // Child did not finish reading input before it closed the output.
+    // Presumably it exited with an error.
+    close(child_stdin_);
+    child_stdin_ = -1;
+  }
+
+  int status;
+  while (waitpid(child_pid_, &status, 0) == -1) {
+    if (errno != EINTR) {
+      std::cerr << "waitpid: " << strerror(errno) << std::endl;
+      UPB_ASSERT(0);
+    }
+  }
+
+  // Restore SIGPIPE handling.
+  signal(SIGPIPE, old_pipe_handler);
+
+  if (WIFEXITED(status)) {
+    if (WEXITSTATUS(status) != 0) {
+      int error_code = WEXITSTATUS(status);
+      *error =
+          absl::Substitute("Plugin failed with status code $0.", error_code);
+      return false;
+    }
+  } else if (WIFSIGNALED(status)) {
+    int signal = WTERMSIG(status);
+    *error = absl::Substitute("Plugin killed by signal $0.", signal);
+    return false;
+  } else {
+    *error = "Neither WEXITSTATUS nor WTERMSIG is true?";
+    return false;
+  }
+
+  return true;
+}
+
+#endif  // !_WIN32
+
+}  // namespace upbc
diff --git a/upbc/subprocess.h b/upbc/subprocess.h
new file mode 100644
index 0000000..b30b5bf
--- /dev/null
+++ b/upbc/subprocess.h
@@ -0,0 +1,102 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  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 LLC 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.
+
+// Shamelessly copied from the protobuf compiler's subprocess.h
+// except this version passes strings instead of Messages.
+
+#ifndef THIRD_PARTY_UPB_UPBC_H_
+#define THIRD_PARTY_UPB_UPBC_H_
+
+#ifdef _WIN32
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN  // right...
+#endif
+#include <windows.h>
+#else  // _WIN32
+#include <sys/types.h>
+#include <unistd.h>
+#endif  // !_WIN32
+#include <string>
+
+namespace upbc {
+
+// Utility class for launching sub-processes.
+class Subprocess {
+ public:
+  Subprocess();
+  ~Subprocess();
+
+  enum SearchMode {
+    SEARCH_PATH,  // Use PATH environment variable.
+    EXACT_NAME    // Program is an exact file name; don't use the PATH.
+  };
+
+  // Start the subprocess.  Currently we don't provide a way to specify
+  // arguments as protoc plugins don't have any.
+  void Start(const std::string& program, SearchMode search_mode);
+
+  // Pipe the input message to the subprocess's stdin, then close the pipe.
+  // Meanwhile, read from the subprocess's stdout and copy into *output.
+  // All this is done carefully to avoid deadlocks.
+  // Returns true if successful.  On any sort of error, returns false and sets
+  // *error to a description of the problem.
+  bool Communicate(const std::string& input_data, std::string* output_data,
+                   std::string* error);
+
+#ifdef _WIN32
+  // Given an error code, returns a human-readable error message.  This is
+  // defined here so that CommandLineInterface can share it.
+  static std::string Win32ErrorMessage(DWORD error_code);
+#endif
+
+ private:
+#ifdef _WIN32
+  DWORD process_start_error_;
+  HANDLE child_handle_;
+
+  // The file handles for our end of the child's pipes.  We close each and
+  // set it to NULL when no longer needed.
+  HANDLE child_stdin_;
+  HANDLE child_stdout_;
+
+#else  // _WIN32
+  pid_t child_pid_;
+
+  // The file descriptors for our end of the child's pipes.  We close each and
+  // set it to -1 when no longer needed.
+  int child_stdin_;
+  int child_stdout_;
+
+#endif  // !_WIN32
+};
+
+}  // namespace upbc
+
+#endif  // THIRD_PARTY_UPB_UPBC_H_
diff --git a/upbc/upbc_so.c b/upbc/upbc_so.c
new file mode 100644
index 0000000..1cfc04b
--- /dev/null
+++ b/upbc/upbc_so.c
@@ -0,0 +1,34 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  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 LLC 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.
+
+// These headers form a spanning tree for the upbc defs needed by FFI layers.
+
+#include "upbc/get_used_fields.h"
+#include "upbc/upbdev.h"
diff --git a/upbc/upbdev.c b/upbc/upbdev.c
new file mode 100644
index 0000000..ffccd07
--- /dev/null
+++ b/upbc/upbdev.c
@@ -0,0 +1,136 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  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 LLC 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 "upbc/upbdev.h"
+
+#ifdef _WIN32
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#else  // _WIN32
+#include <unistd.h>
+#endif  // !_WIN32
+
+#include "google/protobuf/compiler/plugin.upb.h"
+#include "google/protobuf/compiler/plugin.upbdefs.h"
+#include "upb/base/status.h"
+#include "upb/json/decode.h"
+#include "upb/json/encode.h"
+#include "upb/mem/arena.h"
+#include "upbc/code_generator_request.h"
+#include "upbc/code_generator_request.upb.h"
+#include "upbc/code_generator_request.upbdefs.h"
+
+static google_protobuf_compiler_CodeGeneratorResponse* upbc_JsonDecode(
+    const char* data, size_t size, upb_Arena* arena, upb_Status* status) {
+  google_protobuf_compiler_CodeGeneratorResponse* response =
+      google_protobuf_compiler_CodeGeneratorResponse_new(arena);
+
+  upb_DefPool* s = upb_DefPool_New();
+  const upb_MessageDef* m = google_protobuf_compiler_CodeGeneratorResponse_getmsgdef(s);
+
+  (void)upb_JsonDecode(data, size, response, m, s, 0, arena, status);
+  if (!upb_Status_IsOk(status)) return NULL;
+
+  upb_DefPool_Free(s);
+
+  return response;
+}
+
+static upb_StringView upbc_JsonEncode(const upbc_CodeGeneratorRequest* request,
+                                      upb_Arena* arena, upb_Status* status) {
+  upb_StringView out = {.data = NULL, .size = 0};
+
+  upb_DefPool* s = upb_DefPool_New();
+  const upb_MessageDef* m = upbc_CodeGeneratorRequest_getmsgdef(s);
+  const int options = upb_JsonEncode_FormatEnumsAsIntegers;
+
+  out.size = upb_JsonEncode(request, m, s, options, NULL, 0, status);
+  if (!upb_Status_IsOk(status)) goto done;
+
+  char* data = (char*)upb_Arena_Malloc(arena, out.size + 1);
+
+  (void)upb_JsonEncode(request, m, s, options, data, out.size + 1, status);
+  if (!upb_Status_IsOk(status)) goto done;
+
+  out.data = (const char*)data;
+
+done:
+  upb_DefPool_Free(s);
+  return out;
+}
+
+upb_StringView upbdev_ProcessInput(const char* buf, size_t size,
+                                   upb_Arena* arena, upb_Status* status) {
+  upb_StringView out = {.data = NULL, .size = 0};
+
+  google_protobuf_compiler_CodeGeneratorRequest* inner_request =
+      google_protobuf_compiler_CodeGeneratorRequest_parse(buf, size, arena);
+
+  const upbc_CodeGeneratorRequest* outer_request =
+      upbc_MakeCodeGeneratorRequest(inner_request, arena, status);
+  if (!upb_Status_IsOk(status)) return out;
+
+  return upbc_JsonEncode(outer_request, arena, status);
+}
+
+upb_StringView upbdev_ProcessOutput(const char* buf, size_t size,
+                                    upb_Arena* arena, upb_Status* status) {
+  upb_StringView out = {.data = NULL, .size = 0};
+
+  const google_protobuf_compiler_CodeGeneratorResponse* response =
+      upbc_JsonDecode(buf, size, arena, status);
+  if (!upb_Status_IsOk(status)) return out;
+
+  out.data = google_protobuf_compiler_CodeGeneratorResponse_serialize(response, arena,
+                                                             &out.size);
+  return out;
+}
+
+void upbdev_ProcessStdout(const char* buf, size_t size, upb_Arena* arena,
+                          upb_Status* status) {
+  const upb_StringView sv = upbdev_ProcessOutput(buf, size, arena, status);
+  if (!upb_Status_IsOk(status)) return;
+
+  const char* ptr = sv.data;
+  size_t len = sv.size;
+  while (len) {
+    int n = write(1, ptr, len);
+    if (n > 0) {
+      ptr += n;
+      len -= n;
+    }
+  }
+}
+
+upb_Arena* upbdev_Arena_New() { return upb_Arena_New(); }
+
+void upbdev_Status_Clear(upb_Status* status) { upb_Status_Clear(status); }
diff --git a/upbc/upbdev.h b/upbc/upbdev.h
new file mode 100644
index 0000000..047a1be
--- /dev/null
+++ b/upbc/upbdev.h
@@ -0,0 +1,71 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  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 LLC 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.
+
+#ifndef UPBC_UPBDEV_H_
+#define UPBC_UPBDEV_H_
+
+#include "upb/base/status.h"
+#include "upb/base/string_view.h"
+#include "upb/mem/arena.h"
+
+// Must be last.
+#include "upb/port/def.inc"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Consume |buf|, deserialize it to a Code_Generator_Request proto, construct a
+// upbc_Code_Generator_Request, and return it as a JSON-encoded string.
+UPB_API upb_StringView upbdev_ProcessInput(const char* buf, size_t size,
+                                           upb_Arena* arena,
+                                           upb_Status* status);
+
+// Decode |buf| from JSON, serialize to wire format, and return it.
+UPB_API upb_StringView upbdev_ProcessOutput(const char* buf, size_t size,
+                                            upb_Arena* arena,
+                                            upb_Status* status);
+
+// Decode |buf| from JSON, serialize to wire format, and write it to stdout.
+UPB_API void upbdev_ProcessStdout(const char* buf, size_t size,
+                                  upb_Arena* arena, upb_Status* status);
+
+// The following wrappers allow the protoc plugins to call the above functions
+// without pulling in the entire pb_runtime library.
+UPB_API upb_Arena* upbdev_Arena_New(void);
+UPB_API void upbdev_Status_Clear(upb_Status* status);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#include "upb/port/undef.inc"
+
+#endif  // UPBC_UPBDEV_H_