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

Smart pointer with allocator support #2901

Closed
wants to merge 2 commits into from

Conversation

rnlahaye
Copy link
Contributor

@rnlahaye rnlahaye commented Mar 2, 2021

Second part of a two-part fix for #2899.

This change adds a new smart pointer class, Box, which is equivalent to not_null<std::unique_ptr> except that it is parameterized on an allocator instead of a deleter. Because it is within the principia namespace, it will automatically use the allocator from #2900 when the header from #2900 is present.
Unlike std::unique_ptr, there is no way to construct a Box from a raw pointer—it cannot be verified that the raw pointer was constructed with the Box's allocator. For the case of classes with private constructors, there is a factory function in Box which allocates memory for an object without constructing it; the object can then be constructed with placement new.

A nullable version is also provided.

This change also replaces not_null<std::unique_ptr> with Box in ContinuousTrajectory and DiscreteTrajectory. Many other files which transferred unique_ptrs into or out of trajectories had to be changed as well.

Roughly equivalent to not_null<std::unique_ptr<T>> except that it
takes an allocator instead of a deleter.

A Nullable version is also provided.
Specifically, change not_null<std::unique_ptr> to Box in
ContinuousTrajectory and DiscreteTrajectory.
@eggrobin
Copy link
Member

eggrobin commented Mar 2, 2021

The changes away from the standard library types in many unrelated classes make me deeply uneasy, especially for a local semantics-preserving platform-specific performance improvement.

You mentioned in #2899 that overloading ::new led to your allocator being used by Unity. Why is that? Could that custom new be made to link differently so that it does not get picked up by random DLLs?

If that is not feasible, could something more akin to #2900 work? You mention that

unique_ptrs are not affected by this approach as they do not use allocators (they only contain a deleter template parameter)

This is because std::unique_ptr does not do any allocation, only deletion; std::make_unique usually does the allocation (though sometimes new does, notably for private constructors). Could you define a principia::std::make_unique that uses your allocator, and make unique_ptr an alias to something that deletes accordingly?
There would remain the issue of the odd non-make_unique allocation (in particular with private constructors). In order to handle that safely we would need to somehow avoid the unique_ptr(T*) constructor (it is not always desirable anyway, absl::WrapUnique comes to mind) and use a new-expression (since we need a new-expression to retain the magical power of calling a private constructor from a member) with something other than ::new (maybe a trick with a user-defined placement new would work?).

@pleroy
Copy link
Member

pleroy commented Mar 2, 2021

I'll second @eggrobin's comment: not only is the introduction of Box quite intrusive (everybody wants yet another smart pointer), but it also makes the whole thing very brittle: how do we avoid performance regressions on macOS in the future? Remember that we build on macOS but that's pretty much all we do: we are not set up to play, run benchmarks regularly or profile the running game as you did.

For instance, I have been thinking for some time of moving away from std:map for discrete trajectories. If/when I do this, how do I decide whether I should use Box or std::unique_ptr and where? I am more likely than not to make the wrong decision and degrade the macOS version without even noticing (until someone complains, that is).

I like the way that things were done in #2900, because it will Just Work™ without us having to use our brain.

@rnlahaye
Copy link
Contributor Author

rnlahaye commented Mar 2, 2021

I agree that #2900 is much cleaner, and an approach that just works would be preferred (I would also like to replace all the unique ptrs instead of just some of them).

The only ways I can see to do this cleanly are the two ways you mentioned: overload new or add a principia::std::unique_ptr.

Regarding overloading new: what happened, as far as I can tell, is that KSP allocated some object with its allocator, the object was passed into code provided by principia, and principia attempted to delete the object with my allocator (which of course didn't work). To use this idea successfully, the boundary between Unity and Principia would have to be carefully reviewed to make sure no harmful ownership transfers occured.

Regarding principia::std::unique_ptr: this could be done with a wrapper class. The wrapper would have the same api as std::unique_ptr, except that the raw pointer constructor would be private. Creation of uninitialized objects would be made available through a factory function (make_unique_uninit or some such). This would require code changes everywhere the raw pointer constructor is used, which isn’t nothing, but is much better than the current change.
I will implement this approach.

+1 to replacing std::map with something else.

Closing this PR in favor of a future principia::std::unique_ptr PR.

@rnlahaye rnlahaye closed this Mar 2, 2021
@eggrobin
Copy link
Member

eggrobin commented Mar 2, 2021

(To clarify the make_unique & custom placement new hack outlined above: https://gcc.godbolt.org/z/zbsfK5.)

@pleroy
Copy link
Member

pleroy commented Mar 2, 2021

... what happened, as far as I can tell, is that KSP allocated some object with its allocator, the object was passed into code provided by principia, and principia attempted to delete the object with my allocator (which of course didn't work)...

This really surprises me. If this is happening, it is a bug and we should track it down (we should be able to reproduce it on Windows, presumably). We are trying very hard to avoid ownership transfers between C# and C++: for instance, all the pointers allocated in C++ code and passed to C# should come back to C++ for deallocation (look for the Delete entry points in journal.proto); conversely, all the pointers allocated by managed code should be taken as opaque identities by the C++ code.

@rnlahaye
Copy link
Contributor Author

rnlahaye commented Mar 3, 2021

I looked further into the crash induced when overloading operator new.
The crash occurs in some compiled protobuf code invoked at dynamic initialization time.
Crash report is attached.
I also found this StackOverflow answer which indicates that when multiple operator new overloads are present, which one is used is undefined for any given call.

So, no bug in Principia; what I was attempting to do just doesn't seem to work.

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.

None yet

3 participants