Skip to content

Instantly share code, notes, and snippets.

@sheharyaar
Last active May 7, 2024 09:07
Show Gist options
  • Save sheharyaar/946eb2850ccb10aa54d80cf4351981b8 to your computer and use it in GitHub Desktop.
Save sheharyaar/946eb2850ccb10aa54d80cf4351981b8 to your computer and use it in GitHub Desktop.
My notes on Lua and Lua C API for GSoC 2024 Lablua project

Lua C API

Lua C core

  1. lua_State - all the Lua state in this dynamic variable.
  2. luaopen_LIBNAME - opens table and registers the functions (io.readio.write, etc.) inside it using luaL_newlib
  3. luaL_loadbuffer - compile the code and push the chunk to the *stack , returns 0 on no error, else push error message on stack
  4. lua_pcall - pops the chunk from the stack and runs it in protected mode, returns 0 on no error, else push error message on stack
  5. lua_tostring to get the message string and lua_pop to remove the error from the stack

(1) state created → (2) libraries populated → (3) compile the code → (4) run the code → (5) get errors (if any)

Stack

Lua C
variable types dynamic static
memory management automatic manual
garbage collection yes no
  • Lua needs to track the variables for proper garbage collection.
  • Issues can arise if Lua garbage collects a variable whose reference is still held by the C function
  • Sharing of dynamic type variables with C is a difficult task

To prevent creating a function for each type of variable, Lua uses a stack to share variables between Lua and C programs :

  • Each slot of the stack can hold any value.
  • Whenever you want to ask for a value from Lua (such as the value of a global variable), you call Lua → which pushes the required value on the stack.
  • Whenever you want to pass a value to Lua, you first push the value on the stack → then you call Lua (which will pop the value).
  • the stack is managed by Lua, so Lua garbage collector knows which values are being used by C.

Important points to note :

  • Lua manipulates the stack in strictly LIFO order.
  • The C programs are allowed to inspect any element and insert/delete in any arbitrary position.

Indexing

  • positive index : bottom to top (1 being the first object pushed – bottom of stack)
  • negative index : top to bottom (-1 being the top element)

Stack operations

  • lua_gettop - returns the number of elements in the stack or the index of the top element (postive). stack-gettop

  • lua_settop - sets the top (number of elements) to a value.

    • if new top is less than prev top : then extra elements are discarded.
    • if new top is more than prev top : then extra nils are placed.
    • lua_settop(L, 0) empties the stack

    stack-settop

  • lua_pop(L,n) - pops n elements from the stack, implemented as :

#define lua_pop(L,n)  lua_settop(L, -(n)-1)
  • lua_remove -  removes the element at the given index, shifting down all elements on top of that position to fill in the gap
  • lua_insert - moves the top element into the given position, shifting up all elements on top of that position to open space
  • lua_replace pops a value from the top and sets it as the value of the given index, without moving anything

Data Types

  • Strings in Lua are not zero-terminated
  • Lua never keeps pointers to external strings (or to any other object, except to C functions, which are always static).
  • Lua either makes an internal copy or reuses one. Therefore, you can free or modify your buffer as soon as these functions return.
  • Whenever you push an element onto the stack, it is your responsibility to ensure that the stack has space for it.
  • lua_checkstack (lua_State *L, int sz) to check if there is space on the stack.
  • lua_is* functions for checking the type of the element.
  • Any string that lua_tostring returns always has a zero at its end, but it can have other zeros inside it. The lua_strlen function returns the correct length of the string.

Error handling

  • Lua uses the setjmp facility from C for exception handling
  • Instead of using error codes for each operation in its API, Lua uses exceptions to signal these errors.
  • Almost all API functions may throw an error (that is, call longjmp) instead of returning.
  • When Lua faces an error like "not enough memory", there is not much that it can do. It calls a panic function and, if the function returns, exits the application (can be set using lua_atpanic)

