Whilst the Lua Virtual Machine (LVM) can compile Lua source dynamically and this can prove very flexible during development, you will use less RAM resources if you precompile your sources before execution.

Compiling Lua directly on your ESP8266

  • The standard string.dump (function) returns a string containing the binary code for the specified function and you can write this to a SPIFFS file.

  • node.compile() wraps this 'load and dump to file' operation into a single atomic library call.

The issue with both of these approaches is that compilation is RAM-intensive and hence you will find that you will need to break your application into a lot of small and compilable modules in order to avoid hitting RAM constraints. This can be mitigated by doing all compiles immediately after a node.restart()`.

Compiling Lua on your PC for Uploading

If you install lua on your development PC or Laptop then you can use the standard Lua compiler to syntax check any Lua source before downloading it to the ESP8266 module. However, the NodeMCU compiler output uses different data types (e.g. it supports ROMtables) so the compiled output from standard luac cannot run on the ESP8266.

Compiling source on one platform for use on another (e.g. Intel 64-bit Windows to ESP8266) is known as cross-compilation and the NodeMCU firmware build now automatically generates a luac.cross image as standard in the firmware root directory; this can be used to compile and to syntax-check Lua source on the Development machine for execution under NodeMCU Lua on the ESP8266.

luac.cross will translate Lua source files into binary files that can be later loaded and executed by the LVM. Such binary files, which normally have the .lc (lua code) extension are loaded directly by the LVM without the RAM overhead of compilation.

Each luac.cross execution produces a single output file containing the bytecodes for all source files given in the output file luac.out, but you would normally change this with the -o option. If you wish you can mix Lua source files (and even Lua binary files) on the command line. You can use '-' to indicate the standard input as a source file and '--' to signal the end of options (that is, all remaining arguments will be treated as files even if they start with '-').

luac.cross supports the standard luac options -l, -o, -p, -s and -v, as well as the -h option which produces the current help overview.

NodeMCU also implements some major extensions to support the use of the Lua Flash Store (LFS)), in that it can produce an LFS image file which is loaded as an overlay into the firmware in flash memory; the LVM can access and execute this code directly from flash without needing to store code in RAM. This mode is enabled by specifying the -foption.

luac.cross supports two separate image formats:

  • Compact relocatable. This is selected by the -f option. Here the compiler compresses the compiled binary so that image is small for downloading over Wifi/WAN (e.g. a full 64Kb LFS image is compressed down to a 22Kb file.) The LVM processes such image in two passes with the integrity of the image validated on the first, and the LFS itself gets updated on the second. The LVM also checks that the image will fit in the allocated LFS region before loading, but you can also use the -m option to throw a compile error if the image is too large, for example -m 0x10000 will raise an error if the image will not load into a 64Kb regions.

  • Absolute. This is selected by the -a <baseAddr> option. Here the compiler fixes all addresses relative to the base address specified. This allows an LFS absolute image to be loaded directly into the ESP flash using a tool such as esptool.py. _Note that the new NodeMCU loader uses the -f compact relocatable form and does relocation based on the Partition Table, so this option is deprecated and will be removed in future releases.

These two modes target two separate use cases: the compact relocatable format facilitates simple OTA updates to an LFS based Lua application; the absolute format facilitates factory installation of LFS based applications.

Also note that the app/lua/luac_cross make and Makefile can be executed to build just the luac.cross image. You must first ensure that the following options in app/include/user_config.h are matched to your target configuration:

// When using Lua 5.1, two different builds are now supported.
// The main difference is in the // processing of numeric data types.
// If LUA_NUMBER_INTEGRAL is defined, then
// all numeric calculations are done in integer, with divide being an integer
// operation, and decimal fraction constants are illegal.
// Otherwise all floating point operations use doubles. All integer values
// can be represented exactly in floating point.


// When using Lua 5.3, two different builds are now supported. 
// If LUA_NUMBER_64BITS is defined, then doubles are used to hold floating
// point numbers and integers are 64 bits long. Integers smaller than 2^53 can be i
// converted to doubles and back without any loss of precision.
// Otherwise all floating point operations use floats, and integers are 32-bit.
// Only integers under 2^24 can be represented exactly in floating point.
// Note that Lua 5.3 also supports Integers natively, but you have to be careful 
// not to promote an integer to a floating point variable as you can lose precision.

//#define LUA_NUMBER_64BITS

Note that the use of LFS and the LFS region size is now configured through the partition table.

Developers have successfully built this on Linux (including docker builds), MacOS, Win10/WSL and WinX/Cygwin.