Skip to content

Commit

Permalink
Introduce TypeTag<T> template whose int value differs for each T.
Browse files Browse the repository at this point in the history
This replaces type_tag<T>(), which searched and possibly extended the type_tags
unordered_map at runtime. If we called lua_emplace<T>() from different threads,
that would require locking type_tags.

In contrast, the compiler must instantiate a distinct TypeTag<T> for every
distinct T passed to lua_emplace<T>(), so each gets a distinct value at static
initialization time. No locking is required; no lookup; no allocations.

Add a test to llluamanager_test.cpp to verify that each distinct T passed to
lua_emplace<T>() gets its own TypeTag<T>::value, and that each gets its own
destructor -- but that different lua_emplace<T>() calls with the same T share
the same TypeTag<T>::value and the same destructor.
  • Loading branch information
nat-goodspeed committed Jun 27, 2024
1 parent 6ee98f4 commit 0cc7436
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 18 deletions.
2 changes: 2 additions & 0 deletions indra/llcommon/lua_function.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ const S32 INTERRUPTS_SUSPEND_LIMIT = 100;
#define lua_register(L, n, f) (lua_pushcfunction(L, (f), n), lua_setglobal(L, (n)))
#define lua_rawlen lua_objlen

int DistinctInt::mValues{0};

/*****************************************************************************
* luau namespace
*****************************************************************************/
Expand Down
44 changes: 26 additions & 18 deletions indra/llcommon/lua_function.h
Original file line number Diff line number Diff line change
Expand Up @@ -195,26 +195,34 @@ int name##_luasub::call(lua_State* L)
/*****************************************************************************
* lua_emplace<T>(), lua_toclass<T>()
*****************************************************************************/
namespace {
// Every instance of DistinctInt has a different int value, barring int
// wraparound.
class DistinctInt
{
public:
DistinctInt(): mValue(++mValues) {}
int get() const { return mValue; }
operator int() const { return mValue; }
private:
static int mValues;
int mValue;
};

// If we start engaging lua_emplace<T>() from more than one thread, type_tags
// will need locking.
std::unordered_map<std::type_index, int> type_tags;
namespace {

// find or create a new Luau userdata "tag" for type T
template <typename T>
int type_tag()
struct TypeTag
{
// The first time we encounter a given type T, assign a new distinct tag
// value based on the number of previously-created tags. But avoid tag 0,
// which is evidently the default for userdata objects created without
// explicit tags. Don't try to destroy a nonexistent T object in a random
// userdata object!
auto [entry, created] = type_tags.emplace(std::type_index(typeid(T)), int(type_tags.size()+1));
// Luau only permits up to LUA_UTAG_LIMIT distinct userdata tags (ca. 128)
llassert(entry->second < LUA_UTAG_LIMIT);
return entry->second;
}
// For (std::is_same<T, U>), &TypeTag<T>::value == &TypeTag<U>::value.
// For (! std::is_same<T, U>), &TypeTag<T>::value != &TypeTag<U>::value.
// And every distinct instance of DistinctInt has a distinct value.
// Therefore, TypeTag<T>::value is an int uniquely associated with each
// distinct T.
static DistinctInt value;
};

template <typename T>
DistinctInt TypeTag<T>::value;

} // anonymous namespace

Expand All @@ -233,7 +241,7 @@ template <class T, typename... ARGS>
void lua_emplace(lua_State* L, ARGS&&... args)
{
luaL_checkstack(L, 1, nullptr);
int tag{ type_tag<T>() };
int tag{ TypeTag<T>::value };
if (! lua_getuserdatadtor(L, tag))
{
// We haven't yet told THIS lua_State the destructor to use for this tag.
Expand Down Expand Up @@ -267,7 +275,7 @@ template <class T>
T* lua_toclass(lua_State* L, int index)
{
// get void* pointer to userdata (if that's what it is)
void* ptr{ lua_touserdatatagged(L, index, type_tag<T>()) };
void* ptr{ lua_touserdatatagged(L, index, TypeTag<T>::value) };
// Derive the T* from ptr. If in future lua_emplace() must manually
// align our T* within the Lua-provided void*, adjust accordingly.
return static_cast<T*>(ptr);
Expand Down
35 changes: 35 additions & 0 deletions indra/newview/tests/llluamanager_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -465,4 +465,39 @@ namespace tut
ensure_equals(desc + " count: " + result.asString(), count, -1);
ensure_contains(desc + " result", result.asString(), "terminated");
}

template <typename T>
struct Visible
{
Visible(T name): name(name)
{
LL_INFOS() << "Visible<" << LLError::Log::classname<T>() << ">('" << name << "')" << LL_ENDL;
}
Visible(const Visible&) = delete;
Visible& operator=(const Visible&) = delete;
~Visible()
{
LL_INFOS() << "~Visible<" << LLError::Log::classname<T>() << ">('" << name << "')" << LL_ENDL;
}
T name;
};

template<> template<>
void object::test<9>()
{
set_test_name("track distinct lua_emplace<T>() types");
LuaState L;
lua_emplace<Visible<std::string>>(L, "std::string 0");
int st0tag = lua_userdatatag(L, -1);
lua_emplace<Visible<const char*>>(L, "const char* 0");
int cp0tag = lua_userdatatag(L, -1);
lua_emplace<Visible<std::string>>(L, "std::string 1");
int st1tag = lua_userdatatag(L, -1);
lua_emplace<Visible<const char*>>(L, "const char* 1");
int cp1tag = lua_userdatatag(L, -1);
lua_settop(L, 0);
ensure_equals("lua_emplace<std::string>() tags diverge", st0tag, st1tag);
ensure_equals("lua_emplace<const char*>() tags diverge", cp0tag, cp1tag);
ensure_not_equals("lua_emplace<>() tags collide", st0tag, cp0tag);
}
} // namespace tut

0 comments on commit 0cc7436

Please sign in to comment.