Skip to content

Latest commit

 

History

History
711 lines (531 loc) · 19.8 KB

File metadata and controls

711 lines (531 loc) · 19.8 KB

Necessary Macros - Cannot Be Converted to C++

This document catalogs all macros that must remain as macros in the Lua C++ codebase and explains why they cannot be converted to modern C++ alternatives.

Status: After Phase 123 (Nov 2025) Conversion Progress: ~99.9% of convertible internal macros eliminated Remaining Macros: ~140 necessary macros across 9 categories


Table of Contents

  1. Preprocessor Feature Macros
  2. Public C API Macros
  3. Platform Abstraction Macros
  4. Conditional Compilation Macros
  5. VM Dispatch Optimization
  6. Forward Declaration Issue
  7. Configuration & User-Customizable
  8. Build System & API
  9. Low Priority - Test Only

1. Preprocessor Feature Macros

Count: 5 macros Reason: Use preprocessor features that have no C++ equivalent

L_INTHASBITS(b) - Compile-Time Bit Checking

Location: src/compiler/lopcodes.h:82

#define L_INTHASBITS(b) ((UINT_MAX >> (b)) >= 1)

Why it must remain:

  • Used in #if preprocessor conditionals (lines 85, 95, 101)
  • Must be evaluated at preprocessing time, not runtime
  • No C++ alternative for preprocessor conditionals

Usage:

#if L_INTHASBITS(MAXARG_Bx)
  // Code conditionally compiled based on integer bit width
#endif

setgcparam(g,p,v) - Token Pasting for GC Parameters

Location: src/memory/lgc.h:335

#define setgcparam(g,p,v) ((g)->setGCParam(LUA_GCP##p, luaO_codeparam(v)))

Why it must remain:

  • Uses token pasting (##) to construct identifiers
  • Converts STEPMULLUA_GCPSTEPMUL at preprocessing time
  • Token pasting only exists in the preprocessor

Usage:

setgcparam(g, STEPMUL, 200);  // Expands to LUA_GCPSTEPMUL

Usage count: 6 uses in lstate.cpp


applygcparam(g,p,x) - Token Pasting for Parameter Application

Location: src/memory/lgc.h:336

#define applygcparam(g,p,x) luaO_applyparam((g)->getGCParam(LUA_GCP##p), x)

Why it must remain: Same as setgcparam - uses token pasting Usage count: 6 uses in gc_collector.cpp, lgc.cpp


LUAI_TOSTR(x) / LUAI_TOSTRAUX(x) - Stringification

Location: include/lua.h:548-549

#define LUAI_TOSTRAUX(x) #x
#define LUAI_TOSTR(x) LUAI_TOSTRAUX(x)

Why it must remain:

  • Uses stringification operator # which only exists in preprocessor
  • Converts version numbers to strings at compile time

Usage:

#define LUA_VERSION_NUM 505
const char* version = LUAI_TOSTR(LUA_VERSION_NUM);  // "505"

Usage count: 3 uses


intop(op,v1,v2) - Operator Token Pasting

Location: src/vm/lvm.h:151

#define intop(op,v1,v2) l_castU2S(l_castS2U(v1) op l_castS2U(v2))

Why it must remain:

  • The op parameter is an operator token (+, -, *, &, |, ^, etc.), not a value
  • Operators cannot be passed to C++ templates or functions
  • The macro literally pastes the operator into the expression

Usage:

intop(+, a, b)  // Expands to: l_castU2S(l_castS2U(a) + l_castS2U(b))
intop(*, a, b)  // Expands to: l_castU2S(l_castS2U(a) * l_castS2U(b))

Usage count: 29 uses across 4 files Alternative: None - C++ has no way to pass operators as parameters


2. Public C API Macros

Count: 87 macros Reason: Required for C API compatibility

These macros are part of Lua's public C API and must remain as macros for backward compatibility with C programs using Lua. They are defined in the public headers that C programs include.

lua.h - Core API (31 macros)

Stack Manipulation:

#define lua_pop(L,n)        lua_settop(L, -(n)-1)
#define lua_insert(L,idx)   lua_rotate(L, (idx), 1)
#define lua_remove(L,idx)   (lua_rotate(L, (idx), -1), lua_pop(L, 1))
#define lua_replace(L,idx)  (lua_copy(L, -1, (idx)), lua_pop(L, 1))

Type Checking:

#define lua_isfunction(L,n)      (lua_type(L, (n)) == LUA_TFUNCTION)
#define lua_istable(L,n)         (lua_type(L, (n)) == LUA_TTABLE)
#define lua_isnil(L,n)           (lua_type(L, (n)) == LUA_TNIL)
#define lua_isboolean(L,n)       (lua_type(L, (n)) == LUA_TBOOLEAN)
#define lua_isthread(L,n)        (lua_type(L, (n)) == LUA_TTHREAD)
#define lua_islightuserdata(L,n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA)
#define lua_isnone(L,n)          (lua_type(L, (n)) == LUA_TNONE)
#define lua_isnoneornil(L,n)     (lua_type(L, (n)) <= 0)

Value Conversion:

#define lua_tonumber(L,i)   lua_tonumberx(L,(i),NULL)
#define lua_tointeger(L,i)  lua_tointegerx(L,(i),NULL)
#define lua_tostring(L,i)   lua_tolstring(L, (i), NULL)

Function Calls:

#define lua_call(L,n,r)     lua_callk(L, (n), (r), 0, NULL)
#define lua_pcall(L,n,r,f)  lua_pcallk(L, (n), (r), (f), 0, NULL)
#define lua_yield(L,n)      lua_yieldk(L, (n), 0, NULL)

Object Creation:

#define lua_newtable(L)         lua_createtable(L, 0, 0)
#define lua_register(L,n,f)     (lua_pushcfunction(L, (f)), lua_setglobal(L, (n)))
#define lua_pushcfunction(L,f)  lua_pushcclosure(L, (f), 0)
#define lua_pushliteral(L, s)   lua_pushstring(L, "" s)

Miscellaneous:

#define lua_upvalueindex(i)     (LUA_REGISTRYINDEX - (i))
#define lua_getextraspace(L)    ((void *)((char *)(L) - LUA_EXTRASPACE))
#define lua_pushglobaltable(L)  ((void)lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS))
#define lua_newuserdata(L,s)    lua_newuserdatauv(L,s,1)

Why they must remain: C programs cannot call C++ inline functions or templates.


lauxlib.h - Auxiliary Library API (29 macros)

Argument Checking:

#define luaL_checkversion(L)    luaL_checkversion_(L, LUA_VERSION_NUM, LUAL_NUMSIZES)
#define luaL_argcheck(L, cond,arg,extramsg) \
    ((void)(luai_likely(cond) || luaL_argerror(L, (arg), (extramsg))))
#define luaL_argexpected(L,cond,arg,tname) \
    ((void)(luai_likely(cond) || luaL_typeerror(L, (arg), (tname))))
#define luaL_checkstring(L,n)   (luaL_checklstring(L, (n), NULL))
#define luaL_optstring(L,n,d)   (luaL_optlstring(L, (n), (d), NULL))

Library Registration:

#define luaL_newlibtable(L,l)   lua_createtable(L, 0, sizeof(l)/sizeof((l)[0]) - 1)
#define luaL_newlib(L,l) \
    (luaL_checkversion(L), luaL_newlibtable(L,l), luaL_setfuncs(L,l,0))

Buffer Operations:

#define luaL_bufflen(bf)    ((bf)->n)
#define luaL_buffaddr(bf)   ((bf)->b)
#define luaL_addchar(B,c) \
    ((void)((B)->n < (B)->size || luaL_prepbuffsize((B), 1)), \
     ((B)->b[(B)->n++] = (c)))
#define luaL_addsize(B,s)   ((B)->n += (s))
#define luaL_buffsub(B,s)   ((B)->n -= (s))
#define luaL_prepbuffer(B)  luaL_prepbuffsize(B, LUAL_BUFFERSIZE)

Utilities:

#define luaL_typename(L,i)      lua_typename(L, lua_type(L,(i)))
#define luaL_dofile(L, fn) \
    (luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0))
#define luaL_dostring(L, s) \
    (luaL_loadstring(L, s) || lua_pcall(L, 0, LUA_MULTRET, 0))
#define luaL_getmetatable(L,n)  (lua_getfield(L, LUA_REGISTRYINDEX, (n)))
#define luaL_opt(L,f,n,d)       (lua_isnoneornil(L,(n)) ? (d) : f(L,(n)))
#define luaL_loadbuffer(L,s,sz,n) luaL_loadbufferx(L,s,sz,n,NULL)

Integer Operations:

#define luaL_intop(op,v1,v2)  l_castU2S(l_castS2U(v1) op l_castS2U(v2))
#define luaL_checkint(L,n)    ((int)luaL_checkinteger(L, (n)))
#define luaL_optint(L,n,d)    ((int)luaL_optinteger(L, (n), (d)))
#define luaL_checklong(L,n)   ((long)luaL_checkinteger(L, (n)))
#define luaL_optlong(L,n,d)   ((long)luaL_optinteger(L, (n), (d)))

