-
Notifications
You must be signed in to change notification settings - Fork 8
feat!: Handle CallIndirect in Dataflow Analysis #2059
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
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
|
@@ -362,8 +381,7 @@ fn propagate_leaf_op<V: AbstractValue, H: HugrView>( | |||
ins.iter().cloned(), | |||
)])), | |||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I.e. we are ignoring the TypeArgs in Call nodes too. I'll make an issue, but not this PR.
@@ -273,30 +292,32 @@ impl<V: Hash> Hash for PartialSum<V> { | |||
/// for use in dataflow analysis, including that an instance may be a [PartialSum] | |||
/// 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 , Node
s into tests. I could retract those and use the default, or remove the default....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(for now I've removed the explicit , Node
in tests, but easy to reinstate, and still considering whether to remove the default)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great Alan. I think there's a mistake in the indirect_call datalog
hugr-passes/src/dataflow/datalog.rs
Outdated
@@ -322,6 +323,35 @@ pub(super) fn run_datalog<V: AbstractValue, H: HugrView>( | |||
func_call(call, func), | |||
output_child(func, outp), | |||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
@@ -362,8 +392,7 @@ fn propagate_leaf_op<V: AbstractValue, H: HugrView>( | |||
ins.iter().cloned(), | |||
)])), | |||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or occurs in a Case which is never entered, or a function that is never called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not yet - e.g. the output of a LoadConst
inside an unreachable Case does not contain bottom until #1672 (if ever)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice. LatticeWrapper doesn't have test coverage, this would be easy to improve.
value_from_function
AsConcrete
for the result type ofPartialValue::try_into_concrete
andPartialSum::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