Skip to content

Commit 0e32275

Browse files
committed
Speed up method overload resolution
1 parent 23c1dca commit 0e32275

2 files changed

Lines changed: 317 additions & 142 deletions

File tree

‎Source/LuaBridge/detail/CFunctions.h‎

Lines changed: 104 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <cstdint>
2222
#include <optional>
2323
#include <string>
24+
#include <vector>
2425

2526
namespace luabridge {
2627

@@ -947,6 +948,7 @@ inline int newindex_metamethod_simple(lua_State* L)
947948
}
948949

949950
luaL_error(L, "no writable member '%s'", key);
951+
return 0;
950952
}
951953

952954
//=================================================================================================
@@ -958,7 +960,6 @@ inline int newindex_metamethod_simple(lua_State* L)
958960
inline int read_only_error(lua_State* L)
959961
{
960962
raise_lua_error(L, "'%s' is read-only", lua_tostring(L, lua_upvalueindex(1)));
961-
962963
return 0;
963964
}
964965

@@ -1551,67 +1552,140 @@ int invoke_proxy_destructor(lua_State* L)
15511552
return 1;
15521553
}
15531554

1555+
//=================================================================================================
1556+
/**
1557+
* @brief C++ storage for a single overload entry: arity and optional type checker.
1558+
*
1559+
* Stored inside an OverloadSet which is kept as a Lua full userdata (auto-GC'd).
1560+
*/
1561+
struct OverloadEntry
1562+
{
1563+
using TypeChecker = bool (*)(lua_State*, int start);
1564+
1565+
int arity; // -1 for variadic (lua_CFunction): always attempt
1566+
TypeChecker checker; // nullptr for variadic: skip type pre-checking
1567+
};
1568+
1569+
/**
1570+
* @brief C++ storage for all overloads of a function.
1571+
*
1572+
* Stored as a Lua full userdata so it is GC'd automatically when the closure is collected.
1573+
* The actual function closures are stored separately in a flat Lua table (upvalue 2).
1574+
*/
1575+
struct OverloadSet
1576+
{
1577+
std::vector<OverloadEntry> entries;
1578+
};
1579+
1580+
/**
1581+
* @brief Check a single argument type, skipping lua_State* (auto-injected, not on the Lua stack).
1582+
*/
1583+
template <class T>
1584+
bool overload_check_one_arg(lua_State* L, int& idx)
1585+
{
1586+
if constexpr (std::is_pointer_v<T> &&
1587+
std::is_same_v<std::remove_const_t<std::remove_pointer_t<T>>, lua_State>)
1588+
{
1589+
return true; // lua_State* is auto-injected by LuaBridge, not a Lua-visible argument
1590+
}
1591+
else
1592+
{
1593+
return Stack<T>::isInstance(L, idx++);
1594+
}
1595+
}
1596+
1597+
template <class ArgsPack, std::size_t... I>
1598+
bool overload_check_args_impl(lua_State* L, int start, std::index_sequence<I...>)
1599+
{
1600+
int idx = start;
1601+
return (overload_check_one_arg<std::tuple_element_t<I, ArgsPack>>(L, idx) && ...);
1602+
}
1603+
1604+
template <class ArgsPack>
1605+
bool overload_check_args(lua_State* L, int start)
1606+
{
1607+
return overload_check_args_impl<ArgsPack>(L, start,
1608+
std::make_index_sequence<std::tuple_size_v<ArgsPack>>{});
1609+
}
1610+
1611+
/**
1612+
* @brief Type checker instantiable as an OverloadEntry::TypeChecker function pointer.
1613+
*
1614+
* Checks that the Lua stack arguments starting at @p start match the types in @tparam ArgsPack,
1615+
* using Stack<T>::isInstance without raising errors. lua_State* arguments are skipped.
1616+
*/
1617+
template <class ArgsPack>
1618+
bool overload_type_checker(lua_State* L, int start)
1619+
{
1620+
return overload_check_args<ArgsPack>(L, start);
1621+
}
1622+
15541623
//=================================================================================================
15551624
/**
15561625
* @brief lua_CFunction to resolve an invocation between several overloads.
15571626
*
1558-
* The list of overloads is in the first upvalue. The arguments of the function call are at the top of the Lua stack.
1627+
* upvalue[1] = OverloadSet full userdata — C++ vector of {arity, type_checker} per overload.
1628+
* upvalue[2] = flat Lua table {[1]=func1, [2]=func2, ...} — the actual function closures.
1629+
*
1630+
* Dispatch:
1631+
* 1. Arity check in C++ (no Lua call).
1632+
* 2. Type check via Stack<T>::isInstance in C++ (no pcall) — skips clearly mismatched overloads.
1633+
* 3. Only calls lua_pcall for type-matched candidates, eliminating failed pcalls for type mismatches.
15591634
*/
15601635
template <bool Member>
15611636
inline int try_overload_functions(lua_State* L)
15621637
{
15631638
const int nargs = lua_gettop(L);
15641639
const int effective_args = nargs - (Member ? 1 : 0);
1640+
const int start_arg = Member ? 2 : 1;
1641+
1642+
LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1)));
1643+
auto* overload_set = align<OverloadSet>(lua_touserdata(L, lua_upvalueindex(1)));
15651644