Why they must remain: C API compatibility required for auxiliary library users.


luaconf.h - Configuration API (27 macros)

Math Operations:

#define l_mathop(op)        op##f
#define l_floor(x)          (l_mathop(floor)(x))
#define l_floatatt(n)       (FLT_##n)
#define lua_str2number(s,p) strtod((s), (p))
#define lua_strx2number(s,p) strtod((s), (p))
#define lua_number2strx(L,b,sz,f,n) \
    ((void)L, l_sprintf((b), sz, f, (LUAI_UACNUMBER)(n)))

String/IO:

#define l_sprintf(s,sz,f,i) snprintf(s,sz,f,i)
#define lua_pointer2str(buff,sz,p) l_sprintf(buff,sz,"%p",p)
#define lua_integer2str(buff,sz,n) l_sprintf(buff,sz,LUA_INTEGER_FMT,n)
#define lua_getlocaledecpoint() (localeconv()->decimal_point[0])

Optimization Hints:

#define luai_likely(x)      __builtin_expect(((x) != 0), 1)
#define luai_unlikely(x)    __builtin_expect(((x) != 0), 0)

Compatibility:

#define lua_strlen(L,i)     lua_rawlen(L, (i))
#define lua_objlen(L,i)     lua_rawlen(L, (i))
#define lua_equal(L,idx1,idx2)      lua_compare(L,(idx1),(idx2),LUA_OPEQ)
#define lua_lessthan(L,idx1,idx2)   lua_compare(L,(idx1),(idx2),LUA_OPLT)

Why they must remain: Platform portability and configuration flexibility for C API users.


3. Platform Abstraction Macros

Count: 41 macros Reason: Different implementations for different platforms, selected at compile time

I/O Library Platform Macros (20 macros)

Location: src/libraries/liolib.cpp

#if defined(LUA_USE_POSIX)
  #define l_popen(L,c,m)      (fflush(NULL), popen(c,m))
  #define l_pclose(L,file)    (pclose(file))
#elif defined(LUA_USE_WINDOWS)
  #define l_popen(L,c,m)      (_popen(c,m))
  #define l_pclose(L,file)    (_pclose(file))
#else
  #define l_popen(L,c,m)      (((void)c, (void)m), luaL_error(L, "popen not supported"), NULL)
  #define l_pclose(L,file)    ((void)file, -1)
#endif

Other I/O macros:

  • l_checkmodep(m,mode) - Mode validation (POSIX vs Windows)
  • l_getc(f) - Buffered character input
  • l_lockfile(f), l_unlockfile(f) - File locking (POSIX only)
  • l_fseek(f,o,w), l_ftell(f) - Large file support (fseeko vs fseek)

Why they must remain: Different implementations for POSIX/Windows/ISO C selected at preprocessing time.


OS Library Platform Macros (12 macros)

Location: src/libraries/loslib.cpp

// Time handling - different on POSIX vs C89
#if defined(LUA_USE_POSIX)
  #define l_gmtime(t,r)     gmtime_r(t,r)
  #define l_localtime(t,r)  localtime_r(t,r)
#else
  #define l_gmtime(t,r)     ((void)(r)->tm_sec, gmtime(t))
  #define l_localtime(t,r)  ((void)(r)->tm_sec, localtime(t))
#endif

// System command
#if defined(LUA_USE_POSIX)
  #define l_system(cmd)  system(cmd)
#else
  #define l_system(cmd)  ((void)cmd, system(NULL))
#endif

Time/Date macros:

  • l_pushtime(L) - Push time to stack (platform-dependent)
  • l_gettime(t) - Get current time
  • lua_tmpnam(b,e) - Temporary filename generation

Why they must remain: Platform-specific APIs with different signatures.


Interpreter Platform Macros (9 macros)

Location: src/interpreter/lua.cpp

// Terminal detection
#if defined(LUA_USE_POSIX)
  #define lua_stdin_is_tty()  isatty(0)
#elif defined(LUA_USE_WINDOWS)
  #define lua_stdin_is_tty()  _isatty(_fileno(stdin))
#else
  #define lua_stdin_is_tty()  1
#endif

