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

Fix Issue 22536 - CTFE: Missing destruction of array literal argument for scope slice parameter #13496

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

RazvanN7
Copy link
Contributor

@RazvanN7 RazvanN7 commented Jan 6, 2022

It appears that CTFE does not know how to destroy array literal temporaries that are put on stack. My solution first collects the temporary destructors that are created here [1] and interprets them after the function call was interpreted.

[1] https://github.com/dlang/dmd/blob/master/src/dmd/expressionsem.d#L2074

@dlang-bot
Copy link
Contributor

Thanks for your pull request and interest in making D better, @RazvanN7! We are looking forward to reviewing it, and you should be hearing from a maintainer soon.
Please verify that your PR follows this checklist:

  • My PR is fully covered with tests (you can see the coverage diff by visiting the details link of the codecov check)
  • My PR is as minimal as possible (smaller, focused PRs are easier to review than big ones)
  • I have provided a detailed rationale explaining my changes
  • New or modified functions have Ddoc comments (with Params: and Returns:)

Please see CONTRIBUTING.md for more information.


If you have addressed all reviews or aren't sure how to proceed, don't hesitate to ping us with a simple comment.

Bugzilla references

Auto-close Bugzilla Severity Description
22536 blocker CTFE: Missing destruction of array literal argument for scope slice parameter

⚠️⚠️⚠️ Warnings ⚠️⚠️⚠️

To target stable perform these two steps:

  1. Rebase your branch to upstream/stable:
git rebase --onto upstream/stable upstream/master
  1. Change the base branch of your PR to stable

Testing this PR locally

If you don't have a local development environment setup, you can use Digger to test this PR:

dub run digger -- build "master + dmd#13496"

@RazvanN7
Copy link
Contributor Author

RazvanN7 commented Jan 6, 2022

cc @kinke as this is blocking a fix of yours.

@MoonlightSentinel
Copy link
Contributor

MoonlightSentinel commented Jan 6, 2022

Related issue with -preview=in that might be is fixed by this patch:

int previewIn()
{
    int numDtor;

    struct S
    {
        int x;
        ~this() { ++numDtor; }
    }

    static accept(in S s) {}
    accept(S(1));
    return numDtor;
}

static assert(previewIn() == 1);

TEST_OUTPUT:
---
fail_compilation/fail22536.d(22): Error: uncaught CTFE exception `object.Exception("exception")`
fail_compilation/fail22536.d(25): called from here: `foo(((S[2] __arrayliteral_on_stack3 = [S(1), S(2)];) , cast(S[])__arrayliteral_on_stack3))`
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This could be improved, but I'll leave it for a different PR.

@RazvanN7
Copy link
Contributor Author

@MoonlightSentinel is this addition acceptable?

Copy link
Contributor

@MoonlightSentinel MoonlightSentinel left a comment

Choose a reason for hiding this comment

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

There's probably a more general solution for intermediate temporaries but this patch fixes the issue for now.

Please add #13496 (comment) to runnable/previewin.d.

src/dmd/dinterpret.d Outdated Show resolved Hide resolved
@RazvanN7
Copy link
Contributor Author

@MoonlightSentinel Done.

Copy link
Contributor

@MoonlightSentinel MoonlightSentinel left a comment

Choose a reason for hiding this comment

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

Destroying temporaries immediatly after the function actually violates the spec w.r.t. to the lifetime of temporaries.

That causes following test to pass at runtime but fail during CTFE:

struct Inc
{
    int* ptr;
    ~this()
    {
        (*ptr)++;
    }
}

int* foo(scope Inc[] arr)
{
    return arr[0].ptr;
}

int main()
{
    int i;
    assert(*foo([ Inc(&i) ]) == 0);
    return 0;
}

static assert(main() == 0);

Regarding my earlier comment, it might be better to maintain a shared dtors array in the interpreter and destroy all entries at the end of a statement.

@RazvanN7
Copy link
Contributor Author

RazvanN7 commented Feb 1, 2022