Make sure to run your code in protected mode, in unprotected mode, Lua cannot call setjmp to handle exceptions. Protected mode is when the function is called using lua_pcall, which returns an error code even in case of memory-allocation failure.

  • Whenever a C function detects an error, it simply calls lua_error, (or better yet luaL_error, which formats the error message and then calls lua_error).
  • The lua_error function clears whatever needs to be cleared in Lua and jumps back to the lua_pcall that originated that execution, passing along the error message.

Table Manipulation

  • lua_getglobal(L,name) - Lua pushes onto the stack the value of the global name.
  • lua_setglobal(L,name) - Lua pops a value from the stack and sets it as the new value of global name.
  • lua_istable(L,idx) - Check if the element at idx is a table
  • lua_gettable(L,idx) - pushes on to the stack the value t[k] where t is a table at idx in the stack. This function pops the key from the stack and pushes the value on top. It also may trigger the metamethod for the index event. table-gettable
  • lua_getfield(L,idx,k) - pushes on to the stack the value t[k], no popping in this case, this may also trigger the metamethod for the index event.
  • lua_settable(L,idx) - equivalent to t[k] = v, where t is the value at the given valid index, v is the value at the top of the stack, and k is the value just below the top. Pops both the key and the value from the stack. May trigger a metamethod for the newindex event

table-settable

  • lua_setfield(L,idx,k) - Does the equivalent to t[k] = v, where t is the value at the given valid index and v is the value at the top of the stack. Pops the value from the stack. As in Lua, this function may trigger a metamethod for the newindex event

Lua calls from C

  • lua_pcall(L,nargs,nresults,errfunc) calls a function in protected mode.
  • if there is any error, lua_pcall catches it, pushes a single value on the stack (the error message), and returns an error code
  • If errfunc is 0, then the error message returned on the stack is exactly the original error message
  • Else, errfunc is the stack index of an error handler function. (cannot be a pseudo-index)
    • this function will be called with the error message and its return value will be the message returned on the stack by lua_pcall.

Typically, the error handler function is used to add more debug information to the error message, such as a stack traceback. Such information cannot be gathered after the return of lua_pcall, since by then the stack has unwound.

Errors

  • LUA_ERRRUN: a runtime error.
  • LUA_ERRMEM: memory allocation error. For such errors, Lua does not call the error handler function.
  • LUA_ERRERR: error while running the error handler function.

References

Metatable

  •  ordinary Lua table that defines the behavior of the original value under certain special operations (keys → events and values →metamethods)
  • Example, if  a non-numeric value is the operand of an addition, Lua checks for a function in the field __add in its metatable. If it finds one, Lua calls this function to perform the addition. (Similar to __string and other such functions in python)

API

  • getmetatable(object) - nil if the object does not have a metatable, if the object's metatable has a __metatable field, returns the associated value. Otherwise, returns the metatable of the given object.
  • setmetatable(table, metatable) - we can set metatable only for a table from Lua, for other types we can do it from C. If metatable is nil, it removes the metatable. If the original metatable has a __metatable field, raises an error. This function returns table.
  • lua_getmetatable(L,index) - C API for getting the mtetatable.
    • Pushes onto the stack the metatable of the value at the given acceptable index.
    • If the index is not valid, or if the value does not have a metatable, → returns 0 and pushes nothing on the stack.
  • lua_setmetatable(L,index) - C API for setting the metatable. Pops a table from the stack and sets it as the new metatable for the value at the given acceptable index.

Events

  • the keys are prefixed by two underscores __
  • Available events are :
event operation
index indexing access table[key]
newindex indexing assignment table[key] = value
call Lua calls a value
add + add
sub - subtract
mul * multiply
div / divide
mod % mod
pow ^ exponent
unm unary minus
concat .. operation
len # length
eq == equality
lt < less than
le ≤ less than equal to

Registry

