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

feat: Add CallGraph struct, and dead-function-removal pass #1796

Merged
merged 24 commits into from
Dec 24, 2024

Conversation

acl-cqc
Copy link
Contributor

@acl-cqc acl-cqc commented Dec 17, 2024

Closes #1753.

  • remove_polyfuncs preserved but deprecated, some uses in tests replaced to give coverage here.
  • Future (breaking) release to remove the automatic-remove_polyfuncs that currently follows monomorphization.

Copy link

codecov bot commented Dec 17, 2024

Codecov Report

Attention: Patch coverage is 84.07080% with 18 lines in your changes missing coverage. Please review.

Project coverage is 86.63%. Comparing base (1f841ae) to head (4a07dee).
Report is 7 commits behind head on main.

Files with missing lines Patch % Lines
hugr-passes/src/dead_funcs.rs 80.95% 10 Missing and 2 partials ⚠️
hugr-passes/src/call_graph.rs 93.02% 2 Missing and 1 partial ⚠️
hugr-passes/src/monomorphize.rs 57.14% 1 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1796      +/-   ##
==========================================
- Coverage   86.63%   86.63%   -0.01%     
==========================================
  Files         191      194       +3     
  Lines       34738    34885     +147     
  Branches    31571    31698     +127     
==========================================
+ Hits        30097    30224     +127     
- Misses       2940     2954      +14     
- Partials     1701     1707       +6     
Flag Coverage Δ
python 92.31% <ø> (-0.11%) ⬇️
rust 86.06% <84.07%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@acl-cqc acl-cqc changed the title feat: [Slightly WIP] Add CallGraph struct, and dead-function-removal pass feat!: [Slightly WIP] Add CallGraph struct, and dead-function-removal pass Dec 17, 2024
@ss2165
Copy link
Member

ss2165 commented Dec 17, 2024

can we deprecate remove_polyfuncs instead so we can include this in a non-breaking release?

@acl-cqc
Copy link
Contributor Author

acl-cqc commented Dec 17, 2024

can we deprecate remove_polyfuncs instead so we can include this in a non-breaking release?

