Put public message interface into msg.h and moved internal functions to msg.int.h.
diff --git a/BUILD b/BUILD
index 50d171d..0365b4a 100644
--- a/BUILD
+++ b/BUILD
@@ -59,7 +59,7 @@
         "upb/decode.int.h",
         "upb/encode.c",
         "upb/msg.c",
-        "upb/msg.h",
+        "upb/msg.int.h",
         "upb/table.c",
         "upb/table.int.h",
         "upb/upb.c",
@@ -68,6 +68,7 @@
     hdrs = [
         "upb/decode.h",
         "upb/encode.h",
+        "upb/msg.h",
         "upb/upb.h",
         "upb/upb.hpp",
     ],
@@ -87,6 +88,7 @@
         "upb/decode_fast.c",
         "upb/decode_fast.h",
         "upb/msg.h",
+        "upb/msg.int.h",
         "upb/upb.int.h",
     ],
     copts = UPB_DEFAULT_COPTS,
diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt
index 375328d..807eaba 100644
--- a/cmake/CMakeLists.txt
+++ b/cmake/CMakeLists.txt
@@ -67,13 +67,14 @@
   ../upb/decode.int.h
   ../upb/encode.c
   ../upb/msg.c
-  ../upb/msg.h
+  ../upb/msg.int.h
   ../upb/table.c
   ../upb/table.int.h
   ../upb/upb.c
   ../upb/upb.int.h
   ../upb/decode.h
   ../upb/encode.h
