Modifying Lua

lua-users home
wiki

The following provides some first steps to altering the functionality of Lua itself by modifying its source code. The examples below are not necessarily good choices of things to modify, nor the best way to make these modifications. However, they have been chosen due to both their impact and the nature of the code being altered. This tutorial was written for Lua 5.1.1.

The task

This is best illustrated below. We want to modify Lua so that the following code will run as shown.

test1.lua


function configmodified(t,k,v)
	print("Config Modified, call reload")
	rawset(t,k,v)
end

config = { maxthreads = 10, minthreads = 2 }
setmetatable(config, {__setindex = configmodified})

And the results

D:\code\lua\mod\bin>lua -i test1.lua
Lua 5.1.1  Copyright (C) 1994-2006 Lua.org, PUC-Rio
table: 00940180
> =config.minthreads
2
> config.minthreads = 3
Config Modified, call reload
> =config.minthreads
3
> config.sleepdelay = 100
Config Modified, call reload

The Modifications

The internal code calls metamethods 'tag methods' the base list is located in ltm.h

/*
* WARNING: if you change the order of this enumeration,
* grep "ORDER TM"
*/
typedef enum {
  TM_INDEX,
  TM_NEWINDEX,
  TM_GC,
  TM_MODE,
  TM_EQ,  /* last tag method with `fast' access */
  TM_ADD,
  TM_SUB,
  TM_MUL,
  TM_DIV,
  TM_MOD,
  TM_POW,
  TM_UNM,
  TM_LEN,
  TM_LT,
  TM_LE,
  TM_CONCAT,
  TM_CALL,
  TM_N		/* number of elements in the enum */
} TMS;

The comment of TM_EQ is pretty important. Since setting a value needs to be high performance, we need to look at this.

The way 'fast' access works is by storing a flag on each table and marking if the flag exists.

Tables are defined in lobject.h

typedef struct Table {
  CommonHeader;
  lu_byte flags;  /* 1<<p means tagmethod(p) is not present */ 
  lu_byte lsizenode;  /* log2 of size of `node' array */
  struct Table *metatable;
  TValue *array;  /* array part */
  Node *node;
  Node *lastfree;  /* any free position is before this position */
  GCObject *gclist;
  int sizearray;  /* size of `array' array */
} Table;

Table.flags is 1 byte, since each flag takes 1 bit, we can have up to 8 'fast access' metamethods. Base Lua comes with 5 setup as fast access, giving us room for 3 more. We will use one of them for SETINDEX. as such

ltm.h - mods



typedef enum {
  TM_INDEX,
  TM_NEWINDEX,
  TM_SETINDEX,	// AA - new metatag that is called everytime a value is set, its use disables TM_NEWINDEX'''
  TM_GC,
  TM_MODE,
  TM_EQ,  /* last tag method with `fast' access */
  ...

We also need to give this new tag a name this is found inside

ltm.c - luaT_init


  static const char *const luaT_eventname[] = {  /* ORDER TM */
    "__index", "__newindex", 
    "__gc", "__mode", "__eq",
    "__add", "__sub", "__mul", "__div", "__mod",
    "__pow", "__unm", "__len", "__lt", "__le",
    "__concat", "__call"
  };

We will change it to

  static const char *const luaT_eventname[] = {  /* ORDER TM */
    "__index", "__newindex", "__setindex",   // AA - Added name for setindex
    "__gc", "__mode", "__eq",
    ...

Finally we need to make this code work. Most of the heavy work occurs in lvm.c Standard calls for setting values (not rawset) go through luaV_settable. Lets look


void luaV_settable (lua_State *L, const TValue *t, TValue *key, StkId val) {
  int loop;
  for (loop = 0; loop < MAXTAGLOOP; loop++) {
    const TValue *tm;
    if (ttistable(t)) {  /* `t' is a table? */
      Table *h = hvalue(t);
      TValue *oldval = luaH_set(L, h, key); /* do a primitive set */
      if (!ttisnil(oldval) ||  /* result is no nil? */
          (tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL) { /* or no TM? */
        setobj2t(L, oldval, val);
        luaC_barriert(L, h, val);
        return;
      }
      /* else will try the tag method */
    }
    else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_NEWINDEX)))
      luaG_typeerror(L, t, "index");
    if (ttisfunction(tm)) {
      callTM(L, tm, t, key, val);
      return;
    }
    t = tm;  /* else repeat with `tm' */ 
  }
  luaG_runerror(L, "loop in settable");
}

We will modify this like so.



void luaV_settable (lua_State *L, const TValue *t, TValue *key, StkId val) {
  int loop;
  for (loop = 0; loop < MAXTAGLOOP; loop++) {
    const TValue *tm;
    if (ttistable(t)) {  /* `t' is a table? */
      Table *h = hvalue(t);

      TValue *oldval;	// AA -- Have to declare this here

	  // AA - Our new code here
	  tm = fasttm(L, h->metatable, TM_SETINDEX);
	  if(tm != NULL)  {
		if (ttisfunction(tm)) {
		  callTM(L, tm, t, key, val);
		  return;
		}
	  }

      oldval = luaH_set(L, h, key); /* do a primitive set */
      if (!ttisnil(oldval) ||  /* result is no nil? */
          (tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL) { /* or no TM? */
        setobj2t(L, oldval, val);
        luaC_barriert(L, h, val);
        return;
      }
      ...


Now we compile and test. The code at top should now work as advertised.

Comments

This should be a pretty basic but effective first mod to Lua. One other thing I suggest doing is altering the Version and Release constants so its easy to spot this is a modified version of lua. They are located in lua.h

Do not alter the numbers in LUA_VERSION or LUA_RELEASE! Append a string such as '(Kirk3)' . --lhf

One of the problems with this is that every set takes an additional test. This can be mitigated somewhat with the following design considerations.

---

There are papers describing the implementation of Lua[1]. See also A No-Frills Introduction to Lua 5.1 VM Instructions[2]. Yueliang[3] (an implementation of Lua in Lua) can also be helpful.

Alternatives to modifying Lua yourself include MetaLua[4] and LuaTokenParsing. You may not need to even modify Lua given Lua's powerful metaprogramming capabilities (see in particular CodeGeneration).


RecentChanges · preferences
edit · history
Last edited October 21, 2017 12:15 pm GMT (diff)