Decompiler

Volt's decompiler turns Luau bytecode back into readable Luau source. The most important thing to know as a user is that decompilation runs asynchronously: calls that need source can yield while the decompiler works in the background, then resume when the result is ready.

That behavior matters most for saves, where one operation may need source for many scripts before the final file can be written.

How It Works

The decompiler work is scheduled as jobs. The Luau-facing operation yields while those jobs run, so long decompiles do not block the calling script thread.

Scheduler queue Worker threads Call starts Results script A script B script C ... Thread 1script A Thread 2script B Thread 3script C

The decompiler scheduler will configure itself automatically based on the number of available CPU cores. When these threads are not used, they are in a dormant state and don't utilize any CPU time.

decompile schedules one script and yields until the source is ready. saveinstance and saveplace can schedule many scripts during a save, wait for the results, then finish writing the final place or model file.

The Pipeline

This section explains how Volt's decompiler pipeline works, how each option changes the output, and what those choices mean for performance.

Volt prioritizes semantic correctness over raw speed. It is still fast, but it may take longer than some alternatives. In return, it can decompile scripts with 100k+ lines while preserving behavior close to the original source.

Bytecode Build CFG SSA passessimplify valuestidy logicremove extras Flatten CFG AST passesshape codeclean if/elsename variablespolish output Format Source

The diagram gives a simplified view of how Volt turns script bytecode into readable Luau source.

First, the bytecode is lifted into Volt's internal IR, or intermediate representation. This IR uses SSA form, where each temporary value is assigned once, and a CFG, which maps the blocks of code and the jumps between them.

From there, the SSA pass pipeline simplifies the IR and removes unnecessary noise. Once the IR is clean enough, the CFG is lowered into a linear AST. The AST is a tree-shaped representation of the code that is much closer to real source.

Finally, more AST passes clean up the structure, remove artifacts, and improve readability before the result is formatted and printed as Luau source code.

Options and Tradeoffs

Volt exposes SSA and AST pass controls through the DecompilerOptions type. These pass groups run at different stages of the pipeline, so you may want to toggle them for different reasons depending on whether you care more about speed, correctness, or readability.

SSA Passes

As of this writing, the only configurable SSA pass is the DecompilerOptions.ConditionalStructurer pass. This pass rewrites certain if and else control-flow patterns into equivalent and / or expressions.

The output behaves the same, but it is often easier to read. The pass is relatively fast, even on large scripts, but it is not required for semantic correctness. Turning it off can noticeably reduce decompilation time on some scripts.

AST Passes

Most AST passes are worth leaving enabled. Passes like SmartVariableRenamer, FunctionDeclarations, and GuardClauses are very cheap and usually make the output much easier to read.

The main AST setting worth tuning is DoBlockInsertionThreshold. This controls when Volt wraps a region in a do ... end block to keep the number of live locals under Luau's limit.

You usually only need this when you want the decompiled script to compile again. If your goal is readability or analysis, setting the threshold to 0 disables this pass, which can make the output cleaner and slightly reduce decompilation time.

Formatting

Formatting is mostly a matter of preference. The formatter has a negligible performance cost, taking about 30ms on a script with 100k lines of code.

Volt exposes formatter settings through DecompilerFormatter. See that API reference page for the full list of formatting options.