Fiu | Complete Luau bytecode interpreter

Pronounced like “Phew”. This interpreter aims to provide a decently fast and reliable way of executing Luau bytecode without the use of loadstring.

After two years of work the interpreter finally exits pre-release state. Fiu now comes in a completely configurable state with proper emulation for more niche capabilities of the Luau VM such as callbacks. Native namecalling is also available through the namecallHandler setting. See the full list of changes at the github commit.

Github: GitHub - rce-incorporated/Fiu: Luau bytecode interpreter for Luau
README and documentation: Fiu/README.md at main · rce-incorporated/Fiu · GitHub
Examples: Fiu/examples at main · rce-incorporated/Fiu · GitHub
Download: Releases · rce-incorporated/Fiu · GitHub

Release Changes

  • Implement settings
  • Implemented vector constant support
  • Implemented VM callbacks
  • Implemented line info
  • Added native namecall handler support
  • Added VM extensions
  • Made error handling, generalized iteration and error proxying optional
  • Fixed memory leaks
  • Added luau_close as a return to luau_load for closing an interpreted function
  • Small optimisations

Updated post: Fiu | Complete Luau bytecode interpreter - #8 by EtSapientisMagna

25 Likes

Amazing module!

Do you by any chance know any modules written in Lua (such as Yueliang) to compile luau code to bytecode?

Using Wasynth you can transpile Luau.Compiler into Luau, it wont be the fastest thing ever but it is the most reliable way you can do it.

1 Like

Awesome! I’ll update vLua here in a bit to include the latest release + Luau compiler.

1 Like

very interesting resource, good job!

I was wondering if you can provide example use cases? I would want to use this but i don’t know what to use it for

It serves as a good alternative to loadstring in many cases. We provide closing functionality to prevent the interpreter from running which will allow it to be cleaned up after a wrapped closure stops being used. When you use loadstring there can be cases where what you run will never be cleaned up but this isn’t the case with Fiu when you can call lua_close.

Additionally if you sync up with open cloud or http service you can setup dynamically loadable code using Fiu on both the client and server. That way if you have emergency code you need to push to all of the clients in your game or need to push an emergency fix to your servers you can do so with this.

1 Like

Another example is going to be vLua. Although it still uses the legacy FiOne at the moment along with Yueliang (meaning it only supports vanilla Lua 5.1. Currently waiting for Fiu to support Luau 0.619), it is built to be a batteries-included drop-in replacement to loadstring.

It’s really as easy as doing

local loadstring = require(script.vLua)
loadstring("print'hi'", getfenv())()

The best part is you can modify the fenv without causing any deoptimizations. Pretty nifty huh?

4 Likes

Updated feature list

API:

  • luau_load(module | bytecode, env, settings?)
    Accepts a Luau module or bytecode. Returns the main prototype wrapped and a luau_close function to kill the interpreter if needed.
  • luau_deserialize(bytecode, settings?)
    Used to deserialise bytecode.
  • luau_newsettings()
    Used to create a table of default settings.
  • luau_validatesettings(settings)
    Used to validate the passed settings.
  • luau_getcoverage(module, protoid, callback)
    Collects LOP_COVERAGE hits and calls callback with (proto_debugname, proto_linedefined, proto_depth, hit_buffer, max_line)

Settings:

vectorCtor - Vector constructor function
vectorSize - Vector size
useNativeNamecall - Boolean to indicate if namecallHandler should be used
namecallHandler - Function handler for native namecalling
extensions - Table of injectable globals
callHooks - VM callback behaviour emulators
errorHandling - Error handling by the VM for line information and opcode information
generalizedIteration - Use generalized iteration in the VM
staticEnvironment - Table of globals for option useImportConstants
useImportConstants - Boolean to indicate if fiu should optimize GETIMPORT using staticEnvironment
decodeOp - Function that passes an op argument, which is the instruction number and can be processed for any encoded bytecode

Vector constant support

Vector constants can be supported by setting vectorCtor and vectorSize. vectorSize can be 3 or 4, vectorCtor on Roblox would be Vector3.new.

Native namecall support

Namecalling is impossible to support dynamically so in environments where only __namecall is implemented without __index you can define a namecallHandler and set useNativeNamecall to true. In the namecall handler you must return a boolean as the first return value.

Example:

luau_settings.namecallHandler = function(namecallMethod, self, ...)
    if namecallMethod == "FindFirstChild" then
        return true, self:FindFirstChild(...)
    end 

    return false
end

Stack frame, running thread and errors

The interpreter can be accurate 1:1 to existing stack frames if errorHandling is disabled.

Generalized iteration can also cause issues because it requires a new thread to be created so anything that gets iterated will go deeper in the C stack depth limit and __iter and __call will have a different thread when coroutine.running is called. If generialized iteration is disabled with generializedIteration __call will still function but __iter and iterating over tables without an iterator will no longer work.

Fiu cannot provide perfect errors, if errorHandling is enabled all errors are processed by Fiu and will be turned into strings if they are not. If you want to be able to error a table and then grab the table enable allowProxyErrors which will error the errored object again. Note that when errorHandling is disabled this behaviour is done by default because Fiu will not interfere with errors.

Injectable globals

The vm allows globals to be injected into the VM with extensions, these injected globals can never be deleted by the interpreter and any attempts will just result in environment globals being set to nil.

Example:

luau_settings.extensions["foo"] = function()
    print('hello world!')
end

VM callback behaviour

The Luau VM provides simple hooks to see into the VM at certain points, the Fiu VM emulates 4 of these callbacks.

breakHook(stack, debugging, proto, module, upvals) - When a LOP_BREAK/breakpoint is encountered breakHook will be called, the first return must be true or false to specify if the VM is meant to exit and return instead
interruptHook(stack, debugging, proto, module, upvals) - When a vm interrupt occurs the interruptHook will be called
panicHook(message, stack, debugging, proto, module, upvals) - When an unprotected error occurs within the VM the panicHook will be called if errorHandling is enabled
stepHook(stack, debugging, proto, module, upvals) - When a VM step (In Fiu, when the PC increases) occurs stepHook will be called

Example:

settings.callHooks.stepHook = function(stack, debugging)
	print('step occured', debugging.name, debugging.pc)	
end
settings.callHooks.panicHook = function(message, stack, debugging)
	print(debugging.name, message)
	for i,v in stack do 
		print(i,v)
	end
end
settings.callHooks.interruptHook = function(stack, debugging)
	print(debugging.name, "interrupted!")
end

Debug Hook Example:

settings.callHooks.breakHook = function()
    return true, "Hooked by LOP_BREAK!"
end

-- later
print(luau_load()()) --> Hooked by LOP_BREAK!
3 Likes

Amazing resource! I’d like to ask, will this module be available as Roblox package aswell or would I need to update it every time it gets a new commit?

You rarely need to update unless you want the latest features Fiu has or if the Luau bytecode format updated. It is a single file so it is easy to update too

is it called source.lua by any chance? i am planning to use fiu in my games.