WIP.
diff --git a/upb/util/BUILD b/upb/util/BUILD
index 65a4009..0b8fc0b 100644
--- a/upb/util/BUILD
+++ b/upb/util/BUILD
@@ -6,6 +6,46 @@
 )
 
 cc_library(
+    name = "def_to_proto",
+    srcs = ["def_to_proto.c"],
+    hdrs = ["def_to_proto.h"],
+    deps = ["//:reflection"],
+    visibility = ["//visibility:public"],
+)
+
+proto_library(
+    name = "def_to_proto_test_proto",
+    srcs = [
+        "def_to_proto_test.proto",
+        "def_to_proto_regular_import_test.proto",
+        "def_to_proto_public_import_test.proto",
+        "def_to_proto_weak_import_test.proto",
+    ],
+)
+
+upb_proto_library(
+    name = "def_to_proto_test_upb_proto",
+    deps = ["def_to_proto_test_proto"],
+)
+
+upb_proto_reflection_library(
+    name = "def_to_proto_test_upb_proto_reflection",
+    deps = ["def_to_proto_test_proto"],
+)
+
+cc_test(
+    name = "def_to_proto_test",
+    srcs = ["def_to_proto_test.cc"],
+    deps = [
+        "@com_google_absl//absl/strings",
+        "@com_google_googletest//:gtest_main",
+        ":def_to_proto",
+        ":def_to_proto_test_upb_proto",
+        ":def_to_proto_test_upb_proto_reflection",
+    ],
+)
+
+cc_library(
     name = "required_fields",
     srcs = ["required_fields.c"],
     hdrs = ["required_fields.h"],
diff --git a/upb/util/def_to_proto.c b/upb/util/def_to_proto.c
new file mode 100644
index 0000000..4c5c0af
--- /dev/null
+++ b/upb/util/def_to_proto.c
@@ -0,0 +1,498 @@
+/*
+ * Copyright (c) 2009-2021, Google LLC
+ * All rights reserved.
+ *
+ * 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 Google LLC 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 "upb/util/def_to_proto.h"
+
+#include <setjmp.h>
+#include <stdio.h>
+#include <inttypes.h>
+#include "upb/reflection.h"
+
+/* Must be last. */
+#include "upb/port_def.inc"
+
+typedef struct {
+  upb_arena *arena;
+  jmp_buf err;
+} toproto_ctx;
+
+#define CHK_OOM(val) if (!(val)) UPB_LONGJMP(ctx->err, 1);
+
+// We want to copy the options verbatim into the destination options proto.
+// We use serialize+parse as our deep copy.
+#define SET_OPTIONS(proto, desc_type, options_type, src)                    \
+  {                                                                         \
+    size_t size;                                                            \
+    /* MEM: could use a temporary arena here instead. */                    \
+    char *pb =                                                              \
+        google_protobuf_##options_type##_serialize(src, ctx->arena, &size); \
+    CHK_OOM(pb);                                                            \
+    google_protobuf_##options_type *dst =                                   \
+        google_protobuf_##options_type##_parse(pb, size, ctx->arena);       \
+    CHK_OOM(dst);                                                           \
+    google_protobuf_##desc_type##_set_options(proto, dst);                  \
+  }
+
+static upb_strview strviewdup2(toproto_ctx *ctx, upb_strview str) {
+  char *p = upb_arena_malloc(ctx->arena, str.size);
+  CHK_OOM(p);
+  memcpy(p, str.data, str.size);
+  return (upb_strview){.data = p, .size = str.size};
+}
+
+static upb_strview strviewdup(toproto_ctx *ctx, const char *s) {
+  return strviewdup2(ctx, (upb_strview){.data = s, .size = strlen(s)});
+}
+
+static upb_strview qual_dup(toproto_ctx *ctx, const char *s) {
+  size_t n = strlen(s);
+  char *p = upb_arena_malloc(ctx->arena, n + 1);
+  CHK_OOM(p);
+  p[0] = '.';
+  memcpy(p + 1, s, n);
+  return (upb_strview){.data = p, .size = n + 1};
+}
+
+UPB_PRINTF(2, 3)
+static upb_strview printf_dup(toproto_ctx *ctx, const char *fmt, ...) {
+  va_list args;
+  va_start(args, fmt);
+  size_t n = vsnprintf(NULL, 0, fmt, args);
+  va_end(args);
+
+  char *p = upb_arena_malloc(ctx->arena, n + 1);
+  CHK_OOM(p);
+
+  va_start(args, fmt);
+  vsnprintf(p, n, fmt, args);
+  va_end(args);
+  return (upb_strview){.data = p, .size = n};
+}
+
+static upb_strview default_string(toproto_ctx *ctx, const upb_fielddef *f) {
+  upb_msgval d = upb_fielddef_default(f);
+  switch (upb_fielddef_type(f)) {
+    case UPB_TYPE_BOOL:
+      return strviewdup(ctx, d.bool_val ? "true" : "false");
+    case UPB_TYPE_ENUM:  // TODO: encode as string?
+    case UPB_TYPE_INT64:
+      return printf_dup(ctx, "%" PRId64, d.int64_val);
+    case UPB_TYPE_UINT64:
+      return printf_dup(ctx, "%" PRIu64, d.uint64_val);
+    case UPB_TYPE_INT32:
+      return printf_dup(ctx, "%" PRId32, d.int32_val);
+    case UPB_TYPE_UINT32:
+      return printf_dup(ctx, "%" PRIu32, d.uint32_val);
+    case UPB_TYPE_FLOAT:
+      return printf_dup(ctx, "%.9g", d.float_val);
+    case UPB_TYPE_DOUBLE:
+      return printf_dup(ctx, "%.17g", d.double_val);
+    case UPB_TYPE_STRING:
+      return strviewdup2(ctx, d.str_val);
+    case UPB_TYPE_BYTES:
+      return strviewdup2(ctx, d.str_val);  // XXX C-escape
+    default:
+      UPB_UNREACHABLE();
+  }
+}
+
+static google_protobuf_FieldDescriptorProto *fielddef_toproto(
+    toproto_ctx *ctx, const upb_fielddef *f) {
+  google_protobuf_FieldDescriptorProto *proto =
+      google_protobuf_FieldDescriptorProto_new(ctx->arena);
+  CHK_OOM(proto);
+
+  google_protobuf_FieldDescriptorProto_set_name(
+      proto, strviewdup(ctx, upb_fielddef_name(f)));
+  google_protobuf_FieldDescriptorProto_set_number(proto,
+                                                  upb_fielddef_number(f));
+  google_protobuf_FieldDescriptorProto_set_label(proto, upb_fielddef_label(f));
+  google_protobuf_FieldDescriptorProto_set_type(proto,
+                                                upb_fielddef_descriptortype(f));
+
+  // TODO: json_name (needs has_json_name from fielddef).
+
+  if (upb_fielddef_issubmsg(f)) {
+    google_protobuf_FieldDescriptorProto_set_type_name(
+        proto, qual_dup(ctx, upb_msgdef_fullname(upb_fielddef_msgsubdef(f))));
+  } else if (upb_fielddef_type(f) == UPB_TYPE_ENUM) {
+    google_protobuf_FieldDescriptorProto_set_type_name(
+        proto, qual_dup(ctx, upb_enumdef_fullname(upb_fielddef_enumsubdef(f))));
+  }
+
+  if (upb_fielddef_isextension(f)) {
+    google_protobuf_FieldDescriptorProto_set_extendee(
+        proto, qual_dup(ctx, upb_msgdef_fullname(upb_fielddef_containingtype(f))));
+  }
+
+  if (upb_fielddef_hasdefault(f)) {
+    google_protobuf_FieldDescriptorProto_set_default_value(
+        proto, default_string(ctx, f));
+  }
+
+  const upb_oneofdef *o = upb_fielddef_containingoneof(f);
+  if (o) {
+    google_protobuf_FieldDescriptorProto_set_oneof_index(proto,
+                                                         upb_oneofdef_index(o));
+  }
+
+  if (_upb_fielddef_proto3optional(f)) {
+    google_protobuf_FieldDescriptorProto_set_proto3_optional(proto, true);
+  }
+
+  if (upb_fielddef_hasoptions(f)) {
+    SET_OPTIONS(proto, FieldDescriptorProto, FieldOptions,
+                upb_fielddef_options(f));
+  }
+
+  return proto;
+}
+
+static google_protobuf_OneofDescriptorProto *oneofdef_toproto(
+    toproto_ctx *ctx, const upb_oneofdef *o) {
+  google_protobuf_OneofDescriptorProto *proto =
+      google_protobuf_OneofDescriptorProto_new(ctx->arena);
+  CHK_OOM(proto);
+
+  google_protobuf_OneofDescriptorProto_set_name(
+      proto, strviewdup(ctx, upb_oneofdef_name(o)));
+
+  if (upb_oneofdef_hasoptions(o)) {
+    SET_OPTIONS(proto, OneofDescriptorProto, OneofOptions,
+                upb_oneofdef_options(o));
+  }
+
+  return proto;
+}
+
+static google_protobuf_EnumValueDescriptorProto *enumvaldef_toproto(
+    toproto_ctx *ctx, const upb_enumvaldef *e) {
+  google_protobuf_EnumValueDescriptorProto *proto =
+      google_protobuf_EnumValueDescriptorProto_new(ctx->arena);
+  CHK_OOM(proto);
+
+  google_protobuf_EnumValueDescriptorProto_set_name(
+      proto, strviewdup(ctx, upb_enumvaldef_name(e)));
+  google_protobuf_EnumValueDescriptorProto_set_number(proto,
+                                                      upb_enumvaldef_number(e));
+
+  if (upb_enumvaldef_hasoptions(e)) {
+    SET_OPTIONS(proto, EnumValueDescriptorProto, EnumValueOptions,
+                upb_enumvaldef_options(e));
+  }
+
+  return proto;
+}
+
+static google_protobuf_EnumDescriptorProto *enumdef_toproto(
+    toproto_ctx *ctx, const upb_enumdef *e) {
+  google_protobuf_EnumDescriptorProto *proto =
+      google_protobuf_EnumDescriptorProto_new(ctx->arena);
+  CHK_OOM(proto);
+
+  google_protobuf_EnumDescriptorProto_set_name(
+      proto, strviewdup(ctx, upb_enumdef_name(e)));
+
+  int n;
+  
+  n = upb_enumdef_valuecount(e);
+  google_protobuf_EnumValueDescriptorProto **vals =
+      google_protobuf_EnumDescriptorProto_resize_value(proto, n, ctx->arena);
+  CHK_OOM(vals);
+  for (int i = 0; i < n; i++) {
+    vals[i] = enumvaldef_toproto(ctx, upb_enumdef_value(e, i));
+  }
+  
+  // TODO: reserved range, reserved name
+
+  if (upb_enumdef_hasoptions(e)) {
+    SET_OPTIONS(proto, EnumDescriptorProto, EnumOptions,
+                upb_enumdef_options(e));
+  }
+
+  return proto;
+}
+
+static google_protobuf_DescriptorProto_ExtensionRange *extrange_toproto(
+    toproto_ctx *ctx, const upb_extrange *e) {
+  google_protobuf_DescriptorProto_ExtensionRange *proto =
+      google_protobuf_DescriptorProto_ExtensionRange_new(ctx->arena);
+  CHK_OOM(proto);
+
+  google_protobuf_DescriptorProto_ExtensionRange_set_start(
+      proto, upb_extrange_start(e));
+  google_protobuf_DescriptorProto_ExtensionRange_set_end(proto,
+                                                         upb_extrange_end(e));
+
+  if (upb_extrange_hasoptions(e)) {
+    SET_OPTIONS(proto, DescriptorProto_ExtensionRange, ExtensionRangeOptions,
+                upb_extrange_options(e));
+  }
+
+  return proto;
+}
+
+static google_protobuf_DescriptorProto *msgdef_toproto(toproto_ctx *ctx,
+                                                       const upb_msgdef *m) {
+  google_protobuf_DescriptorProto *proto =
+      google_protobuf_DescriptorProto_new(ctx->arena);
+  CHK_OOM(proto);
+
+  google_protobuf_DescriptorProto_set_name(
+      proto, strviewdup(ctx, upb_msgdef_name(m)));
+
+  int n;
+  
+  n = upb_msgdef_fieldcount(m);
+  google_protobuf_FieldDescriptorProto **fields =
+      google_protobuf_DescriptorProto_resize_field(proto, n, ctx->arena);
+  CHK_OOM(fields);
+  for (int i = 0; i < n; i++) {
+    fields[i] = fielddef_toproto(ctx, upb_msgdef_field(m, i));
+  }
+
+  n = upb_msgdef_oneofcount(m);
+  google_protobuf_OneofDescriptorProto **oneofs =
+      google_protobuf_DescriptorProto_resize_oneof_decl(proto, n, ctx->arena);
+  for (int i = 0; i < n; i++) {
+    oneofs[i] = oneofdef_toproto(ctx, upb_msgdef_oneof(m, i));
+  }
+
+  n = upb_msgdef_nestedmsgcount(m);
+  google_protobuf_DescriptorProto **nested_msgs =
+      google_protobuf_DescriptorProto_resize_nested_type(proto, n, ctx->arena);
+  for (int i = 0; i < n; i++) {
+    nested_msgs[i] = msgdef_toproto(ctx, upb_msgdef_nestedmsg(m, i));
+  }
+
+  n = upb_msgdef_nestedenumcount(m);
+  google_protobuf_EnumDescriptorProto **nested_enums =
+      google_protobuf_DescriptorProto_resize_enum_type(proto, n, ctx->arena);
+  for (int i = 0; i < n; i++) {
+    nested_enums[i] = enumdef_toproto(ctx, upb_msgdef_nestedenum(m, i));
+  }
+
+  n = upb_msgdef_nestedextcount(m);
+  google_protobuf_FieldDescriptorProto **nested_exts =
+      google_protobuf_DescriptorProto_resize_extension(proto, n, ctx->arena);
+  for (int i = 0; i < n; i++) {
+    nested_exts[i] = fielddef_toproto(ctx, upb_msgdef_nestedext(m, i));
+  }
+
+  n = upb_msgdef_extrangecount(m);
+  google_protobuf_DescriptorProto_ExtensionRange **ext_ranges =
+      google_protobuf_DescriptorProto_resize_extension_range(proto, n, ctx->arena);
+  for (int i = 0; i < n; i++) {
+    ext_ranges[i] = extrange_toproto(ctx, upb_msgdef_extrange(m, i));
+  }
+
+  // TODO: reserved ranges and reserved names
+
+  if (upb_msgdef_hasoptions(m)) {
+    SET_OPTIONS(proto, DescriptorProto, MessageOptions, upb_msgdef_options(m));
+  }
+
+  return proto;
+}
+
+static google_protobuf_MethodDescriptorProto *methoddef_toproto(
+    toproto_ctx *ctx, const upb_methoddef *m) {
+  google_protobuf_MethodDescriptorProto *proto =
+      google_protobuf_MethodDescriptorProto_new(ctx->arena);
+  CHK_OOM(proto);
+
+  google_protobuf_MethodDescriptorProto_set_name(
+      proto, strviewdup(ctx, upb_methoddef_name(m)));
+
+  google_protobuf_MethodDescriptorProto_set_input_type(
+      proto, qual_dup(ctx, upb_msgdef_fullname(upb_methoddef_inputtype(m))));
+  google_protobuf_MethodDescriptorProto_set_output_type(
+      proto, qual_dup(ctx, upb_msgdef_fullname(upb_methoddef_outputtype(m))));
+
+  if (upb_methoddef_clientstreaming(m)) {
+    google_protobuf_MethodDescriptorProto_set_client_streaming(proto, true);
+  }
+
+  if (upb_methoddef_serverstreaming(m)) {
+    google_protobuf_MethodDescriptorProto_set_server_streaming(proto, true);
+  }
+
+  if (upb_methoddef_hasoptions(m)) {
+    SET_OPTIONS(proto, MethodDescriptorProto, MethodOptions,
+                upb_methoddef_options(m));
+  }
+
+  return proto;
+}
+
+static google_protobuf_ServiceDescriptorProto *servicedef_toproto(
+    toproto_ctx *ctx, const upb_servicedef *s) {
+  google_protobuf_ServiceDescriptorProto *proto =
+      google_protobuf_ServiceDescriptorProto_new(ctx->arena);
+  CHK_OOM(proto);
+
+  google_protobuf_ServiceDescriptorProto_set_name(
+      proto, strviewdup(ctx, upb_servicedef_name(s)));
+
+  size_t n = upb_servicedef_methodcount(s);
+  google_protobuf_MethodDescriptorProto **methods =
+      google_protobuf_ServiceDescriptorProto_resize_method(proto, n,
+                                                           ctx->arena);
+  for (int i = 0; i < n; i++) {
+    methods[i] = methoddef_toproto(ctx, upb_servicedef_method(s, i));
+  }
+
+  if (upb_servicedef_hasoptions(s)) {
+    SET_OPTIONS(proto, ServiceDescriptorProto, ServiceOptions,
+                upb_servicedef_options(s));
+  }
+
+  return proto;
+}
+
+static google_protobuf_FileDescriptorProto *filedef_toproto(
+    toproto_ctx *ctx, const upb_filedef *f) {
+  google_protobuf_FileDescriptorProto *proto =
+      google_protobuf_FileDescriptorProto_new(ctx->arena);
+  CHK_OOM(proto);
+
+  google_protobuf_FileDescriptorProto_set_name(
+      proto, strviewdup(ctx, upb_filedef_name(f)));
+
+  size_t n = strlen(upb_filedef_package(f));
+  if (n) {
+    google_protobuf_FileDescriptorProto_set_package(
+        proto, strviewdup(ctx, upb_filedef_package(f)));
+  }
+
+  if (upb_filedef_syntax(f) == UPB_SYNTAX_PROTO3) {
+    google_protobuf_FileDescriptorProto_set_syntax(proto,
+                                                   strviewdup(ctx, "proto3"));
+  }
+
+  n = upb_filedef_depcount(f);
+  upb_strview *deps =
+      google_protobuf_FileDescriptorProto_resize_dependency(proto, n, ctx->arena);
+  for (int i = 0; i < n; i++) {
+    deps[i] = strviewdup(ctx, upb_filedef_name(upb_filedef_dep(f, i)));
+  }
+
+  n = upb_filedef_publicdepcount(f);
+  int32_t *public_deps =
+      google_protobuf_FileDescriptorProto_resize_public_dependency(proto, n,
+                                                                   ctx->arena);
+  const int32_t *proto_deps = _upb_filedef_publicdepnums(f);
+  memcpy(public_deps, proto_deps, n * sizeof(int32_t));
+
+  n = upb_filedef_toplvlmsgcount(f);
+  google_protobuf_DescriptorProto **msgs =
+      google_protobuf_FileDescriptorProto_resize_message_type(proto, n,
+                                                              ctx->arena);
+  for (int i = 0; i < n; i++) {
+    msgs[i] = msgdef_toproto(ctx, upb_filedef_toplvlmsg(f, i));
+  }
+
+  n = upb_filedef_toplvlenumcount(f);
+  google_protobuf_EnumDescriptorProto **enums =
+      google_protobuf_FileDescriptorProto_resize_enum_type(proto, n,
+                                                           ctx->arena);
+  for (int i = 0; i < n; i++) {
+    enums[i] = enumdef_toproto(ctx, upb_filedef_toplvlenum(f, i));
+  }
+
+  n = upb_filedef_servicecount(f);
+  google_protobuf_ServiceDescriptorProto **services =
+      google_protobuf_FileDescriptorProto_resize_service(proto, n, ctx->arena);
+  for (int i = 0; i < n; i++) {
+    services[i] = servicedef_toproto(ctx, upb_filedef_service(f, i));
+  }
+
+  n = upb_filedef_toplvlextcount(f);
+  google_protobuf_FieldDescriptorProto **exts =
+      google_protobuf_FileDescriptorProto_resize_extension(proto, n,
+                                                           ctx->arena);
+  for (int i = 0; i < n; i++) {
+    exts[i] = fielddef_toproto(ctx, upb_filedef_toplvlext(f, i));
+  }
+
+  if (upb_filedef_hasoptions(f)) {
+    SET_OPTIONS(proto, FileDescriptorProto, FileOptions,
+                upb_filedef_options(f));
+  }
+
+  // TODO: public & weak dependencies.
+
+  return proto;
+}
+
+google_protobuf_DescriptorProto *upb_MessageDef_ToProto(const upb_msgdef *m,
+                                                        upb_arena *a) {
+  toproto_ctx ctx = {a};
+  return UPB_SETJMP(ctx.err) ? NULL : msgdef_toproto(&ctx, m);
+}
+
+google_protobuf_EnumDescriptorProto *upb_EnumDef_ToProto(const upb_enumdef *e,
+                                                         upb_arena *a) {
+  toproto_ctx ctx = {a};
+  return UPB_SETJMP(ctx.err) ? NULL : enumdef_toproto(&ctx, e);
+}
+
+google_protobuf_EnumValueDescriptorProto *upb_EnumValueDef_ToProto(
+    const upb_enumvaldef *e, upb_arena *a) {
+  toproto_ctx ctx = {a};
+  return UPB_SETJMP(ctx.err) ? NULL : enumvaldef_toproto(&ctx, e);
+}
+
+google_protobuf_FieldDescriptorProto *upb_FieldDef_ToProto(
+    const upb_fielddef *f, upb_arena *a) {
+  toproto_ctx ctx = {a};
+  return UPB_SETJMP(ctx.err) ? NULL : fielddef_toproto(&ctx, f);
+}
+
+google_protobuf_OneofDescriptorProto *upb_OneofDef_ToProto(
+    const upb_oneofdef *o, upb_arena *a) {
+  toproto_ctx ctx = {a};
+  return UPB_SETJMP(ctx.err) ? NULL : oneofdef_toproto(&ctx, o);
+}
+
+google_protobuf_FileDescriptorProto *upb_FileDef_ToProto(const upb_filedef *f,
+                                                         upb_arena *a) {
+  toproto_ctx ctx = {a};
+  return UPB_SETJMP(ctx.err) ? NULL : filedef_toproto(&ctx, f);
+}
+
+google_protobuf_MethodDescriptorProto *upb_MethodDef_ToProto(
+    const upb_methoddef *m, upb_arena *a) {
+  toproto_ctx ctx = {a};
+  return UPB_SETJMP(ctx.err) ? NULL : methoddef_toproto(&ctx, m);
+}
+
+google_protobuf_ServiceDescriptorProto *upb_ServiceDef_ToProto(
+    const upb_servicedef *s, upb_arena *a) {
+  toproto_ctx ctx = {a};
+  return UPB_SETJMP(ctx.err) ? NULL : servicedef_toproto(&ctx, s);
+}
diff --git a/upb/util/def_to_proto.h b/upb/util/def_to_proto.h
new file mode 100644
index 0000000..20c8ab4
--- /dev/null
+++ b/upb/util/def_to_proto.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2009-2021, Google LLC
+ * All rights reserved.
+ *
+ * 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 Google LLC 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_UTIL_DEF_TO_PROTO_H_
+#define UPB_UTIL_DEF_TO_PROTO_H_
+
+#include "upb/def.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Functions for converting defs back to the equivalent descriptor proto.
+// Ultimately the goal is that a round-trip proto->def->proto is lossless.
+// Each function creates a
+google_protobuf_DescriptorProto* upb_MessageDef_ToProto(const upb_msgdef* m,
+                                                        upb_arena* a);
+google_protobuf_EnumDescriptorProto* upb_EnumDef_ToProto(const upb_enumdef* e,
+                                                         upb_arena* a);
+google_protobuf_EnumValueDescriptorProto* upb_EnumValueDef_ToProto(
+    const upb_enumvaldef* e, upb_arena* a);
+google_protobuf_FieldDescriptorProto* upb_FieldDef_ToProto(
+    const upb_fielddef* f, upb_arena* a);
+google_protobuf_OneofDescriptorProto* upb_OneofDef_ToProto(
+    const upb_oneofdef* o, upb_arena* a);
+google_protobuf_FileDescriptorProto* upb_FileDef_ToProto(const upb_filedef* f,
+                                                         upb_arena* a);
+google_protobuf_MethodDescriptorProto* upb_MethodDef_ToProto(
+    const upb_methoddef* m, upb_arena* a);
+google_protobuf_ServiceDescriptorProto* upb_ServiceDef_ToProto(
+    const upb_servicedef* s, upb_arena* a);
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif  /* UPB_UTIL_DEF_TO_PROTO_H_ */