Does including getfenv() function in my code and not calling it during runtime disable performance optimization?

I am currently working on a game framework that synchronizes important events during runtime. I want to archieve maximum error / debug info quality while keeping maximum performance when the game is released. To do this I use game.RunService:IsStudio() to check if I can use different performance heavy methods of acquiring debug data. I want to use the getfenv() function to keep track of which script ran which shared function. It looks something like this:

local module = {}
module.InitCalls = {}

function module.Init(f)
if game:GetService("RunService"):IsStudio() then
table.insert(module.InitCalls, {
["script"] = getfenv(2).script
})
end
end

I am afraid that the compiler would mark the whole thing as unsafe after seeing the function mentioned. Alternatively it could ignore it until it is fired. So does including getfenv() function in my code and not calling it during runtime disable performance optimization?

3 Likes

Most likely not, the environment is only marked as unsafe when you run getfenv() or setfenv() during runtime.

1 Like

the compiler doesn’t inline any functions that use getfenv or setfenv, and they also break fastcalls.

it does not matter if you only call it sometimes or not, the compiler does not take this into account, so it will mark the environment as unsafe and disable performance optimization if you use getfenv/setfenv at all.

you can see the code snippet that controls code optimization in the FenvVisitor struct source code here, you can see how all it checks if it is mentioned at all.

instead of using getfenv, you can use debug.info to achieve the same result, take a look at this example:

local module = {}
function module.callee()
	local caller: BaseScript = debug.info(2, "s")
	-- do what you want with the caller here
end
return module

learn more about debug.info in the documentation!

3 Likes

Does it mean that even if I don’t call getfenv() a single time it will still disable code optimization? The function either runs all the time in studio or not a single time in game. Also how do they exactly break fastcalls?

local RunService = game:GetService("RunService")

local module = {}
local bindables = {}

function module.SafeCall(f, ...)
	assert(typeof(f) == "function")
	local Bind = Instance.new("BindableEvent")
	bindables[f] = Bind
	Bind.Event:Connect(f)
	Bind:Fire(...)
	bindables[f] = nil
	Bind:Destroy()
end

local Coroutines = script.Parent:WaitForChild("VGF"):GetAttribute("Coroutines")
local IsStudio = RunService:IsStudio()
function module.Call(f, ...)
	if not(IsStudio) or Coroutines then
		coroutine.wrap(f)(...)
	else
		module.SafeCall(f, ...)
	end
end

function module.Spawn(f, ...) 
	module.Call(function(...) task.wait() f(...) end)
end

function module.SafeSpawn(f, ...) 
	module.SafeCall(function(...) task.wait() f(...) end)
end



return module

This is my current fastcall code. I use the slow bindable event method because I don’t care about performance in studio but want accurate error stack. I use coroutine.wrap() during runtime because I believe it is the fastest one. Spawn and SafeSpawn use task.wait() to protect the main thread from erroring (I need confirmation if this works 100% of the time). Does using fastcall like this interfere with optimization? And does it interfere with it regardless of if the RunService:IsStudio() ?

2 Likes

even if it’s not called it will still disable code optimization, yes.
this example will still make the compiler disable code optimization:

function example()
	if false then
		getfenv()
	end
end

obviously the condition will never be met, but the compiler still thinks it uses getfenv, so it disables code optimization.

if you just use debug.info(2, "s") instead getfenv(2).script to get the calling script it’ll make the compiler keep it enabled and happy :3

and by “breaking fastcalls” I just meant that that is also apart of the optimization, your implementation is fine, it’s only if you reference getfenv or setfenv then it’ll disable optimization.

1 Like
 if (node->name == "getfenv")
      getfenvUsed = true;
 if (node->name == "setfenv")
      setfenvUsed = true;
 // this visitor tracks calls to getfenv/setfenv and disables some optimizations when they are found
    if (options.optimizationLevel >= 1 && (names.get("getfenv").value || names.get("setfenv").value))

Does this check for the “getfenv” and “setfenv” words in the source code or the calls to these functions? The compiler source code says above that it tracks calls to them.

1 Like

I didn’t mean just words in that way, that was a mistake on my end. the ast visitor checks if they’re ever called, not just if they exist in the source code.

1 Like

Why should that disable the optimization if it only checks if they’re called?
The source code says the visitor tracks the calls to those functions, so if these functions aren’t called (perhaps in an if-else statement that will never be run), why should the optimizations be disabled? Which part of the compiler-source code does the check?

By the way, I’m just asking. Don’t get me wrong; I’m not trying to go against you or anything.

1 Like

it really shouldn’t disable optimization, but making it that way is most likely challenging and complicated, but that’s just the way that the compiler is coded.

the ast visitor struct is the code that checks if getfenv and/or setfenv are referenced, are you trying to say something else?

after doing a little bit more research, I believe that it actually is any reference to the functions getfenv and setfenv, not just calls and I believe this snippet would even disable code optimization:

function example()
	local reference = getfenv
end

so I guess the comment above the source code is misleading, and I second guessed myself with the actual implementation and started believing your claim, but in reality it is just any reference to those functions that disable optimization.
the more you know!

1 Like

I wanna learn the part of the code that does it, can you pinpoint me to a line?

1 Like

in my original response, the code I linked was the code that checks if the functions getfenv or setfenv are referenced, the link includes the line number “3884”, that’s where the FenvVisitor is.

an AST is an abstract syntax tree which shows everything related to a script, it basically puts it into a machine readable format into a, literally, “syntax tree”, a tree of nodes of things that relate to things in the code, which is generated by parsing the source code (text/string).

struct FenvVisitor : AstVisitor
{
    bool& getfenvUsed;
    bool& setfenvUsed;

    FenvVisitor(bool& getfenvUsed, bool& setfenvUsed)
        : getfenvUsed(getfenvUsed)
        , setfenvUsed(setfenvUsed)
    {
    }

    bool visit(AstExprGlobal* node) override
    {
        if (node->name == "getfenv")
            getfenvUsed = true;
        if (node->name == "setfenv")
            setfenvUsed = true;

        return false;
    }
};

this AstVisitor will simply look for any AstExprGlobal nodes in the code, which are references to global variables or functions, so it looks for any with the name “getfenv” or “setfenv”, that’s why calling them or declaring a variable with those functions disable code optimization.

that is also why it doesn’t disbale code optimization at just any reference to them, like talking about getfenv/setfenv in a code comment, or an argument name, etc.

if you’re interested in trying to work with Lua/Luau ASTs, you can use Loretta for C#, or the Luau AST in the luau repository itself made in C/C++.

I’m not the best explainer, but you can learn more online.

1 Like

I know how ASTs work, just wanted to see if there’s a specific line for it, anyway thanks for the info.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.