feat!: Handle CallIndirect in Dataflow Analysis#2059
feat!: Handle CallIndirect in Dataflow Analysis#2059acl-cqc merged 20 commits intorelease-rs-v0.16.0from
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## release-rs-v0.16.0 #2059 +/- ##
======================================================
+ Coverage 82.91% 83.01% +0.09%
======================================================
Files 217 217
Lines 41529 41654 +125
Branches 37707 37832 +125
======================================================
+ Hits 34433 34577 +144
+ Misses 5292 5273 -19
Partials 1804 1804
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
This PR contains breaking changes to the public Rust API. cargo-semver-checks summary |
| OpType::Input(_) | OpType::Output(_) | OpType::ExitBlock(_) => None, // handled by parent | ||
| OpType::Call(_) => None, // handled via Input/Output of FuncDefn | ||
| OpType::Const(_) => None, // handled by LoadConstant: | ||
| OpType::Call(_) | OpType::CallIndirect(_) => None, // handled via Input/Output of FuncDefn |
There was a problem hiding this comment.
I dropped Const: it's not a dataflow node (it's a module/static), so never gets here
| (Self::LoadedFunction(lf1), Self::LoadedFunction(lf2)) | ||
| if lf1.func_node == lf2.func_node => | ||
| { | ||
| // TODO we should also require TypeArgs to be equal by at the moment these are ignored |
There was a problem hiding this comment.
I.e. we are ignoring the TypeArgs in Call nodes too. I'll make an issue, but not this PR.
| /// of values of the underlying representation | ||
| #[derive(PartialEq, Clone, Eq, Hash, Debug)] | ||
| pub enum PartialValue<V> { | ||
| pub enum PartialValue<V, N = Node> { |
There was a problem hiding this comment.
Not sure about the default N=Node. To get everything to work I ended up searching for a regex of PartialValue with only one thing between the <>'s...hence putting in a few explicit , Nodes into tests. I could retract those and use the default, or remove the default....
There was a problem hiding this comment.
(for now I've removed the explicit , Node in tests, but easy to reinstate, and still considering whether to remove the default)
doug-q
left a comment
There was a problem hiding this comment.
Looks great Alan. I think there's a mistake in the indirect_call datalog
hugr-passes/src/dataflow/datalog.rs
Outdated
| in_wire_value(outp, p, v); | ||
|
|
||
| // CallIndirect -------------------- | ||
| relation indirect_call(H::Node, H::Node); // <Node> is an `IndirectCall` to `FuncDefn` <Node> |
There was a problem hiding this comment.
I think this needs to be a lattice, and to store the Callee.
As written, if you first get a LoadedFunction, then it goes to TOP, you'll not update the out_wire_values to top.
I think you should deal with polymorphism either by requring 0 type args
There was a problem hiding this comment.
Great point about it needing to be a lattice, thank you! :). Done.
(Annoying about not having a test to show the difference but I bet it'd have show up sooner or later.)
Polymorphism is not wrong atm. Values from different type-instantiations are just joined together so will likely produce Top. If there's a LoadNat - well we don't even handle that yet - but any DFContext::interpret_leaf_op will just see a LoadNat<Var0> and not get the type args, so there's no sensible implementation other than returning Top. It's not ideal, but it's not wrong.
| OpType::Input(_) | OpType::Output(_) | OpType::ExitBlock(_) => None, // handled by parent | ||
| OpType::Call(_) => None, // handled via Input/Output of FuncDefn | ||
| OpType::Const(_) => None, // handled by LoadConstant: | ||
| OpType::Call(_) | OpType::CallIndirect(_) => None, // handled via Input/Output of FuncDefn |
There was a problem hiding this comment.
I think it would be more consistent to deal with callindirect here rather than in the datalog, is that possible?
There was a problem hiding this comment.
Only by a massive refactor of propagate_leaf_op. Which is private, so we could, but I don't feel this is inconsistent - I think it's just like what we have been doing for Call
There was a problem hiding this comment.
Not worth a massive refactor. I think it's inconsistent only because I don't think "CallIndirect" is morally built in(I claim it could be in prelude)
| } | ||
|
|
||
| /// Can this ever occur at runtime? See [PartialValue::contains_bottom] | ||
| pub fn contains_bottom(&self) -> bool { |
There was a problem hiding this comment.
This is confusing. I would expect any here, but I think this really means "surely has at least one bottom field"
There was a problem hiding this comment.
Yes - "definitely contains bottom" not "might contain bottom". I think the best thing to do is rename, which I could do here (this PR is breaking already, of course I could deprecate)...or perhaps a doc update would suffice?
There was a problem hiding this comment.
Happy to deprecate and "rename" to anything we can agree on. definitely_contains_bottom seems too long (esp. with row_ prefix). I don't really like is_bottom, would prefer to invert sense and go with can_occur (which is already noted in comments!) - but IIUC you don't like that. Other opinions welcome, but a rename with deprecation is not breaking so could follow afterwards.
hugr-passes/src/dataflow/datalog.rs
Outdated
| ctx.value_from_function(func_node, &load_op.type_args) | ||
| .map_or(PV::Top, PV::Value), | ||
| )) | ||
| Some(ValueRow::singleton(PartialValue::LoadedFunction( |
There was a problem hiding this comment.
Recommend you use PartialValue::new_load to get coverage on that function
| /// A value contains bottom means that it cannot occur during execution: | ||
| /// it may be an artefact during bootstrapping of the analysis, or else | ||
| /// the value depends upon a `panic` or a loop that | ||
| /// [never terminates](super::TailLoopTermination::NeverBreaks). |
There was a problem hiding this comment.
or occurs in a Case which is never entered, or a function that is never called.
There was a problem hiding this comment.
Not yet - e.g. the output of a LoadConst inside an unreachable Case does not contain bottom until #1672 (if ever)
doug-q
left a comment
There was a problem hiding this comment.
Nice. LatticeWrapper doesn't have test coverage, this would be easy to improve.
* PartialValue now has a LoadedFunction variant, created by LoadFunction nodes (only, although other analyses are able to create PartialValues if they want) * This requires adding a type parameter to PartialValue for the type of Node, which gets everywhere :-(. * Use this to handle CallIndirects *with known targets* (it'll be a single known target or none at all) just like other Calls to the same function * deprecate (and ignore) `value_from_function` * Add a new trait `AsConcrete` for the result type of `PartialValue::try_into_concrete` and `PartialSum::try_into_sum` Note almost no change to constant folding (only to drop impl of `value_from_function`) BREAKING CHANGE: in dataflow framework, PartialValue now has additional variant; `try_into_concrete` requires the target type to implement AsConcrete.
value_from_functionAsConcretefor the result type ofPartialValue::try_into_concreteandPartialSum::try_into_sumNote almost no change to constant folding (only to drop impl of
value_from_function)BREAKING CHANGE: in dataflow framework, PartialValue now has additional variant;
try_into_concreterequires the target type to implement AsConcrete