Skip to content
This repository has been archived by the owner on Oct 12, 2022. It is now read-only.

Fix Issue 21097 - prevent stack allocation when destroying large aggregates #3467

Merged
merged 1 commit into from May 18, 2021

Conversation

JohanEngelen
Copy link
Contributor

@JohanEngelen JohanEngelen commented May 10, 2021

This fixes destroy and core.internal.lifetime.emplaceInitializer for large aggregates by preventing stack allocation for LDC and DMD.
For DMD, it also fixes a codegen issue for large static array of structs where destroy/emplaceInitializer would generate a string of mov instructions proportional to the size of the array.

Intentionally did not add unittests for this issue here, but instead added a runnable test case to the testsuite, to prevent a very large unittest binary. Test is added in separate DMD PR: dlang/dmd#12509.
Tests have been added to this PR.

@dlang-bot
Copy link
Contributor

dlang-bot commented May 10, 2021

Thanks for your pull request and interest in making D better, @JohanEngelen! 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
21097 regression [REG2.083] Stack exhaustion upon large struct .destroy

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 "stable + druntime#3467"

@JohanEngelen JohanEngelen force-pushed the largeaggregates branch 3 times, most recently from 885cf5a to 9f6dc63 Compare May 10, 2021 21:55
@JohanEngelen
Copy link
Contributor Author

Adding @nogc to emplaceInitializer ran into this Phobos unittest error:

std/internal/test/dummyrange.d(40): Error: array literal in `@nogc` function `core.internal.lifetime.emplaceInitializerImpl!(S).emplaceInitializerImpl!(S).emplaceInitializerImpl` may cause a GC allocation
../druntime/import/core/internal/lifetime.d(101): Error: template instance `core.internal.lifetime.emplaceInitializerImpl!(S).emplaceInitializerImpl!(S)` error instantiating
../druntime/import/core/internal/lifetime.d(56):        instantiated from here: `emplaceInitializer!(S)`
std/array.d(132):        instantiated from here: `emplaceRef!(DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random, uint[]), DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random, uint[]), DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random, uint[]))`
std/range/package.d(9581):        instantiated from here: `array!(Result!())`
std/array.d(135): Error: forward reference to inferred return type of function call `function () @trusted => cast(E[])result()`
std/range/package.d(9581): Error: `pure` function `std.range.__unittest_L9550_C20` cannot call impure function `std.array.array!(Result!()).array`
std/range/package.d(9581): Error: template instance `std.array.array!(Result!())` error instantiating
std/internal/test/dummyrange.d(40): Error: array literal in `@nogc` function `core.internal.lifetime.emplaceInitializerImpl!(S).emplaceInitializerImpl!(S).emplaceInitializerImpl` may cause a GC allocation
../druntime/import/core/internal/lifetime.d(101): Error: template instance `core.internal.lifetime.emplaceInitializerImpl!(S).emplaceInitializerImpl!(S)` error instantiating
../druntime/import/core/internal/lifetime.d(56):        instantiated from here: `emplaceInitializer!(S)`
std/array.d(132):        instantiated from here: `emplaceRef!(DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Random, uint[]), DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Random, uint[]), DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Random, uint[]))`
std/range/package.d(9581):        instantiated from here: `array!(Result!())`
std/array.d(135): Error: forward reference to inferred return type of function call `function () @trusted => cast(E[])result()`
std/range/package.d(9581): Error: `pure` function `std.range.__unittest_L9550_C20` cannot call impure function `std.array.array!(Result!()).array`
std/range/package.d(9581): Error: template instance `std.array.array!(Result!())` error instantiating

The problematic line is chunk = T.init; (if commented-out, the Phobus unittest compile error is gone).
I don't know how to fix it. The original function also did not have the @nogc attribute, so I've removed for the improved function in this PR too.

