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

Finalise initialisation calculations #110

Merged
merged 4 commits into from
Sep 3, 2019
Merged

Finalise initialisation calculations #110

merged 4 commits into from
Sep 3, 2019

Conversation

amandasystems
Copy link
Contributor

This PR will:

  • finally remove region_live_at
  • implement enough initialisation tracking to correctly calculate drop-uses for liveness
  • extend polonius-parser to work in this new universe

Notably, it will not calculate the full move analysis, as that would a) probably take a lot of time and b) require refactoring of the entire Polonius pipeline, or at the very least extensions to handle more types of errors.

@amandasystems amandasystems changed the title [WIP] Finalise initialisation calculations Finalise initialisation calculations Aug 19, 2019
Copy link
Contributor

@nikomatsakis nikomatsakis left a comment

Choose a reason for hiding this comment

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

Looking good! Thoughts attached. =)

/// `var_initialized_on_exit(V, P) when the variable `V` is initialized on
/// exit from point `P` in the program flow.
pub var_initialized_on_exit: Vec<(V, P)>,
/// `child(M1, M2) when the move path `M1` is the child of `M2`.
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it'd be useful to give an example. For example, I imagine:


child(M1, M2) would be true If M1 represents a.b.c and M2 represents a.b.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's true. The hand-wavyness was because I was not entirely sure if it was transitive or not. It is, though, which may or may not be what we want.

Copy link
Contributor

Choose a reason for hiding this comment

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

Interesting. The name strongly suggests non-transitive to me. I would have said something like descendant for a transitive child relationship.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

True, I'll make a note of fixing this at some point.

pub child: Vec<(M, M)>,

/// `path_belongs_to_var(M, V) when the move path `M` starts in variable `V`.
pub path_belongs_to_var: Vec<(M, V)>,
Copy link
Contributor

Choose a reason for hiding this comment

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

Why 'belongs to'? I think maybe path_starts_with_var or something.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I called it that originally, but I got confused and was unsure if it held transitively (it doesn't). I'll change it to something better.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm. I'm not sure what "transitive" means in this case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

"Includes descendants"

pub path_belongs_to_var: Vec<(M, V)>,

/// `initialized_at(M, P) when the move path `M` was initialized at point
/// `P`.
Copy link
Contributor

Choose a reason for hiding this comment

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

Again, I think an example would be good --


If we have some Rust code like:

x.y = 3 // point P

where M1 represents x.y, then we would have initialized_at(M1, P).

Copy link
Contributor

Choose a reason for hiding this comment

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

Reading over the code I had a few questions. Are you assuming that:

a = (22, 44);

would generate:

initialized_at(a);
initialized_at(a.0);
initialized_at(a.1);

?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think my preference would NOT to assume that -- that is, I think the compiler should give us just initialized_at(a), and if we care to elaborate the closure with respect to child paths, we do it ourselves.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I had to investigate what the current fact generation actually does, and it only generates the fact for the path being initialized directly. I'll clarify this in the comments and add an example.

/// `P`.
pub initialized_at: Vec<(M, P)>,

/// `moved_out_at(M, P) when the move path `M` was moved at point `P`.
Copy link
Contributor

Choose a reason for hiding this comment

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

As above.

Copy link
Contributor

Choose a reason for hiding this comment

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

Similarly, are we assuming here that drop(a); would generate moved_out(a.0) and moved_out(a.1) facts?

Copy link
Contributor

Choose a reason for hiding this comment

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

Same as above -- I think we should do the elaboration ourselves.


use datafrog::{Iteration, Relation, RelationLeaper};

pub(super) fn init_var_maybe_initialized_on_exit<Region, Loan, Point, Variable, MovePath>(
Copy link
Contributor

Choose a reason for hiding this comment

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

This function definitely wants a comment with some kind of Rust example showing where it's true and not true.


// path_maybe_initialized_on_exit(Mother, P) :-
// path_maybe_initialized_on_exit(Daughter, P),
// child(Daughter, Mother).
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm debating the purpose of this rule. It feels sort of incomplete on its own. It also suggests that the name "maybe initialized" could be refined -- in particular I guess it includes partial initialization? That is, this rule says that if a.b is "maybe initialized", then a is "maybe initialized". There is no corresponding rule saying (for example) that a move from a is also a move from a.b, so I have to assume that moved_out facts are the "transitive closure" over children (and that suggests to me that initialization facts should be the same).

Well, I guess the point of this "maybe partially initialized" relation is precisely to inform the region-live-at computation in NLL, which is probably already imprecise in this manner? I guess I better double check, but I remember us making some decisions like that.

Copy link
Contributor

Choose a reason for hiding this comment

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

Still, if the goal is to ultimately compute this relation, then why do we need the previous rule?

Well, it doesn't seem harmful -- but I guess we can either do the elaboration at the initial point -- i.e., if some path M is initialized at a point P, then we could make the initial value include that all paths which are either parents or children of M are "maybe initialized" at the point P. And then we don't need this rule to be part of the iteration -- rather, we compute this transition closure before hand.

I think this is what Lark did, but I suppose I should go back and try to write up the rules I used there. Maybe a good blog post.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this connects with your next comment, so I'll reply to it there.

// var_maybe_initialized_on_exit(V, P) :-
// path_belongs_to_var(M, V),
// path_maybe_initialized_at(M, P).
var_maybe_initialized_on_exit.from_leapjoin(
Copy link
Contributor

Choose a reason for hiding this comment

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

Something is tickling me here. If the goal ultimately is to compute which variables are maybe initialized, why do we have to extend to the parents? That is, it feels like we should track initialization precisely.

Something like this:

  • When you assign to a path M, that also counts as an assignment to each child path Mc (where Mc is a transitive child of M).
  • When you move from M, that also counts as a move from from child path Mc (as above).
  • If M is initialized at P, then it is initialized at each successor Q, unless Q moves from M (as above).

then compute var_maybe_init as we are doing here.

I guess the example where this differs is something like:

a.1 = foo; // initializes a.1 in my version, but also initializes a in your version
drop(a.1); // moves out from a.1 only
// is "a" considered maybe init here? in your version it is, in mine it isn't.

The existing NLL is somewhat approximate here, so maybe this is intentional.

Am I missing something?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think what you are detecting is that I am computing sort of two different transitive closures. What I want to do is to trace back partial initialisation to a variable for use in the next step to determine if a drop would happen.

Probably, the cleanest design would be to compute the transitive closure over paths downwards, and separately extend the "variable-rootedness" across another transitive closure (tracing all paths back to their variable), and then finally perform the join, but I am also not sure if this is an over-approximation. The more I think about this, the less I feel like I understand it.

I am fairly certain what we did in the original fact generation was this, though: we emitted var_maybe_initialized_at whenever some sub-path of var was (maybe) initialized at that point.

@nikomatsakis
Copy link
Contributor

Per our discussion today, we decided to land this as is

@nikomatsakis
Copy link
Contributor

And consider any follow-ups for later

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.

2 participants