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/lua/BUILD.bazel b/lua/BUILD.bazel
new file mode 100644
index 0000000..6216f9c
--- /dev/null
+++ b/lua/BUILD.bazel
@@ -0,0 +1,112 @@
+# 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(
+    "//lua:lua_proto_library.bzl",
+    "lua_proto_library",
+)
+
+licenses(["notice"])
+
+cc_library(
+    name = "lupb",
+    srcs = [
+        "def.c",
+        "msg.c",
+        "upb.c",
+    ],
+    hdrs = [
+        "upb.h",
+    ],
+    copts = UPB_DEFAULT_COPTS,
+    visibility = ["//visibility:public"],
+    deps = [
+        "//upb:json",
+        "//upb:message",
+        "//upb:reflection",
+        "//upb:text",
+        "@lua//:liblua",
+    ],
+)
+
+cc_binary(
+    name = "protoc-gen-lua",
+    srcs = ["upbc.cc"],
+    copts = UPB_DEFAULT_CPPOPTS,
+    visibility = ["//visibility:public"],
+    deps = [
+        "//src/google/protobuf/compiler:code_generator",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+exports_files(["upb.lua"])
+
+cc_test(
+    name = "test_lua",
+    srcs = ["main.c"],
+    args = ["$(location :test_upb.lua)"],
+    copts = UPB_DEFAULT_COPTS,
+    data = [
+        "test_upb.lua",
+        "upb.lua",
+        ":descriptor_proto_lua",
+        ":empty_proto_lua",
+        ":test_messages_proto2_proto_lua",
+        ":test_messages_proto3_proto_lua",
+        ":test_proto_lua",
+        "//:descriptor_proto",
+        "//conformance:conformance_proto",
+        "//third_party/lunit:console.lua",
+        "//third_party/lunit:lunit.lua",
+    ],
+    linkstatic = 1,
+    deps = [
+        ":lupb",
+        "@lua//:liblua",
+    ],
+)
+
+proto_library(
+    name = "test_proto",
+    testonly = 1,
+    srcs = ["test.proto"],
+    deps = ["//:timestamp_proto"],
+)
+
+lua_proto_library(
+    name = "test_proto_lua",
+    testonly = 1,
+    deps = [":test_proto"],
+)
+
+lua_proto_library(
+    name = "descriptor_proto_lua",
+    deps = ["//:descriptor_proto"],
+)
+
+lua_proto_library(
+    name = "empty_proto_lua",
+    deps = ["//:empty_proto"],
+)
+
+lua_proto_library(
+    name = "test_messages_proto3_proto_lua",
+    testonly = 1,
+    deps = ["//src/google/protobuf:test_messages_proto3_proto"],
+)
+
+lua_proto_library(
+    name = "test_messages_proto2_proto_lua",
+    testonly = 1,
+    deps = ["//src/google/protobuf:test_messages_proto2_proto"],
+)
diff --git a/lua/README.md b/lua/README.md
new file mode 100644
index 0000000..9374f26
--- /dev/null
+++ b/lua/README.md
@@ -0,0 +1,8 @@
+# upb Lua bindings
+
+These are some bare-bones upb bindings for Lua.
+
+These bindings exist primarily for experimentation and testing.
+They are incomplete and are not really intended for use in any application.
+This is by no means a complete or supported protobuf library, and in fact
+we don't even claim it to be functional.
diff --git a/lua/def.c b/lua/def.c
new file mode 100644
index 0000000..9affe52
--- /dev/null
+++ b/lua/def.c
@@ -0,0 +1,943 @@
+// 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 "upb/reflection/def.h"
+
+#include <float.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lauxlib.h"
+#include "lua/upb.h"
+#include "upb/reflection/message.h"
+
+#define LUPB_ENUMDEF "lupb.enumdef"
+#define LUPB_ENUMVALDEF "lupb.enumvaldef"
+#define LUPB_FIELDDEF "lupb.fielddef"
+#define LUPB_FILEDEF "lupb.filedef"
+#define LUPB_MSGDEF "lupb.msgdef"
+#define LUPB_ONEOFDEF "lupb.oneof"
+#define LUPB_SYMTAB "lupb.defpool"
+#define LUPB_OBJCACHE "lupb.objcache"
+
+static void lupb_DefPool_pushwrapper(lua_State* L, int narg, const void* def,
+                                     const char* type);
+
+/* lupb_wrapper ***************************************************************/
+
+/* Wrappers around upb def objects.  The userval contains a reference to the
+ * defpool. */
+
+#define LUPB_SYMTAB_INDEX 1
+
+typedef struct {
+  const void* def; /* upb_MessageDef, upb_EnumDef, upb_OneofDef, etc. */
+} lupb_wrapper;
+
+static const void* lupb_wrapper_check(lua_State* L, int narg,
+                                      const char* type) {
+  lupb_wrapper* w = luaL_checkudata(L, narg, type);
+  return w->def;
+}
+
+static void lupb_wrapper_pushdefpool(lua_State* L, int narg) {
+  lua_getiuservalue(L, narg, LUPB_SYMTAB_INDEX);
+}
+
+/* lupb_wrapper_pushwrapper()
+ *
+ * For a given def wrapper at index |narg|, pushes a wrapper for the given |def|
+ * and the given |type|.  The new wrapper will be part of the same defpool. */
+static void lupb_wrapper_pushwrapper(lua_State* L, int narg, const void* def,
+                                     const char* type) {
+  lupb_wrapper_pushdefpool(L, narg);
+  lupb_DefPool_pushwrapper(L, -1, def, type);
+  lua_replace(L, -2); /* Remove defpool from stack. */
+}
+
+/* lupb_MessageDef_pushsubmsgdef()
+ *
+ * Pops the msgdef wrapper at the top of the stack and replaces it with a msgdef
+ * wrapper for field |f| of this msgdef (submsg may not be direct, for example
+ * it may be the submessage of the map value).
+ */
+void lupb_MessageDef_pushsubmsgdef(lua_State* L, const upb_FieldDef* f) {
+  const upb_MessageDef* m = upb_FieldDef_MessageSubDef(f);
+  assert(m);
+  lupb_wrapper_pushwrapper(L, -1, m, LUPB_MSGDEF);
+  lua_replace(L, -2); /* Replace msgdef with submsgdef. */
+}
+
+/* lupb_FieldDef **************************************************************/
+
+const upb_FieldDef* lupb_FieldDef_check(lua_State* L, int narg) {
+  return lupb_wrapper_check(L, narg, LUPB_FIELDDEF);
+}
+
+static int lupb_FieldDef_ContainingOneof(lua_State* L) {
+  const upb_FieldDef* f = lupb_FieldDef_check(L, 1);
+  const upb_OneofDef* o = upb_FieldDef_ContainingOneof(f);
+  lupb_wrapper_pushwrapper(L, 1, o, LUPB_ONEOFDEF);
+  return 1;
+}
+
+static int lupb_FieldDef_ContainingType(lua_State* L) {
+  const upb_FieldDef* f = lupb_FieldDef_check(L, 1);
+  const upb_MessageDef* m = upb_FieldDef_ContainingType(f);
+  lupb_wrapper_pushwrapper(L, 1, m, LUPB_MSGDEF);
+  return 1;
+}
+
+static int lupb_FieldDef_Default(lua_State* L) {
+  const upb_FieldDef* f = lupb_FieldDef_check(L, 1);
+  upb_CType type = upb_FieldDef_CType(f);
+  if (type == kUpb_CType_Message) {
+    return luaL_error(L, "Message fields do not have explicit defaults.");
+  }
+  lupb_pushmsgval(L, 0, type, upb_FieldDef_Default(f));
+  return 1;
+}
+
+static int lupb_FieldDef_Type(lua_State* L) {
+  const upb_FieldDef* f = lupb_FieldDef_check(L, 1);
+  lua_pushnumber(L, upb_FieldDef_Type(f));
+  return 1;
+}
+
+static int lupb_FieldDef_HasSubDef(lua_State* L) {
+  const upb_FieldDef* f = lupb_FieldDef_check(L, 1);
+  lua_pushboolean(L, upb_FieldDef_HasSubDef(f));
+  return 1;
+}
+
+static int lupb_FieldDef_Index(lua_State* L) {
+  const upb_FieldDef* f = lupb_FieldDef_check(L, 1);
+  lua_pushinteger(L, upb_FieldDef_Index(f));
+  return 1;
+}
+
+static int lupb_FieldDef_IsExtension(lua_State* L) {
+  const upb_FieldDef* f = lupb_FieldDef_check(L, 1);
+  lua_pushboolean(L, upb_FieldDef_IsExtension(f));
+  return 1;
+}
+
+static int lupb_FieldDef_Label(lua_State* L) {
+  const upb_FieldDef* f = lupb_FieldDef_check(L, 1);
+  lua_pushinteger(L, upb_FieldDef_Label(f));
+  return 1;
+}
+
+static int lupb_FieldDef_Name(lua_State* L) {
+  const upb_FieldDef* f = lupb_FieldDef_check(L, 1);
+  lua_pushstring(L, upb_FieldDef_Name(f));
+  return 1;
+}
+
+static int lupb_FieldDef_Number(lua_State* L) {
+  const upb_FieldDef* f = lupb_FieldDef_check(L, 1);
+  int32_t num = upb_FieldDef_Number(f);
+  if (num) {
+    lua_pushinteger(L, num);
+  } else {
+    lua_pushnil(L);
+  }
+  return 1;
+}
+
+static int lupb_FieldDef_IsPacked(lua_State* L) {
+  const upb_FieldDef* f = lupb_FieldDef_check(L, 1);
+  lua_pushboolean(L, upb_FieldDef_IsPacked(f));
+  return 1;
+}
+
+static int lupb_FieldDef_MessageSubDef(lua_State* L) {
+  const upb_FieldDef* f = lupb_FieldDef_check(L, 1);
+  const upb_MessageDef* m = upb_FieldDef_MessageSubDef(f);
+  lupb_wrapper_pushwrapper(L, 1, m, LUPB_MSGDEF);
+  return 1;
+}
+
+static int lupb_FieldDef_EnumSubDef(lua_State* L) {
+  const upb_FieldDef* f = lupb_FieldDef_check(L, 1);
+  const upb_EnumDef* e = upb_FieldDef_EnumSubDef(f);
+  lupb_wrapper_pushwrapper(L, 1, e, LUPB_ENUMDEF);
+  return 1;
+}
+
+static int lupb_FieldDef_CType(lua_State* L) {
+  const upb_FieldDef* f = lupb_FieldDef_check(L, 1);
+  lua_pushinteger(L, upb_FieldDef_CType(f));
+  return 1;
+}
+
+static const struct luaL_Reg lupb_FieldDef_m[] = {
+    {"containing_oneof", lupb_FieldDef_ContainingOneof},
+    {"containing_type", lupb_FieldDef_ContainingType},
+    {"default", lupb_FieldDef_Default},
+    {"descriptor_type", lupb_FieldDef_Type},
+    {"has_subdef", lupb_FieldDef_HasSubDef},
+    {"index", lupb_FieldDef_Index},
+    {"is_extension", lupb_FieldDef_IsExtension},
+    {"label", lupb_FieldDef_Label},
+    {"name", lupb_FieldDef_Name},
+    {"number", lupb_FieldDef_Number},
+    {"packed", lupb_FieldDef_IsPacked},
+    {"msgsubdef", lupb_FieldDef_MessageSubDef},
+    {"enumsubdef", lupb_FieldDef_EnumSubDef},
+    {"type", lupb_FieldDef_CType},
+    {NULL, NULL}};
+
+/* lupb_OneofDef **************************************************************/
+
+const upb_OneofDef* lupb_OneofDef_check(lua_State* L, int narg) {
+  return lupb_wrapper_check(L, narg, LUPB_ONEOFDEF);
+}
+
+static int lupb_OneofDef_ContainingType(lua_State* L) {
+  const upb_OneofDef* o = lupb_OneofDef_check(L, 1);
+  const upb_MessageDef* m = upb_OneofDef_ContainingType(o);
+  lupb_wrapper_pushwrapper(L, 1, m, LUPB_MSGDEF);
+  return 1;
+}
+
+static int lupb_OneofDef_Field(lua_State* L) {
+  const upb_OneofDef* o = lupb_OneofDef_check(L, 1);
+  int32_t idx = lupb_checkint32(L, 2);
+  int count = upb_OneofDef_FieldCount(o);
+
+  if (idx < 0 || idx >= count) {
+    const char* msg =
+        lua_pushfstring(L, "index %d exceeds field count %d", idx, count);
+    return luaL_argerror(L, 2, msg);
+  }
+
+  lupb_wrapper_pushwrapper(L, 1, upb_OneofDef_Field(o, idx), LUPB_FIELDDEF);
+  return 1;
+}
+
+static int lupb_oneofiter_next(lua_State* L) {
+  const upb_OneofDef* o = lupb_OneofDef_check(L, lua_upvalueindex(1));
+  int* index = lua_touserdata(L, lua_upvalueindex(2));
+  const upb_FieldDef* f;
+  if (*index == upb_OneofDef_FieldCount(o)) return 0;
+  f = upb_OneofDef_Field(o, (*index)++);
+  lupb_wrapper_pushwrapper(L, lua_upvalueindex(1), f, LUPB_FIELDDEF);
+  return 1;
+}
+
+static int lupb_OneofDef_Fields(lua_State* L) {
+  int* index = lua_newuserdata(L, sizeof(int));
+  lupb_OneofDef_check(L, 1);
+  *index = 0;
+
+  /* Closure upvalues are: oneofdef, index. */
+  lua_pushcclosure(L, &lupb_oneofiter_next, 2);
+  return 1;
+}
+
+static int lupb_OneofDef_len(lua_State* L) {
+  const upb_OneofDef* o = lupb_OneofDef_check(L, 1);
+  lua_pushinteger(L, upb_OneofDef_FieldCount(o));
+  return 1;
+}
+
+/* lupb_OneofDef_lookupfield()
+ *
+ * Handles:
+ *   oneof.lookup_field(field_number)
+ *   oneof.lookup_field(field_name)
+ */
+static int lupb_OneofDef_lookupfield(lua_State* L) {
+  const upb_OneofDef* o = lupb_OneofDef_check(L, 1);
+  const upb_FieldDef* f;
+
+  switch (lua_type(L, 2)) {
+    case LUA_TNUMBER:
+      f = upb_OneofDef_LookupNumber(o, lua_tointeger(L, 2));
+      break;
+    case LUA_TSTRING:
+      f = upb_OneofDef_LookupName(o, lua_tostring(L, 2));
+      break;
+    default: {
+      const char* msg = lua_pushfstring(L, "number or string expected, got %s",
+                                        luaL_typename(L, 2));
+      return luaL_argerror(L, 2, msg);
+    }
+  }
+
+  lupb_wrapper_pushwrapper(L, 1, f, LUPB_FIELDDEF);
+  return 1;
+}
+
+static int lupb_OneofDef_Name(lua_State* L) {
+  const upb_OneofDef* o = lupb_OneofDef_check(L, 1);
+  lua_pushstring(L, upb_OneofDef_Name(o));
+  return 1;
+}
+
+static const struct luaL_Reg lupb_OneofDef_m[] = {
+    {"containing_type", lupb_OneofDef_ContainingType},
+    {"field", lupb_OneofDef_Field},
+    {"fields", lupb_OneofDef_Fields},
+    {"lookup_field", lupb_OneofDef_lookupfield},
+    {"name", lupb_OneofDef_Name},
+    {NULL, NULL}};
+
+static const struct luaL_Reg lupb_OneofDef_mm[] = {{"__len", lupb_OneofDef_len},
+                                                   {NULL, NULL}};
+
+/* lupb_MessageDef
+ * ****************************************************************/
+
+typedef struct {
+  const upb_MessageDef* md;
+} lupb_MessageDef;
+
+const upb_MessageDef* lupb_MessageDef_check(lua_State* L, int narg) {
+  return lupb_wrapper_check(L, narg, LUPB_MSGDEF);
+}
+
+static int lupb_MessageDef_FieldCount(lua_State* L) {
+  const upb_MessageDef* m = lupb_MessageDef_check(L, 1);
+  lua_pushinteger(L, upb_MessageDef_FieldCount(m));
+  return 1;
+}
+
+static int lupb_MessageDef_OneofCount(lua_State* L) {
+  const upb_MessageDef* m = lupb_MessageDef_check(L, 1);
+  lua_pushinteger(L, upb_MessageDef_OneofCount(m));
+  return 1;
+}
+
+static bool lupb_MessageDef_pushnested(lua_State* L, int msgdef, int name) {
+  const upb_MessageDef* m = lupb_MessageDef_check(L, msgdef);
+  lupb_wrapper_pushdefpool(L, msgdef);
+  upb_DefPool* defpool = lupb_DefPool_check(L, -1);
+  lua_pop(L, 1);
+
+  /* Construct full package.Message.SubMessage name. */
+  lua_pushstring(L, upb_MessageDef_FullName(m));
+  lua_pushstring(L, ".");
+  lua_pushvalue(L, name);
+  lua_concat(L, 3);
+  const char* nested_name = lua_tostring(L, -1);
+
+  /* Try lookup. */
+  const upb_MessageDef* nested =
+      upb_DefPool_FindMessageByName(defpool, nested_name);
+  if (!nested) return false;
+  lupb_wrapper_pushwrapper(L, msgdef, nested, LUPB_MSGDEF);
+  return true;
+}
+
+/* lupb_MessageDef_Field()
+ *
+ * Handles:
+ *   msg.field(field_number) -> fielddef
+ *   msg.field(field_name) -> fielddef
+ */
+static int lupb_MessageDef_Field(lua_State* L) {
+  const upb_MessageDef* m = lupb_MessageDef_check(L, 1);
+  const upb_FieldDef* f;
+
+  switch (lua_type(L, 2)) {
+    case LUA_TNUMBER:
+      f = upb_MessageDef_FindFieldByNumber(m, lua_tointeger(L, 2));
+      break;
+    case LUA_TSTRING:
+      f = upb_MessageDef_FindFieldByName(m, lua_tostring(L, 2));
+      break;
+    default: {
+      const char* msg = lua_pushfstring(L, "number or string expected, got %s",
+                                        luaL_typename(L, 2));
+      return luaL_argerror(L, 2, msg);
+    }
+  }
+
+  lupb_wrapper_pushwrapper(L, 1, f, LUPB_FIELDDEF);
+  return 1;
+}
+
+/* lupb_MessageDef_FindByNameWithSize()
+ *
+ * Handles:
+ *   msg.lookup_name(name) -> fielddef or oneofdef
+ */
+static int lupb_MessageDef_FindByNameWithSize(lua_State* L) {
+  const upb_MessageDef* m = lupb_MessageDef_check(L, 1);
+  const upb_FieldDef* f;
+  const upb_OneofDef* o;
+
+  if (!upb_MessageDef_FindByName(m, lua_tostring(L, 2), &f, &o)) {
+    lua_pushnil(L);
+  } else if (o) {
+    lupb_wrapper_pushwrapper(L, 1, o, LUPB_ONEOFDEF);
+  } else {
+    lupb_wrapper_pushwrapper(L, 1, f, LUPB_FIELDDEF);
+  }
+
+  return 1;
+}
+
+/* lupb_MessageDef_Name()
+ *
+ * Handles:
+ *   msg.name() -> string
+ */
+static int lupb_MessageDef_Name(lua_State* L) {
+  const upb_MessageDef* m = lupb_MessageDef_check(L, 1);
+  lua_pushstring(L, upb_MessageDef_Name(m));
+  return 1;
+}
+
+static int lupb_msgfielditer_next(lua_State* L) {
+  const upb_MessageDef* m = lupb_MessageDef_check(L, lua_upvalueindex(1));
+  int* index = lua_touserdata(L, lua_upvalueindex(2));
+  const upb_FieldDef* f;
+  if (*index == upb_MessageDef_FieldCount(m)) return 0;
+  f = upb_MessageDef_Field(m, (*index)++);
+  lupb_wrapper_pushwrapper(L, lua_upvalueindex(1), f, LUPB_FIELDDEF);
+  return 1;
+}
+
+static int lupb_MessageDef_Fields(lua_State* L) {
+  int* index = lua_newuserdata(L, sizeof(int));
+  lupb_MessageDef_check(L, 1);
+  *index = 0;
+
+  /* Closure upvalues are: msgdef, index. */
+  lua_pushcclosure(L, &lupb_msgfielditer_next, 2);
+  return 1;
+}
+
+static int lupb_MessageDef_File(lua_State* L) {
+  const upb_MessageDef* m = lupb_MessageDef_check(L, 1);
+  const upb_FileDef* file = upb_MessageDef_File(m);
+  lupb_wrapper_pushwrapper(L, 1, file, LUPB_FILEDEF);
+  return 1;
+}
+
+static int lupb_MessageDef_FullName(lua_State* L) {
+  const upb_MessageDef* m = lupb_MessageDef_check(L, 1);
+  lua_pushstring(L, upb_MessageDef_FullName(m));
+  return 1;
+}
+
+static int lupb_MessageDef_index(lua_State* L) {
+  if (!lupb_MessageDef_pushnested(L, 1, 2)) {
+    luaL_error(L, "No such nested message");
+  }
+  return 1;
+}
+
+static int lupb_msgoneofiter_next(lua_State* L) {
+  const upb_MessageDef* m = lupb_MessageDef_check(L, lua_upvalueindex(1));
+  int* index = lua_touserdata(L, lua_upvalueindex(2));
+  const upb_OneofDef* o;
+  if (*index == upb_MessageDef_OneofCount(m)) return 0;
+  o = upb_MessageDef_Oneof(m, (*index)++);
+  lupb_wrapper_pushwrapper(L, lua_upvalueindex(1), o, LUPB_ONEOFDEF);
+  return 1;
+}
+
+static int lupb_MessageDef_Oneofs(lua_State* L) {
+  int* index = lua_newuserdata(L, sizeof(int));
+  lupb_MessageDef_check(L, 1);
+  *index = 0;
+
+  /* Closure upvalues are: msgdef, index. */
+  lua_pushcclosure(L, &lupb_msgoneofiter_next, 2);
+  return 1;
+}
+
+static int lupb_MessageDef_IsMapEntry(lua_State* L) {
+  const upb_MessageDef* m = lupb_MessageDef_check(L, 1);
+  lua_pushboolean(L, upb_MessageDef_IsMapEntry(m));
+  return 1;
+}
+
+static int lupb_MessageDef_Syntax(lua_State* L) {
+  const upb_MessageDef* m = lupb_MessageDef_check(L, 1);
+  lua_pushinteger(L, upb_MessageDef_Syntax(m));
+  return 1;
+}
+
+static int lupb_MessageDef_tostring(lua_State* L) {
+  const upb_MessageDef* m = lupb_MessageDef_check(L, 1);
+  lua_pushfstring(L, "<upb.MessageDef name=%s, field_count=%d>",
+                  upb_MessageDef_FullName(m),
+                  (int)upb_MessageDef_FieldCount(m));
+  return 1;
+}
+
+static const struct luaL_Reg lupb_MessageDef_mm[] = {
+    {"__call", lupb_MessageDef_call},
+    {"__index", lupb_MessageDef_index},
+    {"__len", lupb_MessageDef_FieldCount},
+    {"__tostring", lupb_MessageDef_tostring},
+    {NULL, NULL}};
+
+static const struct luaL_Reg lupb_MessageDef_m[] = {
+    {"field", lupb_MessageDef_Field},
+    {"fields", lupb_MessageDef_Fields},
+    {"field_count", lupb_MessageDef_FieldCount},
+    {"file", lupb_MessageDef_File},
+    {"full_name", lupb_MessageDef_FullName},
+    {"lookup_name", lupb_MessageDef_FindByNameWithSize},
+    {"name", lupb_MessageDef_Name},
+    {"oneof_count", lupb_MessageDef_OneofCount},
+    {"oneofs", lupb_MessageDef_Oneofs},
+    {"syntax", lupb_MessageDef_Syntax},
+    {"_map_entry", lupb_MessageDef_IsMapEntry},
+    {NULL, NULL}};
+
+/* lupb_EnumDef ***************************************************************/
+
+const upb_EnumDef* lupb_EnumDef_check(lua_State* L, int narg) {
+  return lupb_wrapper_check(L, narg, LUPB_ENUMDEF);
+}
+
+static int lupb_EnumDef_len(lua_State* L) {
+  const upb_EnumDef* e = lupb_EnumDef_check(L, 1);
+  lua_pushinteger(L, upb_EnumDef_ValueCount(e));
+  return 1;
+}
+
+static int lupb_EnumDef_File(lua_State* L) {
+  const upb_EnumDef* e = lupb_EnumDef_check(L, 1);
+  const upb_FileDef* file = upb_EnumDef_File(e);
+  lupb_wrapper_pushwrapper(L, 1, file, LUPB_FILEDEF);
+  return 1;
+}
+
+/* lupb_EnumDef_Value()
+ *
+ * Handles:
+ *   enum.value(number) -> enumval
+ *   enum.value(name) -> enumval
+ */
+static int lupb_EnumDef_Value(lua_State* L) {
+  const upb_EnumDef* e = lupb_EnumDef_check(L, 1);
+  const upb_EnumValueDef* ev;
+
+  switch (lua_type(L, 2)) {
+    case LUA_TNUMBER:
+      ev = upb_EnumDef_FindValueByNumber(e, lupb_checkint32(L, 2));
+      break;
+    case LUA_TSTRING:
+      ev = upb_EnumDef_FindValueByName(e, lua_tostring(L, 2));
+      break;
+    default: {
+      const char* msg = lua_pushfstring(L, "number or string expected, got %s",
+                                        luaL_typename(L, 2));
+      return luaL_argerror(L, 2, msg);
+    }
+  }
+
+  lupb_wrapper_pushwrapper(L, 1, ev, LUPB_ENUMVALDEF);
+  return 1;
+}
+
+static const struct luaL_Reg lupb_EnumDef_mm[] = {{"__len", lupb_EnumDef_len},
+                                                  {NULL, NULL}};
+
+static const struct luaL_Reg lupb_EnumDef_m[] = {
+    {"file", lupb_EnumDef_File}, {"value", lupb_EnumDef_Value}, {NULL, NULL}};
+
+/* lupb_EnumValueDef
+ * ************************************************************/
+
+const upb_EnumValueDef* lupb_enumvaldef_check(lua_State* L, int narg) {
+  return lupb_wrapper_check(L, narg, LUPB_ENUMVALDEF);
+}
+
+static int lupb_EnumValueDef_Enum(lua_State* L) {
+  const upb_EnumValueDef* ev = lupb_enumvaldef_check(L, 1);
+  const upb_EnumDef* e = upb_EnumValueDef_Enum(ev);
+  lupb_wrapper_pushwrapper(L, 1, e, LUPB_ENUMDEF);
+  return 1;
+}
+
+static int lupb_EnumValueDef_FullName(lua_State* L) {
+  const upb_EnumValueDef* ev = lupb_enumvaldef_check(L, 1);
+  lua_pushstring(L, upb_EnumValueDef_FullName(ev));
+  return 1;
+}
+
+static int lupb_EnumValueDef_Name(lua_State* L) {
+  const upb_EnumValueDef* ev = lupb_enumvaldef_check(L, 1);
+  lua_pushstring(L, upb_EnumValueDef_Name(ev));
+  return 1;
+}
+
+static int lupb_EnumValueDef_Number(lua_State* L) {
+  const upb_EnumValueDef* ev = lupb_enumvaldef_check(L, 1);
+  lupb_pushint32(L, upb_EnumValueDef_Number(ev));
+  return 1;
+}
+
+static const struct luaL_Reg lupb_enumvaldef_m[] = {
+    {"enum", lupb_EnumValueDef_Enum},
+    {"full_name", lupb_EnumValueDef_FullName},
+    {"name", lupb_EnumValueDef_Name},
+    {"number", lupb_EnumValueDef_Number},
+    {NULL, NULL}};
+
+/* lupb_FileDef ***************************************************************/
+
+const upb_FileDef* lupb_FileDef_check(lua_State* L, int narg) {
+  return lupb_wrapper_check(L, narg, LUPB_FILEDEF);
+}
+
+static int lupb_FileDef_Dependency(lua_State* L) {
+  const upb_FileDef* f = lupb_FileDef_check(L, 1);
+  int index = luaL_checkint(L, 2);
+  const upb_FileDef* dep = upb_FileDef_Dependency(f, index);
+  lupb_wrapper_pushwrapper(L, 1, dep, LUPB_FILEDEF);
+  return 1;
+}
+
+static int lupb_FileDef_DependencyCount(lua_State* L) {
+  const upb_FileDef* f = lupb_FileDef_check(L, 1);
+  lua_pushnumber(L, upb_FileDef_DependencyCount(f));
+  return 1;
+}
+
+static int lupb_FileDef_enum(lua_State* L) {
+  const upb_FileDef* f = lupb_FileDef_check(L, 1);
+  int index = luaL_checkint(L, 2);
+  const upb_EnumDef* e = upb_FileDef_TopLevelEnum(f, index);
+  lupb_wrapper_pushwrapper(L, 1, e, LUPB_ENUMDEF);
+  return 1;
+}
+
+static int lupb_FileDef_enumcount(lua_State* L) {
+  const upb_FileDef* f = lupb_FileDef_check(L, 1);
+  lua_pushnumber(L, upb_FileDef_TopLevelEnumCount(f));
+  return 1;
+}
+
+static int lupb_FileDef_msg(lua_State* L) {
+  const upb_FileDef* f = lupb_FileDef_check(L, 1);
+  int index = luaL_checkint(L, 2);
+  const upb_MessageDef* m = upb_FileDef_TopLevelMessage(f, index);
+  lupb_wrapper_pushwrapper(L, 1, m, LUPB_MSGDEF);
+  return 1;
+}
+
+static int lupb_FileDef_msgcount(lua_State* L) {
+  const upb_FileDef* f = lupb_FileDef_check(L, 1);
+  lua_pushnumber(L, upb_FileDef_TopLevelMessageCount(f));
+  return 1;
+}
+
+static int lupb_FileDef_Name(lua_State* L) {
+  const upb_FileDef* f = lupb_FileDef_check(L, 1);
+  lua_pushstring(L, upb_FileDef_Name(f));
+  return 1;
+}
+
+static int lupb_FileDef_Package(lua_State* L) {
+  const upb_FileDef* f = lupb_FileDef_check(L, 1);
+  lua_pushstring(L, upb_FileDef_Package(f));
+  return 1;
+}
+
+static int lupb_FileDef_Pool(lua_State* L) {
+  const upb_FileDef* f = lupb_FileDef_check(L, 1);
+  const upb_DefPool* defpool = upb_FileDef_Pool(f);
+  lupb_wrapper_pushwrapper(L, 1, defpool, LUPB_SYMTAB);
+  return 1;
+}
+
+static int lupb_FileDef_Syntax(lua_State* L) {
+  const upb_FileDef* f = lupb_FileDef_check(L, 1);
+  lua_pushnumber(L, upb_FileDef_Syntax(f));
+  return 1;
+}
+
+static const struct luaL_Reg lupb_FileDef_m[] = {
+    {"dep", lupb_FileDef_Dependency},
+    {"depcount", lupb_FileDef_DependencyCount},
+    {"enum", lupb_FileDef_enum},
+    {"enumcount", lupb_FileDef_enumcount},
+    {"msg", lupb_FileDef_msg},
+    {"msgcount", lupb_FileDef_msgcount},
+    {"name", lupb_FileDef_Name},
+    {"package", lupb_FileDef_Package},
+    {"defpool", lupb_FileDef_Pool},
+    {"syntax", lupb_FileDef_Syntax},
+    {NULL, NULL}};
+
+/* lupb_DefPool
+ * ****************************************************************/
+
+/* The defpool owns all defs.  Thus GC-rooting the defpool ensures that all
+ * underlying defs stay alive.
+ *
+ * The defpool's userval is a cache of def* -> object. */
+
+#define LUPB_CACHE_INDEX 1
+
+typedef struct {
+  upb_DefPool* defpool;
+} lupb_DefPool;
+
+upb_DefPool* lupb_DefPool_check(lua_State* L, int narg) {
+  lupb_DefPool* ldefpool = luaL_checkudata(L, narg, LUPB_SYMTAB);
+  if (!ldefpool->defpool) {
+    luaL_error(L, "called into dead object");
+  }
+  return ldefpool->defpool;
+}
+
+void lupb_DefPool_pushwrapper(lua_State* L, int narg, const void* def,
+                              const char* type) {
+  narg = lua_absindex(L, narg);
+  assert(luaL_testudata(L, narg, LUPB_SYMTAB));
+
+  if (def == NULL) {
+    lua_pushnil(L);
+    return;
+  }
+
+  lua_getiuservalue(L, narg, LUPB_CACHE_INDEX); /* Get cache. */
+
+  /* Index by "def" pointer. */
+  lua_rawgetp(L, -1, def);
+
+  /* Stack is now: cache, cached value. */
+  if (lua_isnil(L, -1)) {
+    /* Create new wrapper. */
+    lupb_wrapper* w = lupb_newuserdata(L, sizeof(*w), 1, type);
+    w->def = def;
+    lua_replace(L, -2); /* Replace nil */
+
+    /* Set defpool as userval. */
+    lua_pushvalue(L, narg);
+    lua_setiuservalue(L, -2, LUPB_SYMTAB_INDEX);
+
+    /* Add wrapper to the the cache. */
+    lua_pushvalue(L, -1);
+    lua_rawsetp(L, -3, def);
+  }
+
+  lua_replace(L, -2); /* Remove cache, leaving only the wrapper. */
+}
+
+/* upb_DefPool_New()
+ *
+ * Handles:
+ *   upb.DefPool() -> <new instance>
+ */
+static int lupb_DefPool_New(lua_State* L) {
+  lupb_DefPool* ldefpool =
+      lupb_newuserdata(L, sizeof(*ldefpool), 1, LUPB_SYMTAB);
+  ldefpool->defpool = upb_DefPool_New();
+
+  /* Create our object cache. */
+  lua_newtable(L);
+
+  /* Cache metatable: specifies that values are weak. */
+  lua_createtable(L, 0, 1);
+  lua_pushstring(L, "v");
+  lua_setfield(L, -2, "__mode");
+  lua_setmetatable(L, -2);
+
+  /* Put the defpool itself in the cache metatable. */
+  lua_pushvalue(L, -2);
+  lua_rawsetp(L, -2, ldefpool->defpool);
+
+  /* Set the cache as our userval. */
+  lua_setiuservalue(L, -2, LUPB_CACHE_INDEX);
+
+  return 1;
+}
+
+static int lupb_DefPool_gc(lua_State* L) {
+  lupb_DefPool* ldefpool = luaL_checkudata(L, 1, LUPB_SYMTAB);
+  upb_DefPool_Free(ldefpool->defpool);
+  ldefpool->defpool = NULL;
+  return 0;
+}
+
+static int lupb_DefPool_AddFile(lua_State* L) {
+  size_t len;
+  upb_DefPool* s = lupb_DefPool_check(L, 1);
+  const char* str = luaL_checklstring(L, 2, &len);
+  upb_Arena* arena = lupb_Arena_pushnew(L);
+  const google_protobuf_FileDescriptorProto* file;
+  const upb_FileDef* file_def;
+  upb_Status status;
+
+  upb_Status_Clear(&status);
+  file = google_protobuf_FileDescriptorProto_parse(str, len, arena);
+
+  if (!file) {
+    luaL_argerror(L, 2, "failed to parse descriptor");
+  }
+
+  file_def = upb_DefPool_AddFile(s, file, &status);
+  lupb_checkstatus(L, &status);
+
+  lupb_DefPool_pushwrapper(L, 1, file_def, LUPB_FILEDEF);
+
+  return 1;
+}
+
+static int lupb_DefPool_addset(lua_State* L) {
+  size_t i, n, len;
+  const google_protobuf_FileDescriptorProto* const* files;
+  google_protobuf_FileDescriptorSet* set;
+  upb_DefPool* s = lupb_DefPool_check(L, 1);
+  const char* str = luaL_checklstring(L, 2, &len);
+  upb_Arena* arena = lupb_Arena_pushnew(L);
+  upb_Status status;
+
+  upb_Status_Clear(&status);
+  set = google_protobuf_FileDescriptorSet_parse(str, len, arena);
+
+  if (!set) {
+    luaL_argerror(L, 2, "failed to parse descriptor");
+  }
+
+  files = google_protobuf_FileDescriptorSet_file(set, &n);
+  for (i = 0; i < n; i++) {
+    upb_DefPool_AddFile(s, files[i], &status);
+    lupb_checkstatus(L, &status);
+  }
+
+  return 0;
+}
+
+static int lupb_DefPool_FindMessageByName(lua_State* L) {
+  const upb_DefPool* s = lupb_DefPool_check(L, 1);
+  const upb_MessageDef* m =
+      upb_DefPool_FindMessageByName(s, luaL_checkstring(L, 2));
+  lupb_DefPool_pushwrapper(L, 1, m, LUPB_MSGDEF);
+  return 1;
+}
+
+static int lupb_DefPool_FindEnumByName(lua_State* L) {
+  const upb_DefPool* s = lupb_DefPool_check(L, 1);
+  const upb_EnumDef* e = upb_DefPool_FindEnumByName(s, luaL_checkstring(L, 2));
+  lupb_DefPool_pushwrapper(L, 1, e, LUPB_ENUMDEF);
+  return 1;
+}
+
+static int lupb_DefPool_FindEnumByNameval(lua_State* L) {
+  const upb_DefPool* s = lupb_DefPool_check(L, 1);
+  const upb_EnumValueDef* e =
+      upb_DefPool_FindEnumByNameval(s, luaL_checkstring(L, 2));
+  lupb_DefPool_pushwrapper(L, 1, e, LUPB_ENUMVALDEF);
+  return 1;
+}
+
+static int lupb_DefPool_tostring(lua_State* L) {
+  lua_pushfstring(L, "<upb.DefPool>");
+  return 1;
+}
+
+static const struct luaL_Reg lupb_DefPool_m[] = {
+    {"add_file", lupb_DefPool_AddFile},
+    {"add_set", lupb_DefPool_addset},
+    {"lookup_msg", lupb_DefPool_FindMessageByName},
+    {"lookup_enum", lupb_DefPool_FindEnumByName},
+    {"lookup_enumval", lupb_DefPool_FindEnumByNameval},
+    {NULL, NULL}};
+
+static const struct luaL_Reg lupb_DefPool_mm[] = {
+    {"__gc", lupb_DefPool_gc},
+    {"__tostring", lupb_DefPool_tostring},
+    {NULL, NULL}};
+
+/* lupb toplevel **************************************************************/
+
+static void lupb_setfieldi(lua_State* L, const char* field, int i) {
+  lua_pushinteger(L, i);
+  lua_setfield(L, -2, field);
+}
+
+static const struct luaL_Reg lupbdef_toplevel_m[] = {
+    {"DefPool", lupb_DefPool_New}, {NULL, NULL}};
+
+void lupb_def_registertypes(lua_State* L) {
+  lupb_setfuncs(L, lupbdef_toplevel_m);
+
+  /* Register types. */
+  lupb_register_type(L, LUPB_ENUMDEF, lupb_EnumDef_m, lupb_EnumDef_mm);
+  lupb_register_type(L, LUPB_ENUMVALDEF, lupb_enumvaldef_m, NULL);
+  lupb_register_type(L, LUPB_FIELDDEF, lupb_FieldDef_m, NULL);
+  lupb_register_type(L, LUPB_FILEDEF, lupb_FileDef_m, NULL);
+  lupb_register_type(L, LUPB_MSGDEF, lupb_MessageDef_m, lupb_MessageDef_mm);
+  lupb_register_type(L, LUPB_ONEOFDEF, lupb_OneofDef_m, lupb_OneofDef_mm);
+  lupb_register_type(L, LUPB_SYMTAB, lupb_DefPool_m, lupb_DefPool_mm);
+
+  /* Register constants. */
+  lupb_setfieldi(L, "LABEL_OPTIONAL", kUpb_Label_Optional);
+  lupb_setfieldi(L, "LABEL_REQUIRED", kUpb_Label_Required);
+  lupb_setfieldi(L, "LABEL_REPEATED", kUpb_Label_Repeated);
+
+  lupb_setfieldi(L, "TYPE_DOUBLE", kUpb_CType_Double);
+  lupb_setfieldi(L, "TYPE_FLOAT", kUpb_CType_Float);
+  lupb_setfieldi(L, "TYPE_INT64", kUpb_CType_Int64);
+  lupb_setfieldi(L, "TYPE_UINT64", kUpb_CType_UInt64);
+  lupb_setfieldi(L, "TYPE_INT32", kUpb_CType_Int32);
+  lupb_setfieldi(L, "TYPE_BOOL", kUpb_CType_Bool);
+  lupb_setfieldi(L, "TYPE_STRING", kUpb_CType_String);
+  lupb_setfieldi(L, "TYPE_MESSAGE", kUpb_CType_Message);
+  lupb_setfieldi(L, "TYPE_BYTES", kUpb_CType_Bytes);
+  lupb_setfieldi(L, "TYPE_UINT32", kUpb_CType_UInt32);
+  lupb_setfieldi(L, "TYPE_ENUM", kUpb_CType_Enum);
+
+  lupb_setfieldi(L, "DESCRIPTOR_TYPE_DOUBLE", kUpb_FieldType_Double);
+  lupb_setfieldi(L, "DESCRIPTOR_TYPE_FLOAT", kUpb_FieldType_Float);
+  lupb_setfieldi(L, "DESCRIPTOR_TYPE_INT64", kUpb_FieldType_Int64);
+  lupb_setfieldi(L, "DESCRIPTOR_TYPE_UINT64", kUpb_FieldType_UInt64);
+  lupb_setfieldi(L, "DESCRIPTOR_TYPE_INT32", kUpb_FieldType_Int32);
+  lupb_setfieldi(L, "DESCRIPTOR_TYPE_FIXED64", kUpb_FieldType_Fixed64);
+  lupb_setfieldi(L, "DESCRIPTOR_TYPE_FIXED32", kUpb_FieldType_Fixed32);
+  lupb_setfieldi(L, "DESCRIPTOR_TYPE_BOOL", kUpb_FieldType_Bool);
+  lupb_setfieldi(L, "DESCRIPTOR_TYPE_STRING", kUpb_FieldType_String);
+  lupb_setfieldi(L, "DESCRIPTOR_TYPE_GROUP", kUpb_FieldType_Group);
+  lupb_setfieldi(L, "DESCRIPTOR_TYPE_MESSAGE", kUpb_FieldType_Message);
+  lupb_setfieldi(L, "DESCRIPTOR_TYPE_BYTES", kUpb_FieldType_Bytes);
+  lupb_setfieldi(L, "DESCRIPTOR_TYPE_UINT32", kUpb_FieldType_UInt32);
+  lupb_setfieldi(L, "DESCRIPTOR_TYPE_ENUM", kUpb_FieldType_Enum);
+  lupb_setfieldi(L, "DESCRIPTOR_TYPE_SFIXED32", kUpb_FieldType_SFixed32);
+  lupb_setfieldi(L, "DESCRIPTOR_TYPE_SFIXED64", kUpb_FieldType_SFixed64);
+  lupb_setfieldi(L, "DESCRIPTOR_TYPE_SINT32", kUpb_FieldType_SInt32);
+  lupb_setfieldi(L, "DESCRIPTOR_TYPE_SINT64", kUpb_FieldType_SInt64);
+
+  lupb_setfieldi(L, "SYNTAX_PROTO2", kUpb_Syntax_Proto2);
+  lupb_setfieldi(L, "SYNTAX_PROTO3", kUpb_Syntax_Proto3);
+}
diff --git a/lua/lua_proto_library.bzl b/lua/lua_proto_library.bzl
new file mode 100644
index 0000000..579eca1
--- /dev/null
+++ b/lua/lua_proto_library.bzl
@@ -0,0 +1,136 @@
+# 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
+
+"""lua_proto_library(): a rule for building Lua protos."""
+
+load("@bazel_skylib//lib:paths.bzl", "paths")
+
+# Generic support code #########################################################
+
+# begin:github_only
+_is_google3 = False
+# end:github_only
+
+# begin:google_only
+# _is_google3 = True
+# end:google_only
+
+def _get_real_short_path(file):
+    # For some reason, files from other archives have short paths that look like:
+    #   ../com_google_protobuf/google/protobuf/descriptor.proto
+    short_path = file.short_path
+    if short_path.startswith("../"):
+        second_slash = short_path.index("/", 3)
+        short_path = short_path[second_slash + 1:]
+
+    # Sometimes it has another few prefixes like:
+    #   _virtual_imports/any_proto/google/protobuf/any.proto
+    #   benchmarks/_virtual_imports/100_msgs_proto/benchmarks/100_msgs.proto
+    # We want just google/protobuf/any.proto.
+    virtual_imports = "_virtual_imports/"
+    if virtual_imports in short_path:
+        short_path = short_path.split(virtual_imports)[1].split("/", 1)[1]
+    return short_path
+
+def _get_real_root(ctx, file):
+    real_short_path = _get_real_short_path(file)
+    root = file.path[:-len(real_short_path) - 1]
+    if not _is_google3 and ctx.rule.attr.strip_import_prefix:
+        root = paths.join(root, ctx.rule.attr.strip_import_prefix[1:])
+    return root
+
+def _generate_output_file(ctx, src, extension):
+    package = ctx.label.package
+    if not _is_google3 and ctx.rule.attr.strip_import_prefix and ctx.rule.attr.strip_import_prefix != "/":
+        package = package[len(ctx.rule.attr.strip_import_prefix):]
+    real_short_path = _get_real_short_path(src)
+    real_short_path = paths.relativize(real_short_path, package)
+    output_filename = paths.replace_extension(real_short_path, extension)
+    ret = ctx.actions.declare_file(output_filename)
+    return ret
+
+# upb_proto_library / upb_proto_reflection_library shared code #################
+
+_LuaFilesInfo = provider(
+    "A set of lua files generated from .proto files",
+    fields = ["files"],
+)
+
+def _compile_upb_protos(ctx, proto_info, proto_sources):
+    files = [_generate_output_file(ctx, name, "_pb.lua") for name in proto_sources]
+    transitive_sets = proto_info.transitive_descriptor_sets.to_list()
+    ctx.actions.run(
+        inputs = depset(
+            direct = [proto_info.direct_descriptor_set],
+            transitive = [proto_info.transitive_descriptor_sets],
+        ),
+        tools = [ctx.executable._upbc],
+        outputs = files,
+        executable = ctx.executable._protoc,
+        arguments = [
+                        "--lua_out=" + _get_real_root(ctx, files[0]),
+                        "--plugin=protoc-gen-lua=" + ctx.executable._upbc.path,
+                        "--descriptor_set_in=" + ctx.configuration.host_path_separator.join([f.path for f in transitive_sets]),
+                    ] +
+                    [_get_real_short_path(file) for file in proto_sources],
+        progress_message = "Generating Lua protos for :" + ctx.label.name,
+    )
+    return files
+
+def _lua_proto_rule_impl(ctx):
+    if len(ctx.attr.deps) != 1:
+        fail("only one deps dependency allowed.")
+    dep = ctx.attr.deps[0]
+    if _LuaFilesInfo not in dep:
+        fail("proto_library rule must generate _LuaFilesInfo (aspect should have handled this).")
+    files = dep[_LuaFilesInfo].files
+    return [
+        DefaultInfo(
+            files = files,
+            data_runfiles = ctx.runfiles(files = files.to_list()),
+        ),
+    ]
+
+def _lua_proto_library_aspect_impl(target, ctx):
+    proto_info = target[ProtoInfo]
+    files = _compile_upb_protos(ctx, proto_info, proto_info.direct_sources)
+    deps = ctx.rule.attr.deps
+    transitive = [dep[_LuaFilesInfo].files for dep in deps if _LuaFilesInfo in dep]
+    return [_LuaFilesInfo(files = depset(direct = files, transitive = transitive))]
+
+# lua_proto_library() ##########################################################
+
+_lua_proto_library_aspect = aspect(
+    attrs = {
+        "_upbc": attr.label(
+            executable = True,
+            cfg = "exec",
+            default = "//lua:protoc-gen-lua",
+        ),
+        "_protoc": attr.label(
+            executable = True,
+            cfg = "exec",
+            default = "//:protoc",
+        ),
+    },
+    implementation = _lua_proto_library_aspect_impl,
+    provides = [_LuaFilesInfo],
+    attr_aspects = ["deps"],
+    fragments = ["cpp"],
+)
+
+lua_proto_library = rule(
+    output_to_genfiles = True,
+    implementation = _lua_proto_rule_impl,
+    attrs = {
+        "deps": attr.label_list(
+            aspects = [_lua_proto_library_aspect],
+            allow_rules = ["proto_library"],
+            providers = [ProtoInfo],
+        ),
+    },
+)
diff --git a/lua/main.c b/lua/main.c
new file mode 100644
index 0000000..7f32209
--- /dev/null
+++ b/lua/main.c
@@ -0,0 +1,96 @@
+// 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 <lauxlib.h>
+#include <lua.h>
+#include <lualib.h>
+#include <signal.h>
+
+#include "lua/upb.h"
+
+lua_State* L;
+
+static void interrupt(lua_State* L, lua_Debug* ar) {
+  (void)ar;
+  lua_sethook(L, NULL, 0, 0);
+  luaL_error(L, "SIGINT");
+}
+
+static void sighandler(int i) {
+  fprintf(stderr, "Signal!\n");
+  signal(i, SIG_DFL);
+  lua_sethook(L, interrupt, LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT, 1);
+}
+
+const char* init =
+    "package.preload['lupb'] = ... "
+    "package.path = '"
+    "./?.lua;"
+    "./third_party/lunit/?.lua;"
+    "external/com_google_protobuf/?.lua;"
+    "external/com_google_protobuf/src/?.lua;"
+    "bazel-bin/?.lua;"
+    "bazel-bin/external/com_google_protobuf/src/?.lua;"
+    "bazel-bin/external/com_google_protobuf/?.lua;"
+    "lua/?.lua;"
+    // These additional paths handle the case where this test is invoked from
+    // the protobuf repo's Bazel workspace.
+    "external/?.lua;"
+    "external/third_party/lunit/?.lua;"
+    "src/?.lua;"
+    "bazel-bin/external/?.lua;"
+    "external/lua/?.lua"
+    "'";
+
+int main(int argc, char** argv) {
+  if (argc < 2) {
+    fprintf(stderr, "missing argument with path to .lua file\n");
+    return 1;
+  }
+
+  int ret = 0;
+  L = luaL_newstate();
+  luaL_openlibs(L);
+  lua_pushcfunction(L, luaopen_lupb);
+  ret = luaL_loadstring(L, init);
+  lua_pushcfunction(L, luaopen_lupb);
+
+  signal(SIGINT, sighandler);
+  ret = ret || lua_pcall(L, 1, LUA_MULTRET, 0) || luaL_dofile(L, argv[1]);
+  signal(SIGINT, SIG_DFL);
+
+  if (ret) {
+    fprintf(stderr, "error testing Lua: %s\n", lua_tostring(L, -1));
+    ret = 1;
+  }
+
+  lua_close(L);
+  return ret;
+}
diff --git a/lua/msg.c b/lua/msg.c
new file mode 100644
index 0000000..bdec7a2
--- /dev/null
+++ b/lua/msg.c
@@ -0,0 +1,1118 @@
+// 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.
+
+/*
+ * lupb_Message -- Message/Array/Map objects in Lua/C that wrap upb
+ */
+
+#include <float.h>
+#include <math.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lauxlib.h"
+#include "lua/upb.h"
+#include "upb/collections/map.h"
+#include "upb/json/decode.h"
+#include "upb/json/encode.h"
+#include "upb/message/message.h"
+#include "upb/port/def.inc"
+#include "upb/reflection/message.h"
+#include "upb/text/encode.h"
+
+/*
+ * Message/Map/Array objects.  These objects form a directed graph: a message
+ * can contain submessages, arrays, and maps, which can then point to other
+ * messages.  This graph can technically be cyclic, though this is an error and
+ * a cyclic graph cannot be serialized.  So it's better to think of this as a
+ * tree of objects.
+ *
+ * The actual data exists at the upb level (upb_Message, upb_Map, upb_Array),
+ * independently of Lua.  The upb objects contain all the canonical data and
+ * edges between objects.  Lua wrapper objects expose the upb objects to Lua,
+ * but ultimately they are just wrappers.  They pass through all reads and
+ * writes to the underlying upb objects.
+ *
+ * Each upb object lives in a upb arena.  We have a Lua object to wrap the upb
+ * arena, but arenas are never exposed to the user.  The Lua arena object just
+ * serves to own the upb arena and free it at the proper time, once the Lua GC
+ * has determined that there are no more references to anything that lives in
+ * that arena.  All wrapper objects strongly reference the arena to which they
+ * belong.
+ *
+ * A global object cache stores a mapping of C pointer (upb_Message*,
+ * upb_Array*, upb_Map*) to a corresponding Lua wrapper.  These references are
+ * weak so that the wrappers can be collected if they are no longer needed.  A
+ * new wrapper object can always be recreated later.
+ *
+ *                          +-----+
+ *            lupb_Arena    |cache|-weak-+
+ *                 |  ^     +-----+      |
+ *                 |  |                  V
+ * Lua level       |  +------------lupb_Message
+ * ----------------|-----------------|------------------------------------------
+ * upb level       |                 |
+ *                 |            +----V----------------------------------+
+ *                 +->upb_Arena | upb_Message  ...(empty arena storage) |
+ *                              +---------------------------------------+
+ *
+ * If the user creates a reference between two objects that have different
+ * arenas, we need to fuse the two arenas together, so that the blocks will
+ * outlive both arenas.
+ *
+ *                 +-------------------------->(fused)<----------------+
+ *                 |                                                   |
+ *                 V                           +-----+                 V
+ *            lupb_Arena                +-weak-|cache|-weak-+     lupb_Arena
+ *                 |  ^                 |      +-----+      |        ^  |
+ *                 |  |                 V                   V        |  |
+ * Lua level       |  +------------lupb_Message        lupb_Message--+  |
+ * ----------------|-----------------|----------------------|-----------|------
+ * upb level       |                 |                      |           |
+ *                 |            +----V--------+        +----V--------+  V
+ *                 +->upb_Arena | upb_Message |        | upb_Message | upb_Arena
+ *                              +------|------+        +--^----------+
+ *                                     +------------------+
+ * Key invariants:
+ *   1. every wrapper references the arena that contains it.
+ *   2. every fused arena includes all arenas that own upb objects reachable
+ *      from that arena.  In other words, when a wrapper references an arena,
+ *      this is sufficient to ensure that any upb object reachable from that
+ *      wrapper will stay alive.
+ *
+ * Additionally, every message object contains a strong reference to the
+ * corresponding Descriptor object.  Likewise, array/map objects reference a
+ * Descriptor object if they are typed to store message values.
+ */
+
+#define LUPB_ARENA "lupb.arena"
+#define LUPB_ARRAY "lupb.array"
+#define LUPB_MAP "lupb.map"
+#define LUPB_MSG "lupb.msg"
+
+#define LUPB_ARENA_INDEX 1
+#define LUPB_MSGDEF_INDEX 2 /* For msg, and map/array that store msg */
+
+static void lupb_Message_Newmsgwrapper(lua_State* L, int narg,
+                                       upb_MessageValue val);
+static upb_Message* lupb_msg_check(lua_State* L, int narg);
+
+static upb_CType lupb_checkfieldtype(lua_State* L, int narg) {
+  uint32_t n = lupb_checkuint32(L, narg);
+  bool ok = n >= kUpb_CType_Bool && n <= kUpb_CType_Bytes;
+  luaL_argcheck(L, ok, narg, "invalid field type");
+  return n;
+}
+
+char cache_key;
+
+/* lupb_cacheinit()
+ *
+ * Creates the global cache used by lupb_cacheget() and lupb_cacheset().
+ */
+static void lupb_cacheinit(lua_State* L) {
+  /* Create our object cache. */
+  lua_newtable(L);
+
+  /* Cache metatable gives the cache weak values */
+  lua_createtable(L, 0, 1);
+  lua_pushstring(L, "v");
+  lua_setfield(L, -2, "__mode");
+  lua_setmetatable(L, -2);
+
+  /* Set cache in the registry. */
+  lua_rawsetp(L, LUA_REGISTRYINDEX, &cache_key);
+}
+
+/* lupb_cacheget()
+ *
+ * Pushes cache[key] and returns true if this key is present in the cache.
+ * Otherwise returns false and leaves nothing on the stack.
+ */
+static bool lupb_cacheget(lua_State* L, const void* key) {
+  if (key == NULL) {
+    lua_pushnil(L);
+    return true;
+  }
+
+  lua_rawgetp(L, LUA_REGISTRYINDEX, &cache_key);
+  lua_rawgetp(L, -1, key);
+  if (lua_isnil(L, -1)) {
+    lua_pop(L, 2); /* Pop table, nil. */
+    return false;
+  } else {
+    lua_replace(L, -2); /* Replace cache table. */
+    return true;
+  }
+}
+
+/* lupb_cacheset()
+ *
+ * Sets cache[key] = val, where "val" is the value at the top of the stack.
+ * Does not pop the value.
+ */
+static void lupb_cacheset(lua_State* L, const void* key) {
+  lua_rawgetp(L, LUA_REGISTRYINDEX, &cache_key);
+  lua_pushvalue(L, -2);
+  lua_rawsetp(L, -2, key);
+  lua_pop(L, 1); /* Pop table. */
+}
+
+/* lupb_Arena *****************************************************************/
+
+/* lupb_Arena only exists to wrap a upb_Arena.  It is never exposed to users; it
+ * is an internal memory management detail.  Other wrapper objects refer to this
+ * object from their userdata to keep the arena-owned data alive.
+ */
+
+typedef struct {
+  upb_Arena* arena;
+} lupb_Arena;
+
+static upb_Arena* lupb_Arena_check(lua_State* L, int narg) {
+  lupb_Arena* a = luaL_checkudata(L, narg, LUPB_ARENA);
+  return a->arena;
+}
+
+upb_Arena* lupb_Arena_pushnew(lua_State* L) {
+  lupb_Arena* a = lupb_newuserdata(L, sizeof(lupb_Arena), 1, LUPB_ARENA);
+  a->arena = upb_Arena_New();
+  return a->arena;
+}
+
+/**
+ * lupb_Arena_Fuse()
+ *
+ * Merges |from| into |to| so that there is a single arena group that contains
+ * both, and both arenas will point at this new table. */
+static void lupb_Arena_Fuse(lua_State* L, int to, int from) {
+  upb_Arena* to_arena = lupb_Arena_check(L, to);
+  upb_Arena* from_arena = lupb_Arena_check(L, from);
+  upb_Arena_Fuse(to_arena, from_arena);
+}
+
+static void lupb_Arena_Fuseobjs(lua_State* L, int to, int from) {
+  lua_getiuservalue(L, to, LUPB_ARENA_INDEX);
+  lua_getiuservalue(L, from, LUPB_ARENA_INDEX);
+  lupb_Arena_Fuse(L, lua_absindex(L, -2), lua_absindex(L, -1));
+  lua_pop(L, 2);
+}
+
+static int lupb_Arena_gc(lua_State* L) {
+  upb_Arena* a = lupb_Arena_check(L, 1);
+  upb_Arena_Free(a);
+  return 0;
+}
+
+static const struct luaL_Reg lupb_Arena_mm[] = {{"__gc", lupb_Arena_gc},
+                                                {NULL, NULL}};
+
+/* lupb_Arenaget()
+ *
+ * Returns the arena from the given message, array, or map object.
+ */
+static upb_Arena* lupb_Arenaget(lua_State* L, int narg) {
+  upb_Arena* arena;
+  lua_getiuservalue(L, narg, LUPB_ARENA_INDEX);
+  arena = lupb_Arena_check(L, -1);
+  lua_pop(L, 1);
+  return arena;
+}
+
+/* upb <-> Lua type conversion ************************************************/
+
+/* Whether string data should be copied into the containing arena.  We can
+ * avoid a copy if the string data is only needed temporarily (like for a map
+ * lookup).
+ */
+typedef enum {
+  LUPB_COPY, /* Copy string data into the arena. */
+  LUPB_REF   /* Reference the Lua copy of the string data. */
+} lupb_copy_t;
+
+/**
+ * lupb_tomsgval()
+ *
+ * Converts the given Lua value |narg| to a upb_MessageValue.
+ */
+static upb_MessageValue lupb_tomsgval(lua_State* L, upb_CType type, int narg,
+                                      int container, lupb_copy_t copy) {
+  upb_MessageValue ret;
+  switch (type) {
+    case kUpb_CType_Int32:
+    case kUpb_CType_Enum:
+      ret.int32_val = lupb_checkint32(L, narg);
+      break;
+    case kUpb_CType_Int64:
+      ret.int64_val = lupb_checkint64(L, narg);
+      break;
+    case kUpb_CType_UInt32:
+      ret.uint32_val = lupb_checkuint32(L, narg);
+      break;
+    case kUpb_CType_UInt64:
+      ret.uint64_val = lupb_checkuint64(L, narg);
+      break;
+    case kUpb_CType_Double:
+      ret.double_val = lupb_checkdouble(L, narg);
+      break;
+    case kUpb_CType_Float:
+      ret.float_val = lupb_checkfloat(L, narg);
+      break;
+    case kUpb_CType_Bool:
+      ret.bool_val = lupb_checkbool(L, narg);
+      break;
+    case kUpb_CType_String:
+    case kUpb_CType_Bytes: {
+      size_t len;
+      const char* ptr = lupb_checkstring(L, narg, &len);
+      switch (copy) {
+        case LUPB_COPY: {
+          upb_Arena* arena = lupb_Arenaget(L, container);
+          char* data = upb_Arena_Malloc(arena, len);
+          memcpy(data, ptr, len);
+          ret.str_val = upb_StringView_FromDataAndSize(data, len);
+          break;
+        }
+        case LUPB_REF:
+          ret.str_val = upb_StringView_FromDataAndSize(ptr, len);
+          break;
+      }
+      break;
+    }
+    case kUpb_CType_Message:
+      ret.msg_val = lupb_msg_check(L, narg);
+      /* Typecheck message. */
+      lua_getiuservalue(L, container, LUPB_MSGDEF_INDEX);
+      lua_getiuservalue(L, narg, LUPB_MSGDEF_INDEX);
+      luaL_argcheck(L, lua_rawequal(L, -1, -2), narg, "message type mismatch");
+      lua_pop(L, 2);
+      break;
+  }
+  return ret;
+}
+
+void lupb_pushmsgval(lua_State* L, int container, upb_CType type,
+                     upb_MessageValue val) {
+  switch (type) {
+    case kUpb_CType_Int32:
+    case kUpb_CType_Enum:
+      lupb_pushint32(L, val.int32_val);
+      return;
+    case kUpb_CType_Int64:
+      lupb_pushint64(L, val.int64_val);
+      return;
+    case kUpb_CType_UInt32:
+      lupb_pushuint32(L, val.uint32_val);
+      return;
+    case kUpb_CType_UInt64:
+      lupb_pushuint64(L, val.uint64_val);
+      return;
+    case kUpb_CType_Double:
+      lua_pushnumber(L, val.double_val);
+      return;
+    case kUpb_CType_Float:
+      lua_pushnumber(L, val.float_val);
+      return;
+    case kUpb_CType_Bool:
+      lua_pushboolean(L, val.bool_val);
+      return;
+    case kUpb_CType_String:
+    case kUpb_CType_Bytes:
+      lua_pushlstring(L, val.str_val.data, val.str_val.size);
+      return;
+    case kUpb_CType_Message:
+      assert(container);
+      if (!lupb_cacheget(L, val.msg_val)) {
+        lupb_Message_Newmsgwrapper(L, container, val);
+      }
+      return;
+  }
+  LUPB_UNREACHABLE();
+}
+
+/* lupb_array *****************************************************************/
+
+typedef struct {
+  upb_Array* arr;
+  upb_CType type;
+} lupb_array;
+
+static lupb_array* lupb_array_check(lua_State* L, int narg) {
+  return luaL_checkudata(L, narg, LUPB_ARRAY);
+}
+
+/**
+ * lupb_array_checkindex()
+ *
+ * Checks the array index at Lua stack index |narg| to verify that it is an
+ * integer between 1 and |max|, inclusively.  Also corrects it to be zero-based
+ * for C.
+ */
+static int lupb_array_checkindex(lua_State* L, int narg, uint32_t max) {
+  uint32_t n = lupb_checkuint32(L, narg);
+  luaL_argcheck(L, n != 0 && n <= max, narg, "invalid array index");
+  return n - 1; /* Lua uses 1-based indexing. */
+}
+
+/* lupb_array Public API */
+
+/* lupb_Array_New():
+ *
+ * Handles:
+ *   Array(upb.TYPE_INT32)
+ *   Array(message_type)
+ */
+static int lupb_Array_New(lua_State* L) {
+  int arg_count = lua_gettop(L);
+  lupb_array* larray;
+  upb_Arena* arena;
+
+  if (lua_type(L, 1) == LUA_TNUMBER) {
+    upb_CType type = lupb_checkfieldtype(L, 1);
+    larray = lupb_newuserdata(L, sizeof(*larray), 1, LUPB_ARRAY);
+    larray->type = type;
+  } else {
+    lupb_MessageDef_check(L, 1);
+    larray = lupb_newuserdata(L, sizeof(*larray), 2, LUPB_ARRAY);
+    larray->type = kUpb_CType_Message;
+    lua_pushvalue(L, 1);
+    lua_setiuservalue(L, -2, LUPB_MSGDEF_INDEX);
+  }
+
+  arena = lupb_Arena_pushnew(L);
+  lua_setiuservalue(L, -2, LUPB_ARENA_INDEX);
+
+  larray->arr = upb_Array_New(arena, larray->type);
+  lupb_cacheset(L, larray->arr);
+
+  if (arg_count > 1) {
+    /* Set initial fields from table. */
+    int msg = arg_count + 1;
+    lua_pushnil(L);
+    while (lua_next(L, 2) != 0) {
+      lua_pushvalue(L, -2); /* now stack is key, val, key */
+      lua_insert(L, -3);    /* now stack is key, key, val */
+      lua_settable(L, msg);
+    }
+  }
+
+  return 1;
+}
+
+/* lupb_Array_Newindex():
+ *
+ * Handles:
+ *   array[idx] = val
+ *
+ * idx can be within the array or one past the end to extend.
+ */
+static int lupb_Array_Newindex(lua_State* L) {
+  lupb_array* larray = lupb_array_check(L, 1);
+  size_t size = upb_Array_Size(larray->arr);
+  uint32_t n = lupb_array_checkindex(L, 2, size + 1);
+  upb_MessageValue msgval = lupb_tomsgval(L, larray->type, 3, 1, LUPB_COPY);
+
+  if (n == size) {
+    upb_Array_Append(larray->arr, msgval, lupb_Arenaget(L, 1));
+  } else {
+    upb_Array_Set(larray->arr, n, msgval);
+  }
+
+  if (larray->type == kUpb_CType_Message) {
+    lupb_Arena_Fuseobjs(L, 1, 3);
+  }
+
+  return 0; /* 1 for chained assignments? */
+}
+
+/* lupb_array_index():
+ *
+ * Handles:
+ *   array[idx] -> val
+ *
+ * idx must be within the array.
+ */
+static int lupb_array_index(lua_State* L) {
+  lupb_array* larray = lupb_array_check(L, 1);
+  size_t size = upb_Array_Size(larray->arr);
+  uint32_t n = lupb_array_checkindex(L, 2, size);
+  upb_MessageValue val = upb_Array_Get(larray->arr, n);
+
+  lupb_pushmsgval(L, 1, larray->type, val);
+
+  return 1;
+}
+
+/* lupb_array_len():
+ *
+ * Handles:
+ *   #array -> len
+ */
+static int lupb_array_len(lua_State* L) {
+  lupb_array* larray = lupb_array_check(L, 1);
+  lua_pushnumber(L, upb_Array_Size(larray->arr));
+  return 1;
+}
+
+static const struct luaL_Reg lupb_array_mm[] = {
+    {"__index", lupb_array_index},
+    {"__len", lupb_array_len},
+    {"__newindex", lupb_Array_Newindex},
+    {NULL, NULL}};
+
+/* lupb_map *******************************************************************/
+
+typedef struct {
+  upb_Map* map;
+  upb_CType key_type;
+  upb_CType value_type;
+} lupb_map;
+
+#define MAP_MSGDEF_INDEX 1
+
+static lupb_map* lupb_map_check(lua_State* L, int narg) {
+  return luaL_checkudata(L, narg, LUPB_MAP);
+}
+
+/* lupb_map Public API */
+
+/**
+ * lupb_Map_New
+ *
+ * Handles:
+ *   new_map = upb.Map(key_type, value_type)
+ *   new_map = upb.Map(key_type, value_msgdef)
+ */
+static int lupb_Map_New(lua_State* L) {
+  upb_Arena* arena;
+  lupb_map* lmap;
+
+  if (lua_type(L, 2) == LUA_TNUMBER) {
+    lmap = lupb_newuserdata(L, sizeof(*lmap), 1, LUPB_MAP);
+    lmap->value_type = lupb_checkfieldtype(L, 2);
+  } else {
+    lupb_MessageDef_check(L, 2);
+    lmap = lupb_newuserdata(L, sizeof(*lmap), 2, LUPB_MAP);
+    lmap->value_type = kUpb_CType_Message;
+    lua_pushvalue(L, 2);
+    lua_setiuservalue(L, -2, MAP_MSGDEF_INDEX);
+  }
+
+  arena = lupb_Arena_pushnew(L);
+  lua_setiuservalue(L, -2, LUPB_ARENA_INDEX);
+
+  lmap->key_type = lupb_checkfieldtype(L, 1);
+  lmap->map = upb_Map_New(arena, lmap->key_type, lmap->value_type);
+  lupb_cacheset(L, lmap->map);
+
+  return 1;
+}
+
+/**
+ * lupb_map_index
+ *
+ * Handles:
+ *   map[key]
+ */
+static int lupb_map_index(lua_State* L) {
+  lupb_map* lmap = lupb_map_check(L, 1);
+  upb_MessageValue key = lupb_tomsgval(L, lmap->key_type, 2, 1, LUPB_REF);
+  upb_MessageValue val;
+
+  if (upb_Map_Get(lmap->map, key, &val)) {
+    lupb_pushmsgval(L, 1, lmap->value_type, val);
+  } else {
+    lua_pushnil(L);
+  }
+
+  return 1;
+}
+
+/**
+ * lupb_map_len
+ *
+ * Handles:
+ *   map_len = #map
+ */
+static int lupb_map_len(lua_State* L) {
+  lupb_map* lmap = lupb_map_check(L, 1);
+  lua_pushnumber(L, upb_Map_Size(lmap->map));
+  return 1;
+}
+
+/**
+ * lupb_Map_Newindex
+ *
+ * Handles:
+ *   map[key] = val
+ *   map[key] = nil  # to remove from map
+ */
+static int lupb_Map_Newindex(lua_State* L) {
+  lupb_map* lmap = lupb_map_check(L, 1);
+  upb_Map* map = lmap->map;
+  upb_MessageValue key = lupb_tomsgval(L, lmap->key_type, 2, 1, LUPB_REF);
+
+  if (lua_isnil(L, 3)) {
+    upb_Map_Delete(map, key, NULL);
+  } else {
+    upb_MessageValue val = lupb_tomsgval(L, lmap->value_type, 3, 1, LUPB_COPY);
+    upb_Map_Set(map, key, val, lupb_Arenaget(L, 1));
+    if (lmap->value_type == kUpb_CType_Message) {
+      lupb_Arena_Fuseobjs(L, 1, 3);
+    }
+  }
+
+  return 0;
+}
+
+static int lupb_MapIterator_Next(lua_State* L) {
+  int map = lua_upvalueindex(2);
+  size_t* iter = lua_touserdata(L, lua_upvalueindex(1));
+  lupb_map* lmap = lupb_map_check(L, map);
+
+  upb_MessageValue key, val;
+  if (upb_Map_Next(lmap->map, &key, &val, iter)) {
+    lupb_pushmsgval(L, map, lmap->key_type, key);
+    lupb_pushmsgval(L, map, lmap->value_type, val);
+    return 2;
+  } else {
+    return 0;
+  }
+}
+
+/**
+ * lupb_map_pairs()
+ *
+ * Handles:
+ *   pairs(map)
+ */
+static int lupb_map_pairs(lua_State* L) {
+  size_t* iter = lua_newuserdata(L, sizeof(*iter));
+  lupb_map_check(L, 1);
+
+  *iter = kUpb_Map_Begin;
+  lua_pushvalue(L, 1);
+
+  /* Upvalues are [iter, lupb_map]. */
+  lua_pushcclosure(L, &lupb_MapIterator_Next, 2);
+
+  return 1;
+}
+
+/* upb_mapiter ]]] */
+
+static const struct luaL_Reg lupb_map_mm[] = {{"__index", lupb_map_index},
+                                              {"__len", lupb_map_len},
+                                              {"__newindex", lupb_Map_Newindex},
+                                              {"__pairs", lupb_map_pairs},
+                                              {NULL, NULL}};
+
+/* lupb_Message
+ * *******************************************************************/
+
+typedef struct {
+  upb_Message* msg;
+} lupb_Message;
+
+/* lupb_Message helpers */
+
+static upb_Message* lupb_msg_check(lua_State* L, int narg) {
+  lupb_Message* msg = luaL_checkudata(L, narg, LUPB_MSG);
+  return msg->msg;
+}
+
+static const upb_MessageDef* lupb_Message_Getmsgdef(lua_State* L, int msg) {
+  lua_getiuservalue(L, msg, LUPB_MSGDEF_INDEX);
+  const upb_MessageDef* m = lupb_MessageDef_check(L, -1);
+  lua_pop(L, 1);
+  return m;
+}
+
+static const upb_FieldDef* lupb_msg_tofield(lua_State* L, int msg, int field) {
+  size_t len;
+  const char* fieldname = luaL_checklstring(L, field, &len);
+  const upb_MessageDef* m = lupb_Message_Getmsgdef(L, msg);
+  return upb_MessageDef_FindFieldByNameWithSize(m, fieldname, len);
+}
+
+static const upb_FieldDef* lupb_msg_checkfield(lua_State* L, int msg,
+                                               int field) {
+  const upb_FieldDef* f = lupb_msg_tofield(L, msg, field);
+  if (f == NULL) {
+    luaL_error(L, "no such field '%s'", lua_tostring(L, field));
+  }
+  return f;
+}
+
+upb_Message* lupb_msg_pushnew(lua_State* L, int narg) {
+  const upb_MessageDef* m = lupb_MessageDef_check(L, narg);
+  lupb_Message* lmsg = lupb_newuserdata(L, sizeof(lupb_Message), 2, LUPB_MSG);
+  upb_Arena* arena = lupb_Arena_pushnew(L);
+
+  lua_setiuservalue(L, -2, LUPB_ARENA_INDEX);
+  lua_pushvalue(L, 1);
+  lua_setiuservalue(L, -2, LUPB_MSGDEF_INDEX);
+
+  lmsg->msg = upb_Message_New(upb_MessageDef_MiniTable(m), arena);
+  lupb_cacheset(L, lmsg->msg);
+  return lmsg->msg;
+}
+
+/**
+ * lupb_Message_Newmsgwrapper()
+ *
+ * Creates a new wrapper for a message, copying the arena and msgdef references
+ * from |narg| (which should be an array or map).
+ */
+static void lupb_Message_Newmsgwrapper(lua_State* L, int narg,
+                                       upb_MessageValue val) {
+  lupb_Message* lmsg = lupb_newuserdata(L, sizeof(*lmsg), 2, LUPB_MSG);
+  lmsg->msg = (upb_Message*)val.msg_val; /* XXX: cast isn't great. */
+  lupb_cacheset(L, lmsg->msg);
+
+  /* Copy both arena and msgdef into the wrapper. */
+  lua_getiuservalue(L, narg, LUPB_ARENA_INDEX);
+  lua_setiuservalue(L, -2, LUPB_ARENA_INDEX);
+  lua_getiuservalue(L, narg, LUPB_MSGDEF_INDEX);
+  lua_setiuservalue(L, -2, LUPB_MSGDEF_INDEX);
+}
+
+/**
+ * lupb_Message_Newud()
+ *
+ * Creates the Lua userdata for a new wrapper object, adding a reference to
+ * the msgdef if necessary.
+ */
+static void* lupb_Message_Newud(lua_State* L, int narg, size_t size,
+                                const char* type, const upb_FieldDef* f) {
+  if (upb_FieldDef_CType(f) == kUpb_CType_Message) {
+    /* Wrapper needs a reference to the msgdef. */
+    void* ud = lupb_newuserdata(L, size, 2, type);
+    lua_getiuservalue(L, narg, LUPB_MSGDEF_INDEX);
+    lupb_MessageDef_pushsubmsgdef(L, f);
+    lua_setiuservalue(L, -2, LUPB_MSGDEF_INDEX);
+    return ud;
+  } else {
+    return lupb_newuserdata(L, size, 1, type);
+  }
+}
+
+/**
+ * lupb_Message_Newwrapper()
+ *
+ * Creates a new Lua wrapper object to wrap the given array, map, or message.
+ */
+static void lupb_Message_Newwrapper(lua_State* L, int narg,
+                                    const upb_FieldDef* f,
+                                    upb_MutableMessageValue val) {
+  if (upb_FieldDef_IsMap(f)) {
+    const upb_MessageDef* entry = upb_FieldDef_MessageSubDef(f);
+    const upb_FieldDef* key_f =
+        upb_MessageDef_FindFieldByNumber(entry, kUpb_MapEntry_KeyFieldNumber);
+    const upb_FieldDef* val_f =
+        upb_MessageDef_FindFieldByNumber(entry, kUpb_MapEntry_ValueFieldNumber);
+    lupb_map* lmap =
+        lupb_Message_Newud(L, narg, sizeof(*lmap), LUPB_MAP, val_f);
+    lmap->key_type = upb_FieldDef_CType(key_f);
+    lmap->value_type = upb_FieldDef_CType(val_f);
+    lmap->map = val.map;
+  } else if (upb_FieldDef_IsRepeated(f)) {
+    lupb_array* larr =
+        lupb_Message_Newud(L, narg, sizeof(*larr), LUPB_ARRAY, f);
+    larr->type = upb_FieldDef_CType(f);
+    larr->arr = val.array;
+  } else {
+    lupb_Message* lmsg =
+        lupb_Message_Newud(L, narg, sizeof(*lmsg), LUPB_MSG, f);
+    lmsg->msg = val.msg;
+  }
+
+  /* Copy arena ref to new wrapper.  This may be a different arena than the
+   * underlying data was originally constructed from, but if so both arenas
+   * must be in the same group. */
+  lua_getiuservalue(L, narg, LUPB_ARENA_INDEX);
+  lua_setiuservalue(L, -2, LUPB_ARENA_INDEX);
+
+  lupb_cacheset(L, val.msg);
+}
+
+/**
+ * lupb_msg_typechecksubmsg()
+ *
+ * Typechecks the given array, map, or msg against this upb_FieldDef.
+ */
+static void lupb_msg_typechecksubmsg(lua_State* L, int narg, int msgarg,
+                                     const upb_FieldDef* f) {
+  /* Typecheck this map's msgdef against this message field. */
+  lua_getiuservalue(L, narg, LUPB_MSGDEF_INDEX);
+  lua_getiuservalue(L, msgarg, LUPB_MSGDEF_INDEX);
+  lupb_MessageDef_pushsubmsgdef(L, f);
+  luaL_argcheck(L, lua_rawequal(L, -1, -2), narg, "message type mismatch");
+  lua_pop(L, 2);
+}
+
+/* lupb_Message Public API */
+
+/**
+ * lupb_MessageDef_call
+ *
+ * Handles:
+ *   new_msg = MessageClass()
+ *   new_msg = MessageClass{foo = "bar", baz = 3, quux = {foo = 3}}
+ */
+int lupb_MessageDef_call(lua_State* L) {
+  int arg_count = lua_gettop(L);
+  lupb_msg_pushnew(L, 1);
+
+  if (arg_count > 1) {
+    /* Set initial fields from table. */
+    int msg = arg_count + 1;
+    lua_pushnil(L);
+    while (lua_next(L, 2) != 0) {
+      lua_pushvalue(L, -2); /* now stack is key, val, key */
+      lua_insert(L, -3);    /* now stack is key, key, val */
+      lua_settable(L, msg);
+    }
+  }
+
+  return 1;
+}
+
+/**
+ * lupb_msg_index
+ *
+ * Handles:
+ *   msg.foo
+ *   msg["foo"]
+ *   msg[field_descriptor]  # (for extensions) (TODO)
+ */
+static int lupb_msg_index(lua_State* L) {
+  upb_Message* msg = lupb_msg_check(L, 1);
+  const upb_FieldDef* f = lupb_msg_checkfield(L, 1, 2);
+
+  if (upb_FieldDef_IsRepeated(f) || upb_FieldDef_IsSubMessage(f)) {
+    /* Wrapped type; get or create wrapper. */
+    upb_Arena* arena = upb_FieldDef_IsRepeated(f) ? lupb_Arenaget(L, 1) : NULL;
+    upb_MutableMessageValue val = upb_Message_Mutable(msg, f, arena);
+    if (!lupb_cacheget(L, val.msg)) {
+      lupb_Message_Newwrapper(L, 1, f, val);
+    }
+  } else {
+    /* Value type, just push value and return .*/
+    upb_MessageValue val = upb_Message_GetFieldByDef(msg, f);
+    lupb_pushmsgval(L, 0, upb_FieldDef_CType(f), val);
+  }
+
+  return 1;
+}
+
+/**
+ * lupb_Message_Newindex()
+ *
+ * Handles:
+ *   msg.foo = bar
+ *   msg["foo"] = bar
+ *   msg[field_descriptor] = bar  # (for extensions) (TODO)
+ */
+static int lupb_Message_Newindex(lua_State* L) {
+  upb_Message* msg = lupb_msg_check(L, 1);
+  const upb_FieldDef* f = lupb_msg_checkfield(L, 1, 2);
+  upb_MessageValue msgval;
+  bool merge_arenas = true;
+
+  if (upb_FieldDef_IsMap(f)) {
+    lupb_map* lmap = lupb_map_check(L, 3);
+    const upb_MessageDef* entry = upb_FieldDef_MessageSubDef(f);
+    const upb_FieldDef* key_f =
+        upb_MessageDef_FindFieldByNumber(entry, kUpb_MapEntry_KeyFieldNumber);
+    const upb_FieldDef* val_f =
+        upb_MessageDef_FindFieldByNumber(entry, kUpb_MapEntry_ValueFieldNumber);
+    upb_CType key_type = upb_FieldDef_CType(key_f);
+    upb_CType value_type = upb_FieldDef_CType(val_f);
+    luaL_argcheck(L, lmap->key_type == key_type, 3, "key type mismatch");
+    luaL_argcheck(L, lmap->value_type == value_type, 3, "value type mismatch");
+    if (value_type == kUpb_CType_Message) {
+      lupb_msg_typechecksubmsg(L, 3, 1, val_f);
+    }
+    msgval.map_val = lmap->map;
+  } else if (upb_FieldDef_IsRepeated(f)) {
+    lupb_array* larr = lupb_array_check(L, 3);
+    upb_CType type = upb_FieldDef_CType(f);
+    luaL_argcheck(L, larr->type == type, 3, "array type mismatch");
+    if (type == kUpb_CType_Message) {
+      lupb_msg_typechecksubmsg(L, 3, 1, f);
+    }
+    msgval.array_val = larr->arr;
+  } else if (upb_FieldDef_IsSubMessage(f)) {
+    upb_Message* msg = lupb_msg_check(L, 3);
+    lupb_msg_typechecksubmsg(L, 3, 1, f);
+    msgval.msg_val = msg;
+  } else {
+    msgval = lupb_tomsgval(L, upb_FieldDef_CType(f), 3, 1, LUPB_COPY);
+    merge_arenas = false;
+  }
+
+  if (merge_arenas) {
+    lupb_Arena_Fuseobjs(L, 1, 3);
+  }
+
+  upb_Message_SetFieldByDef(msg, f, msgval, lupb_Arenaget(L, 1));
+
+  /* Return the new value for chained assignments. */
+  lua_pushvalue(L, 3);
+  return 1;
+}
+
+/**
+ * lupb_msg_tostring()
+ *
+ * Handles:
+ *   tostring(msg)
+ *   print(msg)
+ *   etc.
+ */
+static int lupb_msg_tostring(lua_State* L) {
+  upb_Message* msg = lupb_msg_check(L, 1);
+  const upb_MessageDef* m;
+  char buf[1024];
+  size_t size;
+
+  lua_getiuservalue(L, 1, LUPB_MSGDEF_INDEX);
+  m = lupb_MessageDef_check(L, -1);
+
+  size = upb_TextEncode(msg, m, NULL, 0, buf, sizeof(buf));
+
+  if (size < sizeof(buf)) {
+    lua_pushlstring(L, buf, size);
+  } else {
+    char* ptr = malloc(size + 1);
+    upb_TextEncode(msg, m, NULL, 0, ptr, size + 1);
+    lua_pushlstring(L, ptr, size);
+    free(ptr);
+  }
+
+  return 1;
+}
+
+static const struct luaL_Reg lupb_msg_mm[] = {
+    {"__index", lupb_msg_index},
+    {"__newindex", lupb_Message_Newindex},
+    {"__tostring", lupb_msg_tostring},
+    {NULL, NULL}};
+
+/* lupb_Message toplevel
+ * **********************************************************/
+
+static int lupb_getoptions(lua_State* L, int narg) {
+  int options = 0;
+  if (lua_gettop(L) >= narg) {
+    size_t len = lua_rawlen(L, narg);
+    for (size_t i = 1; i <= len; i++) {
+      lua_rawgeti(L, narg, i);
+      options |= lupb_checkuint32(L, -1);
+      lua_pop(L, 1);
+    }
+  }
+  return options;
+}
+
+/**
+ * lupb_decode()
+ *
+ * Handles:
+ *   msg = upb.decode(MessageClass, bin_string)
+ */
+static int lupb_decode(lua_State* L) {
+  size_t len;
+  const upb_MessageDef* m = lupb_MessageDef_check(L, 1);
+  const char* pb = lua_tolstring(L, 2, &len);
+  const upb_MiniTable* layout = upb_MessageDef_MiniTable(m);
+  upb_Message* msg = lupb_msg_pushnew(L, 1);
+  upb_Arena* arena = lupb_Arenaget(L, -1);
+  char* buf;
+
+  /* Copy input data to arena, message will reference it. */
+  buf = upb_Arena_Malloc(arena, len);
+  memcpy(buf, pb, len);
+
+  upb_DecodeStatus status = upb_Decode(buf, len, msg, layout, NULL,
+                                       kUpb_DecodeOption_AliasString, arena);
+
+  if (status != kUpb_DecodeStatus_Ok) {
+    lua_pushstring(L, "Error decoding protobuf.");
+    return lua_error(L);
+  }
+
+  return 1;
+}
+
+/**
+ * lupb_Encode()
+ *
+ * Handles:
+ *   bin_string = upb.encode(msg)
+ */
+static int lupb_Encode(lua_State* L) {
+  const upb_Message* msg = lupb_msg_check(L, 1);
+  const upb_MessageDef* m = lupb_Message_Getmsgdef(L, 1);
+  const upb_MiniTable* layout = upb_MessageDef_MiniTable(m);
+  int options = lupb_getoptions(L, 2);
+  upb_Arena* arena = lupb_Arena_pushnew(L);
+  char* buf;
+  size_t size;
+  upb_EncodeStatus status =
+      upb_Encode(msg, (const void*)layout, options, arena, &buf, &size);
+  if (status != kUpb_EncodeStatus_Ok) {
+    lua_pushstring(L, "Error encoding protobuf.");
+    return lua_error(L);
+  }
+
+  lua_pushlstring(L, buf, size);
+
+  return 1;
+}
+
+/**
+ * lupb_jsondecode()
+ *
+ * Handles:
+ *   text_string = upb.json_decode(MessageClass, json_str,
+ * {upb.JSONDEC_IGNOREUNKNOWN})
+ */
+static int lupb_jsondecode(lua_State* L) {
+  size_t len;
+  const upb_MessageDef* m = lupb_MessageDef_check(L, 1);
+  const char* json = lua_tolstring(L, 2, &len);
+  int options = lupb_getoptions(L, 3);
+  upb_Message* msg;
+  upb_Arena* arena;
+  upb_Status status;
+
+  msg = lupb_msg_pushnew(L, 1);
+  arena = lupb_Arenaget(L, -1);
+  upb_Status_Clear(&status);
+  upb_JsonDecode(json, len, msg, m, NULL, options, arena, &status);
+  lupb_checkstatus(L, &status);
+
+  return 1;
+}
+
+/**
+ * lupb_jsonencode()
+ *
+ * Handles:
+ *   text_string = upb.json_encode(msg, {upb.JSONENC_EMITDEFAULTS})
+ */
+static int lupb_jsonencode(lua_State* L) {
+  upb_Message* msg = lupb_msg_check(L, 1);
+  const upb_MessageDef* m = lupb_Message_Getmsgdef(L, 1);
+  int options = lupb_getoptions(L, 2);
+  char buf[1024];
+  size_t size;
+  upb_Status status;
+
+  upb_Status_Clear(&status);
+  size = upb_JsonEncode(msg, m, NULL, options, buf, sizeof(buf), &status);
+  lupb_checkstatus(L, &status);
+
+  if (size < sizeof(buf)) {
+    lua_pushlstring(L, buf, size);
+  } else {
+    char* ptr = malloc(size + 1);
+    upb_JsonEncode(msg, m, NULL, options, ptr, size + 1, &status);
+    lupb_checkstatus(L, &status);
+    lua_pushlstring(L, ptr, size);
+    free(ptr);
+  }
+
+  return 1;
+}
+
+/**
+ * lupb_textencode()
+ *
+ * Handles:
+ *   text_string = upb.text_encode(msg, {upb.TXTENC_SINGLELINE})
+ */
+static int lupb_textencode(lua_State* L) {
+  upb_Message* msg = lupb_msg_check(L, 1);
+  const upb_MessageDef* m = lupb_Message_Getmsgdef(L, 1);
+  int options = lupb_getoptions(L, 2);
+  char buf[1024];
+  size_t size;
+
+  size = upb_TextEncode(msg, m, NULL, options, buf, sizeof(buf));
+
+  if (size < sizeof(buf)) {
+    lua_pushlstring(L, buf, size);
+  } else {
+    char* ptr = malloc(size + 1);
+    upb_TextEncode(msg, m, NULL, options, ptr, size + 1);
+    lua_pushlstring(L, ptr, size);
+    free(ptr);
+  }
+
+  return 1;
+}
+
+static void lupb_setfieldi(lua_State* L, const char* field, int i) {
+  lua_pushinteger(L, i);
+  lua_setfield(L, -2, field);
+}
+
+static const struct luaL_Reg lupb_msg_toplevel_m[] = {
+    {"Array", lupb_Array_New},        {"Map", lupb_Map_New},
+    {"decode", lupb_decode},          {"encode", lupb_Encode},
+    {"json_decode", lupb_jsondecode}, {"json_encode", lupb_jsonencode},
+    {"text_encode", lupb_textencode}, {NULL, NULL}};
+
+void lupb_msg_registertypes(lua_State* L) {
+  lupb_setfuncs(L, lupb_msg_toplevel_m);
+
+  lupb_register_type(L, LUPB_ARENA, NULL, lupb_Arena_mm);
+  lupb_register_type(L, LUPB_ARRAY, NULL, lupb_array_mm);
+  lupb_register_type(L, LUPB_MAP, NULL, lupb_map_mm);
+  lupb_register_type(L, LUPB_MSG, NULL, lupb_msg_mm);
+
+  lupb_setfieldi(L, "TXTENC_SINGLELINE", UPB_TXTENC_SINGLELINE);
+  lupb_setfieldi(L, "TXTENC_SKIPUNKNOWN", UPB_TXTENC_SKIPUNKNOWN);
+  lupb_setfieldi(L, "TXTENC_NOSORT", UPB_TXTENC_NOSORT);
+
+  lupb_setfieldi(L, "ENCODE_DETERMINISTIC", kUpb_EncodeOption_Deterministic);
+  lupb_setfieldi(L, "ENCODE_SKIPUNKNOWN", kUpb_EncodeOption_SkipUnknown);
+
+  lupb_setfieldi(L, "JSONENC_EMITDEFAULTS", upb_JsonEncode_EmitDefaults);
+  lupb_setfieldi(L, "JSONENC_PROTONAMES", upb_JsonEncode_UseProtoNames);
+
+  lupb_setfieldi(L, "JSONDEC_IGNOREUNKNOWN", upb_JsonDecode_IgnoreUnknown);
+
+  lupb_cacheinit(L);
+}
diff --git a/lua/test.proto b/lua/test.proto
new file mode 100644
index 0000000..92bcd1c
--- /dev/null
+++ b/lua/test.proto
@@ -0,0 +1,98 @@
+// 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";
+
+import "google/protobuf/timestamp.proto";
+
+package upb_lua_test;
+
+message MapTest {
+  map<string, double> map_string_double = 1;
+}
+
+message PackedTest {
+  repeated bool bool_packed = 1 [packed = true];
+  repeated int32 i32_packed = 2 [packed = true];
+  repeated int64 i64_packed = 3 [packed = true];
+  repeated fixed32 f32_packed = 4 [packed = true];
+  repeated fixed64 f64_packed = 5 [packed = true];
+}
+
+message UnpackedTest {
+  repeated bool bool_packed = 1 [packed = false];
+  repeated int32 i32_packed = 2 [packed = false];
+  repeated int64 i64_packed = 3 [packed = false];
+  repeated fixed32 f32_packed = 4 [packed = false];
+  repeated fixed64 f64_packed = 5 [packed = false];
+}
+
+message TestLargeFieldNumber {
+  optional int32 i32 = 456214797;
+}
+
+message TestTimestamp {
+  optional google.protobuf.Timestamp ts = 1;
+}
+
+message HelloRequest {
+  optional uint32 id = 1;
+  optional uint32 random_name_a0 = 2;
+  optional uint32 random_name_a1 = 3;
+  optional uint32 random_name_a2 = 4;
+  optional uint32 random_name_a3 = 5;
+  optional uint32 random_name_a4 = 6;
+  optional uint32 random_name_a5 = 7;
+  optional uint32 random_name_a6 = 8;
+  optional uint32 random_name_a7 = 9;
+  optional uint32 random_name_a8 = 10;
+  optional uint32 random_name_a9 = 11;
+  optional uint32 random_name_b0 = 12;
+  optional uint32 random_name_b1 = 13;
+  optional uint32 random_name_b2 = 14;
+  optional uint32 random_name_b3 = 15;
+  optional uint32 random_name_b4 = 16;
+  optional uint32 random_name_b5 = 17;
+  optional uint32 random_name_b6 = 18;
+  optional uint32 random_name_b7 = 19;
+  optional uint32 random_name_b8 = 20;
+  optional uint32 random_name_b9 = 21;
+  optional uint32 random_name_c0 = 22;
+  optional uint32 random_name_c1 = 23;
+  optional uint32 random_name_c2 = 24;
+  optional uint32 random_name_c3 = 25;
+  optional uint32 random_name_c4 = 26;
+  optional uint32 random_name_c5 = 27;
+  optional uint32 random_name_c6 = 28;
+  optional uint32 random_name_c7 = 29;
+  optional uint32 random_name_c8 = 30;
+  optional uint32 random_name_c9 = 31;
+  optional string version = 32;
+}
diff --git a/lua/test_upb.lua b/lua/test_upb.lua
new file mode 100644
index 0000000..8ebf82b
--- /dev/null
+++ b/lua/test_upb.lua
@@ -0,0 +1,852 @@
+--[[--------------------------------------------------------------------------
+
+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.
+
+--]]--------------------------------------------------------------------------
+
+local upb = require "lupb"
+local lunit = require "lunit"
+local upb_test = require "lua.test_pb"
+local test_messages_proto3 = require "google.protobuf.test_messages_proto3_pb"
+local test_messages_proto2 = require "google.protobuf.test_messages_proto2_pb"
+local descriptor = require "google.protobuf.descriptor_pb"
+local empty = require "google.protobuf.empty_pb"
+
+if _VERSION >= 'Lua 5.2' then
+  _ENV = lunit.module("testupb", "seeall")
+else
+  module("testupb", lunit.testcase, package.seeall)
+end
+
+function iter_to_array(iter)
+  local arr = {}
+  for v in iter do
+    arr[#arr + 1] = v
+  end
+  return arr
+end
+
+function test_def_readers()
+  local m = test_messages_proto3.TestAllTypesProto3
+  assert_equal("TestAllTypesProto3", m:name())
+  assert_equal("protobuf_test_messages.proto3.TestAllTypesProto3", m:full_name())
+
+  -- field
+  local f = m:field("optional_int32")
+  local f2 = m:field(1)
+  assert_equal(f, f2)
+  assert_equal(1, f:number())
+  assert_equal("optional_int32", f:name())
+  assert_equal(upb.LABEL_OPTIONAL, f:label())
+  assert_equal(upb.DESCRIPTOR_TYPE_INT32, f:descriptor_type())
+  assert_equal(upb.TYPE_INT32, f:type())
+  assert_nil(f:containing_oneof())
+  assert_equal(m, f:containing_type())
+  assert_equal(0, f:default())
+  local message_field_count = 0
+  for field in m:fields() do
+    message_field_count = message_field_count + 1
+  end
+  assert_equal(message_field_count, #m)
+
+  local message_oneof_count = 0
+  for oneof in m:oneofs() do
+    message_oneof_count = message_oneof_count + 1
+  end
+  assert_equal(message_oneof_count, m:oneof_count())
+
+  -- oneof
+  local o = m:lookup_name("oneof_field")
+  assert_equal("oneof_field", o:name())
+  assert_equal(m, o:containing_type())
+  local oneof_field_count = 0
+  for field in o:fields() do
+    oneof_field_count = oneof_field_count + 1
+  end
+  assert_equal(oneof_field_count, #o)
+
+  -- enum
+  local e = test_messages_proto3['TestAllTypesProto3.NestedEnum']
+  assert_true(#e > 3 and #e < 10)
+  assert_equal(2, e:value("BAZ"):number())
+end
+
+function test_msg_map()
+  msg = test_messages_proto3.TestAllTypesProto3()
+  msg.map_int32_int32[5] = 10
+  msg.map_int32_int32[6] = 12
+  assert_equal(10, msg.map_int32_int32[5])
+  assert_equal(12, msg.map_int32_int32[6])
+
+  -- Test overwrite.
+  msg.map_int32_int32[5] = 20
+  assert_equal(20, msg.map_int32_int32[5])
+  assert_equal(12, msg.map_int32_int32[6])
+  msg.map_int32_int32[5] = 10
+
+  -- Test delete.
+  msg.map_int32_int32[5] = nil
+  assert_nil(msg.map_int32_int32[5])
+  assert_equal(12, msg.map_int32_int32[6])
+  msg.map_int32_int32[5] = 10
+
+  local serialized = upb.encode(msg)
+  assert_true(#serialized > 0)
+  local msg2 = upb.decode(test_messages_proto3.TestAllTypesProto3, serialized)
+  assert_equal(10, msg2.map_int32_int32[5])
+  assert_equal(12, msg2.map_int32_int32[6])
+end
+
+function test_map_sorting()
+  function msg_with_int32_entries(start, expand)
+    local msg = test_messages_proto3.TestAllTypesProto3()
+    for i=start,start + 8 do
+      msg.map_int32_int32[i] = i * 2
+    end
+
+    if expand then
+      for i=start+20,200 do
+        msg.map_int32_int32[i] = i
+      end
+      for i=start+20,200 do
+        msg.map_int32_int32[i] = nil
+      end
+    end
+    return msg
+  end
+
+  function msg_with_msg_entries(expand)
+    local msg = test_messages_proto3.TestAllTypesProto3()
+    -- 8! = 40320 possible orderings makes it overwhelmingly likely that two
+    -- random orderings will be different.
+    for i=1,8 do
+      local submsg = test_messages_proto3.TestAllTypesProto3.NestedMessage()
+      submsg.corecursive = msg_with_int32_entries(i, expand)
+      msg.map_string_nested_message[tostring(i)] = submsg
+    end
+
+    expand = false
+    if expand then
+      for i=21,2000 do
+        local submsg = test_messages_proto3.TestAllTypesProto3.NestedMessage()
+        submsg.corecursive = msg_with_int32_entries(i, expand)
+        msg.map_string_nested_message[tostring(i)] = submsg
+      end
+      for i=21,2000 do
+        msg.map_string_nested_message[tostring(i)] = nil
+      end
+    end
+    return msg
+  end
+
+  -- Create two messages with the same contents but (hopefully) different
+  -- map table orderings.
+  local msg = msg_with_msg_entries(false)
+  local msg2 = msg_with_msg_entries(true)
+
+  local text1 = upb.text_encode(msg)
+  local text2 = upb.text_encode(msg2)
+  assert_equal(text1, text2)
+
+  local binary1 = upb.encode(msg, {upb.ENCODE_DETERMINISTIC})
+  local binary2 = upb.encode(msg2, {upb.ENCODE_DETERMINISTIC})
+  assert_equal(binary1, binary2)
+
+  -- Non-sorted map should compare different.
+  local text3 = upb.text_encode(msg, {upb.TXTENC_NOSORT})
+  assert_not_equal(text1, text3)
+
+  local binary3 = upb.encode(msg)
+  assert_not_equal(binary1, binary3)
+end
+
+function test_utf8()
+  local proto2_msg = test_messages_proto2.TestAllTypesProto2()
+  proto2_msg.optional_string = "\xff"
+  local serialized = upb.encode(proto2_msg)
+
+  -- Decoding invalid UTF-8 succeeds in proto2.
+  upb.decode(test_messages_proto2.TestAllTypesProto2, serialized)
+
+  -- Decoding invalid UTF-8 fails in proto2.
+  assert_error_match("Error decoding protobuf", function()
+    upb.decode(test_messages_proto3.TestAllTypesProto3, serialized)
+  end)
+
+  -- TODO: should proto3 accessors also check UTF-8 at set time?
+end
+
+function test_string_double_map()
+  msg = upb_test.MapTest()
+  msg.map_string_double["one"] = 1.0
+  msg.map_string_double["two point five"] = 2.5
+  assert_equal(1, msg.map_string_double["one"])
+  assert_equal(2.5, msg.map_string_double["two point five"])
+
+  -- Test overwrite.
+  msg.map_string_double["one"] = 2
+  assert_equal(2, msg.map_string_double["one"])
+  assert_equal(2.5, msg.map_string_double["two point five"])
+  msg.map_string_double["one"] = 1.0
+
+  -- Test delete.
+  msg.map_string_double["one"] = nil
+  assert_nil(msg.map_string_double["one"])
+  assert_equal(2.5, msg.map_string_double["two point five"])
+  msg.map_string_double["one"] = 1
+
+  local serialized = upb.encode(msg)
+  assert_true(#serialized > 0)
+  local msg2 = upb.decode(upb_test.MapTest, serialized)
+  assert_equal(1, msg2.map_string_double["one"])
+  assert_equal(2.5, msg2.map_string_double["two point five"])
+end
+
+function test_string_double_map()
+  local function fill_msg(msg)
+    msg.i32_packed[1] = 100
+    msg.i32_packed[2] = 200
+    msg.i32_packed[3] = 50000
+
+    msg.i64_packed[1] = 101
+    msg.i64_packed[2] = 201
+    msg.i64_packed[3] = 50001
+
+    msg.f32_packed[1] = 102
+    msg.f32_packed[2] = 202
+    msg.f32_packed[3] = 50002
+
+    msg.f64_packed[1] = 103
+    msg.f64_packed[2] = 203
+    msg.f64_packed[3] = 50003
+  end
+
+  local function check_msg(msg)
+    assert_equal(100, msg.i32_packed[1])
+    assert_equal(200, msg.i32_packed[2])
+    assert_equal(50000, msg.i32_packed[3])
+    assert_equal(3, #msg.i32_packed)
+
+    assert_equal(101, msg.i64_packed[1])
+    assert_equal(201, msg.i64_packed[2])
+    assert_equal(50001, msg.i64_packed[3])
+    assert_equal(3, #msg.i64_packed)
+
+    assert_equal(102, msg.f32_packed[1])
+    assert_equal(202, msg.f32_packed[2])
+    assert_equal(50002, msg.f32_packed[3])
+    assert_equal(3, #msg.f32_packed)
+
+    assert_equal(103, msg.f64_packed[1])
+    assert_equal(203, msg.f64_packed[2])
+    assert_equal(50003, msg.f64_packed[3])
+    assert_equal(3, #msg.f64_packed)
+  end
+
+  local msg = upb_test.PackedTest()
+  fill_msg(msg)
+  check_msg(msg)
+
+  local serialized_packed = upb.encode(msg)
+  local msg2 = upb.decode(upb_test.PackedTest, serialized_packed)
+  local msg3 = upb.decode(upb_test.UnpackedTest, serialized_packed)
+  check_msg(msg2)
+  check_msg(msg3)
+
+  serialized_unpacked = upb.encode(msg3)
+  local msg4 = upb.decode(upb_test.PackedTest, serialized_unpacked)
+  local msg5 = upb.decode(upb_test.PackedTest, serialized_unpacked)
+  check_msg(msg4)
+  check_msg(msg5)
+
+end
+
+function test_msg_string_map()
+  msg = test_messages_proto3.TestAllTypesProto3()
+  msg.map_string_string["foo"] = "bar"
+  msg.map_string_string["baz"] = "quux"
+  assert_nil(msg.map_string_string["abc"])
+  assert_equal("bar", msg.map_string_string["foo"])
+  assert_equal("quux", msg.map_string_string["baz"])
+
+  -- Test overwrite.
+  msg.map_string_string["foo"] = "123"
+  assert_equal("123", msg.map_string_string["foo"])
+  assert_equal("quux", msg.map_string_string["baz"])
+  msg.map_string_string["foo"] = "bar"
+
+  -- Test delete
+  msg.map_string_string["foo"] = nil
+  assert_nil(msg.map_string_string["foo"])
+  assert_equal("quux", msg.map_string_string["baz"])
+  msg.map_string_string["foo"] = "bar"
+
+  local serialized = upb.encode(msg)
+  assert_true(#serialized > 0)
+  local msg2 = upb.decode(test_messages_proto3.TestAllTypesProto3, serialized)
+  assert_equal("bar", msg2.map_string_string["foo"])
+  assert_equal("quux", msg2.map_string_string["baz"])
+end
+
+function test_msg_array()
+  msg = test_messages_proto3.TestAllTypesProto3()
+
+  assert_not_nil(msg.repeated_int32)
+  assert_equal(msg.repeated_int32, msg.repeated_int32)
+  assert_equal(0, #msg.repeated_int32)
+
+  msg.repeated_int32[1] = 2
+  assert_equal(1, #msg.repeated_int32);
+  assert_equal(2, msg.repeated_int32[1]);
+
+  -- Can't assign a scalar; array is expected.
+  assert_error_match("lupb.array expected", function() msg.repeated_int32 = 5 end)
+
+  -- Can't assign array of the wrong type.
+  local function assign_int64()
+    msg.repeated_int32 = upb.Array(upb.TYPE_INT64)
+  end
+  assert_error_match("array type mismatch", assign_int64)
+
+  local arr = upb.Array(upb.TYPE_INT32)
+  arr[1] = 6
+  assert_equal(1, #arr)
+  msg.repeated_int32 = arr
+  assert_equal(msg.repeated_int32, msg.repeated_int32)
+  assert_equal(arr, msg.repeated_int32)
+  assert_equal(1, #msg.repeated_int32)
+  assert_equal(6, msg.repeated_int32[1])
+
+  -- Can't assign other Lua types.
+  assert_error_match("array expected", function() msg.repeated_int32 = "abc" end)
+  assert_error_match("array expected", function() msg.repeated_int32 = true end)
+  assert_error_match("array expected", function() msg.repeated_int32 = false end)
+  assert_error_match("array expected", function() msg.repeated_int32 = nil end)
+  assert_error_match("array expected", function() msg.repeated_int32 = {} end)
+  assert_error_match("array expected", function() msg.repeated_int32 = print end)
+end
+
+function test_array_append()
+  local arr = upb.Array(upb.TYPE_INT32)
+  for i=1,200000 do
+    arr[i] = i
+  end
+  for i=1,200000 do
+    assert_equal(i, arr[i])
+  end
+end
+
+function test_msg_submsg()
+  --msg = test_messages_proto3.TestAllTypesProto3()
+  msg = test_messages_proto3['TestAllTypesProto3']()
+
+  assert_nil(msg.optional_nested_message)
+
+  -- Can't assign message of the wrong type.
+  local function assign_int64()
+    msg.optional_nested_message = test_messages_proto3.TestAllTypesProto3()
+  end
+  assert_error_match("message type mismatch", assign_int64)
+
+  local nested = test_messages_proto3['TestAllTypesProto3.NestedMessage']()
+  msg.optional_nested_message = nested
+  assert_equal(nested, msg.optional_nested_message)
+
+  -- Can't assign other Lua types.
+  assert_error_match("msg expected", function() msg.optional_nested_message = "abc" end)
+  assert_error_match("msg expected", function() msg.optional_nested_message = true end)
+  assert_error_match("msg expected", function() msg.optional_nested_message = false end)
+  assert_error_match("msg expected", function() msg.optional_nested_message = nil end)
+  assert_error_match("msg expected", function() msg.optional_nested_message = {} end)
+  assert_error_match("msg expected", function() msg.optional_nested_message = print end)
+end
+
+-- Lua 5.1 and 5.2 have slightly different semantics for how a finalizer
+-- can be defined in Lua.
+if _VERSION >= 'Lua 5.2' then
+  function defer(fn)
+    setmetatable({}, { __gc = fn })
+  end
+else
+  function defer(fn)
+    getmetatable(newproxy(true)).__gc = fn
+  end
+end
+
+function test_finalizer()
+  -- Tests that we correctly handle a call into an already-finalized object.
+  -- Collectible objects are finalized in the opposite order of creation.
+  do
+    local t = {}
+    defer(function()
+      assert_error_match("called into dead object", function()
+        -- Generic def call.
+        t[1]:lookup_msg("abc")
+      end)
+    end)
+    t = {
+      upb.DefPool(),
+    }
+  end
+  collectgarbage()
+end
+
+-- in-range of 64-bit types but not exactly representable as double
+local bad64 = 2^68 - 1
+
+local numeric_types = {
+  [upb.TYPE_UINT32] = {
+    valid_val = 2^32 - 1,
+    too_big = 2^32,
+    too_small = -1,
+    other_bad = 5.1
+  },
+  [upb.TYPE_UINT64] = {
+    valid_val = 2^63,
+    too_big = 2^64,
+    too_small = -1,
+    other_bad = bad64
+  },
+  [upb.TYPE_INT32] = {
+    valid_val = 2^31 - 1,
+    too_big = 2^31,
+    too_small = -2^31 - 1,
+    other_bad = 5.1
+  },
+  -- Enums don't exist at a language level in Lua, so we just represent enum
+  -- values as int32s.
+  [upb.TYPE_ENUM] = {
+    valid_val = 2^31 - 1,
+    too_big = 2^31,
+    too_small = -2^31 - 1,
+    other_bad = 5.1
+  },
+  [upb.TYPE_INT64] = {
+    valid_val = 2^62,
+    too_big = 2^63,
+    too_small = -2^64,
+    other_bad = bad64
+  },
+  [upb.TYPE_FLOAT] = {
+    valid_val = 340282306073709652508363335590014353408
+  },
+  [upb.TYPE_DOUBLE] = {
+    valid_val = 10^101
+  },
+}
+
+function test_utf8()
+  local invalid_utf8 = "\xff"
+  local proto2_msg = test_messages_proto2.TestAllTypesProto2{
+    optional_string = invalid_utf8,
+  }
+
+  -- As proto2, invalid UTF-8 parses and serializes fine.
+  local serialized = upb.encode(proto2_msg)
+  local proto2_msg2 = upb.decode(test_messages_proto2.TestAllTypesProto2, serialized)
+
+  -- Decoding as proto3 fails.
+  assert_error(function()
+    upb.decode(test_messages_proto3.TestAllTypesProto3, serialized)
+  end)
+end
+
+function test_msg_primitives()
+  local msg = test_messages_proto3.TestAllTypesProto3{
+    optional_int32 = 10,
+    optional_uint32 = 20,
+    optional_int64 = 30,
+    optional_uint64 = 40,
+    optional_double = 50,
+    optional_float = 60,
+    optional_sint32 = 70,
+    optional_sint64 = 80,
+    optional_fixed32 = 90,
+    optional_fixed64 = 100,
+    optional_sfixed32 = 110,
+    optional_sfixed64 = 120,
+    optional_bool = true,
+    optional_string = "abc",
+    optional_nested_message = test_messages_proto3['TestAllTypesProto3.NestedMessage']{a = 123},
+  }
+
+  -- Attempts to access non-existent fields fail.
+  assert_error_match("no such field", function() msg.no_such = 1 end)
+
+  assert_equal(10, msg.optional_int32)
+  assert_equal(20, msg.optional_uint32)
+  assert_equal(30, msg.optional_int64)
+  assert_equal(40, msg.optional_uint64)
+  assert_equal(50, msg.optional_double)
+  assert_equal(60, msg.optional_float)
+  assert_equal(70, msg.optional_sint32)
+  assert_equal(80, msg.optional_sint64)
+  assert_equal(90, msg.optional_fixed32)
+  assert_equal(100, msg.optional_fixed64)
+  assert_equal(110, msg.optional_sfixed32)
+  assert_equal(120, msg.optional_sfixed64)
+  assert_equal(true, msg.optional_bool)
+  assert_equal("abc", msg.optional_string)
+  assert_equal(123, msg.optional_nested_message.a)
+end
+
+
+function test_string_array()
+  local function test_for_string_type(upb_type)
+    local array = upb.Array(upb_type)
+    assert_equal(0, #array)
+
+    -- 0 is never a valid index in Lua.
+    assert_error_match("array index", function() return array[0] end)
+    -- Past the end of the array.
+    assert_error_match("array index", function() return array[1] end)
+
+    array[1] = "foo"
+    assert_equal("foo", array[1])
+    assert_equal(1, #array)
+    -- Past the end of the array.
+    assert_error_match("array index", function() return array[2] end)
+
+    local array2 = upb.Array(upb_type)
+    assert_equal(0, #array2)
+
+    array[2] = "bar"
+    assert_equal("foo", array[1])
+    assert_equal("bar", array[2])
+    assert_equal(2, #array)
+    -- Past the end of the array.
+    assert_error_match("array index", function() return array[3] end)
+
+    -- Can't assign other Lua types.
+    assert_error_match("Expected string", function() array[3] = 123 end)
+    assert_error_match("Expected string", function() array[3] = true end)
+    assert_error_match("Expected string", function() array[3] = false end)
+    assert_error_match("Expected string", function() array[3] = nil end)
+    assert_error_match("Expected string", function() array[3] = {} end)
+    assert_error_match("Expected string", function() array[3] = print end)
+    assert_error_match("Expected string", function() array[3] = array end)
+  end
+
+  test_for_string_type(upb.TYPE_STRING)
+  test_for_string_type(upb.TYPE_BYTES)
+end
+
+function test_numeric_array()
+  local function test_for_numeric_type(upb_type)
+    local array = upb.Array(upb_type)
+    local vals = numeric_types[upb_type]
+    assert_equal(0, #array)
+
+    -- 0 is never a valid index in Lua.
+    assert_error_match("array index", function() return array[0] end)
+    -- Past the end of the array.
+    assert_error_match("array index", function() return array[1] end)
+
+    array[1] = vals.valid_val
+    assert_equal(vals.valid_val, array[1])
+    assert_equal(1, #array)
+    assert_equal(vals.valid_val, array[1])
+    -- Past the end of the array.
+    assert_error_match("array index", function() return array[2] end)
+
+    array[2] = 10
+    assert_equal(vals.valid_val, array[1])
+    assert_equal(10, array[2])
+    assert_equal(2, #array)
+    -- Past the end of the array.
+    assert_error_match("array index", function() return array[3] end)
+
+    -- Values that are out of range.
+    local errmsg = "not an integer or out of range"
+    if vals.too_small then
+      assert_error_match(errmsg, function() array[3] = vals.too_small end)
+    end
+    if vals.too_big then
+      assert_error_match(errmsg, function() array[3] = vals.too_big end)
+    end
+    if vals.other_bad then
+      assert_error_match(errmsg, function() array[3] = vals.other_bad end)
+    end
+
+    -- Can't assign other Lua types.
+    errmsg = "bad argument #3"
+    assert_error_match(errmsg, function() array[3] = "abc" end)
+    assert_error_match(errmsg, function() array[3] = true end)
+    assert_error_match(errmsg, function() array[3] = false end)
+    assert_error_match(errmsg, function() array[3] = nil end)
+    assert_error_match(errmsg, function() array[3] = {} end)
+    assert_error_match(errmsg, function() array[3] = print end)
+    assert_error_match(errmsg, function() array[3] = array end)
+  end
+
+  for k in pairs(numeric_types) do
+    test_for_numeric_type(k)
+  end
+end
+
+function test_numeric_map()
+  local function test_for_numeric_types(key_type, val_type)
+    local map = upb.Map(key_type, val_type)
+    local key_vals = numeric_types[key_type]
+    local val_vals = numeric_types[val_type]
+
+    assert_equal(0, #map)
+
+    -- Unset keys return nil
+    assert_nil(map[key_vals.valid_val])
+
+    map[key_vals.valid_val] = val_vals.valid_val
+    assert_equal(1, #map)
+    assert_equal(val_vals.valid_val, map[key_vals.valid_val])
+
+    i = 0
+    for k, v in pairs(map) do
+      assert_equal(key_vals.valid_val, k)
+      assert_equal(val_vals.valid_val, v)
+    end
+
+    -- Out of range key/val
+    local errmsg = "not an integer or out of range"
+    if key_vals.too_small then
+      assert_error_match(errmsg, function() map[key_vals.too_small] = 1 end)
+    end
+    if key_vals.too_big then
+      assert_error_match(errmsg, function() map[key_vals.too_big] = 1 end)
+    end
+    if key_vals.other_bad then
+      assert_error_match(errmsg, function() map[key_vals.other_bad] = 1 end)
+    end
+
+    if val_vals.too_small then
+      assert_error_match(errmsg, function() map[1] = val_vals.too_small end)
+    end
+    if val_vals.too_big then
+      assert_error_match(errmsg, function() map[1] = val_vals.too_big end)
+    end
+    if val_vals.other_bad then
+      assert_error_match(errmsg, function() map[1] = val_vals.other_bad end)
+    end
+  end
+
+  for k in pairs(numeric_types) do
+    for v in pairs(numeric_types) do
+      test_for_numeric_types(k, v)
+    end
+  end
+end
+
+function test_unknown()
+  local bytes = string.rep("\x38\x00", 1000)
+  for i=1,1000 do
+    local msg = upb.decode(test_messages_proto3.TestAllTypesProto3, bytes)
+  end
+end
+
+function test_foo()
+  local defpool = upb.DefPool()
+  local filename = "external/com_google_protobuf/src/google/protobuf/descriptor_proto-descriptor-set.proto.bin"
+  local alternate_filename = "src/google/protobuf/descriptor_proto-descriptor-set.proto.bin"
+  local file = io.open(filename, "rb") or io.open("bazel-bin/" .. filename, "rb") or io.open(alternate_filename, "rb")
+  assert_not_nil(file)
+  local descriptor = file:read("*a")
+  assert_true(#descriptor > 0)
+  defpool:add_set(descriptor)
+  local FileDescriptorSet = defpool:lookup_msg("google.protobuf.FileDescriptorSet")
+  assert_not_nil(FileDescriptorSet)
+  set = FileDescriptorSet()
+  assert_equal(#set.file, 0)
+  assert_error_match("lupb.array expected", function () set.file = 1 end)
+
+  set = upb.decode(FileDescriptorSet, descriptor)
+
+  -- Test that we can at least call this without crashing.
+  set_textformat = tostring(set)
+
+  -- print(set_textformat)
+  assert_equal(#set.file, 1)
+  assert_equal(set.file[1].name, "google/protobuf/descriptor.proto")
+end
+
+function test_descriptor()
+  local defpool = upb.DefPool()
+  local file_proto = descriptor.FileDescriptorProto {
+    name = "test.proto",
+    message_type = upb.Array(descriptor.DescriptorProto, {
+      descriptor.DescriptorProto{
+        name = "ABC",
+      },
+    })
+  }
+  local file = defpool:add_file(upb.encode(file_proto))
+  assert_equal(file:defpool(), defpool)
+end
+
+function test_descriptor_error()
+  local defpool = upb.DefPool()
+  local file = descriptor.FileDescriptorProto()
+  file.name = "test.proto"
+  file.message_type[1] = descriptor.DescriptorProto{
+    name = "ABC"
+  }
+  file.message_type[2] = descriptor.DescriptorProto{
+    name = "BC."
+  }
+  assert_error(function () defpool:add_file(upb.encode(file)) end)
+  assert_nil(defpool:lookup_msg("ABC"))
+end
+
+function test_duplicate_enumval()
+  local defpool = upb.DefPool()
+  local file_proto = descriptor.FileDescriptorProto {
+    name = "test.proto",
+    message_type = upb.Array(descriptor.DescriptorProto, {
+      descriptor.DescriptorProto{
+        name = "ABC",
+      },
+    }),
+    enum_type = upb.Array(descriptor.EnumDescriptorProto, {
+      descriptor.EnumDescriptorProto{
+        name = "MyEnum",
+        value = upb.Array(descriptor.EnumValueDescriptorProto, {
+          descriptor.EnumValueDescriptorProto{
+            name = "ABC",
+            number = 1,
+          }
+        }),
+      },
+    })
+  }
+  assert_error(function () defpool:add_file(upb.encode(file_proto)) end)
+end
+
+function test_duplicate_filename_error()
+  local defpool = upb.DefPool()
+  local file = descriptor.FileDescriptorProto()
+  file.name = "test.proto"
+  defpool:add_file(upb.encode(file))
+  -- Second add with the same filename fails.
+  assert_error(function () defpool:add_file(upb.encode(file)) end)
+end
+
+function test_encode_skipunknown()
+  -- Test that upb.ENCODE_SKIPUNKNOWN does not encode unknown fields.
+  local msg = test_messages_proto3.TestAllTypesProto3{
+    optional_int32 = 10,
+    optional_uint32 = 20,
+    optional_int64 = 30,
+  }
+  -- SKIPUNKNOWN here tests that it does *not* affect regular fields.
+  local serialized = upb.encode(msg, {upb.ENCODE_SKIPUNKNOWN})
+  assert_true(#serialized > 0)
+  local empty_with_unknown = upb.decode(empty.Empty, serialized)
+  assert_true(#upb.encode(empty_with_unknown) > 0)
+  -- Verify that unknown fields are not serialized.
+  assert_true(#upb.encode(empty_with_unknown, {upb.ENCODE_SKIPUNKNOWN}) == 0)
+end
+
+function test_json_emit_defaults()
+  local msg = test_messages_proto3.TestAllTypesProto3()
+  local json = upb.json_encode(msg, {upb.JSONENC_EMITDEFAULTS})
+end
+
+function test_json_locale()
+  local msg = test_messages_proto3.TestAllTypesProto3()
+  msg.optional_double = 1.1
+  local original_locale = os.setlocale(nil)
+  os.setlocale("C")
+  local json = upb.json_encode(msg)
+  os.setlocale("de_DE.utf8")
+  assert_equal(json, upb.json_encode(msg))
+  os.setlocale(original_locale)  -- Restore.
+end
+
+function test_encode_depth_limit()
+  local msg = test_messages_proto3.TestAllTypesProto3()
+  msg.recursive_message = msg
+  assert_error(function() upb.encode(msg) end)
+end
+
+function test_large_field_number()
+  local msg = upb_test.TestLargeFieldNumber()
+  msg.i32 = 5
+  local serialized = upb.encode(msg)
+  local msg2 = upb.decode(upb_test.TestLargeFieldNumber, serialized)
+  assert_equal(msg.i32, msg2.i32)
+end
+
+function test_timestamp_minutes()
+  local msg = upb.json_decode(upb_test.TestTimestamp, '{"ts": "2000-01-01T00:00:00-06:59"}')
+  assert_equal(msg.ts.seconds, 946684800 + ((6 * 60) + 59) * 60)
+end
+
+function test_gc()
+  local top = test_messages_proto3.TestAllTypesProto3()
+  local n = 100
+  local m
+
+  for i=1,n do
+    local inner = test_messages_proto3.TestAllTypesProto3()
+    m = inner
+    for j=1,n do
+      local tmp = m
+      m = test_messages_proto3.TestAllTypesProto3()
+      -- This will cause the arenas to fuse. But we stop referring to the child,
+      -- so the Lua object is eligible for collection (and therefore its original
+      -- arena can be collected too). Only the fusing will keep the C mem alivd.
+      m.recursive_message = tmp
+
+    end
+    top.recursive_message = m
+  end
+
+  collectgarbage()
+
+  for i=1,n do
+    -- Verify we can touch all the messages again and without accessing freed
+    -- memory.
+    m = m.recursive_message
+    assert_not_nil(m)
+  end
+end
+
+function test_b9440()
+  local m = upb_test.HelloRequest()
+  m.id = 8
+  assert_equal(8, m.id)
+  m.version = "1"
+  assert_equal(8, m.id)
+end
+
+local stats = lunit.main()
+
+if stats.failed > 0 or stats.errors > 0 then
+  error("One or more errors in test suite")
+end
diff --git a/lua/upb.c b/lua/upb.c
new file mode 100644
index 0000000..4500fb4
--- /dev/null
+++ b/lua/upb.c
@@ -0,0 +1,261 @@
+// 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.
+
+/*
+ * require("lua") -- A Lua extension for upb.
+ *
+ * Exposes only the core library
+ * (sub-libraries are exposed in other extensions).
+ *
+ * 64-bit woes: Lua can only represent numbers of type lua_Number (which is
+ * double unless the user specifically overrides this).  Doubles can represent
+ * the entire range of 64-bit integers, but lose precision once the integers are
+ * greater than 2^53.
+ *
+ * Lua 5.3 is adding support for integers, which will allow for 64-bit
+ * integers (which can be interpreted as signed or unsigned).
+ *
+ * LuaJIT supports 64-bit signed and unsigned boxed representations
+ * through its "cdata" mechanism, but this is not portable to regular Lua.
+ *
+ * Hopefully Lua 5.3 will come soon enough that we can either use Lua 5.3
+ * integer support or LuaJIT 64-bit cdata for users that need the entire
+ * domain of [u]int64 values.
+ */
+
+#include "lua/upb.h"
+
+#include <float.h>
+#include <math.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lauxlib.h"
+#include "upb/message/message.h"
+
+/* Lua compatibility code *****************************************************/
+
+/* Shims for upcoming Lua 5.3 functionality. */
+static bool lua_isinteger(lua_State* L, int argn) {
+  LUPB_UNUSED(L);
+  LUPB_UNUSED(argn);
+  return false;
+}
+
+/* Utility functions **********************************************************/
+
+void lupb_checkstatus(lua_State* L, upb_Status* s) {
+  if (!upb_Status_IsOk(s)) {
+    lua_pushstring(L, upb_Status_ErrorMessage(s));
+    lua_error(L);
+  }
+}
+
+/* Pushes a new userdata with the given metatable. */
+void* lupb_newuserdata(lua_State* L, size_t size, int n, const char* type) {
+#if LUA_VERSION_NUM >= 504
+  void* ret = lua_newuserdatauv(L, size, n);
+#else
+  void* ret = lua_newuserdata(L, size);
+  lua_createtable(L, 0, n);
+  lua_setuservalue(L, -2);
+#endif
+
+  /* Set metatable. */
+  luaL_getmetatable(L, type);
+  assert(!lua_isnil(L, -1)); /* Should have been created by luaopen_upb. */
+  lua_setmetatable(L, -2);
+
+  return ret;
+}
+
+#if LUA_VERSION_NUM < 504
+int lua_setiuservalue(lua_State* L, int index, int n) {
+  lua_getuservalue(L, index);
+  lua_insert(L, -2);
+  lua_rawseti(L, -2, n);
+  lua_pop(L, 1);
+  return 1;
+}
+
+int lua_getiuservalue(lua_State* L, int index, int n) {
+  lua_getuservalue(L, index);
+  lua_rawgeti(L, -1, n);
+  lua_replace(L, -2);
+  return 1;
+}
+#endif
+
+/* We use this function as the __index metamethod when a type has both methods
+ * and an __index metamethod. */
+int lupb_indexmm(lua_State* L) {
+  /* Look up in __index table (which is a closure param). */
+  lua_pushvalue(L, 2);
+  lua_rawget(L, lua_upvalueindex(1));
+  if (!lua_isnil(L, -1)) {
+    return 1;
+  }
+
+  /* Not found, chain to user __index metamethod. */
+  lua_pushvalue(L, lua_upvalueindex(2));
+  lua_pushvalue(L, 1);
+  lua_pushvalue(L, 2);
+  lua_call(L, 2, 1);
+  return 1;
+}
+
+void lupb_register_type(lua_State* L, const char* name, const luaL_Reg* m,
+                        const luaL_Reg* mm) {
+  luaL_newmetatable(L, name);
+
+  if (mm) {
+    lupb_setfuncs(L, mm);
+  }
+
+  if (m) {
+    lua_createtable(L, 0, 0); /* __index table */
+    lupb_setfuncs(L, m);
+
+    /* Methods go in the mt's __index slot.  If the user also specified an
+     * __index metamethod, use our custom lupb_indexmm() that can check both. */
+    lua_getfield(L, -2, "__index");
+    if (lua_isnil(L, -1)) {
+      lua_pop(L, 1);
+    } else {
+      lua_pushcclosure(L, &lupb_indexmm, 2);
+    }
+    lua_setfield(L, -2, "__index");
+  }
+
+  lua_pop(L, 1); /* The mt. */
+}
+
+/* Scalar type mapping ********************************************************/
+
+/* Functions that convert scalar/primitive values (numbers, strings, bool)
+ * between Lua and C/upb.  Handles type/range checking. */
+
+bool lupb_checkbool(lua_State* L, int narg) {
+  if (!lua_isboolean(L, narg)) {
+    luaL_error(L, "must be true or false");
+  }
+  return lua_toboolean(L, narg);
+}
+
+/* Unlike luaL_checkstring(), this does not allow implicit conversion to
+ * string. */
+const char* lupb_checkstring(lua_State* L, int narg, size_t* len) {
+  if (lua_type(L, narg) != LUA_TSTRING) {
+    luaL_error(L, "Expected string");
+  }
+
+  return lua_tolstring(L, narg, len);
+}
+
+/* Unlike luaL_checkinteger, these do not implicitly convert from string or
+ * round an existing double value.  We allow floating-point input, but only if
+ * the actual value is integral. */
+#define INTCHECK(type, ctype, min, max)                                        \
+  ctype lupb_check##type(lua_State* L, int narg) {                             \
+    double n;                                                                  \
+    if (lua_isinteger(L, narg)) {                                              \
+      return lua_tointeger(L, narg);                                           \
+    }                                                                          \
+                                                                               \
+    /* Prevent implicit conversion from string. */                             \
+    luaL_checktype(L, narg, LUA_TNUMBER);                                      \
+    n = lua_tonumber(L, narg);                                                 \
+                                                                               \
+    /* Check this double has no fractional part and remains in bounds.         \
+     * Consider INT64_MIN and INT64_MAX:                                       \
+     * 1. INT64_MIN -(2^63) is a power of 2, so this converts to a double.     \
+     * 2. INT64_MAX (2^63 - 1) is not a power of 2, and conversion of          \
+     * out-of-range integer values to a double can lead to undefined behavior. \
+     * On some compilers, this conversion can return 0, but it also can return \
+     * the max value. To deal with this, we can first divide by 2 to prevent   \
+     * the overflow, multiply it back, and add 1 to find the true limit. */    \
+    double i;                                                                  \
+    double max_value = (((double)max / 2) * 2) + 1;                            \
+    if ((modf(n, &i) != 0.0) || n < min || n >= max_value) {                   \
+      luaL_error(L, "number %f was not an integer or out of range for " #type, \
+                 n);                                                           \
+    }                                                                          \
+    return (ctype)n;                                                           \
+  }                                                                            \
+  void lupb_push##type(lua_State* L, ctype val) {                              \
+    /* TODO: push integer for Lua >= 5.3, 64-bit cdata for LuaJIT. */          \
+    /* This is lossy for some [u]int64 values, which isn't great, but */       \
+    /* crashing when we encounter these values seems worse. */                 \
+    lua_pushnumber(L, val);                                                    \
+  }
+
+INTCHECK(int64, int64_t, INT64_MIN, INT64_MAX)
+INTCHECK(int32, int32_t, INT32_MIN, INT32_MAX)
+INTCHECK(uint64, uint64_t, 0, UINT64_MAX)
+INTCHECK(uint32, uint32_t, 0, UINT32_MAX)
+
+double lupb_checkdouble(lua_State* L, int narg) {
+  /* If we were being really hard-nosed here, we'd check whether the input was
+   * an integer that has no precise double representation.  But doubles aren't
+   * generally expected to be exact like integers are, and worse this could
+   * cause data-dependent runtime errors: one run of the program could work fine
+   * because the integer calculations happened to be exactly representable in
+   * double, while the next could crash because of subtly different input. */
+
+  luaL_checktype(L, narg, LUA_TNUMBER); /* lua_tonumber() auto-converts. */
+  return lua_tonumber(L, narg);
+}
+
+float lupb_checkfloat(lua_State* L, int narg) {
+  /* We don't worry about checking whether the input can be exactly converted to
+   * float -- see above. */
+
+  luaL_checktype(L, narg, LUA_TNUMBER); /* lua_tonumber() auto-converts. */
+  return lua_tonumber(L, narg);
+}
+
+void lupb_pushdouble(lua_State* L, double d) { lua_pushnumber(L, d); }
+
+void lupb_pushfloat(lua_State* L, float d) { lua_pushnumber(L, d); }
+
+/* Library entry point ********************************************************/
+
+int luaopen_lupb(lua_State* L) {
+#if LUA_VERSION_NUM == 501
+  const struct luaL_Reg funcs[] = {{NULL, NULL}};
+  luaL_register(L, "upb_c", funcs);
+#else
+  lua_createtable(L, 0, 8);
+#endif
+  lupb_def_registertypes(L);
+  lupb_msg_registertypes(L);
+  return 1; /* Return package table. */
+}
diff --git a/lua/upb.h b/lua/upb.h
new file mode 100644
index 0000000..46ec911
--- /dev/null
+++ b/lua/upb.h
@@ -0,0 +1,135 @@
+// 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.
+
+/*
+ * Shared definitions for upb Lua modules.
+ */
+
+#ifndef UPB_LUA_UPB_H_
+#define UPB_LUA_UPB_H_
+
+#include "lauxlib.h"
+#include "upb/message/message.h"
+#include "upb/reflection/def.h"
+#include "upb/reflection/message.h"
+
+/* Lua changes its API in incompatible ways in every minor release.
+ * This is some shim code to paper over the differences. */
+
+#if LUA_VERSION_NUM == 501
+#define lua_rawlen lua_objlen
+#define lua_setuservalue(L, idx) lua_setfenv(L, idx)
+#define lua_getuservalue(L, idx) lua_getfenv(L, idx)
+#define lupb_setfuncs(L, l) luaL_register(L, NULL, l)
+#elif LUA_VERSION_NUM >= 502 && LUA_VERSION_NUM <= 504
+#define lupb_setfuncs(L, l) luaL_setfuncs(L, l, 0)
+#else
+#error Only Lua 5.1-5.4 are supported
+#endif
+
+/* Create a new userdata with the given type and |n| uservals, which are popped
+ * from the stack to initialize the userdata. */
+void* lupb_newuserdata(lua_State* L, size_t size, int n, const char* type);
+
+#if LUA_VERSION_NUM < 504
+/* Polyfills for this Lua 5.4 function.  Pushes userval |n| for the userdata at
+ * |index|. */
+int lua_setiuservalue(lua_State* L, int index, int n);
+int lua_getiuservalue(lua_State* L, int index, int n);
+#endif
+
+/* Registers a type with the given name, methods, and metamethods. */
+void lupb_register_type(lua_State* L, const char* name, const luaL_Reg* m,
+                        const luaL_Reg* mm);
+
+/* Checks the given upb_Status and throws a Lua error if it is not ok. */
+void lupb_checkstatus(lua_State* L, upb_Status* s);
+
+int luaopen_lupb(lua_State* L);
+
+/* C <-> Lua value conversions. ***********************************************/
+
+/* Custom check/push functions.  Unlike the Lua equivalents, they are pinned to
+ * specific C types (instead of lua_Number, etc), and do not allow any implicit
+ * conversion or data loss. */
+int64_t lupb_checkint64(lua_State* L, int narg);
+int32_t lupb_checkint32(lua_State* L, int narg);
+uint64_t lupb_checkuint64(lua_State* L, int narg);
+uint32_t lupb_checkuint32(lua_State* L, int narg);
+double lupb_checkdouble(lua_State* L, int narg);
+float lupb_checkfloat(lua_State* L, int narg);
+bool lupb_checkbool(lua_State* L, int narg);
+const char* lupb_checkstring(lua_State* L, int narg, size_t* len);
+const char* lupb_checkname(lua_State* L, int narg);
+
+void lupb_pushint64(lua_State* L, int64_t val);
+void lupb_pushint32(lua_State* L, int32_t val);
+void lupb_pushuint64(lua_State* L, uint64_t val);
+void lupb_pushuint32(lua_State* L, uint32_t val);
+
+/** From def.c. ***************************************************************/
+
+const upb_MessageDef* lupb_MessageDef_check(lua_State* L, int narg);
+const upb_EnumDef* lupb_EnumDef_check(lua_State* L, int narg);
+const upb_FieldDef* lupb_FieldDef_check(lua_State* L, int narg);
+upb_DefPool* lupb_DefPool_check(lua_State* L, int narg);
+void lupb_MessageDef_pushsubmsgdef(lua_State* L, const upb_FieldDef* f);
+
+void lupb_def_registertypes(lua_State* L);
+
+/** From msg.c. ***************************************************************/
+
+void lupb_pushmsgval(lua_State* L, int container, upb_CType type,
+                     upb_MessageValue val);
+int lupb_MessageDef_call(lua_State* L);
+upb_Arena* lupb_Arena_pushnew(lua_State* L);
+
+void lupb_msg_registertypes(lua_State* L);
+
+#define lupb_assert(L, predicate) \
+  if (!(predicate))               \
+    luaL_error(L, "internal error: %s, %s:%d ", #predicate, __FILE__, __LINE__);
+
+#define LUPB_UNUSED(var) (void)var
+
+#if defined(__GNUC__) || defined(__clang__)
+#define LUPB_UNREACHABLE()   \
+  do {                       \
+    assert(0);               \
+    __builtin_unreachable(); \
+  } while (0)
+#else
+#define LUPB_UNREACHABLE() \
+  do {                     \
+    assert(0);             \
+  } while (0)
+#endif
+
+#endif /* UPB_LUA_UPB_H_ */
diff --git a/lua/upb.lua b/lua/upb.lua
new file mode 100644
index 0000000..3533309
--- /dev/null
+++ b/lua/upb.lua
@@ -0,0 +1,58 @@
+--[[--------------------------------------------------------------------------
+
+  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.
+
+--]]--------------------------------------------------------------------------
+
+local upb = require("lupb")
+
+upb.generated_pool = upb.DefPool()
+
+local module_metatable = {
+  __index = function(t, k)
+    local package = t._filedef:package()
+    if package then
+      k = package .. "." .. k
+    end
+    local pool = upb.generated_pool
+    local def = pool:lookup_msg(k) or pool:lookup_enum(k)
+    local v = nil
+    if def and def:file():name() == t._filedef:name() then
+      v = def
+      t[k] = v
+    end
+    return v
+  end
+}
+
+function upb._generated_module(desc_string)
+  local file = upb.generated_pool:add_file(desc_string)
+  local module = {_filedef = file}
+  setmetatable(module, module_metatable)
+  return module
+end
+
+return upb
diff --git a/lua/upbc.cc b/lua/upbc.cc
new file mode 100644
index 0000000..4a274f2
--- /dev/null
+++ b/lua/upbc.cc
@@ -0,0 +1,139 @@
+// 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 "google/protobuf/descriptor.pb.h"
+#include "absl/strings/str_replace.h"
+#include "absl/strings/string_view.h"
+#include "absl/strings/substitute.h"
+#include "google/protobuf/compiler/code_generator.h"
+#include "google/protobuf/compiler/plugin.h"
+#include "google/protobuf/descriptor.h"
+#include "google/protobuf/io/printer.h"
+
+namespace protoc = ::google::protobuf::compiler;
+namespace protobuf = ::google::protobuf;
+
+class LuaGenerator : public protoc::CodeGenerator {
+  bool Generate(const protobuf::FileDescriptor* file,
+                const std::string& parameter, protoc::GeneratorContext* context,
+                std::string* error) const override;
+};
+
+static 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));
+}
+
+static std::string Filename(const protobuf::FileDescriptor* file) {
+  return StripExtension(file->name()) + "_pb.lua";
+}
+
+static std::string ModuleName(const protobuf::FileDescriptor* file) {
+  std::string ret = StripExtension(file->name()) + "_pb";
+  return absl::StrReplaceAll(ret, {{"/", "."}});
+}
+
+static void PrintHexDigit(char digit, protobuf::io::Printer* printer) {
+  char text;
+  if (digit < 10) {
+    text = '0' + digit;
+  } else {
+    text = 'A' + (digit - 10);
+  }
+  printer->WriteRaw(&text, 1);
+}
+
+static void PrintString(int max_cols, absl::string_view* str,
+                        protobuf::io::Printer* printer) {
+  printer->Print("\'");
+  while (max_cols > 0 && !str->empty()) {
+    char ch = (*str)[0];
+    if (ch == '\\') {
+      printer->PrintRaw("\\\\");
+      max_cols--;
+    } else if (ch == '\'') {
+      printer->PrintRaw("\\'");
+      max_cols--;
+    } else if (isprint(ch)) {
+      printer->WriteRaw(&ch, 1);
+      max_cols--;
+    } else {
+      unsigned char byte = ch;
+      printer->PrintRaw("\\x");
+      PrintHexDigit(byte >> 4, printer);
+      PrintHexDigit(byte & 15, printer);
+      max_cols -= 4;
+    }
+    str->remove_prefix(1);
+  }
+  printer->Print("\'");
+}
+
+bool LuaGenerator::Generate(const protobuf::FileDescriptor* file,
+                            const std::string& /* parameter */,
+                            protoc::GeneratorContext* context,
+                            std::string* /* error */) const {
+  std::string filename = Filename(file);
+  protobuf::io::ZeroCopyOutputStream* out = context->Open(filename);
+  protobuf::io::Printer printer(out, '$');
+
+  for (int i = 0; i < file->dependency_count(); i++) {
+    const protobuf::FileDescriptor* dep = file->dependency(i);
+    printer.Print("require('$name$')\n", "name", ModuleName(dep));
+  }
+
+  printer.Print("local upb = require('upb')\n");
+
+  protobuf::FileDescriptorProto file_proto;
+  file->CopyTo(&file_proto);
+  std::string file_data;
+  file_proto.SerializeToString(&file_data);
+
+  printer.Print("local descriptor = table.concat({\n");
+  absl::string_view data(file_data);
+  while (!data.empty()) {
+    printer.Print("  ");
+    PrintString(72, &data, &printer);
+    printer.Print(",\n");
+  }
+  printer.Print("})\n");
+
+  printer.Print("return upb._generated_module(descriptor)\n");
+
+  return true;
+}
+
+int main(int argc, char** argv) {
+  LuaGenerator generator;
+  return google::protobuf::compiler::PluginMain(argc, argv, &generator);
+}