Why a registry ??

  • C functions need to keep some non-local data which outlive the invocation
  • You cannot store a generic Lua value in a C variable, library that uses such variables cannot be used in multiple Lua states
  • An alternative approach is to store such values into Lua global variables
  • Lua global variables store any Lua value and each independent state has its own independent set of global variables
  • However, this is not always a satisfactory solution, because Lua code can tamper with those global variables and therefore compromise the integrity of C data

C code can freely use the registry, but Lua code cannot access it.

Q) What is a registry

  • registry is a regular Lua table
  • you can index it with any Lua value but **nil
  • always located at a pseudo-index, whose value is defined by LUA_REGISTRYINDEX
    • A pseudo-index is like an index into the stack, except that its associated value is not in the stack.

Note: all C libraries share the same registry

  • you must choose with care what values you use as keys, to avoid collisions. A bulletproof method is to use as key the address of a static variable in your code: The C link editor ensures that this key is unique among all libraries
  • lua_pushlightuserdata(L,p) can be used to do this – it pushes a pointer to the stack
    • It is a value (like a number) and not a variable: you do not create it, it has no individual metatable, and it is not collected (as it was never created)
 /* variable with an unique address */
static const char Key = 'k';

/* store a number */
lua_pushlightuserdata(L, (void *)&Key);  /* push address */
lua_pushnumber(L, myNumber);  /* push value */
/* registry[&Key] = myNumber */
lua_settable(L, LUA_REGISTRYINDEX);
    
/* retrieve a number */
lua_pushlightuserdata(L, (void *)&Key);  /* push address */
lua_gettable(L, LUA_REGISTRYINDEX);  /* retrieve value */
myNumber = lua_tonumber(L, -1);  /* convert to number */
  • You can also use strings as keys into the registry, as long as you choose unique names

String keys are particularly useful when you want to allow other independent libraries to access your data, because all they need to know is the key name.

  • Never use numbers as keys in the registry, because such keys are reserved for the reference system.

Userdata

  • A userdatum offers a raw memory area with no predefined operations in Lua.

Full userdata

  •  A full userdata represents a block of memory.
  • It is an object (like a table): you must create it
  • It can have its own metatable
  • You can detect when it is being collected
  • A full userdata is only equal to itself (under raw equality).
  • When Lua collects a full userdata with a gc metamethod (only for userdatata), Lua calls the metamethod and marks the userdata as finalized. When this userdata is collected again then Lua frees its corresponding memory.

API

  • lua_newuserdata(L,size) - allocates a new block of memory with the given size, pushes onto the stack a new full userdata with the block address, and returns this address.

To distinguish <userdata> from other userdata, we create a unique metatable for it. Then, every time we create a <userdata>, we mark it with this metatable; and every time we get an <userdata>, we check whether it has the right metatable.

Because Lua code cannot change the metatable of a userdatum, it cannot fake our code.

  • luaL_newmetatable(L,name)
    • If the registry already has the key tname, returns 0
    • Otherwise, creates a new table to be used as a metatable for userdata, adds it to the registry with key tname, and returns 1.
    • In both cases pushes onto the stack the final value associated with tname in the registry.
  • luaL_getmetatable(L,name) : Pushes onto the stack the metatable associated with name tname in the registry
  • *luaL_checkudata(L,narg,tname) : Checks whether the function argument narg is a userdata of the type tname

Light userdata

  • Light userdata are not buffers, but single pointers.
  • They have no metatables.
  • Like numbers, light userdata do not need to be managed by the garbage collector (and are not). You have to manage memory by yourself.

The real use of light userdata comes from equality.

As a full userdata is an object, it is only equal to itself. A light userdata, on the other hand, represents a C pointer value. As such, it is equal to any userdata that represents the same pointer. Therefore, we can use light userdata to find C objects inside Lua.

If a table has key as light user data and value as full userdata → the table should have weak values. Otherwise, those full userdata would never be collected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment