Skip to content

Commit 8a7358f

Browse files
committed
Add a notion of "free" objects during user thread allocation traversal
This way we can have objects that do not count towards the memory limit for the specific script being run.
1 parent 2f8b214 commit 8a7358f

6 files changed

Lines changed: 56 additions & 11 deletions

File tree

VM/include/lua.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <stdint.h>
88
#include <istream>
99
#include <ostream>
10+
#include <unordered_set>
1011

1112
#include "luaconf.h"
1213

@@ -135,6 +136,9 @@ typedef int lua_Integer;
135136
// unsigned integer type
136137
typedef unsigned lua_Unsigned;
137138

139+
// ServerLua: opaque set of GC object pointers for per-script memory accounting
140+
typedef std::unordered_set<void*> lua_OpaqueGCObjectSet;
141+
138142
/*
139143
** state manipulation
140144
*/
@@ -396,7 +400,11 @@ LUA_API void lua_useconstsstate(lua_State* L, lua_State * constsL);
396400
// Get how much "real" memory is used by pages.
397401
LUA_API int lua_totalmemoverhead(lua_State *L);
398402
// Gets the total size of all user-allocated objects reachable from a user thread.
399-
LUA_API size_t lua_userthreadsize(lua_State *L);
403+
// If free_objects is provided, objects in that set will be excluded from the size calculation.
404+
LUA_API size_t lua_userthreadsize(lua_State *L, const lua_OpaqueGCObjectSet* free_objects);
405+
// Collects all memcat 2+ objects reachable from a user thread into a set.
406+
// Intended to be called immediately after luau_load() to capture bytecode constants.
407+
LUA_API lua_OpaqueGCObjectSet lua_collectfreeobjects(lua_State *L);
400408

401409

402410
/*

VM/src/lapi.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2205,17 +2205,23 @@ CLANG_NOOPT void GCC_NOOPT lua_useconstsstate(lua_State *L, lua_State * constsL)
22052205
luaC_validate(L);
22062206
}
22072207

2208-
size_t lua_userthreadsize(lua_State *L)
2208+
size_t lua_userthreadsize(lua_State *L, const lua_OpaqueGCObjectSet* free_objects)
22092209
{
22102210
size_t total_size = 0;
22112211
luaC_enumreachableuserallocs(
22122212
L,
22132213
&total_size,
22142214
[](void* context, GCObject* ptr, uint8_t tt, uint8_t memcat, size_t size) {
22152215
*((size_t*)context) += size;
2216-
}
2216+
},
2217+
free_objects
22172218
);
22182219
// Make sure we include the size of allocs from not-yet-rooted objects.
22192220
// Note that this only makes sense if we call this on a thread that's currently executing.
22202221
return total_size + L->global->unrooteduserallocs;
22212222
}
2223+
2224+
lua_OpaqueGCObjectSet lua_collectfreeobjects(lua_State *L)
2225+
{
2226+
return luaC_collectfreeobjects(L);
2227+
}

VM/src/lgc.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,13 @@ LUAI_FUNC void luaC_enumheap(
143143
void (*node)(void* context, void* ptr, uint8_t tt, uint8_t memcat, size_t size, const char* name),
144144
void (*edge)(void* context, void* from, void* to, const char* name)
145145
);
146-
// ServerLua: User allocation tracking function
146+
// ServerLua: User allocation tracking functions
147147
LUAI_FUNC void luaC_enumreachableuserallocs(
148148
lua_State* L,
149149
void* context,
150-
void (*node)(void* context, GCObject* ptr, uint8_t tt, uint8_t memcat, size_t size)
150+
void (*node)(void* context, GCObject* ptr, uint8_t tt, uint8_t memcat, size_t size),
151+
const lua_OpaqueGCObjectSet* free_objects
151152
);
153+
LUAI_FUNC lua_OpaqueGCObjectSet luaC_collectfreeobjects(lua_State* L);
152154
LUAI_FUNC int64_t luaC_allocationrate(lua_State* L);
153155
LUAI_FUNC const char* luaC_statename(int state);

VM/src/lgctraverse.cpp

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
typedef struct ReachableContext
2323
{
2424
std::queue<GCObject*> queue;
25-
std::unordered_set<GCObject*> visited;
25+
std::unordered_set<void*> visited;
2626
} ReachableContext;
2727

2828
static void enqueueobj(ReachableContext* ctx, GCObject* obj)
@@ -372,11 +372,18 @@ static size_t calcgcosize(GCObject *obj)
372372
void luaC_enumreachableuserallocs(
373373
lua_State* L,
374374
void* context,
375-
void (*node)(void* context, GCObject* ptr, uint8_t tt, uint8_t memcat, size_t size)
375+
void (*node)(void* context, GCObject* ptr, uint8_t tt, uint8_t memcat, size_t size),
376+
const lua_OpaqueGCObjectSet* free_objects
376377
)
377378
{
378379
ReachableContext ctx;
379380

381+
// Pre-populate visited set with free objects if provided
382+
if (free_objects)
383+
{
384+
ctx.visited = *free_objects;
385+
}
386+
380387
ctx.queue.push(obj2gco(L));
381388
ctx.visited.insert(obj2gco(L));
382389

@@ -390,8 +397,30 @@ void luaC_enumreachableuserallocs(
390397
node(context, current, current->gch.tt, current->gch.memcat, calcgcosize(current));
391398

392399
// Take any new references the current node has and add them to the queue
393-
// Even if we don't want to include their size in the calculation, we may still want
394-
// to traverse them.
395400
traverseobj(&ctx, current);
396401
}
397402
}
403+
404+
lua_OpaqueGCObjectSet luaC_collectfreeobjects(lua_State* L)
405+
{
406+
lua_OpaqueGCObjectSet free_objects;
407+
ReachableContext ctx;
408+
409+
ctx.queue.push(obj2gco(L));
410+
ctx.visited.insert(obj2gco(L));
411+
412+
while (!ctx.queue.empty())
413+
{
414+
GCObject* current = ctx.queue.front();
415+
ctx.queue.pop();
416+
417+
// Collect memcat 2+ objects into the set
418+
if (current->gch.memcat >= 2)
419+
free_objects.insert(current);
420+
421+
// Traverse child references
422+
traverseobj(&ctx, current);
423+
}
424+
425+
return free_objects;
426+
}

VM/src/lll.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -806,7 +806,7 @@ static int ll_getfreememory(lua_State *L)
806806
{
807807
luaC_fullgc(L);
808808
luaX_graphheap(L, "/tmp/whatever.json");
809-
luaSL_pushnativeinteger(L, (int)lua_userthreadsize(L));
809+
luaSL_pushnativeinteger(L, (int)lua_userthreadsize(L, nullptr));
810810
return 1;
811811
}
812812

tests/SLConformance.test.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -600,7 +600,7 @@ TEST_CASE("User thread alloc size calculation")
600600
// or the alloc would push us over the limit given the current approximate size.
601601
if (actual_size == 0 || (approximate_size + net_gain > MAX_MEM))
602602
{
603-
approximate_size = actual_size = lua_userthreadsize(L);
603+
approximate_size = actual_size = lua_userthreadsize(L, nullptr);
604604

605605
if (actual_size + net_gain > MAX_MEM)
606606
return 1;

0 commit comments

Comments
 (0)