+  ../upb/msg.h
   ../upb/upb.h
   ../upb/upb.hpp)
 target_link_libraries(upb
@@ -85,6 +86,7 @@
   ../upb/decode_fast.c
   ../upb/decode_fast.h
   ../upb/msg.h
+  ../upb/msg.int.h
   ../upb/upb.int.h)
 target_link_libraries(fastdecode
   port
diff --git a/cmake/google/protobuf/descriptor.upb.c b/cmake/google/protobuf/descriptor.upb.c
index 339fafa..838aa1b 100644
--- a/cmake/google/protobuf/descriptor.upb.c
+++ b/cmake/google/protobuf/descriptor.upb.c
@@ -7,7 +7,7 @@
  * regenerated. */
 
 #include <stddef.h>
-#include "upb/msg.h"
+#include "upb/msg.int.h"
 #include "google/protobuf/descriptor.upb.h"
 
 #include "upb/port_def.inc"
diff --git a/cmake/google/protobuf/descriptor.upb.h b/cmake/google/protobuf/descriptor.upb.h
index a8ed71a..de86e42 100644
--- a/cmake/google/protobuf/descriptor.upb.h
+++ b/cmake/google/protobuf/descriptor.upb.h
@@ -9,7 +9,7 @@
 #ifndef GOOGLE_PROTOBUF_DESCRIPTOR_PROTO_UPB_H_
 #define GOOGLE_PROTOBUF_DESCRIPTOR_PROTO_UPB_H_
 
-#include "upb/msg.h"
+#include "upb/msg.int.h"
 #include "upb/decode.h"
 #include "upb/decode_fast.h"
 #include "upb/encode.h"
diff --git a/upb/decode.int.h b/upb/decode.int.h
index 7376e21..41313b7 100644
--- a/upb/decode.int.h
+++ b/upb/decode.int.h
@@ -8,7 +8,7 @@
 
 #include <setjmp.h>
 
-#include "upb/msg.h"
+#include "upb/msg.int.h"
 #include "upb/upb.int.h"
 
 /* Must be last. */
diff --git a/upb/encode.c b/upb/encode.c
index 51859b8..4dd6c09 100644
--- a/upb/encode.c
+++ b/upb/encode.c
@@ -5,7 +5,7 @@
 #include <setjmp.h>
 #include <string.h>
 
-#include "upb/msg.h"
+#include "upb/msg.int.h"
 #include "upb/upb.h"
 
 /* Must be last. */
diff --git a/upb/msg.c b/upb/msg.c
index b87cea7..b281ca6 100644
--- a/upb/msg.c
+++ b/upb/msg.c
@@ -1,9 +1,9 @@
 
 #include "upb/msg.h"
 
-#include "upb/table.int.h"
-
+#include "upb/msg.int.h"
 #include "upb/port_def.inc"
+#include "upb/table.int.h"
 
 /** upb_msg *******************************************************************/
 
diff --git a/upb/msg.h b/upb/msg.h
index c4137fd..5e88b65 100644
--- a/upb/msg.h
+++ b/upb/msg.h
@@ -1,611 +1,39 @@
 /*
-** Our memory representation for parsing tables and messages themselves.
-** Functions in this file are used by generated code and possibly reflection.
+** Public APIs for message operations that do not require descriptors.
+** These functions can be used even in build that does not want to depend on
+** reflection or descriptors.
 **
-** The definitions in this file are internal to upb.
-**/
+** Descriptor-based reflection functionality lives in reflection.h.
+*/
 
 #ifndef UPB_MSG_H_
 #define UPB_MSG_H_
 
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
+#include <stddef.h>
 
-#include "upb/table.int.h"
 #include "upb/upb.h"
 
-/* Must be last. */
-#include "upb/port_def.inc"
-
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-#define PTR_AT(msg, ofs, type) (type*)((const char*)msg + ofs)
-
 typedef void upb_msg;
 
-/** upb_msglayout *************************************************************/
-
-/* upb_msglayout represents the memory layout of a given upb_msgdef.  The
- * members are public so generated code can initialize them, but users MUST NOT
- * read or write any of its members. */
-
-/* These aren't real labels according to descriptor.proto, but in the table we
- * use these for map/packed fields instead of UPB_LABEL_REPEATED. */
-enum {
-  _UPB_LABEL_MAP = 4,
-  _UPB_LABEL_PACKED = 7  /* Low 3 bits are common with UPB_LABEL_REPEATED. */
-};
-
-typedef struct {
-  uint32_t number;
-  uint16_t offset;
-  int16_t presence;       /* If >0, hasbit_index.  If <0, ~oneof_index. */
-  uint16_t submsg_index;  /* undefined if descriptortype != MESSAGE or GROUP. */
-  uint8_t descriptortype;
-  uint8_t label;          /* google.protobuf.Label or _UPB_LABEL_* above. */
-} upb_msglayout_field;
-
-struct upb_decstate;
+/* For users these are opaque. They can be obtained from upb_msgdef_layout()
+ * but users cannot access any of the members. */
 struct upb_msglayout;
-
-typedef const char *_upb_field_parser(struct upb_decstate *d, const char *ptr,
-                                      upb_msg *msg, intptr_t table,
-                                      uint64_t hasbits, uint64_t data);
-
-typedef struct {
-  uint64_t field_data;
-  _upb_field_parser *field_parser;
-} _upb_fasttable_entry;
-
-typedef struct upb_msglayout {
-  const struct upb_msglayout *const* submsgs;
-  const upb_msglayout_field *fields;
-  /* Must be aligned to sizeof(void*).  Doesn't include internal members like
-   * unknown fields, extension dict, pointer to msglayout, etc. */
-  uint16_t size;
-  uint16_t field_count;
-  bool extendable;
-  uint8_t table_mask;
-  /* To constant-initialize the tables of variable length, we need a flexible
-   * array member, and we need to compile in C99 mode. */
-  _upb_fasttable_entry fasttable[];
-} upb_msglayout;
-
-/** upb_msg *******************************************************************/
-
-/* Internal members of a upb_msg.  We can change this without breaking binary
- * compatibility.  We put these before the user's data.  The user's upb_msg*
- * points after the upb_msg_internal. */
-
-typedef struct {
-  uint32_t len;
-  uint32_t size;
-  /* Data follows. */
-} upb_msg_unknowndata;
-
-/* Used when a message is not extendable. */
-typedef struct {
-  upb_msg_unknowndata *unknown;
-} upb_msg_internal;
-
-/* Maps upb_fieldtype_t -> memory size. */
-extern char _upb_fieldtype_to_size[12];
-
-UPB_INLINE size_t upb_msg_sizeof(const upb_msglayout *l) {
-  return l->size + sizeof(upb_msg_internal);
-}
-
-UPB_INLINE upb_msg *_upb_msg_new_inl(const upb_msglayout *l, upb_arena *a) {
-  size_t size = upb_msg_sizeof(l);
-  void *mem = upb_arena_malloc(a, size);
-  upb_msg *msg;
-  if (UPB_UNLIKELY(!mem)) return NULL;
-  msg = UPB_PTR_AT(mem, sizeof(upb_msg_internal), upb_msg);
-  memset(mem, 0, size);
-  return msg;
-}
-
-/* Creates a new messages with the given layout on the given arena. */
-upb_msg *_upb_msg_new(const upb_msglayout *l, upb_arena *a);
-
-UPB_INLINE upb_msg_internal *upb_msg_getinternal(upb_msg *msg) {
-  ptrdiff_t size = sizeof(upb_msg_internal);
-  return (upb_msg_internal*)((char*)msg - size);
-}
-
-/* Clears the given message. */
-void _upb_msg_clear(upb_msg *msg, const upb_msglayout *l);
-
-/* Discards the unknown fields for this message only. */
-void _upb_msg_discardunknown_shallow(upb_msg *msg);
+typedef struct upb_msglayout upb_msglayout;
 
 /* Adds unknown data (serialized protobuf data) to the given message.  The data
  * is copied into the message instance. */
-bool _upb_msg_addunknown(upb_msg *msg, const char *data, size_t len,
-                         upb_arena *arena);
+void upb_msg_addunknown(upb_msg *msg, const char *data, size_t len,
+                        upb_arena *arena);
 
 /* Returns a reference to the message's unknown data. */
 const char *upb_msg_getunknown(const upb_msg *msg, size_t *len);
 
-/** Hasbit access *************************************************************/
-
-UPB_INLINE bool _upb_hasbit(const upb_msg *msg, size_t idx) {
-  return (*PTR_AT(msg, idx / 8, const char) & (1 << (idx % 8))) != 0;
-}
-
-UPB_INLINE void _upb_sethas(const upb_msg *msg, size_t idx) {
-  (*PTR_AT(msg, idx / 8, char)) |= (char)(1 << (idx % 8));
-}
-
-UPB_INLINE void _upb_clearhas(const upb_msg *msg, size_t idx) {
-  (*PTR_AT(msg, idx / 8, char)) &= (char)(~(1 << (idx % 8)));
-}
-
-UPB_INLINE size_t _upb_msg_hasidx(const upb_msglayout_field *f) {
-  UPB_ASSERT(f->presence > 0);
-  return f->presence;
-}
-
-UPB_INLINE bool _upb_hasbit_field(const upb_msg *msg,
-                                  const upb_msglayout_field *f) {
-  return _upb_hasbit(msg, _upb_msg_hasidx(f));
-}
-
-UPB_INLINE void _upb_sethas_field(const upb_msg *msg,
-                                  const upb_msglayout_field *f) {
-  _upb_sethas(msg, _upb_msg_hasidx(f));
-}
-
-UPB_INLINE void _upb_clearhas_field(const upb_msg *msg,
-                                    const upb_msglayout_field *f) {
-  _upb_clearhas(msg, _upb_msg_hasidx(f));
-}
-
-/** Oneof case access *********************************************************/
-
-UPB_INLINE uint32_t *_upb_oneofcase(upb_msg *msg, size_t case_ofs) {
-  return PTR_AT(msg, case_ofs, uint32_t);
-}
-
-UPB_INLINE uint32_t _upb_getoneofcase(const void *msg, size_t case_ofs) {
-  return *PTR_AT(msg, case_ofs, uint32_t);
-}
-
-UPB_INLINE size_t _upb_oneofcase_ofs(const upb_msglayout_field *f) {
-  UPB_ASSERT(f->presence < 0);
-  return ~(ptrdiff_t)f->presence;
-}
-
-UPB_INLINE uint32_t *_upb_oneofcase_field(upb_msg *msg,
-                                          const upb_msglayout_field *f) {
-  return _upb_oneofcase(msg, _upb_oneofcase_ofs(f));
-}
-
-UPB_INLINE uint32_t _upb_getoneofcase_field(const upb_msg *msg,
-                                            const upb_msglayout_field *f) {
-  return _upb_getoneofcase(msg, _upb_oneofcase_ofs(f));
-}
-
-UPB_INLINE bool _upb_has_submsg_nohasbit(const upb_msg *msg, size_t ofs) {
-  return *PTR_AT(msg, ofs, const upb_msg*) != NULL;
-}
-
-UPB_INLINE bool _upb_isrepeated(const upb_msglayout_field *field) {
-  return (field->label & 3) == UPB_LABEL_REPEATED;
-}
-
-UPB_INLINE bool _upb_repeated_or_map(const upb_msglayout_field *field) {
-  return field->label >= UPB_LABEL_REPEATED;
-}
-
-/** upb_array *****************************************************************/
-
-/* Our internal representation for repeated fields.  */
-typedef struct {
-  uintptr_t data;   /* Tagged ptr: low 3 bits of ptr are lg2(elem size). */
-  size_t len;   /* Measured in elements. */
-  size_t size;  /* Measured in elements. */
-  uint64_t junk;
-} upb_array;
-
-UPB_INLINE const void *_upb_array_constptr(const upb_array *arr) {
-  UPB_ASSERT((arr->data & 7) <= 4);
-  return (void*)(arr->data & ~(uintptr_t)7);
-}
-
-UPB_INLINE uintptr_t _upb_array_tagptr(void* ptr, int elem_size_lg2) {
-  UPB_ASSERT(elem_size_lg2 <= 4);
-  return (uintptr_t)ptr | elem_size_lg2;
-}
-
-UPB_INLINE void *_upb_array_ptr(upb_array *arr) {
-  return (void*)_upb_array_constptr(arr);
-}
-
-UPB_INLINE uintptr_t _upb_tag_arrptr(void* ptr, int elem_size_lg2) {
-  UPB_ASSERT(elem_size_lg2 <= 4);
-  UPB_ASSERT(((uintptr_t)ptr & 7) == 0);
-  return (uintptr_t)ptr | (unsigned)elem_size_lg2;
-}
-
-UPB_INLINE upb_array *_upb_array_new(upb_arena *a, size_t init_size,
-                                     int elem_size_lg2) {
-  const size_t arr_size = UPB_ALIGN_UP(sizeof(upb_array), 8);
-  const size_t bytes = sizeof(upb_array) + (init_size << elem_size_lg2);
-  upb_array *arr = (upb_array*)upb_arena_malloc(a, bytes);
-  if (!arr) return NULL;
-  arr->data = _upb_tag_arrptr(UPB_PTR_AT(arr, arr_size, void), elem_size_lg2);
-  arr->len = 0;
-  arr->size = init_size;
-  return arr;
-}
-
-/* Resizes the capacity of the array to be at least min_size. */
-bool _upb_array_realloc(upb_array *arr, size_t min_size, upb_arena *arena);
-
-/* Fallback functions for when the accessors require a resize. */
-void *_upb_array_resize_fallback(upb_array **arr_ptr, size_t size,
-                                 int elem_size_lg2, upb_arena *arena);
-bool _upb_array_append_fallback(upb_array **arr_ptr, const void *value,
-                                int elem_size_lg2, upb_arena *arena);
-
-UPB_INLINE bool _upb_array_reserve(upb_array *arr, size_t size,
-                                   upb_arena *arena) {
-  if (arr->size < size) return _upb_array_realloc(arr, size, arena);
-  return true;
-}
-
-UPB_INLINE bool _upb_array_resize(upb_array *arr, size_t size,
-                                  upb_arena *arena) {
-  if (!_upb_array_reserve(arr, size, arena)) return false;
-  arr->len = size;
-  return true;
-}
-
-UPB_INLINE const void *_upb_array_accessor(const void *msg, size_t ofs,
-                                           size_t *size) {
-  const upb_array *arr = *PTR_AT(msg, ofs, const upb_array*);
-  if (arr) {
-    if (size) *size = arr->len;
-    return _upb_array_constptr(arr);
-  } else {
-    if (size) *size = 0;
-    return NULL;
-  }
-}
-
-UPB_INLINE void *_upb_array_mutable_accessor(void *msg, size_t ofs,
-                                             size_t *size) {
-  upb_array *arr = *PTR_AT(msg, ofs, upb_array*);
-  if (arr) {
-    if (size) *size = arr->len;
-    return _upb_array_ptr(arr);
-  } else {
-    if (size) *size = 0;
-    return NULL;
-  }
-}
-
-UPB_INLINE void *_upb_array_resize_accessor2(void *msg, size_t ofs, size_t size,
-                                             int elem_size_lg2,
-                                             upb_arena *arena) {
-  upb_array **arr_ptr = PTR_AT(msg, ofs, upb_array *);
-  upb_array *arr = *arr_ptr;
-  if (!arr || arr->size < size) {
-    return _upb_array_resize_fallback(arr_ptr, size, elem_size_lg2, arena);
-  }
-  arr->len = size;
-  return _upb_array_ptr(arr);
-}
-
-UPB_INLINE bool _upb_array_append_accessor2(void *msg, size_t ofs,
-                                            int elem_size_lg2,
-                                            const void *value,
-                                            upb_arena *arena) {
-  upb_array **arr_ptr = PTR_AT(msg, ofs, upb_array *);
-  size_t elem_size = 1 << elem_size_lg2;
-  upb_array *arr = *arr_ptr;
-  void *ptr;
-  if (!arr || arr->len == arr->size) {
-    return _upb_array_append_fallback(arr_ptr, value, elem_size_lg2, arena);
-  }
-  ptr = _upb_array_ptr(arr);
-  memcpy(PTR_AT(ptr, arr->len * elem_size, char), value, elem_size);
-  arr->len++;
-  return true;
-}
-
-/* Used by old generated code, remove once all code has been regenerated. */
-UPB_INLINE int _upb_sizelg2(upb_fieldtype_t type) {
-  switch (type) {
-    case UPB_TYPE_BOOL:
-      return 0;
-    case UPB_TYPE_FLOAT:
-    case UPB_TYPE_INT32:
-    case UPB_TYPE_UINT32:
-    case UPB_TYPE_ENUM:
-      return 2;
-    case UPB_TYPE_MESSAGE:
-      return UPB_SIZE(2, 3);
-    case UPB_TYPE_DOUBLE:
-    case UPB_TYPE_INT64:
-    case UPB_TYPE_UINT64:
-      return 3;
-    case UPB_TYPE_STRING:
-    case UPB_TYPE_BYTES:
-      return UPB_SIZE(3, 4);
-  }
-  UPB_UNREACHABLE();
-}
-UPB_INLINE void *_upb_array_resize_accessor(void *msg, size_t ofs, size_t size,
-                                             upb_fieldtype_t type,
-                                             upb_arena *arena) {
-  return _upb_array_resize_accessor2(msg, ofs, size, _upb_sizelg2(type), arena);
-}
-UPB_INLINE bool _upb_array_append_accessor(void *msg, size_t ofs,
-                                            size_t elem_size, upb_fieldtype_t type,
-                                            const void *value,
-                                            upb_arena *arena) {
-  (void)elem_size;
-  return _upb_array_append_accessor2(msg, ofs, _upb_sizelg2(type), value,
-                                     arena);
-}
-
-/** upb_map *******************************************************************/
-
-/* Right now we use strmaps for everything.  We'll likely want to use
- * integer-specific maps for integer-keyed maps.*/
-typedef struct {
-  /* Size of key and val, based on the map type.  Strings are represented as '0'
-   * because they must be handled specially. */
-  char key_size;
-  char val_size;
-
-  upb_strtable table;
-} upb_map;
-
-/* Map entries aren't actually stored, they are only used during parsing.  For
- * parsing, it helps a lot if all map entry messages have the same layout.
- * The compiler and def.c must ensure that all map entries have this layout. */
-typedef struct {
-  upb_msg_internal internal;
-  union {
-    upb_strview str;  /* For str/bytes. */
-    upb_value val;    /* For all other types. */
-  } k;
-  union {
-    upb_strview str;  /* For str/bytes. */
-    upb_value val;    /* For all other types. */
-  } v;
-} upb_map_entry;
-
-/* Creates a new map on the given arena with this key/value type. */
-upb_map *_upb_map_new(upb_arena *a, size_t key_size, size_t value_size);
-
-/* Converting between internal table representation and user values.
- *
- * _upb_map_tokey() and _upb_map_fromkey() are inverses.
- * _upb_map_tovalue() and _upb_map_fromvalue() are inverses.
- *
- * These functions account for the fact that strings are treated differently
- * from other types when stored in a map.
- */
-
-UPB_INLINE upb_strview _upb_map_tokey(const void *key, size_t size) {
-  if (size == UPB_MAPTYPE_STRING) {
-    return *(upb_strview*)key;
-  } else {
-    return upb_strview_make((const char*)key, size);
-  }
-}
-
-UPB_INLINE void _upb_map_fromkey(upb_strview key, void* out, size_t size) {
-  if (size == UPB_MAPTYPE_STRING) {
-    memcpy(out, &key, sizeof(key));
-  } else {
-    memcpy(out, key.data, size);
-  }
-}
-
-UPB_INLINE bool _upb_map_tovalue(const void *val, size_t size, upb_value *msgval,
-                                 upb_arena *a) {
-  if (size == UPB_MAPTYPE_STRING) {
-    upb_strview *strp = (upb_strview*)upb_arena_malloc(a, sizeof(*strp));
-    if (!strp) return false;
-    *strp = *(upb_strview*)val;
-    *msgval = upb_value_ptr(strp);
-  } else {
-    memcpy(msgval, val, size);
-  }
-  return true;
-}
-
-UPB_INLINE void _upb_map_fromvalue(upb_value val, void* out, size_t size) {
-  if (size == UPB_MAPTYPE_STRING) {
-    const upb_strview *strp = (const upb_strview*)upb_value_getptr(val);
-    memcpy(out, strp, sizeof(upb_strview));
-  } else {
-    memcpy(out, &val, size);
-  }
-}
-
-/* Map operations, shared by reflection and generated code. */
-
-UPB_INLINE size_t _upb_map_size(const upb_map *map) {
-  return map->table.t.count;
-}
-
-UPB_INLINE bool _upb_map_get(const upb_map *map, const void *key,
-                             size_t key_size, void *val, size_t val_size) {
-  upb_value tabval;
-  upb_strview k = _upb_map_tokey(key, key_size);
-  bool ret = upb_strtable_lookup2(&map->table, k.data, k.size, &tabval);
-  if (ret && val) {
-    _upb_map_fromvalue(tabval, val, val_size);
-  }
-  return ret;
-}
-
-UPB_INLINE void* _upb_map_next(const upb_map *map, size_t *iter) {
-  upb_strtable_iter it;
-  it.t = &map->table;
-  it.index = *iter;
-  upb_strtable_next(&it);
-  *iter = it.index;
-  if (upb_strtable_done(&it)) return NULL;
-  return (void*)str_tabent(&it);
-}
-
-UPB_INLINE bool _upb_map_set(upb_map *map, const void *key, size_t key_size,
-                             void *val, size_t val_size, upb_arena *a) {
-  upb_strview strkey = _upb_map_tokey(key, key_size);
-  upb_value tabval = {0};
-  if (!_upb_map_tovalue(val, val_size, &tabval, a)) return false;
-
-  /* TODO(haberman): add overwrite operation to minimize number of lookups. */
-  upb_strtable_remove(&map->table, strkey.data, strkey.size, NULL);
-  return upb_strtable_insert(&map->table, strkey.data, strkey.size, tabval, a);
-}
-
-UPB_INLINE bool _upb_map_delete(upb_map *map, const void *key, size_t key_size) {
-  upb_strview k = _upb_map_tokey(key, key_size);
-  return upb_strtable_remove(&map->table, k.data, k.size, NULL);
-}
-
-UPB_INLINE void _upb_map_clear(upb_map *map) {
-  upb_strtable_clear(&map->table);
-}
-
-/* Message map operations, these get the map from the message first. */
-
-UPB_INLINE size_t _upb_msg_map_size(const upb_msg *msg, size_t ofs) {
-  upb_map *map = *UPB_PTR_AT(msg, ofs, upb_map *);
-  return map ? _upb_map_size(map) : 0;
-}
-
-UPB_INLINE bool _upb_msg_map_get(const upb_msg *msg, size_t ofs,
-                                 const void *key, size_t key_size, void *val,
-                                 size_t val_size) {
-  upb_map *map = *UPB_PTR_AT(msg, ofs, upb_map *);
-  if (!map) return false;
-  return _upb_map_get(map, key, key_size, val, val_size);
-}
-
-UPB_INLINE void *_upb_msg_map_next(const upb_msg *msg, size_t ofs,
-                                   size_t *iter) {
-  upb_map *map = *UPB_PTR_AT(msg, ofs, upb_map *);
-  if (!map) return NULL;
-  return _upb_map_next(map, iter);
-}
-
-UPB_INLINE bool _upb_msg_map_set(upb_msg *msg, size_t ofs, const void *key,
-                                 size_t key_size, void *val, size_t val_size,
-                                 upb_arena *arena) {
-  upb_map **map = PTR_AT(msg, ofs, upb_map *);
-  if (!*map) {
-    *map = _upb_map_new(arena, key_size, val_size);
-  }
-  return _upb_map_set(*map, key, key_size, val, val_size, arena);
-}
-
-UPB_INLINE bool _upb_msg_map_delete(upb_msg *msg, size_t ofs, const void *key,
-                                    size_t key_size) {
-  upb_map *map = *UPB_PTR_AT(msg, ofs, upb_map *);
-  if (!map) return false;
-  return _upb_map_delete(map, key, key_size);
-}
-
-UPB_INLINE void _upb_msg_map_clear(upb_msg *msg, size_t ofs) {
-  upb_map *map = *UPB_PTR_AT(msg, ofs, upb_map *);
-  if (!map) return;
-  _upb_map_clear(map);
-}
-
-/* Accessing map key/value from a pointer, used by generated code only. */
-
-UPB_INLINE void _upb_msg_map_key(const void* msg, void* key, size_t size) {
-  const upb_tabent *ent = (const upb_tabent*)msg;
-  uint32_t u32len;
-  upb_strview k;
-  k.data = upb_tabstr(ent->key, &u32len);
-  k.size = u32len;
-  _upb_map_fromkey(k, key, size);
-}
-
-UPB_INLINE void _upb_msg_map_value(const void* msg, void* val, size_t size) {
-  const upb_tabent *ent = (const upb_tabent*)msg;
-  upb_value v = {ent->val.val};
-  _upb_map_fromvalue(v, val, size);
-}
-
-UPB_INLINE void _upb_msg_map_set_value(void* msg, const void* val, size_t size) {
-  upb_tabent *ent = (upb_tabent*)msg;
-  /* This is like _upb_map_tovalue() except the entry already exists so we can
-   * reuse the allocated upb_strview for string fields. */
-  if (size == UPB_MAPTYPE_STRING) {
-    upb_strview *strp = (upb_strview*)(uintptr_t)ent->val.val;
-    memcpy(strp, val, sizeof(*strp));
-  } else {
-    memcpy(&ent->val.val, val, size);
-  }
-}
-
-/** _upb_mapsorter *************************************************************/
-
-/* _upb_mapsorter sorts maps and provides ordered iteration over the entries.
- * Since maps can be recursive (map values can be messages which contain other maps).
- * _upb_mapsorter can contain a stack of maps. */
-
-typedef struct {
-  upb_tabent const**entries;
-  int size;
-  int cap;
-} _upb_mapsorter;
-
-typedef struct {
-  int start;
-  int pos;
-  int end;
-} _upb_sortedmap;
-
-UPB_INLINE void _upb_mapsorter_init(_upb_mapsorter *s) {
-  s->entries = NULL;
-  s->size = 0;
-  s->cap = 0;
-}
-
-UPB_INLINE void _upb_mapsorter_destroy(_upb_mapsorter *s) {
-  if (s->entries) free(s->entries);
-}
-
-bool _upb_mapsorter_pushmap(_upb_mapsorter *s, upb_descriptortype_t key_type,
-                            const upb_map *map, _upb_sortedmap *sorted);
-
-UPB_INLINE void _upb_mapsorter_popmap(_upb_mapsorter *s, _upb_sortedmap *sorted) {
-  s->size = sorted->start;
-}
-
-UPB_INLINE bool _upb_sortedmap_next(_upb_mapsorter *s, const upb_map *map,
-                                    _upb_sortedmap *sorted,
-                                    upb_map_entry *ent) {
-  if (sorted->pos == sorted->end) return false;
-  const upb_tabent *tabent = s->entries[sorted->pos++];
-  upb_strview key = upb_tabstrview(tabent->key);
-  _upb_map_fromkey(key, &ent->k, map->key_size);
-  upb_value val = {tabent->val.val};
-  _upb_map_fromvalue(val, &ent->v, map->val_size);
-  return true;
-}
-
-#undef PTR_AT
-
 #ifdef __cplusplus
 }  /* extern "C" */
 #endif
 
