Skip to content

implement "dynamic drop" semantics using flags on the stack rather than zeroing #5016

Description

@thestinger

(Tracking issue for RFC 320.)

Original Description

The current liveness check has a concept of "alive" and "maybe dead", so I'll just describe this in terms of a hypothetical "move optimization pass" (although liveness can probably be extended with "surely dead").

Hypothetical move optimization pass:

In each scope where they exist, all variables are given an associated boolean representing whether they were certainly moved from. Moving from a variable in a scope marks it as such for that scope. This can be bubbled up if and only if the variable is also moved from in every other branch.

If the compiler can bubble this up to the scope where the variable is declared, the drop glue can be omitted and all moves from that variable do not need to zero it.

This can probably be extended to fields too, but I'm not sure on what the semantics would be.

@nikomatsakis: does this look like something that would be reasonable to implement (probably as part of liveness)? It doesn't actually need to bubble it up to be useful - even if it only worked in a local scope, it would allow the destructor of the save variable in the TreeMap split and skew functions to be omitted (since it's moved from in the same scope where it's declared) and that would be a big performance win.

Activity

  1. nikomatsakis commented on Feb 19, 2013

    @nikomatsakis
    Contributor

    This seems doable and straightforward. It's basically adding another bit to track per variable ("was moved") which uses intersection on join when propagating.

    However, there is another approach that @pcwalton and I have toyed with from time to time, which is saying that when you move things, the destructor for the moved thing runs "early"---basically at the point where control flow on the non-moved path (if any) rejoins the control flow from the moved path. The idea here was to remove the need to zero 100% of the time. However, I am not sure if that idea (early destructing) is such a good idea, because it seems hard to specify and predict, whereas the current semantics (destructors run at the same time unless moved) is easier.

    So if we keep current semantics, then I think something like this seems like a good optimization. In principle we could do better and identify nodes in the CFG where, upon entering the node, we know the value will be moved (this is what liveness will be computing, actually). Then we could avoid zeroing as long as we dominate the node. That might be trickier though given the way failure and unrolling works, so maybe it's better to only optimize the case where the variable declaration is such a node.

  2. graydon commented on Mar 4, 2013

    @graydon
    Contributor
  3. catamorphism commented on Apr 29, 2013

    @catamorphism
    Contributor

    Nominating for milestone 1, well-defined.

  4. graydon commented on May 2, 2013

    @graydon
    Contributor

    Accepted for well defined. This needs to be documented.

  5. glaebhoerl commented on May 27, 2013

    @glaebhoerl
    Contributor

    I don't think the alternative would necessarily be harder to specify and predict. It's basically dynamic vs. static transfer of ownership. With the current rule, a variable's destructor is run at the end of the scope it was declared in, unless ownership of it was dynamically transferred to an inner scope. With the alternative rule, referring to the variable in an inner scope statically transfers ownership of it to that inner scope. The compiler already uses the latter rule to determine when a variable is legal to access, so the programmer has to think about it (if not, the compiler will make her). The alternative would make destructors follow the same rules. Currently you can have points in the code where the compiler won't let you access a variable but the variable's destructor may not have been run, which might be unintuitive itself.

  6. nikomatsakis commented on May 28, 2013

    @nikomatsakis
    Contributor

    Current plan (based on meeting discussion) is to have the compiler issue an error if there are ever two control flows that meet where one does a move and the other does not, so that this issue becomes a moot point. I am in the process of preparing a pull request taking some steps in this direction.

  7. nikomatsakis commented on Nov 15, 2013

    @nikomatsakis
    Contributor

    I'm somewhat working on this, btw. Also, it will affect the semantics of what moves are legal, particularly around vectors, since in those cases the compiler will not be able to determine statically which indices have been moved and which have not. I imagine we'll just disallow moves from like let x = vec[y] and instead offer a method like let x = vec.move(y) where move is a fn(self) method.

  8. nikomatsakis commented on Nov 15, 2013

    @nikomatsakis
    Contributor

    Thinking a bit more, there is no need for fn(self) method. Something like pop etc will suffice, particularly combined with swap:

    impl<T> ~[T] {
        fn remove(&mut self, index: uint) -> T {
            assert!(index < self.len());
            if (index != self.len() - 1) {
                self.swap(index, self.len() - 1);
            }
            self.pop();
        }
    }
    
  9. huonw commented on Nov 15, 2013

    @huonw
    Contributor

    FWIW, that method currently exists: .swap_remove.

  10. nikomatsakis commented on Nov 15, 2013

    @nikomatsakis
    Contributor

    @huonw that's right, I knew I'd seen this somewhere :)

  11. pnkfelix commented on Nov 28, 2013

    @pnkfelix
    Contributor

    cc me

  12. pnkfelix commented on Feb 3, 2014

    @pnkfelix
    Contributor

    This interacts with the meaning/utility of the std::ptr::read_and_zero_ptr function, right?

    I've been playing with that function recently in attempts to prototype some hypothetical finalization APIs. So I am curious to know what would replace it.

    If a struct currently carries a ~T, I believe I can use read_and_zero_ptr to zero out that reference and keep that destructor for ~T (and thus for T itself) from running.

    What is the replacement for that?

    (Or should the struct field use Option<~T> instead of ~T and thus I would be able to swap in None to prevent a destructor for ~T from running when the struct is destroyed?)

  13. 44 remaining items

  14. aldanor commented on Apr 7, 2016

    @aldanor

    @pnkfelix Thanks for clarifying! Didn't mean to sound any negative, was mostly just wondering if it's wise for library developers to assume this will land in some (finite) time and make their choices accordingly :)

  15. nikomatsakis commented on Apr 12, 2016

    @nikomatsakis
    Contributor

    I expect it to land this year.

    On Wed, Apr 06, 2016 at 05:39:55PM -0700, Ivan Smirnov wrote:

    @pnkfelix Thanks for clarifying! Didn't mean to sound any negative, mostly just wondering if it's wise for library developers to assume this will land in some (finite) time and make their choices accordingly :)


    You are receiving this because you were mentioned.
    Reply to this email directly or view it on GitHub:
    #5016 (comment)

  16. ticki commented on Jun 5, 2016

    @ticki
    Contributor

    🎉

  17. sorear commented on Jun 18, 2016

    @sorear
    Contributor

    I have some code that spends a measurable amount of time checking for drop flags (there's an inner loop which drops an Option<Box<...>> which is virtually always None), and so I'm kind of wondering if we have a ticket for the other side of this, where we delete POST_DROP_U8 and stop generating code in drop methods to check for it. Do we have a tracking ticket for that?

    Incidentally, this ticket is currently the tracking issue for the POST_DROP_U8 constant; maybe that should be repointed now that this ticket is closed?

  18. added a commit that references this issue on May 10, 2026
  19. added a commit that references this issue on Aug 21, 2026
    0fbfab3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

A-codegenArea: Code generationB-RFC-approvedBlocker: Approved by a merged RFC but not yet implemented.B-unstableBlocker: Implemented in the nightly compiler and unstable.I-slowIssue: Problems and improvements with respect to performance of generated code.P-mediumMedium priorityT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.T-libs-api[DEPRECATED; DO NOT USE]

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions