Function Inlining
A function call is an indirection. Execution pauses here, jumps to code defined somewhere else, runs it with the arguments you passed, and comes back with a result. Function inlining is the transformation that removes the indirection: it replaces the call with a copy of the function's body, with the parameters substituted by the actual arguments. No jump, no return, just the work itself, spliced in where it was needed.
Think of a recipe that says make the sauce from the card in the drawer. Inlining copies the sauce instructions directly into the main recipe at that step. Nothing about the meal changes. What changes is the reading experience: no more flipping to the card, but the recipe got longer, and if three steps use the sauce, the instructions now appear three times.
Before and after
Here is a small function and one call to it, in a generic pseudocode, then the same program after inlining. The parameter p is replaced by the argument, and the return value flows to where the call's result went.
function price_with_tax(p) {
tax = p * 8 / 100
return p + tax
}
total = price_with_tax(50)
print "total:", totalp = 50
tax = p * 8 / 100
total = p + tax
print "total:", totalStep through the inlined version below. Notice that execution is now a straight line: the same arithmetic happens, in the same order, without a call boundary in the middle.
Why compilers love it
The direct win is removing call overhead: setting up arguments, jumping, returning. On modern hardware that saving is real but small. The much bigger win is what inlining enables. Once the body sits at the call site, the optimizer can see the actual argument values in context, and the other passes you have met suddenly get traction.
- After inlining price_with_tax(50), the parameter is the literal 50, so constant folding can compute 50 * 8 / 100 and then 50 + 4 at transform time. The whole call can collapse to total = 54.
- If the inlined body has a branch on a parameter, and the argument is a constant, folding turns the condition into a literal and dead code elimination deletes the untaken side.
- Passes compose: inline, fold, eliminate, repeat. Inlining is the pass that feeds the others.
This is why optimizer pipelines treat inlining as a keystone decision. It rarely wins much by itself. It wins by turning cross-function facts into local facts that simpler passes can act on.
What it costs
Inlining copies code. A function called from ten places, fully inlined, exists as ten copies. The program grows, and growth has real costs: bigger downloads, more memory, and on hardware, worse instruction cache behaviour that can make the program slower, the exact opposite of the goal. So compilers use heuristics: inline small functions eagerly, hot call sites preferentially, and huge or rarely-called functions almost never.
Two hard limits are worth knowing. Recursion cannot be fully inlined, since the body contains the call itself, so compilers at most peel a bounded number of layers. And a call whose target is not known until runtime, through a function value or dynamic dispatch, cannot be inlined at all without first proving what it will be. Both limits reappear in reverse engineering: they are the places where even aggressive optimization leaves call structure visible.
What it does to a reader
Function names and boundaries are the map of a codebase. A reader who sees validate, compute_price, and save can navigate by intent without reading a single body. Inlining erases entries from that map. After a heavy inlining pass, one long function does everything, the borders between what were separate responsibilities are gone, and the names of the inlined functions have vanished from the program entirely, taking their documentation value with them.
Obfuscators use inlining for exactly this reason, and honesty requires the other half: the logic is all still there, in order, fully executable. A patient reader or a decompiler still recovers the behaviour, and tooling can even re-split long functions into digestible pieces. Inlining removes signposts. It does not remove the road.
Inlining replaces a call with the function's body, trading size for locality. Compilers prize it because it feeds folding and dead code elimination, and they ration it because copies cost space. For obfuscation it erases the reader's map of named boundaries, a genuine reading tax, while leaving every step of the logic in plain sight.
Frequently asked questions
If inlining removes the function, is the function gone from the program?
The call is gone, and if every call was inlined and nothing else references the function, a cleanup pass can delete the definition too. Its behaviour survives inside each call site. What is genuinely lost is the boundary and the name, which existed for human navigation, not for the machine.
Does aggressive inlining stop reverse engineering?
No, and no honest vendor claims it does. The logic remains present and executable, and decompilers routinely make long straight-line functions readable again. Inlining raises reading cost by removing structure. It is one layer among many, not a wall.