luau/VM/src/lvmutils.cpp

620 lines
19 KiB
C++
Raw Normal View History

// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
#include "lvm.h"
#include "lstate.h"
#include "lstring.h"
#include "ltable.h"
#include "lgc.h"
#include "ldo.h"
#include "lnumutils.h"
#include <string.h>
2022-07-28 21:24:07 -07:00
2022-08-04 15:35:33 -07:00
// limit for table tag-method chains (to avoid loops)
#define MAXTAGLOOP 100
const TValue* luaV_tonumber(const TValue* obj, TValue* n)
{
double num;
if (ttisnumber(obj))
return obj;
if (ttisstring(obj) && luaO_str2d(svalue(obj), &num))
{
setnvalue(n, num);
return n;
}
else
return NULL;
}
int luaV_tostring(lua_State* L, StkId obj)
{
if (!ttisnumber(obj))
return 0;
else
{
char s[LUAI_MAXNUM2STR];
double n = nvalue(obj);
char* e = luai_num2str(s, n);
LUAU_ASSERT(e < s + sizeof(s));
setsvalue(L, obj, luaS_newlstr(L, s, e - s));
return 1;
}
}
const float* luaV_tovector(const TValue* obj)
{
if (ttisvector(obj))
Sync to upstream/release/605 (#1118) - Implemented [Require by String with Relative Paths](https://github.com/luau-lang/rfcs/blob/master/docs/new-require-by-string-semantics.md) RFC - Implemented [Require by String with Aliases](https://github.com/luau-lang/rfcs/blob/master/docs/require-by-string-aliases.md) RFC with support for `paths` and `alias` arrays in .luarc - Added SUBRK and DIVRK bytecode instructions to speed up constant-number and constant/number operations - Added `--vector-lib`, `--vector-ctor` and `--vector-type` options to luau-compile to support code with vectors New Solver - Correctness fixes to subtyping - Improvements to dataflow analysis Native Code Generation - Added bytecode analysis pass to predict type tags used in operations - Fixed rare cases of numerical loops being generated without an interrupt instruction - Restored optimization data propagation into the linear block - Duplicate buffer length checks are optimized away Miscellaneous - Small performance improvements to new non-strict mode - Introduced more scripts for fuzzing Luau and processing the results, including fuzzer build support for CMake Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: David Cope <dcope@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com> Co-authored-by: Vighnesh Vijay <vvijay@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: David Cope <dcope@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
2023-12-01 23:46:57 -08:00
return vvalue(obj);
return nullptr;
}
2022-06-30 16:52:43 -07:00
static StkId callTMres(lua_State* L, StkId res, const TValue* f, const TValue* p1, const TValue* p2)
{
ptrdiff_t result = savestack(L, res);
// using stack room beyond top is technically safe here, but for very complicated reasons:
2022-02-17 17:18:01 -08:00
// * The stack guarantees EXTRA_STACK room beyond stack_last (see luaD_reallocstack) will be allocated
// * we cannot move luaD_checkstack above because the arguments are *sometimes* pointers to the lua
// stack and checkstack may invalidate those pointers
// * we cannot use savestack/restorestack because the arguments are sometimes on the C++ stack
// * during stack reallocation all of the allocated stack is copied (even beyond stack_last) so these
// values will be preserved even if they go past stack_last
LUAU_ASSERT((L->top + 3) < (L->stack + L->stacksize));
2022-08-04 15:35:33 -07:00
setobj2s(L, L->top, f); // push function
setobj2s(L, L->top + 1, p1); // 1st argument
setobj2s(L, L->top + 2, p2); // 2nd argument
luaD_checkstack(L, 3);
L->top += 3;
luaD_call(L, L->top - 3, 1);
res = restorestack(L, result);
L->top--;
setobj2s(L, res, L->top);
2022-06-30 16:52:43 -07:00
return res;
}
static void callTM(lua_State* L, const TValue* f, const TValue* p1, const TValue* p2, const TValue* p3)
{
// using stack room beyond top is technically safe here, but for very complicated reasons:
2022-02-17 17:18:01 -08:00
// * The stack guarantees EXTRA_STACK room beyond stack_last (see luaD_reallocstack) will be allocated
// * we cannot move luaD_checkstack above because the arguments are *sometimes* pointers to the lua
// stack and checkstack may invalidate those pointers
// * we cannot use savestack/restorestack because the arguments are sometimes on the C++ stack
// * during stack reallocation all of the allocated stack is copied (even beyond stack_last) so these
// values will be preserved even if they go past stack_last
LUAU_ASSERT((L->top + 4) < (L->stack + L->stacksize));
2022-08-04 15:35:33 -07:00
setobj2s(L, L->top, f); // push function
setobj2s(L, L->top + 1, p1); // 1st argument
setobj2s(L, L->top + 2, p2); // 2nd argument
setobj2s(L, L->top + 3, p3); // 3th argument
luaD_checkstack(L, 4);
L->top += 4;
luaD_call(L, L->top - 4, 0);
}
void luaV_gettable(lua_State* L, const TValue* t, TValue* key, StkId val)
{
int loop;
for (loop = 0; loop < MAXTAGLOOP; loop++)
{
const TValue* tm;
if (ttistable(t))
2022-08-04 15:35:33 -07:00
{ // `t' is a table?
Table* h = hvalue(t);
2022-08-04 15:35:33 -07:00
const TValue* res = luaH_get(h, key); // do a primitive get
if (res != luaO_nilobject)
2022-08-04 15:35:33 -07:00
L->cachedslot = gval2slot(h, res); // remember slot to accelerate future lookups
2022-08-04 15:35:33 -07:00
if (!ttisnil(res) // result is no nil?
|| (tm = fasttm(L, h->metatable, TM_INDEX)) == NULL)
2022-08-04 15:35:33 -07:00
{ // or no TM?
setobj2s(L, val, res);
return;
}
2022-08-04 15:35:33 -07:00
// t isn't a table, so see if it has an INDEX meta-method to look up the key with
}
else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_INDEX)))
luaG_indexerror(L, t, key);
if (ttisfunction(tm))
{
callTMres(L, val, tm, t, key);
return;
}
2022-08-04 15:35:33 -07:00
t = tm; // else repeat with `tm'
}
2022-07-21 14:16:54 -07:00
luaG_runerror(L, "'__index' chain too long; possible loop");
}
void luaV_settable(lua_State* L, const TValue* t, TValue* key, StkId val)
{
int loop;
TValue temp;
for (loop = 0; loop < MAXTAGLOOP; loop++)
{
const TValue* tm;
if (ttistable(t))
2022-08-04 15:35:33 -07:00
{ // `t' is a table?
Table* h = hvalue(t);
const TValue* oldval = luaH_get(h, key);
2022-07-28 21:24:07 -07:00
// should we assign the key? (if key is valid or __newindex is not set)
if (!ttisnil(oldval) || (tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL)
2022-07-28 21:24:07 -07:00
{
if (h->readonly)
luaG_readonlyerror(L);
// luaH_set would work but would repeat the lookup so we use luaH_setslot that can reuse oldval if it's safe
TValue* newval = luaH_setslot(L, h, oldval, key);
2022-07-28 21:24:07 -07:00
L->cachedslot = gval2slot(h, newval); // remember slot to accelerate future lookups
2022-07-28 21:24:07 -07:00
setobj2t(L, newval, val);
luaC_barriert(L, h, val);
return;
}
// fallthrough to metamethod
}
else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_NEWINDEX)))
luaG_indexerror(L, t, key);
2022-07-28 21:24:07 -07:00
if (ttisfunction(tm))
{
callTM(L, tm, t, key, val);
return;
}
2022-08-04 15:35:33 -07:00
// else repeat with `tm'
setobj(L, &temp, tm); // avoid pointing inside table (may rehash)
t = &temp;
}
2022-07-21 14:16:54 -07:00
luaG_runerror(L, "'__newindex' chain too long; possible loop");
}
static int call_binTM(lua_State* L, const TValue* p1, const TValue* p2, StkId res, TMS event)
{
2022-08-04 15:35:33 -07:00
const TValue* tm = luaT_gettmbyobj(L, p1, event); // try first operand
if (ttisnil(tm))
2022-08-04 15:35:33 -07:00
tm = luaT_gettmbyobj(L, p2, event); // try second operand
if (ttisnil(tm))
return 0;
callTMres(L, res, tm, p1, p2);
return 1;
}
static const TValue* get_compTM(lua_State* L, Table* mt1, Table* mt2, TMS event)
{
const TValue* tm1 = fasttm(L, mt1, event);
const TValue* tm2;
if (tm1 == NULL)
2022-08-04 15:35:33 -07:00
return NULL; // no metamethod
if (mt1 == mt2)
2022-08-04 15:35:33 -07:00
return tm1; // same metatables => same metamethods
tm2 = fasttm(L, mt2, event);
if (tm2 == NULL)
2022-08-04 15:35:33 -07:00
return NULL; // no metamethod
if (luaO_rawequalObj(tm1, tm2)) // same metamethods?
return tm1;
return NULL;
}
static int call_orderTM(lua_State* L, const TValue* p1, const TValue* p2, TMS event, bool error = false)
{
const TValue* tm1 = luaT_gettmbyobj(L, p1, event);
const TValue* tm2;
if (ttisnil(tm1))
{
if (error)
luaG_ordererror(L, p1, p2, event);
2022-08-04 15:35:33 -07:00
return -1; // no metamethod?
}
tm2 = luaT_gettmbyobj(L, p2, event);
2022-08-04 15:35:33 -07:00
if (!luaO_rawequalObj(tm1, tm2)) // different metamethods?
{
if (error)
luaG_ordererror(L, p1, p2, event);
return -1;
}
callTMres(L, L->top, tm1, p1, p2);
return !l_isfalse(L->top);
}
int luaV_strcmp(const TString* ls, const TString* rs)
{
if (ls == rs)
return 0;
const char* l = getstr(ls);
const char* r = getstr(rs);
// always safe to read one character because even empty strings are nul terminated
if (*l != *r)
return uint8_t(*l) - uint8_t(*r);
size_t ll = ls->len;
size_t lr = rs->len;
size_t lmin = ll < lr ? ll : lr;
int res = memcmp(l, r, lmin);
if (res != 0)
return res;
return ll == lr ? 0 : ll < lr ? -1 : 1;
}
int luaV_lessthan(lua_State* L, const TValue* l, const TValue* r)
{
if (LUAU_UNLIKELY(ttype(l) != ttype(r)))
luaG_ordererror(L, l, r, TM_LT);
else if (LUAU_LIKELY(ttisnumber(l)))
return luai_numlt(nvalue(l), nvalue(r));
else if (ttisstring(l))
return luaV_strcmp(tsvalue(l), tsvalue(r)) < 0;
else
return call_orderTM(L, l, r, TM_LT, /* error= */ true);
}
int luaV_lessequal(lua_State* L, const TValue* l, const TValue* r)
{
int res;
if (ttype(l) != ttype(r))
luaG_ordererror(L, l, r, TM_LE);
else if (ttisnumber(l))
return luai_numle(nvalue(l), nvalue(r));
else if (ttisstring(l))
return luaV_strcmp(tsvalue(l), tsvalue(r)) <= 0;
2022-08-04 15:35:33 -07:00
else if ((res = call_orderTM(L, l, r, TM_LE)) != -1) // first try `le'
return res;
2022-08-04 15:35:33 -07:00
else if ((res = call_orderTM(L, r, l, TM_LT)) == -1) // error if not `lt'
luaG_ordererror(L, l, r, TM_LE);
return !res;
}
int luaV_equalval(lua_State* L, const TValue* t1, const TValue* t2)
{
const TValue* tm;
LUAU_ASSERT(ttype(t1) == ttype(t2));
switch (ttype(t1))
{
case LUA_TNIL:
return 1;
case LUA_TNUMBER:
return luai_numeq(nvalue(t1), nvalue(t2));
case LUA_TVECTOR:
return luai_veceq(vvalue(t1), vvalue(t2));
case LUA_TBOOLEAN:
2022-08-04 15:35:33 -07:00
return bvalue(t1) == bvalue(t2); // true must be 1 !!
case LUA_TLIGHTUSERDATA:
Add tagged lightuserdata (#1087) This change adds support for tagged lightuserdata and optional custom typenames for lightuserdata. Background: Lightuserdata is an efficient representation for many kinds of unmanaged handles and resources in a game engine. However, currently the VM only supports one kind of lightuserdata, which makes it problematic in practice. For example, it's not possible to distinguish between different kinds of lightuserdata in Lua bindings, which can lead to unsafe practices and even crashes when a wrong kind of lightuserdata is passed to a binding function. Tagged lightuserdata work similarly to tagged userdata, i.e. they allow checking the tag quickly using lua_tolightuserdatatagged (or lua_lightuserdatatag). The tag is stored in the 'extra' field of TValue so it will add no cost to the (untagged) lightuserdata type. Alternatives would be to use full userdata values or use bitpacking to embed type information into lightuserdata on application level. Unfortunately these options are not that great in practice: full userdata have major performance implications and bitpacking fails in cases where full 64 bits are already used (e.g. pointers or 64-bit hashes). Lightuserdata names are not strictly necessary but they are rather convenient when debugging Lua code. More precise error messages and tostring returning more specific typename are useful to have in practice (e.g. "resource" or "entity" instead of the more generic "userdata"). Impl note: I did not add support for renaming tags in lua_setlightuserdataname as I'm not sure if it's possible to free fixed strings. If it's simple enough, maybe we should allow renaming (although I can't think of a specific need for it)? --------- Co-authored-by: Petri Häkkinen <petrih@rmd.remedy.fi>
2023-12-15 01:05:51 +02:00
return pvalue(t1) == pvalue(t2) && (!FFlag::TaggedLuData || lightuserdatatag(t1) == lightuserdatatag(t2));
case LUA_TUSERDATA:
{
tm = get_compTM(L, uvalue(t1)->metatable, uvalue(t2)->metatable, TM_EQ);
if (!tm)
return uvalue(t1) == uvalue(t2);
2022-08-04 15:35:33 -07:00
break; // will try TM
}
case LUA_TTABLE:
{
tm = get_compTM(L, hvalue(t1)->metatable, hvalue(t2)->metatable, TM_EQ);
if (!tm)
return hvalue(t1) == hvalue(t2);
2022-08-04 15:35:33 -07:00
break; // will try TM
}
default:
return gcvalue(t1) == gcvalue(t2);
}
2022-08-04 15:35:33 -07:00
callTMres(L, L->top, tm, t1, t2); // call TM
return !l_isfalse(L->top);
}
void luaV_concat(lua_State* L, int total, int last)
{
do
{
StkId top = L->base + last + 1;
2022-08-04 15:35:33 -07:00
int n = 2; // number of elements handled in this pass (at least 2)
if (!(ttisstring(top - 2) || ttisnumber(top - 2)) || !tostring(L, top - 1))
{
if (!call_binTM(L, top - 2, top - 1, top - 2, TM_CONCAT))
luaG_concaterror(L, top - 2, top - 1);
}
2022-08-04 15:35:33 -07:00
else if (tsvalue(top - 1)->len == 0) // second op is empty?
(void)tostring(L, top - 2); // result is first op (as string)
else
{
2022-08-04 15:35:33 -07:00
// at least two string values; get as many as possible
size_t tl = tsvalue(top - 1)->len;
char* buffer;
int i;
2022-08-04 15:35:33 -07:00
// collect total length
for (n = 1; n < total && tostring(L, top - n - 1); n++)
{
size_t l = tsvalue(top - n - 1)->len;
if (l > MAXSSIZE - tl)
luaG_runerror(L, "string length overflow");
tl += l;
}
char buf[LUA_BUFFERSIZE];
TString* ts = nullptr;
if (tl < LUA_BUFFERSIZE)
{
buffer = buf;
}
else
{
ts = luaS_bufstart(L, tl);
buffer = ts->data;
}
tl = 0;
for (i = n; i > 0; i--)
2022-08-04 15:35:33 -07:00
{ // concat all strings
size_t l = tsvalue(top - i)->len;
memcpy(buffer + tl, svalue(top - i), l);
tl += l;
}
if (tl < LUA_BUFFERSIZE)
{
setsvalue(L, top - n, luaS_newlstr(L, buffer, tl));
}
else
{
setsvalue(L, top - n, luaS_buffinish(L, ts));
}
}
2022-08-04 15:35:33 -07:00
total -= n - 1; // got `n' strings to create 1 new
last -= n - 1;
2022-08-04 15:35:33 -07:00
} while (total > 1); // repeat until only 1 result left
}
void luaV_doarith(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TMS op)
{
TValue tempb, tempc;
const TValue *b, *c;
if ((b = luaV_tonumber(rb, &tempb)) != NULL && (c = luaV_tonumber(rc, &tempc)) != NULL)
{
double nb = nvalue(b), nc = nvalue(c);
switch (op)
{
case TM_ADD:
setnvalue(ra, luai_numadd(nb, nc));
break;
case TM_SUB:
setnvalue(ra, luai_numsub(nb, nc));
break;
case TM_MUL:
setnvalue(ra, luai_nummul(nb, nc));
break;
case TM_DIV:
setnvalue(ra, luai_numdiv(nb, nc));
break;
case TM_IDIV:
setnvalue(ra, luai_numidiv(nb, nc));
break;
case TM_MOD:
setnvalue(ra, luai_nummod(nb, nc));
break;
case TM_POW:
setnvalue(ra, luai_numpow(nb, nc));
break;
case TM_UNM:
setnvalue(ra, luai_numunm(nb));
break;
default:
LUAU_ASSERT(0);
break;
}
}
else
{
// vector operations that we support:
// v+v v-v -v (add/sub/neg)
// v*v s*v v*s (mul)
// v/v s/v v/s (div)
// v//v s//v v//s (floor div)
const float* vb = luaV_tovector(rb);
const float* vc = luaV_tovector(rc);
if (vb && vc)
{
switch (op)
{
case TM_ADD:
setvvalue(ra, vb[0] + vc[0], vb[1] + vc[1], vb[2] + vc[2], vb[3] + vc[3]);
return;
case TM_SUB:
setvvalue(ra, vb[0] - vc[0], vb[1] - vc[1], vb[2] - vc[2], vb[3] - vc[3]);
return;
case TM_MUL:
setvvalue(ra, vb[0] * vc[0], vb[1] * vc[1], vb[2] * vc[2], vb[3] * vc[3]);
return;
case TM_DIV:
setvvalue(ra, vb[0] / vc[0], vb[1] / vc[1], vb[2] / vc[2], vb[3] / vc[3]);
return;
case TM_IDIV:
setvvalue(ra, float(luai_numidiv(vb[0], vc[0])), float(luai_numidiv(vb[1], vc[1])), float(luai_numidiv(vb[2], vc[2])),
float(luai_numidiv(vb[3], vc[3])));
return;
case TM_UNM:
setvvalue(ra, -vb[0], -vb[1], -vb[2], -vb[3]);
return;
default:
break;
}
}
else if (vb)
{
c = luaV_tonumber(rc, &tempc);
if (c)
{
float nc = cast_to(float, nvalue(c));
switch (op)
{
case TM_MUL:
setvvalue(ra, vb[0] * nc, vb[1] * nc, vb[2] * nc, vb[3] * nc);
return;
case TM_DIV:
setvvalue(ra, vb[0] / nc, vb[1] / nc, vb[2] / nc, vb[3] / nc);
return;
case TM_IDIV:
setvvalue(ra, float(luai_numidiv(vb[0], nc)), float(luai_numidiv(vb[1], nc)), float(luai_numidiv(vb[2], nc)),
float(luai_numidiv(vb[3], nc)));
return;
default:
break;
}
}
}
else if (vc)
{
b = luaV_tonumber(rb, &tempb);
if (b)
{
float nb = cast_to(float, nvalue(b));
switch (op)
{
case TM_MUL:
setvvalue(ra, nb * vc[0], nb * vc[1], nb * vc[2], nb * vc[3]);
return;
case TM_DIV:
setvvalue(ra, nb / vc[0], nb / vc[1], nb / vc[2], nb / vc[3]);
return;
case TM_IDIV:
setvvalue(ra, float(luai_numidiv(nb, vc[0])), float(luai_numidiv(nb, vc[1])), float(luai_numidiv(nb, vc[2])),
float(luai_numidiv(nb, vc[3])));
return;
default:
break;
}
}
}
if (!call_binTM(L, rb, rc, ra, op))
{
luaG_aritherror(L, rb, rc, op);
}
}
}
void luaV_dolen(lua_State* L, StkId ra, const TValue* rb)
{
2022-06-30 16:52:43 -07:00
const TValue* tm = NULL;
switch (ttype(rb))
{
case LUA_TTABLE:
{
2022-06-30 16:52:43 -07:00
Table* h = hvalue(rb);
if ((tm = fasttm(L, h->metatable, TM_LEN)) == NULL)
{
setnvalue(ra, cast_num(luaH_getn(h)));
return;
}
break;
}
case LUA_TSTRING:
{
2022-06-30 16:52:43 -07:00
TString* ts = tsvalue(rb);
setnvalue(ra, cast_num(ts->len));
return;
}
default:
2022-06-30 16:52:43 -07:00
tm = luaT_gettmbyobj(L, rb, TM_LEN);
}
2022-06-30 16:52:43 -07:00
if (ttisnil(tm))
luaG_typeerror(L, rb, "get length of");
StkId res = callTMres(L, ra, tm, rb, luaO_nilobject);
if (!ttisnumber(res))
2022-08-04 15:35:33 -07:00
luaG_runerror(L, "'__len' must return a number"); // note, we can't access rb since stack may have been reallocated
}
2022-09-23 12:17:25 -07:00
LUAU_NOINLINE void luaV_prepareFORN(lua_State* L, StkId plimit, StkId pstep, StkId pinit)
{
if (!ttisnumber(pinit) && !luaV_tonumber(pinit, pinit))
luaG_forerror(L, pinit, "initial value");
if (!ttisnumber(plimit) && !luaV_tonumber(plimit, plimit))
luaG_forerror(L, plimit, "limit");
if (!ttisnumber(pstep) && !luaV_tonumber(pstep, pstep))
luaG_forerror(L, pstep, "step");
}
// calls a C function f with no yielding support; optionally save one resulting value to the res register
// the function and arguments have to already be pushed to L->top
LUAU_NOINLINE void luaV_callTM(lua_State* L, int nparams, int res)
{
++L->nCcalls;
if (L->nCcalls >= LUAI_MAXCCALLS)
luaD_checkCstack(L);
luaD_checkstack(L, LUA_MINSTACK);
StkId top = L->top;
StkId fun = top - nparams - 1;
CallInfo* ci = incr_ci(L);
ci->func = fun;
ci->base = fun + 1;
ci->top = top + LUA_MINSTACK;
ci->savedpc = NULL;
ci->flags = 0;
ci->nresults = (res >= 0);
LUAU_ASSERT(ci->top <= L->stack_last);
LUAU_ASSERT(ttisfunction(ci->func));
LUAU_ASSERT(clvalue(ci->func)->isC);
L->base = fun + 1;
LUAU_ASSERT(L->top == L->base + nparams);
lua_CFunction func = clvalue(fun)->c.f;
int n = func(L);
LUAU_ASSERT(n >= 0); // yields should have been blocked by nCcalls
// ci is our callinfo, cip is our parent
// note that we read L->ci again since it may have been reallocated by the call
CallInfo* cip = L->ci - 1;
// copy return value into parent stack
if (res >= 0)
{
if (n > 0)
{
setobj2s(L, &cip->base[res], L->top - n);
}
else
{
setnilvalue(&cip->base[res]);
}
}
L->ci = cip;
L->base = cip->base;
L->top = cip->top;
--L->nCcalls;
}
LUAU_NOINLINE void luaV_tryfuncTM(lua_State* L, StkId func)
{
const TValue* tm = luaT_gettmbyobj(L, func, TM_CALL);
if (!ttisfunction(tm))
luaG_typeerror(L, func, "call");
for (StkId p = L->top; p > func; p--) // open space for metamethod
setobj2s(L, p, p - 1);
2022-09-23 12:17:25 -07:00
L->top++; // stack space pre-allocated by the caller
setobj2s(L, func, tm); // tag method is the new function to be called
}