Skip to content

perf: avoid unrelated multi-return correlation#1095

Merged
CppCXY merged 1 commit into
EmmyLuaLs:mainfrom
lewis6991:perf/avoid-unrelated-multi-return-correlation
May 27, 2026
Merged

perf: avoid unrelated multi-return correlation#1095
CppCXY merged 1 commit into
EmmyLuaLs:mainfrom
lewis6991:perf/avoid-unrelated-multi-return-correlation

Conversation

@lewis6991
Copy link
Copy Markdown
Collaborator

Summary

  • Gate return-overload correlation to variable histories from the same multi-return call.
  • Preserve same-call tuple narrowing while skipping unrelated call histories that only waste inference work.
  • Add ntest as a dev dependency for a bounded issue Stuck loading #1094 stress regression.

Fixes #1094

Testing

  • cargo test -p emmylua_code_analysis test_issue_1094_self_call_fallback_stress
  • cargo test -p emmylua_code_analysis return_overload_flow_test
  • cargo test -p emmylua_code_analysis pcall_test
  • cargo run -p emmylua_check -- /private/tmp/issue1094
  • cargo run -p emmylua_check -- /private/tmp/issue1094-selfcall
  • git diff --check

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request addresses a performance stress test issue (issue 1094) related to repeated self-call fallbacks. It introduces a new method has_shared_multi_return_refs in FlowTree to check for shared multi-return references between two declarations, replacing separate checks. Additionally, the ntest crate is added to support test timeouts, and a new stress test is included. The reviewer suggested optimizing has_shared_multi_return_refs to avoid potential O(N * M) complexity in pathological cases by using a HashSet for larger lists.

Comment on lines +54 to 76
pub fn has_shared_multi_return_refs(
&self,
left_decl_id: &LuaDeclId,
right_decl_id: &LuaDeclId,
) -> bool {
let Some(left_refs) = self.decl_multi_return_ref.get(left_decl_id) else {
return false;
};
let Some(right_refs) = self.decl_multi_return_ref.get(right_decl_id) else {
return false;
};

left_refs
.iter()
.filter_map(|entry| entry.reference.as_ref())
.any(|left_ref| {
let left_call_id = left_ref.call_expr.get_syntax_id();
right_refs
.iter()
.filter_map(|entry| entry.reference.as_ref())
.any(|right_ref| right_ref.call_expr.get_syntax_id() == left_call_id)
})
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The nested loop in has_shared_multi_return_refs has O(N * M) complexity. While N and M are typically small, pathological cases or stress tests (like the one introduced in this PR) can cause performance degradation.

We can optimize this by using a HashSet when both lists are relatively large, while keeping the fast nested loop path for the common case of small lists.

    pub fn has_shared_multi_return_refs(
        &self,
        left_decl_id: &LuaDeclId,
        right_decl_id: &LuaDeclId,
    ) -> bool {
        let Some(left_refs) = self.decl_multi_return_ref.get(left_decl_id) else {
            return false;
        };
        let Some(right_refs) = self.decl_multi_return_ref.get(right_decl_id) else {
            return false;
        };

        if left_refs.len() > 8 && right_refs.len() > 8 {
            let left_set: HashSet<_> = left_refs
                .iter()
                .filter_map(|entry| entry.reference.as_ref().map(|r| r.call_expr.get_syntax_id()))
                .collect();
            right_refs
                .iter()
                .filter_map(|entry| entry.reference.as_ref())
                .any(|right_ref| left_set.contains(&right_ref.call_expr.get_syntax_id()))
        } else {
            left_refs
                .iter()
                .filter_map(|entry| entry.reference.as_ref())
                .any(|left_ref| {
                    let left_call_id = left_ref.call_expr.get_syntax_id();
                    right_refs
                        .iter()
                        .filter_map(|entry| entry.reference.as_ref())
                        .any(|right_ref| right_ref.call_expr.get_syntax_id() == left_call_id)
                })
        }
    }

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The O(N*M) shape is theoretically valid, but I do not think a thresholded HashSet is the right fix here.

This helper is only a preflight gate before the expensive correlated subquery path. Adding an allocation and a magic cutoff makes the common path more complex without evidence that this helper is the bottleneck.

The main perf fix in this PR is skipping unrelated correlation entirely. If profiling later shows this membership check is hot, I would rather fix that at the data-model level by indexing the call ids in FlowTree, so the check becomes a real set intersection instead of a local micro-op.

For now I am keeping the simple nested scan.

@CppCXY
Copy link
Copy Markdown
Member

CppCXY commented May 26, 2026

Why is the GitHub Action not triggering?🤔

@xuhuanzy
Copy link
Copy Markdown
Member

Why is the GitHub Action not triggering?🤔

github actions崩溃了
https://www.githubstatus.com/

Only run return-overload correlation when the compared variable
histories refer to results from the same multi-return call expression.
Same-call cases like `local ok, value = pcall(fn)` still narrow through
the shared overload row.

Different assignments can both have multi-return refs without sharing a
return tuple. Treating those unrelated call histories as correlation
candidates sends the analyzer into expensive subqueries and does not
improve narrowing accuracy.

Add ntest as a dev dependency so the issue EmmyLuaLs#1094 stress regression has a
bounded timeout.

Fixes EmmyLuaLs#1094

Assisted-by: Codex
@lewis6991 lewis6991 force-pushed the perf/avoid-unrelated-multi-return-correlation branch from 26fe4a2 to 63e00d8 Compare May 27, 2026 08:41
@CppCXY CppCXY merged commit cb4f295 into EmmyLuaLs:main May 27, 2026
16 checks passed
@lewis6991 lewis6991 deleted the perf/avoid-unrelated-multi-return-correlation branch May 27, 2026 12:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stuck loading

3 participants