microsoft / microsoft/snmalloc
fallible memory allocation
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 2k
- Forks
- 138
- Avg merge
- 11h 19m
- Merged PRs (30d)
- 5
Description
Client language like rust may want to provide fallible memory allocation APIs.
https://rust-lang.github.io/rfcs/2116-alloc-me-maybe.html
It is of course not a required feature. The std library states that Alloc implementation is allowed to be implemented on top of a native allocator that aborts on allocation failure. However, it can be good is consider support fallible allocation in snmalloc.
#pragma once
#include <variant>
namespace snmalloc
{
using Unit = std::monostate;
enum class ErrorCode : size_t
{
Uninitialised = 0,
OutOfMemory = 1,
InternalLogicError = 2,
InvalidArgument = 3,
OsFailure = 4
};
struct Error
{
ErrorCode err_code;
char const* description;
};
template<typename T>
class Result
{
using ImplType = std::variant<Error, T>;
std::variant<Error, T> impl; // 0 => failed, 1 => success
static constexpr size_t SUCCESS = 1;
static constexpr size_t FAILURE = 0;
static constexpr bool EXCEPTION_STATE =
noexcept(std::construct_at(std::declval<T*>(), std::declval<T&&>()));
constexpr Result(std::variant<Error, T> x) : impl(std::move(x)) {}
public:
Result(const Result&) = default;
Result(Result&&) noexcept(EXCEPTION_STATE) = default;
static constexpr Result<T> uninitialised()
{
return ImplType(Error{ErrorCode::Uninitialised, "uninitialised result"});
}
template<typename... Args>
static constexpr Result<T> success(Args&&... args)
{
return ImplType(T{std::forward<Args>(args)...});
}
static constexpr Result<T>
failure(ErrorCode err_code, char const* description)
{
return ImplType(Error{err_code, description});
}
T unsafe_unwrap()
{
auto storage = uninitialised();
impl.swap(storage.impl);
auto* pointer = std::get_if<T>(&storage.impl);
SNMALLOC_ASSUME(pointer);
return std::move(*pointer);
}
T unwrap()
{
auto storage = uninitialised();
impl.swap(storage.impl);
if (auto* pointer = std::get_if<T>(&storage.impl))
{
return std::move(*pointer);
}
snmalloc::error("unwrap value from a failed result");
}
Error unsafe_error()
{
auto* pointer = std::get_if<Error>(&impl);
SNMALLOC_ASSUME(pointer);
return *pointer;
}
Error error()
{
if (auto* pointer = std::get_if<Error>(&impl))
{
return *pointer;
}
snmalloc::error("expecting error from a successful result");
}
T& unsafe_ref()
{
auto* pointer = std::get_if<T>(&impl);
SNMALLOC_ASSUME(pointer);
return *pointer;
}
T& ref()
{
if (auto* pointer = std::get_if<T>(&impl))
{
return *pointer;
}
snmalloc::error("referencing value from a failed result");
}
T const& unsafe_ref() const
{
auto const* pointer = std::get_if<T>(&impl);
SNMALLOC_ASSUME(pointer);
return *pointer;
}
T const& ref() const
{
if (auto const* pointer = std::get_if<T>(&impl))
{
return *pointer;
}
snmalloc::error("referencing value from a failed result");
}
template<class F>
auto map(F func)
{
using TargetType = decltype(func(unsafe_unwrap()));
if (std::get_if<T>(&impl))
{
return Result<TargetType>{func(unsafe_unwrap())};
}
// without assume, GCC will generate ud2
// another option is to use std::visit, but its codegen scares me.
auto err = std::get_if<Error>(&impl);
SNMALLOC_ASSUME(err);
return Result<TargetType>{*err};
}
template<class F>
auto flat_map(F func)
{
using TargetType = decltype(func(unsafe_unwrap()));
if (std::get_if<T>(&impl))
{
return func(unsafe_unwrap());
}
// without assume, GCC will generate ud2
// another option is to use std::visit, but its codegen scares me.
auto err = std::get_if<Error>(&impl);
SNMALLOC_ASSUME(err);
return TargetType{*err};
}
bool is_success() const
{
return SUCCESS == impl.index();
}
bool is_failure() const
{
return FAILURE == impl.index();
}
};
} // namespace snmalloc
I am now prototyping this with the structure above. We can provide sth like the following on par:
template<typename PAL>
concept ConceptPAL_get_entropy64 = requires()
{
{ PAL::get_entropy64() } -> ConceptSame<uint64_t>;
{ PAL::get_entropy64_fallible() } -> ConceptSame<Result<uint64_t>>;
};
But it may require a big refactor across the project to finally support the feature.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
No project files or tests are named. Start by reviewing the proposed Result structure and the ConceptPAL_get_entropy64 interface in the issue, then trace how allocator and PAL operations currently report failure. Done means defining an agreed fallible-allocation design and applying it consistently across the project.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, rust
- Domain
- operating-systems
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100