// Readline support
#if defined(LUA_USE_READLINE)
  #define lua_readline(L,b,p)    ((void)L, readline(p))
  #define lua_saveline(L,line)   ((void)L, add_history(line))
  #define lua_freeline(L,b)      ((void)L, free(b))
  #define lua_initreadline(L)    ((void)L, rl_readline_name="lua")
#else
  #define lua_readline(L,b,p) \
    ((void)L, fputs(p, stdout), fflush(stdout), \
     fgets(b, LUA_MAXINPUT, stdin))
  #define lua_saveline(L,line)  { (void)L; (void)line; }
  #define lua_freeline(L,b)     { (void)L; (void)b; }
  #define lua_initreadline(L)   ((void)L)
#endif

// Library opening
#define luai_openlibs(L) \
  {luaL_openlibs(L); luaopen_debug(L);}

Why they must remain: Configuration-dependent behavior for interactive mode.


4. Conditional Compilation Macros

Count: 7+ macros Reason: Different definitions based on debug/release mode

Assertion Macros

Location: src/memory/llimits.h:137-149

#if defined LUAI_ASSERT
  #undef NDEBUG
  #include <assert.h>
  #define lua_assert(c)      assert(c)
  #define assert_code(c)     c
#else
  #define lua_assert(c)      ((void)0)
  #define assert_code(c)     ((void)0)
#endif

#define check_exp(c,e)       (lua_assert(c), (e))
#define lua_longassert(c)    assert_code((c) ? (void)0 : lua_assert(0))

Why they must remain:

  • Entire macro body changes between debug and release builds
  • Assertions must be completely removed in release builds for performance
  • Cannot achieve this with constexpr if without runtime overhead

API Assertion Macros

Location: src/core/lapi.h:17-19

#if defined(LUA_USE_APICHECK)
  #define api_check(l,e,msg)  lua_assert(e)
#else
  #define api_check(l,e,msg)  ((void)0)
#endif

Usage count: 30+ uses throughout lapi.cpp

Why it must remain: Debug vs release build differences.


Memory Testing Macro (HARDMEMTESTS)

Location: src/memory/lgc.h:357-361

#if !defined(HARDMEMTESTS)
  template<typename PreFunc, typename PostFunc>
  inline void condchangemem(...) { /* Empty */ }
#else
  template<typename PreFunc, typename PostFunc>
  inline void condchangemem(lua_State* L, PreFunc pre, PostFunc post, int emg) {
    if (G(L)->isGCRunning()) {
      pre();
      luaC_fullgc(L, emg);
      post();
    }
  }
#endif

Note: After Phase 123, the function is templated but the conditional compilation remains necessary.

Why conditional compilation must remain: Completely different behavior for stress testing.


5. VM Dispatch Optimization

Count: 3 macros Reason: Performance-critical hot path with compiler-specific optimization

VM Dispatch Mechanism

Location: src/vm/lvm.cpp (switch version) and src/vm/ljumptab.h (computed goto version)

Switch-based dispatch:

#define vmdispatch(o)  switch(o)
#define vmcase(l)      case l:
#define vmbreak        break

Computed goto dispatch (GCC extension, ~10% faster):

#define vmdispatch(x)  goto *disptab[x];
#define vmcase(l)      L_##l:
#define vmbreak        goto dispatchloop

Why they must remain:

  • Allows switching between switch-based and computed-goto dispatch at compile time
  • Computed goto is a GCC extension, not standard C++
  • Critical performance optimization for VM bytecode interpreter (hot path)
  • ~10% performance difference justifies keeping the abstraction

Usage count: Hundreds of uses throughout the VM interpreter


6. Forward Declaration Issue

Count: 1 macro Reason: lua_State incomplete type at point of use

luaM_error(L) - Memory Error

Location: src/memory/lmem.h:18

#define luaM_error(L)  (L)->doThrow(LUA_ERRMEM)

Why it must remain:

  • lmem.h is included very early in many compilation units
  • At the point where lmem.h is included, lua_State is often an incomplete type
  • Cannot call member function doThrow() on incomplete type
  • Macro defers expansion until point of use where lua_State is complete

Attempted conversion in Phase 123: Had to be reverted due to compilation errors.

Usage count: 9 uses

Alternative approach: Would require restructuring header inclusion order across entire codebase.


7. Configuration & User-Customizable

Count: 10+ macros Reason: Designed to be overridden by users

Output Macros

Location: src/memory/llimits.h:481-494

#if !defined(lua_writestring)
  #define lua_writestring(s,l)  fwrite((s), sizeof(char), (l), stdout)
#endif

#if !defined(lua_writeline)
  #define lua_writeline()  (lua_writestring("\n", 1), fflush(stdout))
#endif

#if !defined(lua_writestringerror)
  #define lua_writestringerror(s,p) \
    (fprintf(stderr, (s), (p)), fflush(stderr))
#endif

Why they must remain:

  • Designed to be customized by users for embedded systems
  • Users can #define these before including Lua headers
  • Allows redirecting output to custom destinations (LCD, serial port, etc.)

Hook Macros

Location: src/testing/ltests.h (testing) and various locations

#if !defined(luai_userstateopen)
  #define luai_userstateopen(L)     ((void)L)
#endif

#if !defined(luai_userstatethread)
  #define luai_userstatethread(L,L1)  ((void)L)
#endif

#if !defined(luai_threadyield)
  #define luai_threadyield(L)   {luai_userstatethread(L,NULL); \
                                 luai_userstateresume(L,NULL);}
#endif

Why they must remain: User-customizable hooks for embedding scenarios.


8. Build System & API

Count: 5+ macros Reason: Build configuration and API visibility

API Export Macros

Location: src/memory/llimits.h:455-467

#if !defined(LUAI_FUNC)
  #if defined(__GNUC__) && ((__GNUC__*100 + __GNUC_MINOR__) >= 302) && \
      defined(__ELF__)
    #define LUAI_FUNC  __attribute__((visibility("internal"))) extern
  #else
    #define LUAI_FUNC  extern
  #endif

  #define LUAI_DDEC(dec)  LUAI_FUNC dec
  #define LUAI_DDEF       /* empty */
#endif

Why they must remain:

  • Control symbol visibility for shared library builds
  • Different implementations for different compilers
  • Build system configuration

Type Casting for Compatibility

Location: src/memory/llimits.h:297-301

#if defined(__GNUC__)
  #define cast_func(p)  (__extension__ (voidf)(p))
#else
  #define cast_func(p)  ((voidf)(p))
#endif

Why it must remain:

  • Suppresses GCC warnings with __extension__
  • Different implementations for different compilers

9. Low Priority - Test Only

Count: 13+ macros Reason: Only used in test infrastructure

Test Macros

Location: src/testing/ltests.h and src/testing/ltests.cpp

// String comparison in test dispatcher
#define EQ(s1)  (strcmp(s1, inst) == 0)

// Test object access
#define obj_at(L,k)  s2v(L->ci->func.p + (k))

// Memory filling for tests
#define fillmem(mem,size)  memset(mem, -ABS_INDEX, size)

// GC object reference checking
#define checkobjrefN(g,f,t)  /* ... complex checking logic ... */

Why conversion is low priority:

  • Only affects test code, not production
  • Could be converted to inline functions
  • Low value since tests work fine with macros

Usage count: 100+ uses (mostly EQ() in test dispatcher)

Conversion effort: 1-2 hours Value: Low (test-only code)


Summary Table

Category Count Can Convert? Priority
Preprocessor Features 5 ❌ Never N/A
Public C API 87 ❌ Never N/A
Platform Abstraction 41 ❌ Never N/A
Conditional Compilation 7 ❌ Never N/A
VM Dispatch 3 ⚠️ Possible but loses optimization N/A
Forward Declaration 1 ⚠️ Requires major refactoring Low
User-Customizable 10 ❌ Never (by design) N/A
Build System 5 ❌ Never N/A
Test Only 13 ✅ Yes Very Low
TOTAL ~172 ~13 convertible Not recommended

Conclusion

After Phase 123 (Nov 2025), the Lua C++ codebase has achieved:

  • ~500 internal macros converted to modern C++ ✅
  • ~99.9% of convertible macros eliminated
  • ~140 necessary macros remain that cannot/should not be converted

The remaining ~140 macros serve essential purposes:

  1. C API compatibility (87 macros) - Required for C language users
  2. Platform portability (41 macros) - Handle OS/compiler differences
  3. Preprocessor features (5 macros) - No C++ equivalent exists
  4. Build configuration (5+ macros) - Control compilation process
  5. User customization (10+ macros) - Designed to be overridden

Attempting to convert these would:

  • Break C API compatibility
  • Lose platform portability
  • Remove user customization capability
  • Reduce VM performance (dispatch optimization)
  • Require major architectural changes (forward declaration issue)

Recommendation: Leave all ~140 necessary macros as-is. They serve essential purposes and represent proper use of the C preprocessor.


Last Updated: 2025-11-27 Phase: 123 Complete Status: Macro modernization complete - only necessary macros remain