-#include "upb/port_undef.inc"
-
-#endif /* UPB_MSG_H_ */
+#endif /* UPB_MSG_INT_H_ */
diff --git a/upb/msg.int.h b/upb/msg.int.h
new file mode 100644
index 0000000..6674032
--- /dev/null
+++ b/upb/msg.int.h
@@ -0,0 +1,603 @@
+/*
+** Our memory representation for parsing tables and messages themselves.
+** Functions in this file are used by generated code and possibly reflection.
+**
+** The definitions in this file are internal to upb.
+**/
+
+#ifndef UPB_MSG_INT_H_
+#define UPB_MSG_INT_H_
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "upb/msg.h"
+#include "upb/table.int.h"
+#include "upb/upb.h"
+
+/* Must be last. */
+#include "upb/port_def.inc"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** upb_msglayout *************************************************************/
+
+/* upb_msglayout represents the memory layout of a given upb_msgdef.  The
+ * members are public so generated code can initialize them, but users MUST NOT
+ * read or write any of its members. */
+
+/* These aren't real labels according to descriptor.proto, but in the table we
+ * use these for map/packed fields instead of UPB_LABEL_REPEATED. */
+enum {
+  _UPB_LABEL_MAP = 4,
+  _UPB_LABEL_PACKED = 7  /* Low 3 bits are common with UPB_LABEL_REPEATED. */
+};
+
+typedef struct {
+  uint32_t number;
+  uint16_t offset;
+  int16_t presence;       /* If >0, hasbit_index.  If <0, ~oneof_index. */
+  uint16_t submsg_index;  /* undefined if descriptortype != MESSAGE or GROUP. */
+  uint8_t descriptortype;
+  uint8_t label;          /* google.protobuf.Label or _UPB_LABEL_* above. */
+} upb_msglayout_field;
+
+struct upb_decstate;
+struct upb_msglayout;
+
+typedef const char *_upb_field_parser(struct upb_decstate *d, const char *ptr,
+                                      upb_msg *msg, intptr_t table,
+                                      uint64_t hasbits, uint64_t data);
+
+typedef struct {
+  uint64_t field_data;
+  _upb_field_parser *field_parser;
+} _upb_fasttable_entry;
+
+struct upb_msglayout {
+  const struct upb_msglayout *const* submsgs;
+  const upb_msglayout_field *fields;
+  /* Must be aligned to sizeof(void*).  Doesn't include internal members like
+   * unknown fields, extension dict, pointer to msglayout, etc. */
+  uint16_t size;
+  uint16_t field_count;
+  bool extendable;
+  uint8_t table_mask;
+  /* To constant-initialize the tables of variable length, we need a flexible
+   * array member, and we need to compile in C99 mode. */
+  _upb_fasttable_entry fasttable[];
+};
+
+/** upb_msg *******************************************************************/
+
+/* Internal members of a upb_msg.  We can change this without breaking binary
+ * compatibility.  We put these before the user's data.  The user's upb_msg*
+ * points after the upb_msg_internal. */
+
+typedef struct {
+  uint32_t len;
+  uint32_t size;
+  /* Data follows. */
+} upb_msg_unknowndata;
+
+/* Used when a message is not extendable. */
+typedef struct {
+  upb_msg_unknowndata *unknown;
+} upb_msg_internal;
+
+/* Maps upb_fieldtype_t -> memory size. */
+extern char _upb_fieldtype_to_size[12];
+
+UPB_INLINE size_t upb_msg_sizeof(const upb_msglayout *l) {
+  return l->size + sizeof(upb_msg_internal);
+}
+
+UPB_INLINE upb_msg *_upb_msg_new_inl(const upb_msglayout *l, upb_arena *a) {
+  size_t size = upb_msg_sizeof(l);
+  void *mem = upb_arena_malloc(a, size);
+  upb_msg *msg;
+  if (UPB_UNLIKELY(!mem)) return NULL;
+  msg = UPB_PTR_AT(mem, sizeof(upb_msg_internal), upb_msg);
+  memset(mem, 0, size);
+  return msg;
+}
+
+/* Creates a new messages with the given layout on the given arena. */
+upb_msg *_upb_msg_new(const upb_msglayout *l, upb_arena *a);
+
+UPB_INLINE upb_msg_internal *upb_msg_getinternal(upb_msg *msg) {
+  ptrdiff_t size = sizeof(upb_msg_internal);
+  return (upb_msg_internal*)((char*)msg - size);
+}
+
+/* Clears the given message. */
+void _upb_msg_clear(upb_msg *msg, const upb_msglayout *l);
+
+/* Discards the unknown fields for this message only. */
+void _upb_msg_discardunknown_shallow(upb_msg *msg);
+
+/* Adds unknown data (serialized protobuf data) to the given message.  The data
+ * is copied into the message instance. */
+bool _upb_msg_addunknown(upb_msg *msg, const char *data, size_t len,
+                         upb_arena *arena);
+
+/** Hasbit access *************************************************************/
+
+UPB_INLINE bool _upb_hasbit(const upb_msg *msg, size_t idx) {
+  return (*UPB_PTR_AT(msg, idx / 8, const char) & (1 << (idx % 8))) != 0;
+}
+
+UPB_INLINE void _upb_sethas(const upb_msg *msg, size_t idx) {
+  (*UPB_PTR_AT(msg, idx / 8, char)) |= (char)(1 << (idx % 8));
+}
+
+UPB_INLINE void _upb_clearhas(const upb_msg *msg, size_t idx) {
+  (*UPB_PTR_AT(msg, idx / 8, char)) &= (char)(~(1 << (idx % 8)));
+}
+
+UPB_INLINE size_t _upb_msg_hasidx(const upb_msglayout_field *f) {
+  UPB_ASSERT(f->presence > 0);
+  return f->presence;
+}
+
+UPB_INLINE bool _upb_hasbit_field(const upb_msg *msg,
+                                  const upb_msglayout_field *f) {
+  return _upb_hasbit(msg, _upb_msg_hasidx(f));
+}
+
+UPB_INLINE void _upb_sethas_field(const upb_msg *msg,
+                                  const upb_msglayout_field *f) {
+  _upb_sethas(msg, _upb_msg_hasidx(f));
+}
+
+UPB_INLINE void _upb_clearhas_field(const upb_msg *msg,
+                                    const upb_msglayout_field *f) {
+  _upb_clearhas(msg, _upb_msg_hasidx(f));
+}
+
+/** Oneof case access *********************************************************/
+
+UPB_INLINE uint32_t *_upb_oneofcase(upb_msg *msg, size_t case_ofs) {
+  return UPB_PTR_AT(msg, case_ofs, uint32_t);
+}
+
+UPB_INLINE uint32_t _upb_getoneofcase(const void *msg, size_t case_ofs) {
+  return *UPB_PTR_AT(msg, case_ofs, uint32_t);
+}
+
+UPB_INLINE size_t _upb_oneofcase_ofs(const upb_msglayout_field *f) {
+  UPB_ASSERT(f->presence < 0);
+  return ~(ptrdiff_t)f->presence;
+}
+
+UPB_INLINE uint32_t *_upb_oneofcase_field(upb_msg *msg,
+                                          const upb_msglayout_field *f) {
+  return _upb_oneofcase(msg, _upb_oneofcase_ofs(f));
+}
+
+UPB_INLINE uint32_t _upb_getoneofcase_field(const upb_msg *msg,
+                                            const upb_msglayout_field *f) {
+  return _upb_getoneofcase(msg, _upb_oneofcase_ofs(f));
+}
+
+UPB_INLINE bool _upb_has_submsg_nohasbit(const upb_msg *msg, size_t ofs) {
+  return *UPB_PTR_AT(msg, ofs, const upb_msg*) != NULL;
+}
+
+UPB_INLINE bool _upb_isrepeated(const upb_msglayout_field *field) {
+  return (field->label & 3) == UPB_LABEL_REPEATED;
+}
+
+UPB_INLINE bool _upb_repeated_or_map(const upb_msglayout_field *field) {
+  return field->label >= UPB_LABEL_REPEATED;
+}
+
+/** upb_array *****************************************************************/
+
+/* Our internal representation for repeated fields.  */
+typedef struct {
+  uintptr_t data;   /* Tagged ptr: low 3 bits of ptr are lg2(elem size). */
+  size_t len;   /* Measured in elements. */
+  size_t size;  /* Measured in elements. */
+  uint64_t junk;
+} upb_array;
+
+UPB_INLINE const void *_upb_array_constptr(const upb_array *arr) {
+  UPB_ASSERT((arr->data & 7) <= 4);
+  return (void*)(arr->data & ~(uintptr_t)7);
+}
+
+UPB_INLINE uintptr_t _upb_array_tagptr(void* ptr, int elem_size_lg2) {
+  UPB_ASSERT(elem_size_lg2 <= 4);
+  return (uintptr_t)ptr | elem_size_lg2;
+}
+
+UPB_INLINE void *_upb_array_ptr(upb_array *arr) {
+  return (void*)_upb_array_constptr(arr);
+}
+
+UPB_INLINE uintptr_t _upb_tag_arrptr(void* ptr, int elem_size_lg2) {
+  UPB_ASSERT(elem_size_lg2 <= 4);
+  UPB_ASSERT(((uintptr_t)ptr & 7) == 0);
+  return (uintptr_t)ptr | (unsigned)elem_size_lg2;
+}
+
+UPB_INLINE upb_array *_upb_array_new(upb_arena *a, size_t init_size,
+                                     int elem_size_lg2) {
+  const size_t arr_size = UPB_ALIGN_UP(sizeof(upb_array), 8);
+  const size_t bytes = sizeof(upb_array) + (init_size << elem_size_lg2);
+  upb_array *arr = (upb_array*)upb_arena_malloc(a, bytes);
+  if (!arr) return NULL;
+  arr->data = _upb_tag_arrptr(UPB_PTR_AT(arr, arr_size, void), elem_size_lg2);
+  arr->len = 0;
+  arr->size = init_size;
+  return arr;
+}
+
+/* Resizes the capacity of the array to be at least min_size. */
+bool _upb_array_realloc(upb_array *arr, size_t min_size, upb_arena *arena);
+
+/* Fallback functions for when the accessors require a resize. */
+void *_upb_array_resize_fallback(upb_array **arr_ptr, size_t size,
+                                 int elem_size_lg2, upb_arena *arena);
+bool _upb_array_append_fallback(upb_array **arr_ptr, const void *value,
+                                int elem_size_lg2, upb_arena *arena);
+
+UPB_INLINE bool _upb_array_reserve(upb_array *arr, size_t size,
+                                   upb_arena *arena) {
+  if (arr->size < size) return _upb_array_realloc(arr, size, arena);
+  return true;
+}
+
+UPB_INLINE bool _upb_array_resize(upb_array *arr, size_t size,
+                                  upb_arena *arena) {
+  if (!_upb_array_reserve(arr, size, arena)) return false;
+  arr->len = size;
+  return true;
+}
+
+UPB_INLINE const void *_upb_array_accessor(const void *msg, size_t ofs,
+                                           size_t *size) {
+  const upb_array *arr = *UPB_PTR_AT(msg, ofs, const upb_array*);
+  if (arr) {
+    if (size) *size = arr->len;
+    return _upb_array_constptr(arr);
+  } else {
+    if (size) *size = 0;
+    return NULL;
+  }
+}
+
+UPB_INLINE void *_upb_array_mutable_accessor(void *msg, size_t ofs,
+                                             size_t *size) {
+  upb_array *arr = *UPB_PTR_AT(msg, ofs, upb_array*);
+  if (arr) {
+    if (size) *size = arr->len;
+    return _upb_array_ptr(arr);
+  } else {
+    if (size) *size = 0;
+    return NULL;
+  }
+}
+
+UPB_INLINE void *_upb_array_resize_accessor2(void *msg, size_t ofs, size_t size,
+                                             int elem_size_lg2,
+                                             upb_arena *arena) {
+  upb_array **arr_ptr = UPB_PTR_AT(msg, ofs, upb_array *);
+  upb_array *arr = *arr_ptr;
+  if (!arr || arr->size < size) {
+    return _upb_array_resize_fallback(arr_ptr, size, elem_size_lg2, arena);
+  }
+  arr->len = size;
+  return _upb_array_ptr(arr);
+}
+
+UPB_INLINE bool _upb_array_append_accessor2(void *msg, size_t ofs,
+                                            int elem_size_lg2,
+                                            const void *value,
+                                            upb_arena *arena) {
+  upb_array **arr_ptr = UPB_PTR_AT(msg, ofs, upb_array *);
+  size_t elem_size = 1 << elem_size_lg2;
+  upb_array *arr = *arr_ptr;
+  void *ptr;
+  if (!arr || arr->len == arr->size) {
+    return _upb_array_append_fallback(arr_ptr, value, elem_size_lg2, arena);
+  }
+  ptr = _upb_array_ptr(arr);
+  memcpy(UPB_PTR_AT(ptr, arr->len * elem_size, char), value, elem_size);
+  arr->len++;
+  return true;
+}
+
+/* Used by old generated code, remove once all code has been regenerated. */
+UPB_INLINE int _upb_sizelg2(upb_fieldtype_t type) {
+  switch (type) {
+    case UPB_TYPE_BOOL:
+      return 0;
+    case UPB_TYPE_FLOAT:
+    case UPB_TYPE_INT32:
+    case UPB_TYPE_UINT32:
+    case UPB_TYPE_ENUM:
+      return 2;
+    case UPB_TYPE_MESSAGE:
+      return UPB_SIZE(2, 3);
+    case UPB_TYPE_DOUBLE:
+    case UPB_TYPE_INT64:
+    case UPB_TYPE_UINT64:
+      return 3;
+    case UPB_TYPE_STRING:
+    case UPB_TYPE_BYTES:
+      return UPB_SIZE(3, 4);
+  }
+  UPB_UNREACHABLE();
+}
+UPB_INLINE void *_upb_array_resize_accessor(void *msg, size_t ofs, size_t size,
+                                             upb_fieldtype_t type,
+                                             upb_arena *arena) {
+  return _upb_array_resize_accessor2(msg, ofs, size, _upb_sizelg2(type), arena);
+}
+UPB_INLINE bool _upb_array_append_accessor(void *msg, size_t ofs,
+                                            size_t elem_size, upb_fieldtype_t type,
+                                            const void *value,
+                                            upb_arena *arena) {
+  (void)elem_size;
+  return _upb_array_append_accessor2(msg, ofs, _upb_sizelg2(type), value,
+                                     arena);
+}
+
+/** upb_map *******************************************************************/
+
+/* Right now we use strmaps for everything.  We'll likely want to use
+ * integer-specific maps for integer-keyed maps.*/
+typedef struct {
+  /* Size of key and val, based on the map type.  Strings are represented as '0'
+   * because they must be handled specially. */
+  char key_size;
+  char val_size;
+
+  upb_strtable table;
+} upb_map;
+
+/* Map entries aren't actually stored, they are only used during parsing.  For
+ * parsing, it helps a lot if all map entry messages have the same layout.
+ * The compiler and def.c must ensure that all map entries have this layout. */
+typedef struct {
+  upb_msg_internal internal;
+  union {
+    upb_strview str;  /* For str/bytes. */
+    upb_value val;    /* For all other types. */
+  } k;
+  union {
+    upb_strview str;  /* For str/bytes. */
+    upb_value val;    /* For all other types. */
+  } v;
+} upb_map_entry;
+
+/* Creates a new map on the given arena with this key/value type. */
+upb_map *_upb_map_new(upb_arena *a, size_t key_size, size_t value_size);
+
+/* Converting between internal table representation and user values.
+ *
+ * _upb_map_tokey() and _upb_map_fromkey() are inverses.
+ * _upb_map_tovalue() and _upb_map_fromvalue() are inverses.
+ *
+ * These functions account for the fact that strings are treated differently
+ * from other types when stored in a map.
+ */
+
+UPB_INLINE upb_strview _upb_map_tokey(const void *key, size_t size) {
+  if (size == UPB_MAPTYPE_STRING) {
+    return *(upb_strview*)key;
+  } else {
+    return upb_strview_make((const char*)key, size);
+  }
+}
+
+UPB_INLINE void _upb_map_fromkey(upb_strview key, void* out, size_t size) {
+  if (size == UPB_MAPTYPE_STRING) {
+    memcpy(out, &key, sizeof(key));
+  } else {
+    memcpy(out, key.data, size);
+  }
+}
+
+UPB_INLINE bool _upb_map_tovalue(const void *val, size_t size, upb_value *msgval,
+                                 upb_arena *a) {
+  if (size == UPB_MAPTYPE_STRING) {
+    upb_strview *strp = (upb_strview*)upb_arena_malloc(a, sizeof(*strp));
+    if (!strp) return false;
+    *strp = *(upb_strview*)val;
+    *msgval = upb_value_ptr(strp);
+  } else {
+    memcpy(msgval, val, size);
+  }
+  return true;
+}
+
+UPB_INLINE void _upb_map_fromvalue(upb_value val, void* out, size_t size) {
+  if (size == UPB_MAPTYPE_STRING) {
+    const upb_strview *strp = (const upb_strview*)upb_value_getptr(val);
+    memcpy(out, strp, sizeof(upb_strview));
+  } else {
+    memcpy(out, &val, size);
+  }
+}
+
+/* Map operations, shared by reflection and generated code. */
+
+UPB_INLINE size_t _upb_map_size(const upb_map *map) {
+  return map->table.t.count;
+}
+
+UPB_INLINE bool _upb_map_get(const upb_map *map, const void *key,
+                             size_t key_size, void *val, size_t val_size) {
+  upb_value tabval;
+  upb_strview k = _upb_map_tokey(key, key_size);
+  bool ret = upb_strtable_lookup2(&map->table, k.data, k.size, &tabval);
+  if (ret && val) {
+    _upb_map_fromvalue(tabval, val, val_size);
+  }
+  return ret;
+}
+
+UPB_INLINE void* _upb_map_next(const upb_map *map, size_t *iter) {
+  upb_strtable_iter it;
+  it.t = &map->table;
+  it.index = *iter;
+  upb_strtable_next(&it);
+  *iter = it.index;
+  if (upb_strtable_done(&it)) return NULL;
+  return (void*)str_tabent(&it);
+}
+
+UPB_INLINE bool _upb_map_set(upb_map *map, const void *key, size_t key_size,
+                             void *val, size_t val_size, upb_arena *a) {
+  upb_strview strkey = _upb_map_tokey(key, key_size);
+  upb_value tabval = {0};
+  if (!_upb_map_tovalue(val, val_size, &tabval, a)) return false;
+
+  /* TODO(haberman): add overwrite operation to minimize number of lookups. */
+  upb_strtable_remove(&map->table, strkey.data, strkey.size, NULL);
+  return upb_strtable_insert(&map->table, strkey.data, strkey.size, tabval, a);
+}
+
+UPB_INLINE bool _upb_map_delete(upb_map *map, const void *key, size_t key_size) {
+  upb_strview k = _upb_map_tokey(key, key_size);
+  return upb_strtable_remove(&map->table, k.data, k.size, NULL);
+}
+
+UPB_INLINE void _upb_map_clear(upb_map *map) {
+  upb_strtable_clear(&map->table);
+}
+
+/* Message map operations, these get the map from the message first. */
+
+UPB_INLINE size_t _upb_msg_map_size(const upb_msg *msg, size_t ofs) {
+  upb_map *map = *UPB_PTR_AT(msg, ofs, upb_map *);
+  return map ? _upb_map_size(map) : 0;
+}
+
+UPB_INLINE bool _upb_msg_map_get(const upb_msg *msg, size_t ofs,
+                                 const void *key, size_t key_size, void *val,
+                                 size_t val_size) {
+  upb_map *map = *UPB_PTR_AT(msg, ofs, upb_map *);
+  if (!map) return false;
+  return _upb_map_get(map, key, key_size, val, val_size);
+}
+
+UPB_INLINE void *_upb_msg_map_next(const upb_msg *msg, size_t ofs,
+                                   size_t *iter) {
+  upb_map *map = *UPB_PTR_AT(msg, ofs, upb_map *);
+  if (!map) return NULL;
+  return _upb_map_next(map, iter);
+}
+
+UPB_INLINE bool _upb_msg_map_set(upb_msg *msg, size_t ofs, const void *key,
+                                 size_t key_size, void *val, size_t val_size,
+                                 upb_arena *arena) {
+  upb_map **map = UPB_PTR_AT(msg, ofs, upb_map *);
+  if (!*map) {
+    *map = _upb_map_new(arena, key_size, val_size);
+  }
+  return _upb_map_set(*map, key, key_size, val, val_size, arena);
+}
+
+UPB_INLINE bool _upb_msg_map_delete(upb_msg *msg, size_t ofs, const void *key,
+                                    size_t key_size) {
+  upb_map *map = *UPB_PTR_AT(msg, ofs, upb_map *);
+  if (!map) return false;
+  return _upb_map_delete(map, key, key_size);
+}
+
+UPB_INLINE void _upb_msg_map_clear(upb_msg *msg, size_t ofs) {
+  upb_map *map = *UPB_PTR_AT(msg, ofs, upb_map *);
+  if (!map) return;
+  _upb_map_clear(map);
+}
+
+/* Accessing map key/value from a pointer, used by generated code only. */
+
+UPB_INLINE void _upb_msg_map_key(const void* msg, void* key, size_t size) {
+  const upb_tabent *ent = (const upb_tabent*)msg;
+  uint32_t u32len;
+  upb_strview k;
+  k.data = upb_tabstr(ent->key, &u32len);
+  k.size = u32len;
+  _upb_map_fromkey(k, key, size);
+}
+
+UPB_INLINE void _upb_msg_map_value(const void* msg, void* val, size_t size) {
+  const upb_tabent *ent = (const upb_tabent*)msg;
+  upb_value v = {ent->val.val};
+  _upb_map_fromvalue(v, val, size);
+}
+
+UPB_INLINE void _upb_msg_map_set_value(void* msg, const void* val, size_t size) {
+  upb_tabent *ent = (upb_tabent*)msg;
+  /* This is like _upb_map_tovalue() except the entry already exists so we can
+   * reuse the allocated upb_strview for string fields. */
+  if (size == UPB_MAPTYPE_STRING) {
+    upb_strview *strp = (upb_strview*)(uintptr_t)ent->val.val;
+    memcpy(strp, val, sizeof(*strp));
+  } else {
+    memcpy(&ent->val.val, val, size);
+  }
+}
+
+/** _upb_mapsorter *************************************************************/
+
+/* _upb_mapsorter sorts maps and provides ordered iteration over the entries.
+ * Since maps can be recursive (map values can be messages which contain other maps).
+ * _upb_mapsorter can contain a stack of maps. */
+
+typedef struct {
+  upb_tabent const**entries;
+  int size;
+  int cap;
+} _upb_mapsorter;
+
+typedef struct {
+  int start;
+  int pos;
+  int end;
+} _upb_sortedmap;
+
+UPB_INLINE void _upb_mapsorter_init(_upb_mapsorter *s) {
+  s->entries = NULL;
+  s->size = 0;
+  s->cap = 0;
+}
+
+UPB_INLINE void _upb_mapsorter_destroy(_upb_mapsorter *s) {
+  if (s->entries) free(s->entries);
+}
+
+bool _upb_mapsorter_pushmap(_upb_mapsorter *s, upb_descriptortype_t key_type,
+                            const upb_map *map, _upb_sortedmap *sorted);
+
+UPB_INLINE void _upb_mapsorter_popmap(_upb_mapsorter *s, _upb_sortedmap *sorted) {
+  s->size = sorted->start;
+}
+
+UPB_INLINE bool _upb_sortedmap_next(_upb_mapsorter *s, const upb_map *map,
+                                    _upb_sortedmap *sorted,
+                                    upb_map_entry *ent) {
+  if (sorted->pos == sorted->end) return false;
+  const upb_tabent *tabent = s->entries[sorted->pos++];
+  upb_strview key = upb_tabstrview(tabent->key);
+  _upb_map_fromkey(key, &ent->k, map->key_size);
+  upb_value val = {tabent->val.val};
+  _upb_map_fromvalue(val, &ent->v, map->val_size);
+  return true;
+}
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#include "upb/port_undef.inc"
+
+#endif /* UPB_MSG_INT_H_ */
diff --git a/upb/reflection.h b/upb/reflection.h
index 70e6ec2..5f82c97 100644
--- a/upb/reflection.h
+++ b/upb/reflection.h
@@ -84,17 +84,9 @@
                   const upb_symtab *ext_pool, const upb_fielddef **f,
                   upb_msgval *val, size_t *iter);
 