Yes, well, we can, and I have, but I admit I am not singing the praises of the tooling here - deprecate has a "since" tag for tools that might use it, but IIUC I have to guess what the next version number will be (i.e. the version in which it'll be deprecated); and the re-export causes problems (I have to suppress a warning there - I tried putting the #[deprecated] only on the re-export but that did not generate a warning even when I used the function via the re-export). If I'm doing this wrong @ss2165 then please shout...

@acl-cqc acl-cqc changed the title feat!: [Slightly WIP] Add CallGraph struct, and dead-function-removal pass feat: [Slightly WIP] Add CallGraph struct, and dead-function-removal pass Dec 17, 2024
@acl-cqc acl-cqc changed the title feat: [Slightly WIP] Add CallGraph struct, and dead-function-removal pass feat: Add CallGraph struct, and dead-function-removal pass Dec 17, 2024
@acl-cqc acl-cqc force-pushed the acl/remove_dead_funcs branch from 31a7b44 to 08d9ebc Compare December 17, 2024 18:58
@acl-cqc acl-cqc force-pushed the acl/remove_dead_funcs branch from 08d9ebc to e29ffa2 Compare December 17, 2024 19:02
@acl-cqc acl-cqc marked this pull request as ready for review December 17, 2024 19:11
@acl-cqc acl-cqc requested a review from a team as a code owner December 17, 2024 19:11
@acl-cqc acl-cqc requested a review from tatiana-s December 17, 2024 19:11
Copy link
Contributor

@tatiana-s tatiana-s left a comment

Choose a reason for hiding this comment

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

Generally looks good to me, just one comment mainly for my understanding.

fn traverse(
h: &impl HugrView,
node: Node,
enclosing: NodeIndex<u32>,
Copy link
Contributor

Choose a reason for hiding this comment

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

Just for clarification - is enclosing the node in the call graph corresponding to the node node in the hugr or something else?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's the (callgraph representation of) the nearest enclosing FuncDefn, so maybe the node's parent or an ancestor. I'll rename...

@tatiana-s tatiana-s requested a review from doug-q December 18, 2024 15:39
hugr-passes/src/call_graph.rs Outdated Show resolved Hide resolved
Comment on lines 149 to 152
/// * If the Hugr is non-Module-rooted and `entry_points` is non-empty
/// * If the Hugr is Module-rooted, but does not declare `main`, and `entry_points` is empty
/// * If the Hugr is Module-rooted, and `entry_points` is non-empty but contains nodes that
/// are not [FuncDefn](OpType::FuncDefn)s
Copy link
Collaborator

Choose a reason for hiding this comment

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

As above, I don't think the second should panic.

I think the interface would be cleaner if entry_points, maybe with a different name: must be FuncDefn or FuncDecl nodes that are immediate children of the root.

Now the first panic goes away, and the third would be an error with the offending nodes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed to error, indeed. But FuncDefns inside a non-Module are invisible from outside (so unless you're gonna add new stuff inside the root - which you can do, but that's not linking, that's....arbitrary editing), so I've not allowed those as entry_points. I could be persuaded, it'd be easier from a code perspective not to check, but it feels wrong :-!

hugr-passes/src/call_graph.rs Outdated Show resolved Hide resolved
hugr-passes/src/call_graph.rs Outdated Show resolved Hide resolved
hugr-passes/src/call_graph.rs Outdated Show resolved Hide resolved
hugr-passes/src/call_graph.rs Outdated Show resolved Hide resolved
let reachable = reachable_funcs(&CallGraph::new(h), h, entry_points).collect::<HashSet<_>>();
let unreachable = h
.nodes()
.filter(|n| h.get_optype(*n).is_func_defn() && !reachable.contains(n))
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should remove dead FuncDecls too

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done (no tests I admit)

hugr-passes/src/call_graph.rs Outdated Show resolved Hide resolved
hugr-passes/src/call_graph.rs Outdated Show resolved Hide resolved
hugr-passes/src/call_graph.rs Outdated Show resolved Hide resolved
@doug-q
Copy link
Collaborator

doug-q commented Dec 19, 2024

Did you consider tracking in the Node weight whether it's a FuncDecl or a FuncDefn?

@doug-q doug-q closed this Dec 19, 2024
@doug-q doug-q reopened this Dec 19, 2024
@acl-cqc
Copy link
Contributor Author

acl-cqc commented Dec 20, 2024

Did you consider tracking in the Node weight whether it's a FuncDecl or a FuncDefn?

No, good idea, done

@acl-cqc acl-cqc requested a review from doug-q December 20, 2024 15:35
Copy link
Collaborator

@doug-q doug-q left a comment

Choose a reason for hiding this comment

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

Great, thank you.

@acl-cqc
Copy link
Contributor Author

acl-cqc commented Dec 23, 2024

Ok, so the big question here (@doug-q) is what happens to the "automatic" remove_polyfuncs on non-Module Hugrs. The uncertainty suggests that perhaps that was always a bad move, so options:

  1. Remove it: no automatic dead/poly-function removal. It is always up to the caller. Definitely the easiest/least-code option, but also the most breaking.
  2. Switch to removing all dead functions (for non-Module-rooted Hugrs). This is what the PR does currently. It seems a bit bizarre/random that dead non-polymorphic functions are removed by monomorphization (and this could potentially break some client code at runtime), but non-Module Hugrs are only really here for tests, production users are probably unaffected.
  3. Keep remove_polyfuncs, as per before this PR. In that case ideally I'd like to automatically remove polymorphic FuncDefns from Modules too where they were not previously children of the Module (most will become children of the Module afterwards because of flattening), as this would be more consistent with non-Modules. That could be a follow-up, I guess for this PR we'd leave things, and/or we could still deprecate the public remove_polyfuncs (i.e. moving towards a situation where remove_polyfuncs lives on but as an internal thing for monomorphization, and external users have only remove_dead_funcs - which could be extended via a filter-callback if we wanted). This might be best in some way, but clearly involves the most code, and the step to remove "invisible" polyfuncs from Modules is slightly breaking.

Thoughts?

@doug-q
Copy link
Collaborator

doug-q commented Dec 23, 2024

Ok, so the big question here (@doug-q) is what happens to the "automatic" remove_polyfuncs on non-Module Hugrs. The uncertainty suggests that perhaps that was always a bad move, so options:

  1. Remove it: no automatic dead/poly-function removal. It is always up to the caller. Definitely the easiest/least-code option, but also the most breaking.

I don't think breaking changes are a concern here. There are no users of this on non-module hugrs, so no users to break. I think this is my favoured option, I consider it not-onerous-at-all to ask callers to remove the dead polymorphic functions.

  1. Switch to removing all dead functions (for non-Module-rooted Hugrs). This is what the PR does currently. It seems a bit bizarre/random that dead non-polymorphic functions are removed by monomorphization (and this could potentially break some client code at runtime), but non-Module Hugrs are only really here for tests, production users are probably unaffected.

Agree that non-polymorphic functions being removed is bizarre. I think it would be better to do no removal.

  1. Keep remove_polyfuncs, as per before this PR. In that case ideally I'd like to automatically remove polymorphic FuncDefns from Modules too where they were not previously children of the Module (most will become children of the Module afterwards because of flattening), as this would be more consistent with non-Modules. That could be a follow-up, I guess for this PR we'd leave things, and/or we could still deprecate the public remove_polyfuncs (i.e. moving towards a situation where remove_polyfuncs lives on but as an internal thing for monomorphization, and external users have only remove_dead_funcs - which could be extended via a filter-callback if we wanted). This might be best in some way, but clearly involves the most code, and the step to remove "invisible" polyfuncs from Modules is slightly breaking.

I think it's fine to remove polymorphic FuncDefns where they were not previously children of the module. Having said that, I have a weak preference to not do so. To me it makes the pass strictly less useful:

  • You still have to run a dead-func-removal (to get the module-level polymorphic functions. We can't do any better because we can't reason about public functions in general yet)
  • If you cared about some interior function for some reason deleting it may be annoying
    Why would you care about said interior function? I don't know, but deleting it here does not seem to accomplish anything

Thoughts?

For dead func removal:

I would prefer a completely uniform treatment of Module and non-Module rooted HUGRs. In both cases:

  • pass a list of roots(FuncDecl/FuncDefn nodes). They are not constrained to be children of the root
  • Every function reachable from a root is kept alive
  • The HUGR root is kept alive
  • If a roots parent would be removed, first move the root to be a child of the root.

I'm also ok with the pass as-is, if we need the additional flexibility afforded by the above we can change it then.

For monomorphise:
I think we should do the simplest thing: no removing functions, documentation pointing to DeadFuncRemoval

@acl-cqc
Copy link
Contributor Author

acl-cqc commented Dec 23, 2024

For dead func removal:

I would prefer a completely uniform treatment of Module and non-Module rooted HUGRs. In both cases:

  • pass a list of roots(FuncDecl/FuncDefn nodes). They are not constrained to be children of the root
  • Every function reachable from a root is kept alive
  • The HUGR root is kept alive

Ok - sounds reasonable - I would probably move to adding a callback/filter at the same time, but either way, this can be done later as a non-breaking change (it just allows more things), so no urgency, I'll leave it for now.

For monomorphise: I think we should do the simplest thing: no removing functions, documentation pointing to DeadFuncRemoval

Ok, we agree that option 1 is the best target 👍 👍. I'm a bit hazy on the definition of breaking change (does that include runtime-breaking i.e. any behaviour change but not compile-breaking?), but erring on the side of caution I'll categorize dropping the automatic call to remove_polyfuncs as breaking, so not do that here - I've marked it with a TODO for the next release and kept the call to the now-deprecated remove_polyfuncs instead.

@acl-cqc acl-cqc enabled auto-merge December 24, 2024 09:08
@acl-cqc acl-cqc added this pull request to the merge queue Dec 24, 2024
Merged via the queue into main with commit cad7484 Dec 24, 2024
27 checks passed
@acl-cqc acl-cqc deleted the acl/remove_dead_funcs branch December 24, 2024 09:12
@hugrbot hugrbot mentioned this pull request Dec 24, 2024
@hugrbot hugrbot mentioned this pull request Jan 16, 2025
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.

Add a pass to remove "unused" functions
4 participants