Luau Recap: October 2023
We’re still quite busy working on some big type checking updates that we hope to talk about soon, but we have a few equally exciting updates to share in the meantime!
Let’s dive in!
Floor Division
Luau now has a floor division operator. It is spelled //
:
local a = 10 // 3 -- a == 3
a //= 2 -- a == 1
For numbers, a // b
is equivalent to math.floor(a / b)
, and you can also overload this operator by implementing the __idiv
metamethod. The syntax and semantics are borrowed from Lua 5.3 (although Lua 5.3 has an integer type while we don’t, we tried to match the behavior to be as close as possible).
Native Codegen Preview
We are actively working on our new native code generation module that can significantly improve the performance of compute-dense scripts by compiling them to X64 (Intel/AMD) or A64 (ARM) machine code and executing that natively. We aim to support all AArch64 hardware with the current focus being Apple Silicon (M1-M3) chips, and all Intel/AMD hardware that supports AVX1 (with no planned support for earlier systems). When the hardware does not support native code generation, any code that would be compiled as native just falls back to the interpreted execution.
When working with open-source releases, binaries now have native code generation support compiled in by default; you need to pass --codegen
command line flag to enable it. If you use Luau as a library in a third-party application, you would need to manually link Luau.CodeGen
library and call the necessary functions to compile specific modules as needed - or keep using the interpreter if you want to! If you work in Roblox Studio, we have integrated native code generation preview as a beta feature, which currently requires manual annotation of select scripts with --!native
comment.
Our goal for the native code generation is to help reach ultimate performance for code that needs to process data very efficiently, but not necessarily to accelerate every line of code, and not to replace the interpreter. We remain committed to maximizing interpreted execution performance, as not all platforms will support native code generation, and it’s not always practical to use native code generation for large code bases because it has a larger memory impact than bytecode. We intend for this to unlock new performance opportunities for complex features and algorithms, e.g. code that spends a lot of time working with numbers and arrays, but not to dramatically change performance on UI code or code that spends a lot of its time calling Lua functions like table.sort
, or external C functions (like Roblox engine APIs).
Importantly, native code generation does not change our behavior or correctness expectations. Code compiled natively should give the same results when it executes as non-native code (just take a little less time), and it should not result in any memory safety or sandboxing issues. If you ever notice native code giving a different result from non-native code, please submit a bug report.
We continue to work on many code size and performance improvements; here’s a short summary of what we’ve done in the last couple of months, and there’s more to come!
- Repeated access to table fields with the same object and name are now optimized (e.g.
t.x = t.x + 5
is faster) - Numerical
for
loops are now compiled more efficiently, yielding significant speedups on hot loops - Bit operations with constants are now compiled more efficiently on X64 (for example,
bit32.lshift(x, 1)
is faster); this optimization was already in place for A64 - Repeated access to array elements with the same object and index is now faster in certain cases
- Performance of function calls has been marginally improved on X64 and A64
- Fix code generation for some
bit32.extract
variants where we could produce incorrect results table.insert
is now faster when called with two arguments as it’s compiled directly to native code- To reduce code size, module code outside of functions is not compiled natively unless it has loops
Analysis Improvements
The break
and continue
keywords can now be used in loop bodies to refine variables. This was contributed by a community member - thank you, AmberGraceSoftware!
function f(objects: { { value: string? } })
for _, object in objects do
if not object.value then
continue
end
local x: string = object.value -- ok!
end
end
When type information is present, we will now emit a warning when #
or ipairs
is used on a table that has no numeric keys or indexers. This helps avoid common bugs like using #t == 0
to check if a dictionary is empty.
local message = { data = { 1, 2, 3 } }
if #message == 0 then -- Using '#' on a table without an array part is likely a bug
end
Finally, some uses of getfenv
/setfenv
are now flagged as deprecated. We do not plan to remove support for getfenv
/setfenv
but we actively discourage its use as it disables many optimizations throughout the compiler, runtime, and native code generation, and interferes with type checking and linting.
Autocomplete Improvements
We used to have a bug that would arise in the following situation:
--!strict
type Direction = "Left" | "Right"
local dir: Direction = "Left"
if dir == ""| then
end
(imagine the cursor is at the position of the |
character in the if
statement)
We used to suggest Left
and Right
even though they are not valid completions at that position. This is now fixed.
We’ve also added a complete suggestion for anonymous functions if one would be valid at the requested position. For example:
local p = Instance.new('Part')
p.Touched:Connect(
You will see a completion suggestion function (anonymous autofilled)
. Selecting that will cause the following to be inserted into your code:
local p = Instance.new('Part')
p.Touched:Connect(function(otherPart: BasePart) end
We also fixed some confusing editor feedback in the following case:
game:FindFirstChild(
Previously, the signature help tooltip would erroneously tell you that you needed to pass a self
argument. We now correctly offer the signature FindFirstChild(name: string, recursive: boolean?): Instance
Runtime Improvements
string.format
’s handling of%*
and%s
is now 1.5-2x fastertonumber
andtostring
are now 1.5x and 2.5x faster respectively when working on primitive types- Compiler now recognizes
math.pi
andmath.huge
and performs constant folding on the expressions that involve these at-O2
; for example,math.pi*2
is now free. - Compiler now optimizes
if...then...else
expressions into AND/OR form when possible (for example,if x then x else y
now compiles asx or y
) - We had a few bugs around
repeat..until
statements when theuntil
condition referred to local variables defined in the loop body. These bugs have been fixed. - Fix an oversight that could lead to
string.char
andstring.sub
generating potentially unlimited amounts of garbage and exhausting all available memory. - We had a bug that could cause the compiler to unroll loops that it really shouldn’t. This could result in massive bytecode bloat. It is now fixed.
luau-lang on GitHub
If you’ve been paying attention to our GitHub projects, you may have noticed that we’ve moved luau
repository to a new luau-lang GitHub organization! This is purely an organizational change but it’s helping us split a few repositories for working with documentation and RFCs and be more organized with pull requests in different areas. Make sure to update your bookmarks and star our main repository if you haven’t already!
Lastly, a big thanks to our open source community for their generous contributions: