diff --git a/VM/include/lua.h b/VM/include/lua.h index 8e7d6468..86b296df 100644 --- a/VM/include/lua.h +++ b/VM/include/lua.h @@ -65,6 +65,7 @@ enum lua_Type LUA_TLIGHTUSERDATA, + LUA_TFATUSERDATA, LUA_TNUMBER, LUA_TVECTOR, @@ -166,6 +167,7 @@ LUA_API void lua_pushcfunction( lua_State* L, lua_CFunction fn, const char* debugname = NULL, int nup = 0, lua_Continuation cont = NULL); LUA_API void lua_pushboolean(lua_State* L, int b); LUA_API void lua_pushlightuserdata(lua_State* L, void* p); +LUA_API void lua_pushfatuserdata(lua_State* L); LUA_API int lua_pushthread(lua_State* L); /* diff --git a/VM/include/luaconf.h b/VM/include/luaconf.h index aa008a24..1702a087 100644 --- a/VM/include/luaconf.h +++ b/VM/include/luaconf.h @@ -102,6 +102,14 @@ /* maximum number of captures supported by pattern matching */ #define LUA_MAXCAPTURES 32 +/* +@@ LUAU_FATUSERDATA_WORDS defines the amount of extra space embedded in +@* fatuserdata values. +** This inflates every value in the VM, so use it carefully. +** At least 1 is required to store the `vector` value type. +*/ +#define LUAU_FATUSERDATA_WORDS 1 + /* }================================================================== */ /* Default number printing format and the string length limit */ diff --git a/VM/src/lapi.cpp b/VM/src/lapi.cpp index c0c78b93..3de7fc6b 100644 --- a/VM/src/lapi.cpp +++ b/VM/src/lapi.cpp @@ -447,6 +447,8 @@ int lua_objlen(lua_State* L, int idx) return tsvalue(o)->len; case LUA_TUSERDATA: return uvalue(o)->len; + case LUA_TFATUSERDATA: + return LUAU_FATUSERDATA_WORDS; case LUA_TTABLE: return luaH_getn(hvalue(o)); case LUA_TNUMBER: @@ -473,6 +475,7 @@ void* lua_touserdata(lua_State* L, int idx) case LUA_TUSERDATA: return uvalue(o)->data; case LUA_TLIGHTUSERDATA: + case LUA_TFATUSERDATA: return pvalue(o); default: return NULL; @@ -625,6 +628,15 @@ void lua_pushlightuserdata(lua_State* L, void* p) return; } +const int UNINITIALIZED_FATUSERDATA[LUAU_FATUSERDATA_WORDS] = {}; + +void lua_pushfatuserdata(lua_State* L) +{ + setmpvalue(L->top, 0, &UNINITIALIZED_FATUSERDATA); + api_incr_top(L); + return; +} + int lua_pushthread(lua_State* L) { luaC_checkthreadsleep(L); @@ -740,6 +752,9 @@ int lua_getmetatable(lua_State* L, int objindex) case LUA_TUSERDATA: mt = uvalue(obj)->metatable; break; + case LUA_TFATUSERDATA: + mt = (Table*)(mpvalue(obj)); + break; default: mt = L->global->mt[ttype(obj)]; break; @@ -864,6 +879,13 @@ int lua_setmetatable(lua_State* L, int objindex) luaC_objbarrier(L, uvalue(obj), mt); break; } + case LUA_TFATUSERDATA: + { + mpvalue(obj) = (void*)mt; + if (mt) + luaC_objbarrier(L, uvalue(obj), mt); + break; + } default: { L->global->mt[ttype(obj)] = mt; diff --git a/VM/src/lgc.cpp b/VM/src/lgc.cpp index 6553009f..3785019a 100644 --- a/VM/src/lgc.cpp +++ b/VM/src/lgc.cpp @@ -42,6 +42,8 @@ LUAU_FASTFLAG(LuauArrayBoundary) checkconsistency(o); \ if (iscollectable(o) && iswhite(gcvalue(o))) \ reallymarkobject(g, gcvalue(o)); \ + else if (ttisfatuserdata(o) && mpvalue(o) && iswhite((GCObject*)mpvalue(o))) \ + reallymarkobject(g, (GCObject*)mpvalue(o)); \ } #define markobject(g, t) \ diff --git a/VM/src/lmem.cpp b/VM/src/lmem.cpp index d8b265cb..1b6f8e35 100644 --- a/VM/src/lmem.cpp +++ b/VM/src/lmem.cpp @@ -33,11 +33,16 @@ #define ABISWITCH(x64, ms32, gcc32) (sizeof(void*) == 8 ? x64 : ms32) #endif +// Padding makes it difficult to verify these statically, we'd basically be mirroring the compiler's layout algorithm +// So instead, only assert these when LUAU_FATUSERDATA_WORDS is 1, which is the go-to default/embedded configuration +#if LUAU_FATUSERDATA_WORDS == 1 static_assert(sizeof(TValue) == ABISWITCH(16, 16, 16), "size mismatch for value"); +static_assert(sizeof(LuaNode) == ABISWITCH(32, 32, 32), "size mismatch for table entry"); +#endif + static_assert(offsetof(TString, data) == ABISWITCH(24, 20, 20), "size mismatch for string header"); static_assert(offsetof(Udata, data) == ABISWITCH(24, 16, 16), "size mismatch for userdata header"); static_assert(sizeof(Table) == ABISWITCH(56, 36, 36), "size mismatch for table header"); -static_assert(sizeof(LuaNode) == ABISWITCH(32, 32, 32), "size mismatch for table entry"); const size_t kSizeClasses = LUA_SIZECLASSES; const size_t kMaxSmallSize = 512; diff --git a/VM/src/lobject.h b/VM/src/lobject.h index c5f2e2f4..08242ce1 100644 --- a/VM/src/lobject.h +++ b/VM/src/lobject.h @@ -47,7 +47,7 @@ typedef union typedef struct lua_TValue { Value value; - int extra; + int extra[LUAU_FATUSERDATA_WORDS]; int tt; } TValue; @@ -61,6 +61,7 @@ typedef struct lua_TValue #define ttisuserdata(o) (ttype(o) == LUA_TUSERDATA) #define ttisthread(o) (ttype(o) == LUA_TTHREAD) #define ttislightuserdata(o) (ttype(o) == LUA_TLIGHTUSERDATA) +#define ttisfatuserdata(o) (ttype(o) == LUA_TFATUSERDATA) #define ttisvector(o) (ttype(o) == LUA_TVECTOR) #define ttisupval(o) (ttype(o) == LUA_TUPVAL) @@ -68,6 +69,7 @@ typedef struct lua_TValue #define ttype(o) ((o)->tt) #define gcvalue(o) check_exp(iscollectable(o), (o)->value.gc) #define pvalue(o) check_exp(ttislightuserdata(o), (o)->value.p) +#define mpvalue(o) check_exp(ttisfatuserdata(o), (o)->value.p) #define nvalue(o) check_exp(ttisnumber(o), (o)->value.n) #define vvalue(o) check_exp(ttisvector(o), (o)->value.v) #define tsvalue(o) check_exp(ttisstring(o), &(o)->value.gc->ts) @@ -122,6 +124,14 @@ typedef struct lua_TValue i_o->tt = LUA_TLIGHTUSERDATA; \ } +#define setmpvalue(obj, mt, data) \ + { \ + TValue* i_o = (obj); \ + i_o->value.p = (void*)(mt); \ + memcpy(&i_o->extra, data, LUAU_FATUSERDATA_WORDS * sizeof(int)); \ + i_o->tt = LUA_TLIGHTUSERDATA; \ + } + #define setbvalue(obj, x) \ { \ TValue* i_o = (obj); \ @@ -364,7 +374,7 @@ typedef struct Closure typedef struct TKey { ::Value value; - int extra; + int extra[LUAU_FATUSERDATA_WORDS]; unsigned tt : 4; int next : 28; /* for chaining */ } TKey; @@ -381,7 +391,7 @@ typedef struct LuaNode LuaNode* n_ = (node); \ const TValue* i_o = (obj); \ n_->key.value = i_o->value; \ - n_->key.extra = i_o->extra; \ + memcpy(&n_->key.extra, &i_o->extra, LUAU_FATUSERDATA_WORDS * sizeof(int)); \ n_->key.tt = i_o->tt; \ checkliveness(L->global, i_o); \ } @@ -392,7 +402,7 @@ typedef struct LuaNode TValue* i_o = (obj); \ const LuaNode* n_ = (node); \ i_o->value = n_->key.value; \ - i_o->extra = n_->key.extra; \ + memcpy(&i_o->extra, &n_->key.extra, LUAU_FATUSERDATA_WORDS * sizeof(int)); \ i_o->tt = n_->key.tt; \ checkliveness(L->global, i_o); \ } diff --git a/VM/src/ltable.cpp b/VM/src/ltable.cpp index 07d22d59..cbf26802 100644 --- a/VM/src/ltable.cpp +++ b/VM/src/ltable.cpp @@ -32,17 +32,17 @@ LUAU_FASTFLAGVARIABLE(LuauArrayBoundary, false) static_assert(offsetof(LuaNode, val) == 0, "Unexpected Node memory layout, pointer cast in gval2slot is incorrect"); // TKey is bitpacked for memory efficiency so we need to validate bit counts for worst case -static_assert(TKey{{NULL}, 0, LUA_TDEADKEY, 0}.tt == LUA_TDEADKEY, "not enough bits for tt"); -static_assert(TKey{{NULL}, 0, LUA_TNIL, MAXSIZE - 1}.next == MAXSIZE - 1, "not enough bits for next"); -static_assert(TKey{{NULL}, 0, LUA_TNIL, -(MAXSIZE - 1)}.next == -(MAXSIZE - 1), "not enough bits for next"); +static_assert(TKey{{NULL}, {}, LUA_TDEADKEY, 0}.tt == LUA_TDEADKEY, "not enough bits for tt"); +static_assert(TKey{{NULL}, {}, LUA_TNIL, MAXSIZE - 1}.next == MAXSIZE - 1, "not enough bits for next"); +static_assert(TKey{{NULL}, {}, LUA_TNIL, -(MAXSIZE - 1)}.next == -(MAXSIZE - 1), "not enough bits for next"); // reset cache of absent metamethods, cache is updated in luaT_gettm #define invalidateTMcache(t) t->flags = 0 // empty hash data points to dummynode so that we can always dereference it const LuaNode luaH_dummynode = { - {{NULL}, 0, LUA_TNIL}, /* value */ - {{NULL}, 0, LUA_TNIL, 0} /* key */ + {{NULL}, {}, LUA_TNIL}, /* value */ + {{NULL}, {}, LUA_TNIL, 0} /* key */ }; #define dummynode (&luaH_dummynode) diff --git a/VM/src/ltm.cpp b/VM/src/ltm.cpp index a77a7c72..8656d646 100644 --- a/VM/src/ltm.cpp +++ b/VM/src/ltm.cpp @@ -16,6 +16,7 @@ const char* const luaT_typenames[] = { "boolean", + "userdata", "userdata", "number", "vector", @@ -108,6 +109,9 @@ const TValue* luaT_gettmbyobj(lua_State* L, const TValue* o, TMS event) case LUA_TUSERDATA: mt = uvalue(o)->metatable; break; + case LUA_TFATUSERDATA: + mt = (Table*)(mpvalue(o)); + break; default: mt = L->global->mt[ttype(o)]; }