Clear interface between references and predefines

The reference system has a defined way to add initial values to the
table where it operates.
This commit is contained in:
Roberto Ierusalimschy
2024-01-15 11:31:49 -03:00
parent 8eb0abc9db
commit 17e0c29d9b
7 changed files with 92 additions and 43 deletions

View File

@@ -672,13 +672,10 @@ LUALIB_API char *luaL_buffinitsize (lua_State *L, luaL_Buffer *B, size_t sz) {
** ======================================================= ** =======================================================
*/ */
/* index of free-list header (after the predefined values) */
#define freelist (LUA_RIDX_LAST + 1)
/* /*
** The previously freed references form a linked list: ** The previously freed references form a linked list: t[1] is the index
** t[freelist] is the index of a first free index, or zero if list is ** of a first free index, t[t[1]] is the index of the second element,
** empty; t[t[freelist]] is the index of the second element; etc. ** etc. A zero signals the end of the list.
*/ */
LUALIB_API int luaL_ref (lua_State *L, int t) { LUALIB_API int luaL_ref (lua_State *L, int t) {
int ref; int ref;
@@ -687,19 +684,18 @@ LUALIB_API int luaL_ref (lua_State *L, int t) {
return LUA_REFNIL; /* 'nil' has a unique fixed reference */ return LUA_REFNIL; /* 'nil' has a unique fixed reference */
} }
t = lua_absindex(L, t); t = lua_absindex(L, t);
if (lua_rawgeti(L, t, freelist) == LUA_TNIL) { /* first access? */ if (lua_rawgeti(L, t, 1) == LUA_TNUMBER) /* already initialized? */
ref = (int)lua_tointeger(L, -1); /* ref = t[1] */
else { /* first access */
lua_assert(!lua_toboolean(L, -1)); /* must be nil or false */
ref = 0; /* list is empty */ ref = 0; /* list is empty */
lua_pushinteger(L, 0); /* initialize as an empty list */ lua_pushinteger(L, 0); /* initialize as an empty list */
lua_rawseti(L, t, freelist); /* ref = t[freelist] = 0 */ lua_rawseti(L, t, 1); /* ref = t[1] = 0 */
}
else { /* already initialized */
lua_assert(lua_isinteger(L, -1));
ref = (int)lua_tointeger(L, -1); /* ref = t[freelist] */
} }
lua_pop(L, 1); /* remove element from stack */ lua_pop(L, 1); /* remove element from stack */
if (ref != 0) { /* any free element? */ if (ref != 0) { /* any free element? */
lua_rawgeti(L, t, ref); /* remove it from list */ lua_rawgeti(L, t, ref); /* remove it from list */
lua_rawseti(L, t, freelist); /* (t[freelist] = t[ref]) */ lua_rawseti(L, t, 1); /* (t[1] = t[ref]) */
} }
else /* no free elements */ else /* no free elements */
ref = (int)lua_rawlen(L, t) + 1; /* get a new reference */ ref = (int)lua_rawlen(L, t) + 1; /* get a new reference */
@@ -711,11 +707,11 @@ LUALIB_API int luaL_ref (lua_State *L, int t) {
LUALIB_API void luaL_unref (lua_State *L, int t, int ref) { LUALIB_API void luaL_unref (lua_State *L, int t, int ref) {
if (ref >= 0) { if (ref >= 0) {
t = lua_absindex(L, t); t = lua_absindex(L, t);
lua_rawgeti(L, t, freelist); lua_rawgeti(L, t, 1);
lua_assert(lua_isinteger(L, -1)); lua_assert(lua_isinteger(L, -1));
lua_rawseti(L, t, ref); /* t[ref] = t[freelist] */ lua_rawseti(L, t, ref); /* t[ref] = t[1] */
lua_pushinteger(L, ref); lua_pushinteger(L, ref);
lua_rawseti(L, t, freelist); /* t[freelist] = ref */ lua_rawseti(L, t, 1); /* t[1] = ref */
} }
} }

View File

@@ -189,6 +189,9 @@ static void init_registry (lua_State *L, global_State *g) {
Table *registry = luaH_new(L); Table *registry = luaH_new(L);
sethvalue(L, &g->l_registry, registry); sethvalue(L, &g->l_registry, registry);
luaH_resize(L, registry, LUA_RIDX_LAST, 0); luaH_resize(L, registry, LUA_RIDX_LAST, 0);
/* registry[1] = false */
setbfvalue(&aux);
luaH_setint(L, registry, 1, &aux);
/* registry[LUA_RIDX_MAINTHREAD] = L */ /* registry[LUA_RIDX_MAINTHREAD] = L */
setthvalue(L, &aux, L); setthvalue(L, &aux, L);
luaH_setint(L, registry, LUA_RIDX_MAINTHREAD, &aux); luaH_setint(L, registry, LUA_RIDX_MAINTHREAD, &aux);

View File

@@ -1084,27 +1084,39 @@ static int string_query (lua_State *L) {
} }
static int getreftable (lua_State *L) {
if (lua_istable(L, 2)) /* is there a table as second argument? */
return 2; /* use it as the table */
else
return LUA_REGISTRYINDEX; /* default is to use the register */
}
static int tref (lua_State *L) { static int tref (lua_State *L) {
int t = getreftable(L);
int level = lua_gettop(L); int level = lua_gettop(L);
luaL_checkany(L, 1); luaL_checkany(L, 1);
lua_pushvalue(L, 1); lua_pushvalue(L, 1);
lua_pushinteger(L, luaL_ref(L, LUA_REGISTRYINDEX)); lua_pushinteger(L, luaL_ref(L, t));
cast_void(level); /* to avoid warnings */ cast_void(level); /* to avoid warnings */
lua_assert(lua_gettop(L) == level+1); /* +1 for result */ lua_assert(lua_gettop(L) == level+1); /* +1 for result */
return 1; return 1;
} }
static int getref (lua_State *L) { static int getref (lua_State *L) {
int t = getreftable(L);
int level = lua_gettop(L); int level = lua_gettop(L);
lua_rawgeti(L, LUA_REGISTRYINDEX, luaL_checkinteger(L, 1)); lua_rawgeti(L, t, luaL_checkinteger(L, 1));
cast_void(level); /* to avoid warnings */ cast_void(level); /* to avoid warnings */
lua_assert(lua_gettop(L) == level+1); lua_assert(lua_gettop(L) == level+1);
return 1; return 1;
} }
static int unref (lua_State *L) { static int unref (lua_State *L) {
int t = getreftable(L);
int level = lua_gettop(L); int level = lua_gettop(L);
luaL_unref(L, LUA_REGISTRYINDEX, cast_int(luaL_checkinteger(L, 1))); luaL_unref(L, t, cast_int(luaL_checkinteger(L, 1)));
cast_void(level); /* to avoid warnings */ cast_void(level); /* to avoid warnings */
lua_assert(lua_gettop(L) == level); lua_assert(lua_gettop(L) == level);
return 0; return 0;
@@ -1373,6 +1385,16 @@ static int getnum_aux (lua_State *L, lua_State *L1, const char **pc) {
(*pc)++; (*pc)++;
return res; return res;
} }
else if (**pc == '!') {
(*pc)++;
if (**pc == 'G')
res = LUA_RIDX_GLOBALS;
else if (**pc == 'M')
res = LUA_RIDX_MAINTHREAD;
else lua_assert(0);
(*pc)++;
return res;
}
else if (**pc == '-') { else if (**pc == '-') {
sig = -1; sig = -1;
(*pc)++; (*pc)++;

5
lua.h
View File

@@ -80,9 +80,10 @@ typedef struct lua_State lua_State;
/* predefined values in the registry */ /* predefined values in the registry */
#define LUA_RIDX_MAINTHREAD 1 /* index 1 is reserved for the reference mechanism */
#define LUA_RIDX_GLOBALS 2 #define LUA_RIDX_GLOBALS 2
#define LUA_RIDX_LAST LUA_RIDX_GLOBALS #define LUA_RIDX_MAINTHREAD 3
#define LUA_RIDX_LAST 3
/* type of numbers in Lua */ /* type of numbers in Lua */

View File

@@ -2645,8 +2645,8 @@ string keys starting with an underscore followed by
uppercase letters are reserved for Lua. uppercase letters are reserved for Lua.
The integer keys in the registry are used The integer keys in the registry are used
by the reference mechanism @seeC{luaL_ref} by the reference mechanism @seeC{luaL_ref},
and by some predefined values. with some predefined values.
Therefore, integer keys in the registry Therefore, integer keys in the registry
must not be used for other purposes. must not be used for other purposes.
@@ -6018,11 +6018,21 @@ Creates and returns a @def{reference},
in the table at index @id{t}, in the table at index @id{t},
for the object on the top of the stack (and pops the object). for the object on the top of the stack (and pops the object).
A reference is a unique integer key. The reference system uses the integer keys of the table.
As long as you do not manually add integer keys into the table @id{t}, A reference is a unique integer key;
@Lid{luaL_ref} ensures the uniqueness of the key it returns. @Lid{luaL_ref} ensures the uniqueness of the keys it returns.
The entry 1 is reserved for internal use.
Before the first use of @Lid{luaL_ref},
the integer keys of the table
should form a proper sequence (no holes),
and the value at entry 1 should be false:
@nil if the sequence is empty,
@false otherwise.
You should not manually set integer keys in the table
after the first use of @Lid{luaL_ref}.
You can retrieve an object referred by the reference @id{r} You can retrieve an object referred by the reference @id{r}
by calling @T{lua_rawgeti(L, t, r)}. by calling @T{lua_rawgeti(L, t, r)} or @T{lua_geti(L, t, r)}.
The function @Lid{luaL_unref} frees a reference. The function @Lid{luaL_unref} frees a reference.
If the object on the top of the stack is @nil, If the object on the top of the stack is @nil,
@@ -6188,8 +6198,8 @@ Returns the name of the type of the value at the given index.
Releases the reference @id{ref} from the table at index @id{t} Releases the reference @id{ref} from the table at index @id{t}
@seeC{luaL_ref}. @seeC{luaL_ref}.
The entry is removed from the table, The entry is removed from the table,
so that the referred object can be collected. so that the referred object can be collected and
The reference @id{ref} is also freed to be used again. the reference @id{ref} can be used again by @Lid{luaL_ref}.
If @id{ref} is @Lid{LUA_NOREF} or @Lid{LUA_REFNIL}, If @id{ref} is @Lid{LUA_NOREF} or @Lid{LUA_REFNIL},
@Lid{luaL_unref} does nothing. @Lid{luaL_unref} does nothing.

View File

@@ -467,7 +467,7 @@ for i = 1,lim do
prog[#prog + 1] = "pushnum " .. i * 10 prog[#prog + 1] = "pushnum " .. i * 10
end end
prog[#prog + 1] = "rawgeti R 2" -- get global table in registry prog[#prog + 1] = "rawgeti R !G" -- get global table in registry
prog[#prog + 1] = "insert " .. -(2*lim + 2) prog[#prog + 1] = "insert " .. -(2*lim + 2)
for i = 1,lim do for i = 1,lim do
@@ -930,28 +930,30 @@ checkerr("FILE%* expected, got userdata", io.input, x)
assert(debug.getmetatable(x) == nil and debug.getmetatable(y) == nil) assert(debug.getmetatable(x) == nil and debug.getmetatable(y) == nil)
local d = T.ref(a); -- Test references in an arbitrary table
local e = T.ref(b); local reftable = {}
local f = T.ref(c); local d = T.ref(a, reftable);
t = {T.getref(d), T.getref(e), T.getref(f)} local e = T.ref(b, reftable);
local f = T.ref(c, reftable);
t = {T.getref(d, reftable), T.getref(e, reftable), T.getref(f, reftable)}
assert(t[1] == a and t[2] == b and t[3] == c) assert(t[1] == a and t[2] == b and t[3] == c)
t=nil; a=nil; c=nil; t=nil; a=nil; c=nil;
T.unref(e); T.unref(f) T.unref(e, reftable); T.unref(f, reftable)
collectgarbage() collectgarbage()
-- check that unref objects have been collected -- check that unref objects have been collected
assert(#cl == 1 and cl[1] == nc) assert(#cl == 1 and cl[1] == nc)
x = T.getref(d) x = T.getref(d, reftable)
assert(type(x) == 'userdata' and debug.getmetatable(x) == tt) assert(type(x) == 'userdata' and debug.getmetatable(x) == tt)
x =nil x =nil
tt.b = b -- create cycle tt.b = b -- create cycle
tt=nil -- frees tt for GC tt=nil -- frees tt for GC
A = nil A = nil
b = nil b = nil
T.unref(d); T.unref(d, reftable);
local n5 = T.newuserdata(0) local n5 = T.newuserdata(0)
debug.setmetatable(n5, {__gc=F}) debug.setmetatable(n5, {__gc=F})
n5 = T.udataval(n5) n5 = T.udataval(n5)
@@ -960,6 +962,21 @@ assert(#cl == 4)
-- check order of collection -- check order of collection
assert(cl[2] == n5 and cl[3] == nb and cl[4] == na) assert(cl[2] == n5 and cl[3] == nb and cl[4] == na)
-- reuse a reference in 'reftable'
T.unref(T.ref(23, reftable), reftable)
do -- check reftable
local count = 0
local i = 1
while reftable[i] ~= 0 do
i = reftable[i] -- traverse linked list of free references
count = count + 1
end
-- maximum number of simultaneously locked objects was 3
assert(count == 3 and #reftable == 3 + 1) -- +1 for reserved [1]
end
collectgarbage"restart" collectgarbage"restart"
@@ -1363,8 +1380,8 @@ end)
-- testing threads -- testing threads
-- get main thread from registry (at index LUA_RIDX_MAINTHREAD == 1) -- get main thread from registry
local mt = T.testC("rawgeti R 1; return 1") local mt = T.testC("rawgeti R !M; return 1")
assert(type(mt) == "thread" and coroutine.running() == mt) assert(type(mt) == "thread" and coroutine.running() == mt)

View File

@@ -681,7 +681,7 @@ else
c == "ERRRUN" and d == 4) c == "ERRRUN" and d == 4)
a, b, c, d = T.testC([[ a, b, c, d = T.testC([[
rawgeti R 1 # get main thread rawgeti R !M # get main thread
pushnum 10; pushnum 10;
pushnum 20; pushnum 20;
resume -3 2; resume -3 2;
@@ -699,7 +699,7 @@ else
assert(T.testC(state, "newthread; isyieldable -1; remove 1; return 1")) assert(T.testC(state, "newthread; isyieldable -1; remove 1; return 1"))
-- main thread is not yieldable -- main thread is not yieldable
assert(not T.testC(state, "rawgeti R 1; isyieldable -1; remove 1; return 1")) assert(not T.testC(state, "rawgeti R !M; isyieldable -1; remove 1; return 1"))
T.testC(state, "settop 0") T.testC(state, "settop 0")
@@ -711,7 +711,7 @@ else
return 'ok']])) return 'ok']]))
local t = table.pack(T.testC(state, [[ local t = table.pack(T.testC(state, [[
rawgeti R 1 # get main thread rawgeti R !M # get main thread
pushstring 'XX' pushstring 'XX'
getglobal X # get function for body getglobal X # get function for body
pushstring AA # arg pushstring AA # arg
@@ -720,7 +720,7 @@ else
setglobal T # top setglobal T # top
setglobal B # second yielded value setglobal B # second yielded value
setglobal A # fist yielded value setglobal A # fist yielded value
rawgeti R 1 # get main thread rawgeti R !M # get main thread
pushnum 5 # arg (noise) pushnum 5 # arg (noise)
resume 1 1 # after coroutine ends, previous stack is back resume 1 1 # after coroutine ends, previous stack is back
pushstatus pushstatus