Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose should_inline_call from Function.inlined to allow more fine grained inlining control #7148

Open
TomAFrench opened this issue Jan 22, 2025 · 0 comments
Assignees

Comments

@TomAFrench
Copy link
Member

In #7083 we allowed passing custom closures into the inliner to determine whether we should inline a call or not. We currently still have a single closure which defines standard logic for all of the inlining passes.

pub(super) fn inlined(
&self,
ssa: &Ssa,
inline_no_predicates_functions: bool,
inline_infos: &InlineInfos,
) -> Function {
let caller_runtime = self.runtime();
let should_inline_call =
|_context: &PerFunctionContext, ssa: &Ssa, called_func_id: FunctionId| -> bool {
// Do not inline self-recursive functions on the top level.
// Inlining a self-recursive function works when there is something to inline into
// by importing all the recursive blocks, but for the entry function there is no wrapper.
if called_func_id == self.id() {
return false;
}
let callee = &ssa.functions[&called_func_id];
match callee.runtime() {
RuntimeType::Acir(inline_type) => {
// If the called function is acir, we inline if it's not an entry point
// If we have not already finished the flattening pass, functions marked
// to not have predicates should be preserved.
let preserve_function =
!inline_no_predicates_functions && callee.is_no_predicates();
!inline_type.is_entry_point() && !preserve_function
}
RuntimeType::Brillig(_) => {
if caller_runtime.is_acir() {
// We never inline a brillig function into an ACIR function.
return false;
}
// We inline inline if the function called wasn't ruled out as too costly or recursive.
inline_infos
.get(&called_func_id)
.map(|info| info.should_inline)
.unwrap_or_default()
}
}
};

Which pass is being handled is determined by the inline_no_predicates_functions and inline_infos args. The first determines whether we're in the "Inlining (1st)" or "Inlining (2nd)" pass. The second arg, inline_infos, contains the functions which we want to start inlining from and is overloaded by the pass added in #7072 to add a "weight" parameter to functions to determine whether we should inline sooner or delay until the full inlining pass.

We've then got a lot of flexibility on choosing how we inline calls but we're not making best use of it currently. For instance, we're currently not inlining:

  • functions which are empty and just return inputs.
  • functions which contain a single instruction and return the inputs.
  • functions which are only called once in the entire program.

These are optimization cases which I can see are not being handled currently in the protocol circuits (at least before the main inlining pass).

This should then allow us to more aggressively inline these calls without needing to manipulate the weight score which is being applied to functions currently.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: 📋 Backlog
Development

No branches or pull requests

2 participants