alias Unshared = T;
// Avoid stack allocation by hacking to get to the init symbol.
pragma(mangle, "_D" ~ T.mangleof[1..$] ~ "6__initZ")
__gshared extern immutable typeof(T.init) initializer;
Copy link
Contributor

Choose a reason for hiding this comment

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

This is only used in the else block

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed.
I guess your comment is to static-if this away, but I would have to replicate the full static-if chain, and it does not harm to have it here.

Copy link
Contributor

Choose a reason for hiding this comment

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

You could move it into the else block?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it needs to be a global variable (and what i remember is that making it static did not work)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's just a fwd decl, so no harm done if unused.

Copy link
Contributor

Choose a reason for hiding this comment

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

That's weird but should not block this PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

It's just a fwd decl, so no harm done if unused.

Not for LDC:

FAILED: runtime/objects-unittest-debug/std/experimental/allocator/building_blocks/affix_allocator.o 
cd /home/runner/work/ldc/ldc/runtime/phobos && /home/runner/work/ldc/ldc/bin/ldc2 -c --output-o -conf= -w -de -dip1000 -preview=dtorfields -g -link-defaultlib-debug -d-debug -d-version=StdUnittest -unittest -linkonce-templates -relocation-model=pic -I/home/runner/work/ldc/ldc/runtime/druntime/src -I/home/runner/work/ldc/ldc/runtime/phobos -of=/home/runner/work/ldc/ldc/runtime/objects-unittest-debug/std/experimental/allocator/building_blocks/affix_allocator.o std/experimental/allocator/building_blocks/affix_allocator.d
/home/runner/work/ldc/ldc/runtime/druntime/src/core/internal/lifetime.d(99): Error: Global variable type does not match previous declaration with same mangled name: `_D6__initZ`
/home/runner/work/ldc/ldc/runtime/druntime/src/core/internal/lifetime.d(99):        Previous IR type: i64, const, non-thread-local
/home/runner/work/ldc/ldc/runtime/druntime/src/core/internal/lifetime.d(99):        New IR type:      i32, const, non-thread-local

@JohanEngelen
Copy link
Contributor Author

ping

@MoonlightSentinel
Copy link
Contributor

Your test is not executed by make, you'll need to add the directory to this list.

@JohanEngelen
Copy link
Contributor Author

Thanks @MoonlightSentinel . Added it to the makefile (really, this test framework we have here is so horrible...), and squashed all fixup commits.

@JohanEngelen JohanEngelen changed the base branch from master to stable May 15, 2021 18:28
@JohanEngelen
Copy link
Contributor Author

JohanEngelen commented May 15, 2021

And rebased to target stable branch.
Rebased back to master, because stable branch testing is broken.

@JohanEngelen JohanEngelen changed the base branch from stable to master May 15, 2021 18:37
@JohanEngelen
Copy link
Contributor Author

OK to rebase onto stable and merge?

@MoonlightSentinel
Copy link
Contributor

Rebased back to master, because stable branch testing is broken.

What was the failure?

OK to rebase onto stable and merge?

Yes as this fixes a regression.

import core.stdc.string : memset;
memset(cast(void*) &chunk, 0, T.sizeof);
}
else static if (T.sizeof <= 16 && !hasElaborateAssign!T && __traits(compiles, (){ T chunk; chunk = T.init; }))
Copy link
Contributor

Choose a reason for hiding this comment

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

Out of curiousity, why 16 instead of a larger value?

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 value is indeed pretty arbitrary. It's to limit the stack space used.

…alizer for large aggregates by preventing stack allocation
@JohanEngelen JohanEngelen changed the base branch from master to stable May 18, 2021 21:09
@JohanEngelen
Copy link
Contributor Author

Rebased onto stable, PR targets stable now.

@thewilsonator
Copy link
Contributor

Build fails because CI checks out phobos master instead of phobos stable. I'm going to tentatively force merge this.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Bug Fix Include reference to corresponding bugzilla issue
Projects
None yet
8 participants