@MoonlightSentinel I have addressed your review.


// destroy temporaries
// https://issues.dlang.org/show_bug.cgi?id=22536
auto dtors = ctfeGlobals.dtorsForTemporaries.copy();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Making a copy because the dtor also contains ExpStatements so when it is interpreted it will mess up the shared dtor array.

Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't this state be saved/restored for other statements too? ExpStatement isn't the only statement that may contain expressions with temporaries, e.g. while (foo( [ ... ] ))

// destroy temporaries
// https://issues.dlang.org/show_bug.cgi?id=22536
auto dtors = ctfeGlobals.dtorsForTemporaries.copy();
ctfeGlobals.dtorsForTemporaries.setDim(0);
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 just noticed that setting the dim to 0 does not actually free any memory, it simply sets the length to 0. Since struct Array uses a dynamic array underneath, it should also set the length of it. Currently, the memory is not freed until the actual Expression pointers (in this case) are overwritten by later element additions.

Copy link
Contributor

Choose a reason for hiding this comment

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

Eagerly releasing the underlying memory might not be ideal if the array is reused immediately. But it should zero the "removed" elements to avoid false pointers with -lowmem.

Suggested change
ctfeGlobals.dtorsForTemporaries.setDim(0);
ctfeGlobals.dtorsForTemporaries.setDim(0);
ctfeGlobals.zero();

Copy link
Contributor

@MoonlightSentinel MoonlightSentinel left a comment

Choose a reason for hiding this comment

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

Looks better but doesn't respect the eager cleanup of temporaries of temporaries in || / &&:

struct S
{
    const int num;
    int* dtorCount;

    this(int num, int* dtorCount)
    {
    	this.num = num;
    	this.dtorCount = dtorCount;

        if (num == 4)
            assert(*dtorCount == 2); // Should've been destroyed already
    }

    ~this()
    {
        const destr = ++(*dtorCount);
        assert(num == destr);
    }
}

int conditionals()
{
    int dtorCount;
    int* p = &dtorCount;

    bool b = (S(6, p) == S(5, p) || S(2, p) != S(1, p)) && S(4, p) == S(3, p);
    return 0;
}

static assert(conditionals() == 0);

Comment on lines +484 to +494
if (auto commaExp = earg.isCommaExp())
{
if (auto declExp = commaExp.e1.isDeclarationExp())
{
if (auto vd = declExp.declaration.isVarDeclaration())
{
if (vd.edtor)
ctfeGlobals.dtorsForTemporaries.push(vd.edtor);
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This doesn't belong here anymore with the more general solution. Dtors should probably be registered by visit(DeclarationExp) s.t. it works without this special casing.

That should also fix the example from the spec:

bool b = (S(1) == S(2) || S(3) != S(4)) && S(5) == S(6);

Neither of those temporaries is currently destroyed.

@@ -0,0 +1,69 @@
// https://issues.dlang.org/show_bug.cgi?id=22536

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// PERMUTE_ARGS:

This test doesn't need to be run with backend permutations

// destroy temporaries
// https://issues.dlang.org/show_bug.cgi?id=22536
auto dtors = ctfeGlobals.dtorsForTemporaries.copy();
ctfeGlobals.dtorsForTemporaries.setDim(0);
Copy link
Contributor

Choose a reason for hiding this comment

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

Eagerly releasing the underlying memory might not be ideal if the array is reused immediately. But it should zero the "removed" elements to avoid false pointers with -lowmem.

Suggested change
ctfeGlobals.dtorsForTemporaries.setDim(0);
ctfeGlobals.dtorsForTemporaries.setDim(0);
ctfeGlobals.zero();


// destroy temporaries
// https://issues.dlang.org/show_bug.cgi?id=22536
auto dtors = ctfeGlobals.dtorsForTemporaries.copy();
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't this state be saved/restored for other statements too? ExpStatement isn't the only statement that may contain expressions with temporaries, e.g. while (foo( [ ... ] ))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants