Skip to content

Fast flags

Luau ships new compiler and VM work behind boolean flags declared in its C++ source. A flag defaults to off, and the new code path runs only where the flag is on. Roblox turns flags on remotely, from its servers, some time after the Luau release that added them.

So the Luau version alone does not say how a script runs. Two builds of the same Luau release, one with Luau’s defaults and one with Roblox’s values, compile and execute the same source differently.

LuneBlox carries a table of flags and the value the live Roblox client has for each. Before the first Luau VM is created, it sets every flag in the table. This happens once per process, in:

  • luneblox run
  • the REPL
  • luneblox build, before the script is compiled to bytecode
  • a standalone executable, before it runs its bytecode

The table is the file crates/lune/src/rt/roblox_fflags.rs. Its header says what it was generated from:

Luau fast flags as the Roblox client 0.744.0.7441080 sets them, limited to the
flags Luau 0.740 declares.

It holds 38 flags. A few of them:

Flag Value
DebugCodegenOptSize false
LuauCallFeedback true
LuauCodegenPropagateFallbackTags false
LuauCompileIifeInline true
LuauCompileLoopUnrollZero true
LuauFastpcall true
LuauPcallOptimize true
LuauSplitTableLookups true

The table records flags Roblox has explicitly set to false as well as those it has turned on. A flag Roblox publishes no value for is not in the table and keeps Luau’s default.

The table is compiled into the binary. A given LuneBlox release always runs with the same flags; it does not contact Roblox at run time.

Set LUNE_ROBLOX_FFLAGS to 0 to keep Luau’s own defaults:

Terminal window
LUNE_ROBLOX_FFLAGS=0 luneblox run bench.luau

Only the exact value 0 does this. Any other value, or no value, applies the table.

The use for it is measurement. Run the same benchmark both ways to see what the flags change:

Terminal window
luneblox run bench.luau
LUNE_ROBLOX_FFLAGS=0 luneblox run bench.luau

The variable is all or nothing. There is no setting for a single flag.

DebugCodegenOptSize skips a native code optimization: block linearization, and constant propagation along the chains it builds. Roblox set it until client 0.744, which turned it off, and LuneBlox follows: since 0.11.2 the table holds it as false, and native code gets the optimization again. On an Apple M1, a Luau networking library’s generated modules fired a thousand events 13 to 36% faster natively, and decoded them 19 to 38% faster; interpreted code did not move.

luneblox build sets the flags before compiling, since some of them change the bytecode the compiler emits. The built executable sets them again at startup.

LUNE_ROBLOX_FFLAGS=0 is honoured at both points separately: in the environment of luneblox build it affects compilation, and in the environment of the built executable it affects execution.

The table goes stale when Roblox changes a flag or LuneBlox moves to a new Luau. It is generated by a script in the repository, run from the repository root:

Terminal window
luneblox run scripts/generate_roblox_fflags.luau
Wrote 38 flags (36 enabled) for Roblox 0.744.0.7441080 and Luau 0.740

The script does this:

  1. Runs cargo metadata to find the Luau sources the build uses (the luau0-src package). Cargo must be installed and the dependencies fetched.
  2. Scans those sources for every flag declared with LUAU_FASTFLAGVARIABLE or LUAU_DYNAMIC_FASTFLAGVARIABLE, leaving out the sources the script lists as never linked into the runtime.
  3. Downloads Roblox’s published client settings for the desktop client, and the current Windows player version.
  4. Keeps the settings named FFlag<name> or DFFlag<name> whose name Luau declares and whose value is True or False.
  5. Writes them, sorted by name, to crates/lune/src/rt/roblox_fflags.rs.
  6. Rewrites the lines of this page that quote the table: the header, the number of flags and the output above.

Limiting the table to flags that this Luau declares is what makes it safe: Roblox’s settings include flags for Luau versions older and newer than the one built here.

Declaring a flag is not enough for it to be set. Luau registers a flag only when the source file that declares it is linked into the binary, and the runtime does not link all of Luau: nothing in it calls the AST pretty printer, for one, so the linker leaves that file out. The script lists such files as UNLINKED_SOURCES and ignores the flags they declare. LuauPrettyPrintVisualizeIndexerAccess is one: the Roblox client sets it, and it is not in the table.

A flag in the table that fails to set is reported on stderr when the table is applied, and the rest of the table is still applied:

warning: Luau fast flag `LuauExample` did not apply

No release prints this. One test sets every flag in the table and fails if one does not take, and another runs a script through the built binary and fails if anything reaches stderr. The warning is for a tree where the table and the Luau sources have moved apart. The fix is to regenerate the table, or to add the file that declares the flag to UNLINKED_SOURCES if the runtime does not link it.

The file is generated. Do not edit it by hand; rerun the script and rebuild.

Keeping Luau current puts this step in the context of a Luau upgrade.