-/* Adds unknown data (serialized protobuf data) to the given message.  The data
- * is copied into the message instance. */
-void upb_msg_addunknown(upb_msg *msg, const char *data, size_t len,
-                        upb_arena *arena);
-
 /* Clears all unknown field data from this message and all submessages. */
 bool upb_msg_discardunknown(upb_msg *msg, const upb_msgdef *m, int maxdepth);
 
-/* Returns a reference to the message's unknown data. */
-const char *upb_msg_getunknown(const upb_msg *msg, size_t *len);
-
 /** upb_array *****************************************************************/
 
 /* Creates a new array on the given arena that holds elements of this type. */
diff --git a/upbc/protoc-gen-upb.cc b/upbc/protoc-gen-upb.cc
index e099ad4..092355a 100644
--- a/upbc/protoc-gen-upb.cc
+++ b/upbc/protoc-gen-upb.cc
@@ -487,7 +487,7 @@
   output(
       "#ifndef $0_UPB_H_\n"
       "#define $0_UPB_H_\n\n"
-      "#include \"upb/msg.h\"\n"
+      "#include \"upb/msg.int.h\"\n"
       "#include \"upb/decode.h\"\n"
       "#include \"upb/decode_fast.h\"\n"
       "#include \"upb/encode.h\"\n\n",
@@ -825,7 +825,7 @@
 
   output(
       "#include <stddef.h>\n"
-      "#include \"upb/msg.h\"\n"
+      "#include \"upb/msg.int.h\"\n"
       "#include \"$0\"\n",
       HeaderFilename(file->name()));