Skip to content

Conversation

@mjp41
Copy link
Member

@mjp41 mjp41 commented Jul 4, 2025

The new operator in the snmalloc did not throw exceptions in the case of allocation failure. Moreover, it was not possible to override the behaviour of the failure of the new operator using the std::set_new_handler function.

This commit adds the necessary code to the snmalloc new operator to throw std::bad_alloc when the allocation fails. It also allows the std::set_new_handler function to be used to set a custom handler for allocation failures.

The PR also adds a new build target libsnmalloc-minimal.so that does not override new. This has smaller dependencies.

@mjp41 mjp41 marked this pull request as draft July 4, 2025 12:38
@mjp41
Copy link
Member Author

mjp41 commented Jul 4, 2025

Addresses #785

@mjp41 mjp41 force-pushed the new_exceptions branch from 40fed99 to ffe4765 Compare July 4, 2025 12:49
@SchrodingerZhu
Copy link
Collaborator

SchrodingerZhu commented Jul 4, 2025

There are both throwing and non-throwing variants of the new operator:

https://en.cppreference.com/w/cpp/memory/new/operator_new

Is the idea that even for the throwing one, we will also actively avoid adding any additional branch in the fast path?
Is it possible to separate the throwing ones in a different TU/Shim?

@mjp41
Copy link
Member Author

mjp41 commented Jul 4, 2025

There are both throwing and non-throwing variants of the new operator:

https://en.cppreference.com/w/cpp/memory/new/operator_new

Is the idea that even for the throwing one, we will also actively avoid adding any additional branch in the fast path?

Yes, the template parameter:

template<bool ShouldThrow = true>

https://github.com/microsoft/snmalloc/pull/791/files#diff-d40107b20785f1091aa8d32178beac25eac712d3c2934f4c5d8cce7c6b3343d6R26

Basically creates two versions of the failure continuation, one that throws, and one that doesn't. Then

void* operator new(size_t size)
{
  return snmalloc::alloc<snmalloc::Throw>(size);
}

will throw without a branch, and

void* operator new(size_t size, std::nothrow_t&)
{
  return snmalloc::alloc<snmalloc::NoThrow>(size);
}

will not throw. They will both call the function returned by get_new_handler.

Is it possible to separate the throwing ones in a different TU/Shim?

That is a good idea. I am not sure quite what the right default is, and what to leave in libsnmallocshim.so. I am wondering if it should be

  • libsnmallocshim.so doesn't do the C++ overrides, but we provide a second library
  • libsnmallocnew.so that does provide the C++ overrides and has the exception stuff linked in.

What do you think?

@mjp41
Copy link
Member Author

mjp41 commented Jul 4, 2025

I also have no idea how I have broken the Bazel build again.

@SchrodingerZhu
Copy link
Collaborator

libsnmallocnew.so sounds good.

mjp41 added 3 commits July 10, 2025 13:22
The new operator in the snmalloc did not throw exceptions in the case of
allocation failure.  Moreover, it was not possible to override the
behaviour of the failure of the new operator using the
std::set_new_handler function.

This commit adds the necessary code to the snmalloc new operator to
throw std::bad_alloc when the allocation fails. It also allows the
std::set_new_handler function to be used to set a custom handler for
allocation failures.
This makes most components compilable without needing exceptions.  The tails calls in the main code should mean we land on the exception path without any other snmalloc frames in the way.
@mjp41
Copy link
Member Author

mjp41 commented Jul 10, 2025

@SchrodingerZhu, so after some experiments,

  • if we statically link libstdc++,
  • dynamically link libgcc_s,

Then I believe we get valid exception behaviour, and the memory footprint does not increase. If we dynamically link libstdc++, then the memory footprint increases by 1-2Mb.

I am tempted to just make this the default going forward.

@davidchisnall pinging you, in case you have opinions on this.

@mjp41 mjp41 marked this pull request as ready for review July 11, 2025 08:35
@mjp41 mjp41 requested a review from SchrodingerZhu July 11, 2025 08:35
@mjp41
Copy link
Member Author

mjp41 commented Jul 11, 2025

@SchrodingerZhu I think this is stable now. I found a few places where the codegen was not doing tail calls, but now I have it working nicely I think. Discovering the noexcept(noexcept(<expr>)) pattern to enable templating of whether the function is noexcept or not was a massive help/relief.

Copy link
Collaborator

@SchrodingerZhu SchrodingerZhu left a comment

Choose a reason for hiding this comment

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

LGTM. I like the way how noexcept gets propagated.

@mjp41 mjp41 merged commit 452fcc4 into microsoft:main Jul 15, 2025
79 checks passed
@mjp41 mjp41 deleted the new_exceptions branch July 15, 2025 08:57
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