1566-
// get the list of overloads
1567-
lua_pushvalue(L, lua_upvalueindex(1));
1645+
// push flat functions table (upvalue 2)
1646+
lua_pushvalue(L, lua_upvalueindex(2));
15681647
LUABRIDGE_ASSERT(lua_istable(L, -1));
1569-
const int idx_overloads = nargs + 1;
1570-
const int num_overloads = get_length(L, idx_overloads);
1648+
const int idx_funcs = nargs + 1;
15711649

15721650
// create table to hold error messages
1573-
lua_createtable(L, num_overloads, 0);
1651+
lua_createtable(L, static_cast<int>(overload_set->entries.size()), 0);
15741652
const int idx_errors = nargs + 2;
15751653
int nerrors = 0;
15761654

1577-
// iterate through table, snippet taken from Lua docs
1578-
lua_pushnil(L); // first key
1579-
while (lua_next(L, idx_overloads) != 0)
1655+
for (int i = 0; i < static_cast<int>(overload_set->entries.size()); ++i)
15801656
{
1581-
LUABRIDGE_ASSERT(lua_istable(L, -1));
1582-
1583-
// check matching arity
1584-
lua_rawgeti(L, -1, 1);
1585-
LUABRIDGE_ASSERT(lua_isnumber(L, -1));
1657+
const auto& entry = overload_set->entries[i];
15861658

1587-
const int overload_arity = static_cast<int>(lua_tointeger(L, -1));
1588-
if (overload_arity >= 0 && overload_arity != effective_args)
1659+
// fast arity check (C++, no Lua calls)
1660+
if (entry.arity >= 0 && entry.arity != effective_args)
15891661
{
1590-
// store error message and try next overload
1591-
lua_pushfstring(L, "Skipped overload #%d with unmatched arity of %d instead of %d", nerrors, overload_arity, effective_args);
1662+
lua_pushfstring(L, "Skipped overload #%d with unmatched arity of %d instead of %d", i, entry.arity, effective_args);
15921663
lua_rawseti(L, idx_errors, ++nerrors);
1593-
1594-
lua_pop(L, 2); // pop arity, value (table)
15951664
continue;
15961665
}
15971666

1598-
lua_pop(L, 1); // pop arity
1667+
// fast type check (C++, no pcall) — avoids expensive pcall for clearly mismatched types
1668+
if (entry.checker != nullptr && !entry.checker(L, start_arg))
1669+
{
1670+
lua_pushfstring(L, "Skipped overload #%d with unmatched argument types", i);
1671+
lua_rawseti(L, idx_errors, ++nerrors);
1672+
continue;
1673+
}
15991674

1600-
// push function
1601-
lua_pushnumber(L, 2);
1602-
lua_gettable(L, -2);
1675+
// O(1) function lookup from flat table
1676+
lua_rawgeti(L, idx_funcs, i + 1);
16031677
LUABRIDGE_ASSERT(lua_isfunction(L, -1));
16041678

16051679
// push arguments
1606-
for (int i = 1; i <= nargs; ++i)
1607-
lua_pushvalue(L, i);
1680+
for (int j = 1; j <= nargs; ++j)
1681+
lua_pushvalue(L, j);
16081682

16091683
// call f, this pops the function and its args, pushes result(s)
16101684
const int err = lua_pcall(L, nargs, LUA_MULTRET, 0);
16111685
if (err == LUABRIDGE_LUA_OK)
16121686
{
1613-
// calculate number of return values and return
1614-
return lua_gettop(L) - nargs - 4; // 4: overloads, errors, key, table
1687+
// 2 extra items on stack below results: idx_funcs, idx_errors
1688+
return lua_gettop(L) - nargs - 2;
16151689
}
16161690
else if (err == LUA_ERRRUN)
16171691
{
@@ -1620,10 +1694,8 @@ inline int try_overload_functions(lua_State* L)
16201694
}
16211695
else
16221696
{
1623-
return lua_error_x(L); // critical error: rethrow
1697+
return lua_error_x(L); // critical error: rethrow
16241698
}
1625-
1626-
lua_pop(L, 1); // pop value (table)
16271699
}
16281700

16291701
lua_Debug debug;

0 commit comments

Comments
 (0)