mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-19 00:41:43 +00:00
Merge branch 'main' into 2027
This commit is contained in:
6
.github/workflows/cmake.yml
vendored
6
.github/workflows/cmake.yml
vendored
@@ -23,8 +23,8 @@ jobs:
|
||||
- os: macOS-14
|
||||
name: macOS
|
||||
container: ""
|
||||
env: "PATH=\"/opt/homebrew/opt/protobuf@3/bin:$PATH\""
|
||||
flags: "--preset with-sccache -DCMAKE_BUILD_TYPE=Release -DWITH_EXAMPLES=ON -DCMAKE_LIBRARY_PATH=/opt/homebrew/opt/protobuf@3/lib -DProtobuf_INCLUDE_DIR=/opt/homebrew/opt/protobuf@3/include -DProtobuf_PROTOC_EXECUTABLE=/opt/homebrew/opt/protobuf@3/bin/protoc"
|
||||
env: ""
|
||||
flags: "--preset with-sccache -DCMAKE_BUILD_TYPE=Release -DWITH_EXAMPLES=ON"
|
||||
- os: windows-2022
|
||||
name: Windows
|
||||
container: ""
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
|
||||
- name: Install dependencies (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: brew install opencv protobuf@3 ninja
|
||||
run: brew install opencv protobuf@29 ninja
|
||||
|
||||
- uses: ilammy/msvc-dev-cmd@v1.13.0
|
||||
if: runner.os == 'Windows'
|
||||
|
||||
6
.github/workflows/upstream-utils.yml
vendored
6
.github/workflows/upstream-utils.yml
vendored
@@ -120,12 +120,6 @@ jobs:
|
||||
./mpack.py clone
|
||||
./mpack.py copy-src
|
||||
./mpack.py format-patch
|
||||
- name: Run memory.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./memory.py clone
|
||||
./memory.py copy-src
|
||||
./memory.py format-patch
|
||||
- name: Run protobuf.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
|
||||
As members, contributors, and leaders, we commit to fostering a community where everyone feels safe, respected, and valued. We are dedicated to ensuring that participation in this community is harassment-free, inclusive, and welcoming, regardless of age, body type, abilities (visible or invisible), ethnicity, gender identity or expression, sexual orientation, socioeconomic background, education, nationality, personal appearance, race, or religion.
|
||||
|
||||
Above all, we pledge to act with integrity, kindness, and empathy—striving to be not only good participants but also good humans.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
|
||||
@@ -48,14 +48,13 @@ apriltag apriltag/src/main/native/thirdparty/apriltag
|
||||
glfw thirdparty/imgui_suite/glfw
|
||||
Dear ImGui thirdparty/imgui_suite/imgui
|
||||
implot thirdparty/imgui_suite/implot
|
||||
memory wpiutil/src/main/native/thirdparty/memory
|
||||
nanopb wpiutil/src/main/native/thirdparty/nanopb
|
||||
protobuf wpiutil/src/main/native/thirdparty/protobuf
|
||||
mrcal wpical/src/main/native/thirdparty/mrcal
|
||||
libdogleg wpical/src/main/native/thirdparty/libdogleg
|
||||
Simd hal/src/main/native/athena/simd
|
||||
|
||||
Additionally, glfw, memory, and nanopb were all modified for use in WPILib.
|
||||
Additionally, glfw and nanopb were modified for use in WPILib.
|
||||
|
||||
==============================================================================
|
||||
Google Test License
|
||||
@@ -1331,31 +1330,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
==============
|
||||
memory License
|
||||
==============
|
||||
Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
|
||||
This software is provided 'as-is', without any express or
|
||||
implied warranty. In no event will the authors be held
|
||||
liable for any damages arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute
|
||||
it freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented;
|
||||
you must not claim that you wrote the original software.
|
||||
If you use this software in a product, an acknowledgment
|
||||
in the product documentation would be appreciated but
|
||||
is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such,
|
||||
and must not be misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any
|
||||
source distribution.
|
||||
|
||||
==============
|
||||
nanopb License
|
||||
==============
|
||||
|
||||
@@ -47,7 +47,14 @@ macro(wpilib_target_warnings target)
|
||||
# Suppress warning "enumeration types with a fixed underlying type are a
|
||||
# Clang extension"
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NOT EMSCRIPTEN)
|
||||
target_compile_options(${target} PRIVATE $<$<COMPILE_LANGUAGE:C>:-Wno-fixed-enum-extension>)
|
||||
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 20.0)
|
||||
target_compile_options(
|
||||
${target}
|
||||
PRIVATE $<$<COMPILE_LANGUAGE:C>:-Wno-fixed-enum-extension>
|
||||
)
|
||||
else()
|
||||
target_compile_options(${target} PRIVATE $<$<COMPILE_LANGUAGE:C>:-Wno-c23-extensions>)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Compress debug info with GCC
|
||||
|
||||
@@ -136,9 +136,6 @@ doxygen {
|
||||
exclude 'wpi/ordered_map.h'
|
||||
exclude 'wpi/thirdparty/**'
|
||||
|
||||
// memory
|
||||
exclude 'wpi/memory/**'
|
||||
|
||||
// mpack
|
||||
exclude 'wpi/mpack.h'
|
||||
|
||||
|
||||
@@ -30,80 +30,15 @@ static void ConvertColor(std::string_view in, ImU32* out) {
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class NTMechanismObjectModel;
|
||||
|
||||
class NTMechanismGroupImpl final {
|
||||
public:
|
||||
NTMechanismGroupImpl(nt::NetworkTableInstance inst, std::string_view path,
|
||||
std::string_view name)
|
||||
: m_inst{inst}, m_path{path}, m_name{name} {}
|
||||
|
||||
const char* GetName() const { return m_name.c_str(); }
|
||||
void ForEachObject(wpi::function_ref<void(MechanismObjectModel& model)> func);
|
||||
|
||||
void NTUpdate(const nt::Event& event, std::string_view name);
|
||||
|
||||
protected:
|
||||
nt::NetworkTableInstance m_inst;
|
||||
std::string m_path;
|
||||
std::string m_name;
|
||||
std::vector<std::unique_ptr<NTMechanismObjectModel>> m_objects;
|
||||
};
|
||||
|
||||
class NTMechanismObjectModel final : public MechanismObjectModel {
|
||||
public:
|
||||
NTMechanismObjectModel(nt::NetworkTableInstance inst, std::string_view path,
|
||||
std::string_view name)
|
||||
: m_group{inst, path, name},
|
||||
m_typeTopic{inst.GetTopic(fmt::format("{}/.type", path))},
|
||||
m_colorTopic{inst.GetTopic(fmt::format("{}/color", path))},
|
||||
m_weightTopic{inst.GetTopic(fmt::format("{}/weight", path))},
|
||||
m_angleTopic{inst.GetTopic(fmt::format("{}/angle", path))},
|
||||
m_lengthTopic{inst.GetTopic(fmt::format("{}/length", path))} {}
|
||||
|
||||
const char* GetName() const final { return m_group.GetName(); }
|
||||
void ForEachObject(
|
||||
wpi::function_ref<void(MechanismObjectModel& model)> func) final {
|
||||
m_group.ForEachObject(func);
|
||||
}
|
||||
|
||||
const char* GetType() const final { return m_typeValue.c_str(); }
|
||||
ImU32 GetColor() const final { return m_colorValue; }
|
||||
double GetWeight() const final { return m_weightValue; }
|
||||
frc::Rotation2d GetAngle() const final { return m_angleValue; }
|
||||
units::meter_t GetLength() const final { return m_lengthValue; }
|
||||
|
||||
bool NTUpdate(const nt::Event& event, std::string_view name);
|
||||
|
||||
private:
|
||||
NTMechanismGroupImpl m_group;
|
||||
|
||||
nt::Topic m_typeTopic;
|
||||
nt::Topic m_colorTopic;
|
||||
nt::Topic m_weightTopic;
|
||||
nt::Topic m_angleTopic;
|
||||
nt::Topic m_lengthTopic;
|
||||
|
||||
std::string m_typeValue;
|
||||
ImU32 m_colorValue = IM_COL32_WHITE;
|
||||
double m_weightValue = 1.0;
|
||||
frc::Rotation2d m_angleValue;
|
||||
units::meter_t m_lengthValue = 0.0_m;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void NTMechanismGroupImpl::ForEachObject(
|
||||
void NTMechanism2DModel::NTMechanismGroupImpl::ForEachObject(
|
||||
wpi::function_ref<void(MechanismObjectModel& model)> func) {
|
||||
for (auto&& obj : m_objects) {
|
||||
func(*obj);
|
||||
}
|
||||
}
|
||||
|
||||
void NTMechanismGroupImpl::NTUpdate(const nt::Event& event,
|
||||
std::string_view name) {
|
||||
void NTMechanism2DModel::NTMechanismGroupImpl::NTUpdate(const nt::Event& event,
|
||||
std::string_view name) {
|
||||
if (name.empty()) {
|
||||
return;
|
||||
}
|
||||
@@ -140,8 +75,8 @@ void NTMechanismGroupImpl::NTUpdate(const nt::Event& event,
|
||||
}
|
||||
}
|
||||
|
||||
bool NTMechanismObjectModel::NTUpdate(const nt::Event& event,
|
||||
std::string_view childName) {
|
||||
bool NTMechanism2DModel::NTMechanismObjectModel::NTUpdate(
|
||||
const nt::Event& event, std::string_view childName) {
|
||||
if (auto info = event.GetTopicInfo()) {
|
||||
if (info->topic == m_typeTopic.GetHandle()) {
|
||||
if (event.flags & nt::EventFlags::kUnpublish) {
|
||||
@@ -181,31 +116,6 @@ bool NTMechanismObjectModel::NTUpdate(const nt::Event& event,
|
||||
return false;
|
||||
}
|
||||
|
||||
class NTMechanism2DModel::RootModel final : public MechanismRootModel {
|
||||
public:
|
||||
RootModel(nt::NetworkTableInstance inst, std::string_view path,
|
||||
std::string_view name)
|
||||
: m_group{inst, path, name},
|
||||
m_xTopic{inst.GetTopic(fmt::format("{}/x", path))},
|
||||
m_yTopic{inst.GetTopic(fmt::format("{}/y", path))} {}
|
||||
|
||||
const char* GetName() const final { return m_group.GetName(); }
|
||||
void ForEachObject(
|
||||
wpi::function_ref<void(MechanismObjectModel& model)> func) final {
|
||||
m_group.ForEachObject(func);
|
||||
}
|
||||
|
||||
bool NTUpdate(const nt::Event& event, std::string_view childName);
|
||||
|
||||
frc::Translation2d GetPosition() const override { return m_pos; };
|
||||
|
||||
private:
|
||||
NTMechanismGroupImpl m_group;
|
||||
nt::Topic m_xTopic;
|
||||
nt::Topic m_yTopic;
|
||||
frc::Translation2d m_pos;
|
||||
};
|
||||
|
||||
bool NTMechanism2DModel::RootModel::NTUpdate(const nt::Event& event,
|
||||
std::string_view childName) {
|
||||
if (auto info = event.GetTopicInfo()) {
|
||||
|
||||
@@ -1448,7 +1448,7 @@ static void EmitEntryValueEditable(NetworkTablesModel* model,
|
||||
}
|
||||
case NT_INTEGER: {
|
||||
int64_t v = val.GetInteger();
|
||||
if (InputExpr<int64_t>(typeStr, &v, "%d",
|
||||
if (InputExpr<int64_t>(typeStr, &v, "%" PRId64,
|
||||
ImGuiInputTextFlags_EnterReturnsTrue)) {
|
||||
if (entry.publisher == 0) {
|
||||
entry.publisher = nt::Publish(entry.info.topic, NT_INTEGER, "int");
|
||||
|
||||
@@ -55,7 +55,92 @@ class NTMechanism2DModel : public Mechanism2DModel {
|
||||
frc::Translation2d m_dimensionsValue;
|
||||
ImU32 m_bgColorValue = 0;
|
||||
|
||||
class RootModel;
|
||||
class NTMechanismObjectModel;
|
||||
class NTMechanismGroupImpl final {
|
||||
public:
|
||||
NTMechanismGroupImpl(nt::NetworkTableInstance inst, std::string_view path,
|
||||
std::string_view name)
|
||||
: m_inst{inst}, m_path{path}, m_name{name} {}
|
||||
|
||||
const char* GetName() const { return m_name.c_str(); }
|
||||
void ForEachObject(
|
||||
wpi::function_ref<void(MechanismObjectModel& model)> func);
|
||||
|
||||
void NTUpdate(const nt::Event& event, std::string_view name);
|
||||
|
||||
protected:
|
||||
nt::NetworkTableInstance m_inst;
|
||||
std::string m_path;
|
||||
std::string m_name;
|
||||
std::vector<std::unique_ptr<NTMechanismObjectModel>> m_objects;
|
||||
};
|
||||
|
||||
class NTMechanismObjectModel final : public MechanismObjectModel {
|
||||
public:
|
||||
NTMechanismObjectModel(nt::NetworkTableInstance inst, std::string_view path,
|
||||
std::string_view name)
|
||||
: m_group{inst, path, name},
|
||||
m_typeTopic{inst.GetTopic(fmt::format("{}/.type", path))},
|
||||
m_colorTopic{inst.GetTopic(fmt::format("{}/color", path))},
|
||||
m_weightTopic{inst.GetTopic(fmt::format("{}/weight", path))},
|
||||
m_angleTopic{inst.GetTopic(fmt::format("{}/angle", path))},
|
||||
m_lengthTopic{inst.GetTopic(fmt::format("{}/length", path))} {}
|
||||
|
||||
const char* GetName() const final { return m_group.GetName(); }
|
||||
void ForEachObject(
|
||||
wpi::function_ref<void(MechanismObjectModel& model)> func) final {
|
||||
m_group.ForEachObject(func);
|
||||
}
|
||||
|
||||
const char* GetType() const final { return m_typeValue.c_str(); }
|
||||
ImU32 GetColor() const final { return m_colorValue; }
|
||||
double GetWeight() const final { return m_weightValue; }
|
||||
frc::Rotation2d GetAngle() const final { return m_angleValue; }
|
||||
units::meter_t GetLength() const final { return m_lengthValue; }
|
||||
|
||||
bool NTUpdate(const nt::Event& event, std::string_view name);
|
||||
|
||||
private:
|
||||
NTMechanismGroupImpl m_group;
|
||||
|
||||
nt::Topic m_typeTopic;
|
||||
nt::Topic m_colorTopic;
|
||||
nt::Topic m_weightTopic;
|
||||
nt::Topic m_angleTopic;
|
||||
nt::Topic m_lengthTopic;
|
||||
|
||||
std::string m_typeValue;
|
||||
ImU32 m_colorValue = IM_COL32_WHITE;
|
||||
double m_weightValue = 1.0;
|
||||
frc::Rotation2d m_angleValue;
|
||||
units::meter_t m_lengthValue = 0.0_m;
|
||||
};
|
||||
|
||||
class RootModel final : public MechanismRootModel {
|
||||
public:
|
||||
RootModel(nt::NetworkTableInstance inst, std::string_view path,
|
||||
std::string_view name)
|
||||
: m_group{inst, path, name},
|
||||
m_xTopic{inst.GetTopic(fmt::format("{}/x", path))},
|
||||
m_yTopic{inst.GetTopic(fmt::format("{}/y", path))} {}
|
||||
|
||||
const char* GetName() const final { return m_group.GetName(); }
|
||||
void ForEachObject(
|
||||
wpi::function_ref<void(MechanismObjectModel& model)> func) final {
|
||||
m_group.ForEachObject(func);
|
||||
}
|
||||
|
||||
bool NTUpdate(const nt::Event& event, std::string_view childName);
|
||||
|
||||
frc::Translation2d GetPosition() const override { return m_pos; };
|
||||
|
||||
private:
|
||||
NTMechanismGroupImpl m_group;
|
||||
nt::Topic m_xTopic;
|
||||
nt::Topic m_yTopic;
|
||||
frc::Translation2d m_pos;
|
||||
};
|
||||
|
||||
std::vector<std::unique_ptr<RootModel>> m_roots;
|
||||
};
|
||||
|
||||
|
||||
@@ -163,6 +163,11 @@ Clients may publish a value at any time following clock synchronization. Client
|
||||
|
||||
Clients should subscribe to the `$sub$<topic>` meta topic for each topic published and use this metadata to determine how frequently to send updates to the network. However, this is not required--clients may choose to ignore this and send updates at any time.
|
||||
|
||||
[[ids]]
|
||||
=== IDs and UIDs
|
||||
|
||||
IDs and UIDs should be selected by clients and servers to be as small as possible for transmission efficiency. Clients and servers should support a signed 32-bit range for IDs and UIDs, but exceptions can be made for implementation reasons. Clients and servers must ignore messages with unsupported IDs and UIDs, and should report a diagnostic if this occurs.
|
||||
|
||||
[[meta-topics]]
|
||||
=== Server-Published Meta Topics
|
||||
|
||||
|
||||
@@ -174,6 +174,12 @@ static bool WireDecodeTextImpl(std::string_view in, T& out,
|
||||
goto err;
|
||||
}
|
||||
|
||||
// limit to 32-bit range and exclude endpoints used by DenseMap
|
||||
if (pubuid >= 0x7fffffffLL || pubuid <= (-0x7fffffffLL - 1)) {
|
||||
error = "pubuid out of range";
|
||||
goto err;
|
||||
}
|
||||
|
||||
// properties; allow missing (treated as empty)
|
||||
wpi::json* properties = nullptr;
|
||||
auto propertiesIt = params->find("properties");
|
||||
@@ -200,6 +206,12 @@ static bool WireDecodeTextImpl(std::string_view in, T& out,
|
||||
goto err;
|
||||
}
|
||||
|
||||
// limit to 32-bit range and exclude endpoints used by DenseMap
|
||||
if (pubuid >= 0x7fffffffLL || pubuid <= (-0x7fffffffLL - 1)) {
|
||||
error = "pubuid out of range";
|
||||
goto err;
|
||||
}
|
||||
|
||||
// complete
|
||||
out.ClientUnpublish(pubuid);
|
||||
rv = true;
|
||||
@@ -231,6 +243,12 @@ static bool WireDecodeTextImpl(std::string_view in, T& out,
|
||||
goto err;
|
||||
}
|
||||
|
||||
// limit to 32-bit range and exclude endpoints used by DenseMap
|
||||
if (subuid >= 0x7fffffffLL || subuid <= (-0x7fffffffLL - 1)) {
|
||||
error = "subuid out of range";
|
||||
goto err;
|
||||
}
|
||||
|
||||
// options
|
||||
PubSubOptionsImpl options;
|
||||
auto optionsIt = params->find("options");
|
||||
@@ -303,6 +321,12 @@ static bool WireDecodeTextImpl(std::string_view in, T& out,
|
||||
goto err;
|
||||
}
|
||||
|
||||
// limit to 32-bit range and exclude endpoints used by DenseMap
|
||||
if (subuid >= 0x7fffffffLL || subuid <= (-0x7fffffffLL - 1)) {
|
||||
error = "pubuid out of range";
|
||||
goto err;
|
||||
}
|
||||
|
||||
// complete
|
||||
out.ClientUnsubscribe(subuid);
|
||||
rv = true;
|
||||
@@ -324,6 +348,12 @@ static bool WireDecodeTextImpl(std::string_view in, T& out,
|
||||
goto err;
|
||||
}
|
||||
|
||||
// limit to 32-bit range and exclude endpoints used by DenseMap
|
||||
if (id >= 0x7fffffffLL || id <= (-0x7fffffffLL - 1)) {
|
||||
error = "id out of range";
|
||||
goto err;
|
||||
}
|
||||
|
||||
// type
|
||||
auto typeStr = ObjGetString(*params, "type", &error);
|
||||
if (!typeStr) {
|
||||
@@ -339,6 +369,13 @@ static bool WireDecodeTextImpl(std::string_view in, T& out,
|
||||
error = "pubuid value must be a number";
|
||||
goto err;
|
||||
}
|
||||
|
||||
// limit to 32-bit range and exclude endpoints used by DenseMap
|
||||
if (val >= 0x7fffffffLL || val <= (-0x7fffffffLL - 1)) {
|
||||
error = "pubuid out of range";
|
||||
goto err;
|
||||
}
|
||||
|
||||
pubuid = val;
|
||||
}
|
||||
|
||||
@@ -369,6 +406,12 @@ static bool WireDecodeTextImpl(std::string_view in, T& out,
|
||||
goto err;
|
||||
}
|
||||
|
||||
// limit to 32-bit range and exclude endpoints used by DenseMap
|
||||
if (id >= 0x7fffffffLL || id <= (-0x7fffffffLL - 1)) {
|
||||
error = "id out of range";
|
||||
goto err;
|
||||
}
|
||||
|
||||
// complete
|
||||
out.ServerUnannounce(*name, id);
|
||||
} else if (*method == PropertiesUpdateMsg::kMethodStr) {
|
||||
|
||||
@@ -423,4 +423,20 @@ TEST_F(ServerImplTest, ZeroTimestampNegativeTime) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ServerImplTest, InvalidPubUid) {
|
||||
EXPECT_CALL(logger, Call(_, _, _, "0: pubuid out of range"));
|
||||
server.SetLocal(&local, &queue);
|
||||
|
||||
// connect client
|
||||
::testing::StrictMock<net::MockWireConnection> wire;
|
||||
MockSetPeriodicFunc setPeriodic;
|
||||
auto [name, id] = server.AddClient("test", "connInfo", false, wire,
|
||||
setPeriodic.AsStdFunction());
|
||||
|
||||
server.ProcessIncomingText(
|
||||
id,
|
||||
"[{\"method\":\"publish\",\"params\":{\"type\":\"string\",\"name\":"
|
||||
"\"myvalue\",\"pubuid\":2147483647,\"properties\":{}}}]");
|
||||
}
|
||||
|
||||
} // namespace nt
|
||||
|
||||
@@ -287,5 +287,6 @@ module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
|
||||
</module>
|
||||
<module name="CommentsIndentation" />
|
||||
<module name="PackageDeclaration" />
|
||||
<module name="MissingDeprecated"/>
|
||||
</module>
|
||||
</module>
|
||||
|
||||
@@ -208,8 +208,6 @@ class Analyzer : public glass::View {
|
||||
std::string m_exception;
|
||||
std::vector<std::string> m_missingTests;
|
||||
|
||||
bool m_calcDefaults = false;
|
||||
|
||||
// This is true if the error popup needs to be displayed
|
||||
bool m_errorPopup = false;
|
||||
|
||||
@@ -235,12 +233,8 @@ class Analyzer : public glass::View {
|
||||
// Data analysis
|
||||
std::unique_ptr<AnalysisManager> m_manager;
|
||||
int m_dataset = 0;
|
||||
int m_window = 8;
|
||||
double m_threshold = 0.2;
|
||||
float m_stepTestDuration = 0;
|
||||
|
||||
bool combinedGraphFit = false;
|
||||
|
||||
// Logger
|
||||
wpi::Logger& m_logger;
|
||||
|
||||
|
||||
4
thirdparty/imgui_suite/CMakeLists.txt
vendored
4
thirdparty/imgui_suite/CMakeLists.txt
vendored
@@ -53,5 +53,9 @@ target_include_directories(
|
||||
|
||||
target_compile_features(imgui PUBLIC cxx_std_20)
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 20.0)
|
||||
target_compile_options(imgui PUBLIC -Wno-nontrivial-memcall)
|
||||
endif()
|
||||
|
||||
install(TARGETS imgui EXPORT imgui)
|
||||
export(TARGETS imgui FILE imgui.cmake NAMESPACE imgui::)
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
from upstream_utils import Lib, walk_cwd_and_copy_if
|
||||
|
||||
|
||||
def run_source_replacements(memory_files: list[Path]):
|
||||
for wpi_file in memory_files:
|
||||
with open(wpi_file) as f:
|
||||
content = f.read()
|
||||
|
||||
# Fix #includes
|
||||
content = content.replace('include "', 'include "wpi/memory/')
|
||||
content = content.replace(
|
||||
"wpi/memory/free_list_utils.hpp", "free_list_utils.hpp"
|
||||
)
|
||||
|
||||
with open(wpi_file, "w") as f:
|
||||
f.write(content)
|
||||
|
||||
|
||||
def run_header_replacements(memory_files: list[Path]):
|
||||
for wpi_file in memory_files:
|
||||
if "detail" not in wpi_file.parts:
|
||||
continue
|
||||
with open(wpi_file) as f:
|
||||
content = f.read()
|
||||
|
||||
# Fix #includes
|
||||
content = content.replace('include "config.hpp', 'include "../config.hpp')
|
||||
|
||||
with open(wpi_file, "w") as f:
|
||||
f.write(content)
|
||||
|
||||
|
||||
def run_global_replacements(memory_files: list[Path]):
|
||||
for wpi_file in memory_files:
|
||||
with open(wpi_file) as f:
|
||||
content = f.read()
|
||||
|
||||
# Rename namespace from foonathan to wpi
|
||||
content = content.replace("namespace foonathan", "namespace wpi")
|
||||
content = content.replace("foonathan::", "wpi::")
|
||||
content = content.replace("FOONATHAN_", "WPI_")
|
||||
|
||||
# Fix #includes
|
||||
content = content.replace('include "foonathan', 'include "wpi')
|
||||
|
||||
with open(wpi_file, "w") as f:
|
||||
f.write(content)
|
||||
|
||||
|
||||
def copy_upstream_src(wpilib_root: Path):
|
||||
upstream_root = Path(".").absolute()
|
||||
wpiutil = wpilib_root / "wpiutil"
|
||||
|
||||
# Delete old install
|
||||
for d in [
|
||||
"src/main/native/thirdparty/memory/src",
|
||||
"src/main/native/thirdparty/memory/include",
|
||||
]:
|
||||
shutil.rmtree(wpiutil / d, ignore_errors=True)
|
||||
|
||||
# Copy sources
|
||||
os.chdir(upstream_root / "src")
|
||||
src_files = walk_cwd_and_copy_if(
|
||||
lambda dp, f: f.endswith(".cpp") or f.endswith(".hpp"),
|
||||
wpiutil / "src/main/native/thirdparty/memory/src",
|
||||
)
|
||||
run_global_replacements(src_files)
|
||||
run_source_replacements(src_files)
|
||||
|
||||
# Copy headers
|
||||
os.chdir(upstream_root / "include/foonathan")
|
||||
include_files = walk_cwd_and_copy_if(
|
||||
lambda dp, f: f.endswith(".hpp"),
|
||||
wpiutil / "src/main/native/thirdparty/memory/include/wpi",
|
||||
)
|
||||
os.chdir(upstream_root)
|
||||
run_global_replacements(include_files)
|
||||
run_header_replacements(include_files)
|
||||
|
||||
# Copy config_impl.hpp
|
||||
shutil.copyfile(
|
||||
wpilib_root / "upstream_utils/memory_files/config_impl.hpp",
|
||||
wpiutil
|
||||
/ "src/main/native/thirdparty/memory/include/wpi/memory/config_impl.hpp",
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
name = "memory"
|
||||
url = "https://github.com/foonathan/memory"
|
||||
tag = "v0.7-3"
|
||||
|
||||
memory = Lib(name, url, tag, copy_upstream_src)
|
||||
memory.main()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,34 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
// Copyright (C) 2015-2020 Jonathan Müller <jonathanmueller.dev@gmail.com>
|
||||
// This file is subject to the license terms in the LICENSE file
|
||||
// found in the top-level directory of this distribution.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
//=== options ===//
|
||||
#define WPI_MEMORY_CHECK_ALLOCATION_SIZE 1
|
||||
#define WPI_MEMORY_IMPL_DEFAULT_ALLOCATOR heap_allocator
|
||||
#ifdef NDEBUG
|
||||
#define WPI_MEMORY_DEBUG_ASSERT 0
|
||||
#define WPI_MEMORY_DEBUG_FILL 0
|
||||
#define WPI_MEMORY_DEBUG_FENCE 0
|
||||
#define WPI_MEMORY_DEBUG_LEAK_CHECK 0
|
||||
#define WPI_MEMORY_DEBUG_POINTER_CHECK 0
|
||||
#define WPI_MEMORY_DEBUG_DOUBLE_DEALLOC_CHECK 0
|
||||
#else
|
||||
#define WPI_MEMORY_DEBUG_ASSERT 1
|
||||
#define WPI_MEMORY_DEBUG_FILL 1
|
||||
#define WPI_MEMORY_DEBUG_FENCE 8
|
||||
#define WPI_MEMORY_DEBUG_LEAK_CHECK 1
|
||||
#define WPI_MEMORY_DEBUG_POINTER_CHECK 1
|
||||
#define WPI_MEMORY_DEBUG_DOUBLE_DEALLOC_CHECK 1
|
||||
#endif
|
||||
#define WPI_MEMORY_EXTERN_TEMPLATE 1
|
||||
#define WPI_MEMORY_TEMPORARY_STACK_MODE 2
|
||||
|
||||
#define WPI_MEMORY_NO_NODE_SIZE 1
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,52 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Tyler Veness <calcmogul@gmail.com>
|
||||
Date: Tue, 8 Apr 2025 15:30:06 -0700
|
||||
Subject: [PATCH 3/3] Fix deprecation warning for UDLs
|
||||
|
||||
---
|
||||
include/foonathan/memory/memory_arena.hpp | 12 ++++++------
|
||||
1 file changed, 6 insertions(+), 6 deletions(-)
|
||||
|
||||
diff --git a/include/foonathan/memory/memory_arena.hpp b/include/foonathan/memory/memory_arena.hpp
|
||||
index eb969a677329b5b2d536f39f9a15817f040cf79f..d91f2a58cef56278cdb091daf34cebba7ec5b92c 100644
|
||||
--- a/include/foonathan/memory/memory_arena.hpp
|
||||
+++ b/include/foonathan/memory/memory_arena.hpp
|
||||
@@ -656,32 +656,32 @@ namespace foonathan
|
||||
/// \returns The number of bytes `value` is in the given unit.
|
||||
/// \ingroup memory_core
|
||||
/// @{
|
||||
- constexpr std::size_t operator"" _KiB(unsigned long long value) noexcept
|
||||
+ constexpr std::size_t operator""_KiB(unsigned long long value) noexcept
|
||||
{
|
||||
return std::size_t(value * 1024);
|
||||
}
|
||||
|
||||
- constexpr std::size_t operator"" _KB(unsigned long long value) noexcept
|
||||
+ constexpr std::size_t operator""_KB(unsigned long long value) noexcept
|
||||
{
|
||||
return std::size_t(value * 1000);
|
||||
}
|
||||
|
||||
- constexpr std::size_t operator"" _MiB(unsigned long long value) noexcept
|
||||
+ constexpr std::size_t operator""_MiB(unsigned long long value) noexcept
|
||||
{
|
||||
return std::size_t(value * 1024 * 1024);
|
||||
}
|
||||
|
||||
- constexpr std::size_t operator"" _MB(unsigned long long value) noexcept
|
||||
+ constexpr std::size_t operator""_MB(unsigned long long value) noexcept
|
||||
{
|
||||
return std::size_t(value * 1000 * 1000);
|
||||
}
|
||||
|
||||
- constexpr std::size_t operator"" _GiB(unsigned long long value) noexcept
|
||||
+ constexpr std::size_t operator""_GiB(unsigned long long value) noexcept
|
||||
{
|
||||
return std::size_t(value * 1024 * 1024 * 1024);
|
||||
}
|
||||
|
||||
- constexpr std::size_t operator"" _GB(unsigned long long value) noexcept
|
||||
+ constexpr std::size_t operator""_GB(unsigned long long value) noexcept
|
||||
{
|
||||
return std::size_t(value * 1000 * 1000 * 1000);
|
||||
}
|
||||
@@ -172,6 +172,8 @@ public class MecanumControllerCommand extends Command {
|
||||
* @param outputDriveVoltages A MecanumDriveMotorVoltages object containing the output motor
|
||||
* voltages.
|
||||
* @param requirements The subsystems to require.
|
||||
* @deprecated Use {@link MecanumVoltagesConsumer} instead of {@code
|
||||
* Consumer<MecanumDriveMotorVoltages}.
|
||||
*/
|
||||
@Deprecated(since = "2025", forRemoval = true)
|
||||
public MecanumControllerCommand(
|
||||
@@ -307,6 +309,8 @@ public class MecanumControllerCommand extends Command {
|
||||
* @param outputDriveVoltages A MecanumDriveMotorVoltages object containing the output motor
|
||||
* voltages.
|
||||
* @param requirements The subsystems to require.
|
||||
* @deprecated Use {@link MecanumVoltagesConsumer} instead of {@code
|
||||
* Consumer<MecanumDriveMotorVoltages>}.
|
||||
*/
|
||||
@Deprecated(since = "2025", forRemoval = true)
|
||||
public MecanumControllerCommand(
|
||||
|
||||
@@ -112,8 +112,8 @@ DifferentialDrive::WheelSpeeds DifferentialDrive::ArcadeDriveIK(
|
||||
// Square the inputs (while preserving the sign) to increase fine control
|
||||
// while permitting full power.
|
||||
if (squareInputs) {
|
||||
xSpeed = std::copysign(xSpeed * xSpeed, xSpeed);
|
||||
zRotation = std::copysign(zRotation * zRotation, zRotation);
|
||||
xSpeed = CopySignPow(xSpeed, 2);
|
||||
zRotation = CopySignPow(zRotation, 2);
|
||||
}
|
||||
|
||||
double leftSpeed = xSpeed - zRotation;
|
||||
@@ -167,8 +167,8 @@ DifferentialDrive::WheelSpeeds DifferentialDrive::TankDriveIK(
|
||||
// Square the inputs (while preserving the sign) to increase fine control
|
||||
// while permitting full power.
|
||||
if (squareInputs) {
|
||||
leftSpeed = std::copysign(leftSpeed * leftSpeed, leftSpeed);
|
||||
rightSpeed = std::copysign(rightSpeed * rightSpeed, rightSpeed);
|
||||
leftSpeed = CopySignPow(leftSpeed, 2);
|
||||
rightSpeed = CopySignPow(rightSpeed, 2);
|
||||
}
|
||||
|
||||
return {leftSpeed, rightSpeed};
|
||||
|
||||
@@ -102,7 +102,7 @@ class Notifier {
|
||||
* The user-provided callback should be written so that it completes before
|
||||
* the next time it's scheduled to run.
|
||||
*
|
||||
* @param period Period after which to to call the callback starting one
|
||||
* @param period Period after which to call the callback starting one
|
||||
* period after the call to this method.
|
||||
*/
|
||||
void StartPeriodic(units::second_t period);
|
||||
|
||||
@@ -192,8 +192,8 @@ public class Notifier implements AutoCloseable {
|
||||
* <p>The user-provided callback should be written so that it completes before the next time it's
|
||||
* scheduled to run.
|
||||
*
|
||||
* @param period Period in seconds after which to to call the callback starting one period after
|
||||
* the call to this method.
|
||||
* @param period Period in seconds after which to call the callback starting one period after the
|
||||
* call to this method.
|
||||
*/
|
||||
public void startPeriodic(double period) {
|
||||
m_processLock.lock();
|
||||
|
||||
@@ -96,6 +96,15 @@ public final class Preferences {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the network table used for preferences entries.
|
||||
*
|
||||
* @return the network table used for preferences entries
|
||||
*/
|
||||
public static NetworkTable getNetworkTable() {
|
||||
return m_table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the preferences keys.
|
||||
*
|
||||
|
||||
@@ -260,8 +260,8 @@ public class DifferentialDrive extends RobotDriveBase implements Sendable, AutoC
|
||||
// Square the inputs (while preserving the sign) to increase fine control
|
||||
// while permitting full power.
|
||||
if (squareInputs) {
|
||||
xSpeed = Math.copySign(xSpeed * xSpeed, xSpeed);
|
||||
zRotation = Math.copySign(zRotation * zRotation, zRotation);
|
||||
xSpeed = MathUtil.copySignPow(xSpeed, 2);
|
||||
zRotation = MathUtil.copySignPow(zRotation, 2);
|
||||
}
|
||||
|
||||
double leftSpeed = xSpeed - zRotation;
|
||||
@@ -335,8 +335,8 @@ public class DifferentialDrive extends RobotDriveBase implements Sendable, AutoC
|
||||
// Square the inputs (while preserving the sign) to increase fine control
|
||||
// while permitting full power.
|
||||
if (squareInputs) {
|
||||
leftSpeed = Math.copySign(leftSpeed * leftSpeed, leftSpeed);
|
||||
rightSpeed = Math.copySign(rightSpeed * rightSpeed, rightSpeed);
|
||||
leftSpeed = MathUtil.copySignPow(leftSpeed, 2);
|
||||
rightSpeed = MathUtil.copySignPow(rightSpeed, 2);
|
||||
}
|
||||
|
||||
return new WheelSpeeds(leftSpeed, rightSpeed);
|
||||
|
||||
@@ -91,6 +91,13 @@ class PreferencesTest {
|
||||
"Preferences was not empty! Preferences in table: " + Arrays.toString(keys.toArray()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getNetworkTableTest() {
|
||||
NetworkTable networkTable = Preferences.getNetworkTable();
|
||||
|
||||
assertEquals(m_table, networkTable);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("defaultKeyProvider")
|
||||
void defaultKeysTest(String key) {
|
||||
|
||||
@@ -107,6 +107,42 @@ public final class MathUtil {
|
||||
return applyDeadband(value, deadband, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Raises the input to the power of the given exponent while preserving its sign.
|
||||
*
|
||||
* <p>The function normalizes the input value to the range [0, 1] based on the maximum magnitude,
|
||||
* raises it to the power of the exponent, then scales the result back to the original range and
|
||||
* copying the sign. This keeps the value in the original range and gives consistent curve
|
||||
* behavior regardless of the input value's scale.
|
||||
*
|
||||
* <p>This is useful for applying smoother or more aggressive control response curves (e.g.
|
||||
* joystick input shaping).
|
||||
*
|
||||
* @param value The input value to transform.
|
||||
* @param exponent The exponent to apply (e.g. 1.0 = linear, 2.0 = squared curve). Must be
|
||||
* positive.
|
||||
* @param maxMagnitude The maximum expected absolute value of input. Must be positive.
|
||||
* @return The transformed value with the same sign and scaled to the input range.
|
||||
*/
|
||||
public static double copySignPow(double value, double exponent, double maxMagnitude) {
|
||||
return Math.copySign(Math.pow(Math.abs(value) / maxMagnitude, exponent), value) * maxMagnitude;
|
||||
}
|
||||
|
||||
/**
|
||||
* Raises the input to the power of the given exponent while preserving its sign.
|
||||
*
|
||||
* <p>This is useful for applying smoother or more aggressive control response curves (e.g.
|
||||
* joystick input shaping).
|
||||
*
|
||||
* @param value The input value to transform.
|
||||
* @param exponent The exponent to apply (e.g. 1.0 = linear, 2.0 = squared curve). Must be
|
||||
* positive.
|
||||
* @return The transformed value with the same sign.
|
||||
*/
|
||||
public static double copySignPow(double value, double exponent) {
|
||||
return copySignPow(value, exponent, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns modulus of input.
|
||||
*
|
||||
|
||||
@@ -189,6 +189,7 @@ public class ArmFeedforward implements ProtobufSerializable, StructSerializable
|
||||
* @param currentVelocity The current velocity setpoint in radians per second.
|
||||
* @param nextVelocity The next velocity setpoint in radians per second.
|
||||
* @return The computed feedforward in volts.
|
||||
* @deprecated Use {@link #calculateWithVelocities(double, double, double)} instead.
|
||||
*/
|
||||
public double calculate(double currentAngle, double currentVelocity, double nextVelocity) {
|
||||
return ArmFeedforwardJNI.calculate(
|
||||
|
||||
@@ -20,9 +20,9 @@ import edu.wpi.first.math.numbers.N3;
|
||||
import edu.wpi.first.units.measure.Distance;
|
||||
import edu.wpi.first.util.protobuf.ProtobufSerializable;
|
||||
import edu.wpi.first.util.struct.StructSerializable;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/** Represents a 2D pose containing translational and rotational elements. */
|
||||
@@ -347,13 +347,13 @@ public class Pose2d implements Interpolatable<Pose2d>, ProtobufSerializable, Str
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the nearest Pose2d from a list of poses. If two or more poses in the list have the same
|
||||
* distance from this pose, return the one with the closest rotation component.
|
||||
* Returns the nearest Pose2d from a collection of poses. If two or more poses in the collection
|
||||
* have the same distance from this pose, return the one with the closest rotation component.
|
||||
*
|
||||
* @param poses The list of poses to find the nearest.
|
||||
* @return The nearest Pose2d from the list.
|
||||
* @param poses The collection of poses to find the nearest.
|
||||
* @return The nearest Pose2d from the collection.
|
||||
*/
|
||||
public Pose2d nearest(List<Pose2d> poses) {
|
||||
public Pose2d nearest(Collection<Pose2d> poses) {
|
||||
return Collections.min(
|
||||
poses,
|
||||
Comparator.comparing(
|
||||
|
||||
@@ -21,9 +21,9 @@ import edu.wpi.first.math.numbers.N4;
|
||||
import edu.wpi.first.units.measure.Distance;
|
||||
import edu.wpi.first.util.protobuf.ProtobufSerializable;
|
||||
import edu.wpi.first.util.struct.StructSerializable;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/** Represents a 3D pose containing translational and rotational elements. */
|
||||
@@ -400,13 +400,13 @@ public class Pose3d implements Interpolatable<Pose3d>, ProtobufSerializable, Str
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the nearest Pose3d from a list of poses. If two or more poses in the list have the same
|
||||
* distance from this pose, return the one with the closest rotation component.
|
||||
* Returns the nearest Pose3d from a collection of poses. If two or more poses in the collection
|
||||
* have the same distance from this pose, return the one with the closest rotation component.
|
||||
*
|
||||
* @param poses The list of poses to find the nearest.
|
||||
* @return The nearest Pose3d from the list.
|
||||
* @param poses The collection of poses to find the nearest.
|
||||
* @return The nearest Pose3d from the collection.
|
||||
*/
|
||||
public Pose3d nearest(List<Pose3d> poses) {
|
||||
public Pose3d nearest(Collection<Pose3d> poses) {
|
||||
return Collections.min(
|
||||
poses,
|
||||
Comparator.comparing(
|
||||
|
||||
@@ -20,9 +20,9 @@ import edu.wpi.first.math.numbers.N2;
|
||||
import edu.wpi.first.units.measure.Distance;
|
||||
import edu.wpi.first.util.protobuf.ProtobufSerializable;
|
||||
import edu.wpi.first.util.struct.StructSerializable;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
@@ -273,12 +273,12 @@ public class Translation2d
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the nearest Translation2d from a list of translations.
|
||||
* Returns the nearest Translation2d from a collection of translations.
|
||||
*
|
||||
* @param translations The list of translations.
|
||||
* @return The nearest Translation2d from the list.
|
||||
* @param translations The collection of translations.
|
||||
* @return The nearest Translation2d from the collection.
|
||||
*/
|
||||
public Translation2d nearest(List<Translation2d> translations) {
|
||||
public Translation2d nearest(Collection<Translation2d> translations) {
|
||||
return Collections.min(translations, Comparator.comparing(this::getDistance));
|
||||
}
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@ import edu.wpi.first.math.numbers.N3;
|
||||
import edu.wpi.first.units.measure.Distance;
|
||||
import edu.wpi.first.util.protobuf.ProtobufSerializable;
|
||||
import edu.wpi.first.util.struct.StructSerializable;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
@@ -305,7 +305,7 @@ public class Translation3d
|
||||
* @param translations The collection of translations to find the nearest.
|
||||
* @return The nearest Translation3d from the collection.
|
||||
*/
|
||||
public Translation3d nearest(List<Translation3d> translations) {
|
||||
public Translation3d nearest(Collection<Translation3d> translations) {
|
||||
return Collections.min(translations, Comparator.comparing(this::getDistance));
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,12 @@
|
||||
|
||||
package edu.wpi.first.math.kinematics;
|
||||
|
||||
/** Represents the motor voltages for a mecanum drive drivetrain. */
|
||||
/**
|
||||
* Represents the motor voltages for a mecanum drive drivetrain.
|
||||
*
|
||||
* @deprecated Use {@link
|
||||
* edu.wpi.first.wpilibj2.command.MecanumControllerCommand.MecanumVoltagesConsumer}
|
||||
*/
|
||||
@Deprecated(since = "2025", forRemoval = true)
|
||||
public class MecanumDriveMotorVoltages {
|
||||
/** Voltage of the front left motor. */
|
||||
|
||||
@@ -59,10 +59,14 @@ public class TrapezoidProfile {
|
||||
/**
|
||||
* Constructs constraints for a TrapezoidProfile.
|
||||
*
|
||||
* @param maxVelocity maximum velocity
|
||||
* @param maxAcceleration maximum acceleration
|
||||
* @param maxVelocity Maximum velocity, must be non-negative.
|
||||
* @param maxAcceleration Maximum acceleration, must be non-negative.
|
||||
*/
|
||||
public Constraints(double maxVelocity, double maxAcceleration) {
|
||||
if (maxVelocity < 0.0 || maxAcceleration < 0.0) {
|
||||
throw new IllegalArgumentException("Constraints must be non-negative");
|
||||
}
|
||||
|
||||
this.maxVelocity = maxVelocity;
|
||||
this.maxAcceleration = maxAcceleration;
|
||||
MathSharedStore.reportUsage("TrapezoidProfile", "");
|
||||
@@ -127,8 +131,8 @@ public class TrapezoidProfile {
|
||||
m_current = direct(current);
|
||||
goal = direct(goal);
|
||||
|
||||
if (m_current.velocity > m_constraints.maxVelocity) {
|
||||
m_current.velocity = m_constraints.maxVelocity;
|
||||
if (Math.abs(m_current.velocity) > m_constraints.maxVelocity) {
|
||||
m_current.velocity = Math.copySign(m_constraints.maxVelocity, m_current.velocity);
|
||||
}
|
||||
|
||||
// Deal with a possibly truncated motion profile (with nonzero initial or
|
||||
|
||||
@@ -94,6 +94,42 @@ constexpr T ApplyDeadband(T value, T deadband, T maxMagnitude = T{1.0}) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Raises the input to the power of the given exponent while preserving its
|
||||
* sign.
|
||||
*
|
||||
* The function normalizes the input value to the range [0, 1] based on the
|
||||
* maximum magnitude, raises it to the power of the exponent, then scales the
|
||||
* result back to the original range and copying the sign. This keeps the value
|
||||
* in the original range and gives consistent curve behavior regardless of the
|
||||
* input value's scale.
|
||||
*
|
||||
* This is useful for applying smoother or more aggressive control response
|
||||
* curves (e.g. joystick input shaping).
|
||||
*
|
||||
* @param value The input value to transform.
|
||||
* @param exponent The exponent to apply (e.g. 1.0 = linear, 2.0 = squared
|
||||
* curve). Must be positive.
|
||||
* @param maxMagnitude The maximum expected absolute value of input. Must be
|
||||
* positive.
|
||||
* @return The transformed value with the same sign and scaled to the input
|
||||
* range.
|
||||
*/
|
||||
template <typename T>
|
||||
requires std::is_arithmetic_v<T> || units::traits::is_unit_t_v<T>
|
||||
constexpr T CopySignPow(T value, double exponent, T maxMagnitude = T{1.0}) {
|
||||
if constexpr (std::is_arithmetic_v<T>) {
|
||||
return gcem::copysign(
|
||||
gcem::pow(gcem::abs(value) / maxMagnitude, exponent) * maxMagnitude,
|
||||
value);
|
||||
} else {
|
||||
return units::math::copysign(
|
||||
gcem::pow((units::math::abs(value) / maxMagnitude).value(), exponent) *
|
||||
maxMagnitude,
|
||||
value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns modulus of input.
|
||||
*
|
||||
|
||||
@@ -76,8 +76,8 @@ class TrapezoidProfile {
|
||||
/**
|
||||
* Constructs constraints for a Trapezoid Profile.
|
||||
*
|
||||
* @param maxVelocity Maximum velocity.
|
||||
* @param maxAcceleration Maximum acceleration.
|
||||
* @param maxVelocity Maximum velocity, must be non-negative.
|
||||
* @param maxAcceleration Maximum acceleration, must be non-negative.
|
||||
*/
|
||||
constexpr Constraints(Velocity_t maxVelocity,
|
||||
Acceleration_t maxAcceleration)
|
||||
@@ -85,6 +85,10 @@ class TrapezoidProfile {
|
||||
if (!std::is_constant_evaluated()) {
|
||||
wpi::math::MathSharedStore::ReportUsage("TrapezoidProfile", "");
|
||||
}
|
||||
|
||||
if (maxVelocity < Velocity_t{0} || maxAcceleration < Acceleration_t{0}) {
|
||||
throw std::domain_error("Constraints must be non-negative");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -129,8 +133,9 @@ class TrapezoidProfile {
|
||||
m_direction = ShouldFlipAcceleration(current, goal) ? -1 : 1;
|
||||
m_current = Direct(current);
|
||||
goal = Direct(goal);
|
||||
if (m_current.velocity > m_constraints.maxVelocity) {
|
||||
m_current.velocity = m_constraints.maxVelocity;
|
||||
if (units::math::abs(m_current.velocity) > m_constraints.maxVelocity) {
|
||||
m_current.velocity =
|
||||
units::math::copysign(m_constraints.maxVelocity, m_current.velocity);
|
||||
}
|
||||
|
||||
// Deal with a possibly truncated motion profile (with nonzero initial or
|
||||
|
||||
@@ -72,6 +72,44 @@ class MathUtilTest extends UtilityClassTest<MathUtil> {
|
||||
assertEquals(80.0, MathUtil.applyDeadband(100.0, 20, Double.POSITIVE_INFINITY));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCopySignPow() {
|
||||
assertEquals(0.5, MathUtil.copySignPow(0.5, 1.0));
|
||||
assertEquals(-0.5, MathUtil.copySignPow(-0.5, 1.0));
|
||||
|
||||
assertEquals(0.5 * 0.5, MathUtil.copySignPow(0.5, 2.0));
|
||||
assertEquals(-(0.5 * 0.5), MathUtil.copySignPow(-0.5, 2.0));
|
||||
|
||||
assertEquals(Math.sqrt(0.5), MathUtil.copySignPow(0.5, 0.5));
|
||||
assertEquals(-Math.sqrt(0.5), MathUtil.copySignPow(-0.5, 0.5));
|
||||
|
||||
assertEquals(0.0, MathUtil.copySignPow(0.0, 2.0));
|
||||
assertEquals(1.0, MathUtil.copySignPow(1.0, 2.0));
|
||||
assertEquals(-1.0, MathUtil.copySignPow(-1.0, 2.0));
|
||||
|
||||
assertEquals(Math.pow(0.8, 0.3), MathUtil.copySignPow(0.8, 0.3));
|
||||
assertEquals(-Math.pow(0.8, 0.3), MathUtil.copySignPow(-0.8, 0.3));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCopySignPowMaxMagnitude() {
|
||||
assertEquals(5, MathUtil.copySignPow(5.0, 1.0, 10.0));
|
||||
assertEquals(-5, MathUtil.copySignPow(-5.0, 1.0, 10.0));
|
||||
|
||||
assertEquals(0.5 * 0.5 * 10, MathUtil.copySignPow(5.0, 2.0, 10.0));
|
||||
assertEquals(-0.5 * 0.5 * 10, MathUtil.copySignPow(-5.0, 2.0, 10.0));
|
||||
|
||||
assertEquals(Math.sqrt(0.5) * 10, MathUtil.copySignPow(5.0, 0.5, 10.0));
|
||||
assertEquals(-Math.sqrt(0.5) * 10, MathUtil.copySignPow(-5.0, 0.5, 10.0));
|
||||
|
||||
assertEquals(0.0, MathUtil.copySignPow(0.0, 2.0, 5.0));
|
||||
assertEquals(5.0, MathUtil.copySignPow(5.0, 2.0, 5.0));
|
||||
assertEquals(-5.0, MathUtil.copySignPow(-5.0, 2.0, 5.0));
|
||||
|
||||
assertEquals(Math.pow(0.8, 0.3) * 100, MathUtil.copySignPow(80, 0.3, 100.0));
|
||||
assertEquals(-Math.pow(0.8, 0.3) * 100, MathUtil.copySignPow(-80, 0.3, 100.0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInputModulus() {
|
||||
// These tests check error wrapping. That is, the result of wrapping the
|
||||
|
||||
@@ -253,4 +253,46 @@ class TrapezoidProfileTest {
|
||||
assertNear(profile.timeLeftUntil(0), 0, 1e-10);
|
||||
assertNear(profile.totalTime(), 0, 1e-10);
|
||||
}
|
||||
|
||||
@Test
|
||||
void initialVelocityConstraints() {
|
||||
TrapezoidProfile.Constraints constraints = new TrapezoidProfile.Constraints(0.75, 0.75);
|
||||
TrapezoidProfile.State goal = new TrapezoidProfile.State(10, 0);
|
||||
TrapezoidProfile.State state = new TrapezoidProfile.State(0, -10);
|
||||
|
||||
TrapezoidProfile profile = new TrapezoidProfile(constraints);
|
||||
|
||||
for (int i = 0; i < 200; ++i) {
|
||||
state = profile.calculate(kDt, state, goal);
|
||||
assertLessThanOrEquals(Math.abs(state.velocity), Math.abs(constraints.maxVelocity));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void goalVelocityConstraints() {
|
||||
TrapezoidProfile.Constraints constraints = new TrapezoidProfile.Constraints(0.75, 0.75);
|
||||
TrapezoidProfile.State goal = new TrapezoidProfile.State(10, 5);
|
||||
TrapezoidProfile.State state = new TrapezoidProfile.State(0, 0.75);
|
||||
|
||||
TrapezoidProfile profile = new TrapezoidProfile(constraints);
|
||||
|
||||
for (int i = 0; i < 200; ++i) {
|
||||
state = profile.calculate(kDt, state, goal);
|
||||
assertLessThanOrEquals(Math.abs(state.velocity), Math.abs(constraints.maxVelocity));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void negativeGoalVelocityConstraints() {
|
||||
TrapezoidProfile.Constraints constraints = new TrapezoidProfile.Constraints(0.75, 0.75);
|
||||
TrapezoidProfile.State goal = new TrapezoidProfile.State(10, -5);
|
||||
TrapezoidProfile.State state = new TrapezoidProfile.State(0, 0.75);
|
||||
|
||||
TrapezoidProfile profile = new TrapezoidProfile(constraints);
|
||||
|
||||
for (int i = 0; i < 200; ++i) {
|
||||
state = profile.calculate(kDt, state, goal);
|
||||
assertLessThanOrEquals(Math.abs(state.velocity), Math.abs(constraints.maxVelocity));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,61 @@ TEST(MathUtilTest, ApplyDeadbandLargeMaxMagnitude) {
|
||||
frc::ApplyDeadband(100.0, 20.0, std::numeric_limits<double>::infinity()));
|
||||
}
|
||||
|
||||
TEST(MathUtilTest, CopySignPow) {
|
||||
EXPECT_DOUBLE_EQ(0.5, frc::CopySignPow(0.5, 1.0));
|
||||
EXPECT_DOUBLE_EQ(-0.5, frc::CopySignPow(-0.5, 1.0));
|
||||
|
||||
EXPECT_DOUBLE_EQ(0.5 * 0.5, frc::CopySignPow(0.5, 2.0));
|
||||
EXPECT_DOUBLE_EQ(-(0.5 * 0.5), frc::CopySignPow(-0.5, 2.0));
|
||||
|
||||
EXPECT_DOUBLE_EQ(std::sqrt(0.5), frc::CopySignPow(0.5, 0.5));
|
||||
EXPECT_DOUBLE_EQ(-std::sqrt(0.5), frc::CopySignPow(-0.5, 0.5));
|
||||
|
||||
EXPECT_DOUBLE_EQ(0.0, frc::CopySignPow(0.0, 2.0));
|
||||
EXPECT_DOUBLE_EQ(1.0, frc::CopySignPow(1.0, 2.0));
|
||||
EXPECT_DOUBLE_EQ(-1.0, frc::CopySignPow(-1.0, 2.0));
|
||||
|
||||
EXPECT_DOUBLE_EQ(std::pow(0.8, 0.3), frc::CopySignPow(0.8, 0.3));
|
||||
EXPECT_DOUBLE_EQ(-std::pow(0.8, 0.3), frc::CopySignPow(-0.8, 0.3));
|
||||
}
|
||||
|
||||
TEST(MathUtilTest, CopySignPowWithMaxMagnitude) {
|
||||
EXPECT_DOUBLE_EQ(5.0, frc::CopySignPow(5.0, 1.0, 10.0));
|
||||
EXPECT_DOUBLE_EQ(-5.0, frc::CopySignPow(-5.0, 1.0, 10.0));
|
||||
|
||||
EXPECT_DOUBLE_EQ(0.5 * 0.5 * 10, frc::CopySignPow(5.0, 2.0, 10.0));
|
||||
EXPECT_DOUBLE_EQ(-0.5 * 0.5 * 10, frc::CopySignPow(-5.0, 2.0, 10.0));
|
||||
|
||||
EXPECT_DOUBLE_EQ(std::sqrt(0.5) * 10, frc::CopySignPow(5.0, 0.5, 10.0));
|
||||
EXPECT_DOUBLE_EQ(-std::sqrt(0.5) * 10, frc::CopySignPow(-5.0, 0.5, 10.0));
|
||||
|
||||
EXPECT_DOUBLE_EQ(0.0, frc::CopySignPow(0.0, 2.0, 5.0));
|
||||
EXPECT_DOUBLE_EQ(5.0, frc::CopySignPow(5.0, 2.0, 5.0));
|
||||
EXPECT_DOUBLE_EQ(-5.0, frc::CopySignPow(-5.0, 2.0, 5.0));
|
||||
|
||||
EXPECT_DOUBLE_EQ(std::pow(0.8, 0.3) * 100,
|
||||
frc::CopySignPow(80.0, 0.3, 100.0));
|
||||
EXPECT_DOUBLE_EQ(-std::pow(0.8, 0.3) * 100,
|
||||
frc::CopySignPow(-80.0, 0.3, 100.0));
|
||||
}
|
||||
|
||||
TEST(MathUtilTest, CopySignPowWithUnits) {
|
||||
EXPECT_DOUBLE_EQ(
|
||||
0, frc::CopySignPow<units::meters_per_second_t>(0_mps, 2.0).value());
|
||||
EXPECT_DOUBLE_EQ(
|
||||
1, frc::CopySignPow<units::meters_per_second_t>(1_mps, 2.0).value());
|
||||
EXPECT_DOUBLE_EQ(
|
||||
-1, frc::CopySignPow<units::meters_per_second_t>(-1_mps, 2.0).value());
|
||||
|
||||
EXPECT_DOUBLE_EQ(
|
||||
0.5 * 0.5 * 10,
|
||||
frc::CopySignPow<units::meters_per_second_t>(5_mps, 2.0, 10_mps).value());
|
||||
EXPECT_DOUBLE_EQ(
|
||||
-0.5 * 0.5 * 10,
|
||||
frc::CopySignPow<units::meters_per_second_t>(-5_mps, 2.0, 10_mps)
|
||||
.value());
|
||||
}
|
||||
|
||||
TEST(MathUtilTest, InputModulus) {
|
||||
// These tests check error wrapping. That is, the result of wrapping the
|
||||
// result of an angle reference minus the measurement.
|
||||
|
||||
@@ -241,3 +241,48 @@ TEST(TrapezoidProfileTest, InitalizationOfCurrentState) {
|
||||
EXPECT_NEAR_UNITS(profile.TimeLeftUntil(0_m), 0_s, 1e-10_s);
|
||||
EXPECT_NEAR_UNITS(profile.TotalTime(), 0_s, 1e-10_s);
|
||||
}
|
||||
|
||||
TEST(TrapezoidProfileTest, InitialVelocityConstraints) {
|
||||
frc::TrapezoidProfile<units::meter>::Constraints constraints{0.75_mps,
|
||||
0.75_mps_sq};
|
||||
frc::TrapezoidProfile<units::meter>::State goal{10_m, 0_mps};
|
||||
frc::TrapezoidProfile<units::meter>::State state{0_m, -10_mps};
|
||||
|
||||
frc::TrapezoidProfile<units::meter> profile{constraints};
|
||||
|
||||
for (int i = 0; i < 200; ++i) {
|
||||
state = profile.Calculate(kDt, state, goal);
|
||||
EXPECT_LE(units::math::abs(state.velocity),
|
||||
units::math::abs(constraints.maxVelocity));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TrapezoidProfileTest, GoalVelocityConstraints) {
|
||||
frc::TrapezoidProfile<units::meter>::Constraints constraints{0.75_mps,
|
||||
0.75_mps_sq};
|
||||
frc::TrapezoidProfile<units::meter>::State goal{10_m, 5_mps};
|
||||
frc::TrapezoidProfile<units::meter>::State state{0_m, 0.75_mps};
|
||||
|
||||
frc::TrapezoidProfile<units::meter> profile{constraints};
|
||||
|
||||
for (int i = 0; i < 200; ++i) {
|
||||
state = profile.Calculate(kDt, state, goal);
|
||||
EXPECT_LE(units::math::abs(state.velocity),
|
||||
units::math::abs(constraints.maxVelocity));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TrapezoidProfileTest, NegativeGoalVelocityConstraints) {
|
||||
frc::TrapezoidProfile<units::meter>::Constraints constraints{0.75_mps,
|
||||
0.75_mps_sq};
|
||||
frc::TrapezoidProfile<units::meter>::State goal{10_m, -5_mps};
|
||||
frc::TrapezoidProfile<units::meter>::State state{0_m, 0.75_mps};
|
||||
|
||||
frc::TrapezoidProfile<units::meter> profile{constraints};
|
||||
|
||||
for (int i = 0; i < 200; ++i) {
|
||||
state = profile.Calculate(kDt, state, goal);
|
||||
EXPECT_LE(units::math::abs(state.velocity),
|
||||
units::math::abs(constraints.maxVelocity));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,12 +92,6 @@ third_party_cc_lib_helper(
|
||||
src_root = "src/main/native/thirdparty/llvm/cpp",
|
||||
)
|
||||
|
||||
third_party_cc_lib_helper(
|
||||
name = "memory",
|
||||
include_root = "src/main/native/thirdparty/memory/include",
|
||||
src_root = "src/main/native/thirdparty/memory/src",
|
||||
)
|
||||
|
||||
third_party_cc_lib_helper(
|
||||
name = "mpack",
|
||||
include_root = "src/main/native/thirdparty/mpack/include",
|
||||
@@ -159,7 +153,6 @@ wpilib_cc_library(
|
||||
":debugging",
|
||||
":fmtlib",
|
||||
":llvm",
|
||||
":memory",
|
||||
":mpack",
|
||||
":nanopb",
|
||||
":protobuf",
|
||||
|
||||
@@ -130,9 +130,8 @@ file(GLOB_RECURSE wpiutil_macos_src src/main/native/macOS/*.cpp)
|
||||
file(GLOB_RECURSE wpiutil_windows_src src/main/native/windows/*.cpp)
|
||||
|
||||
file(GLOB fmtlib_native_src src/main/native/thirdparty/fmtlib/src/*.cpp)
|
||||
file(GLOB_RECURSE memory_native_src src/main/native/thirdparty/memory/src/*.cpp)
|
||||
|
||||
add_library(wpiutil ${wpiutil_native_src} ${memory_native_src} ${wpiutil_resources_src})
|
||||
add_library(wpiutil ${wpiutil_native_src} ${wpiutil_resources_src})
|
||||
set_target_properties(wpiutil PROPERTIES DEBUG_POSTFIX "d")
|
||||
|
||||
set_property(TARGET wpiutil PROPERTY FOLDER "libraries")
|
||||
@@ -199,7 +198,6 @@ install(
|
||||
src/main/native/thirdparty/expected/include/
|
||||
src/main/native/thirdparty/json/include/
|
||||
src/main/native/thirdparty/llvm/include/
|
||||
src/main/native/thirdparty/memory/include/
|
||||
src/main/native/thirdparty/mpack/include/
|
||||
src/main/native/thirdparty/nanopb/include/
|
||||
src/main/native/thirdparty/sigslot/include/
|
||||
@@ -214,7 +212,6 @@ target_include_directories(
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/thirdparty/expected/include>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/thirdparty/json/include>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/thirdparty/llvm/include>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/thirdparty/memory/include>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/thirdparty/mpack/include>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/thirdparty/nanopb/include>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/thirdparty/sigslot/include>
|
||||
|
||||
@@ -80,16 +80,6 @@ ext {
|
||||
srcDirs 'src/main/native/thirdparty/sigslot/include'
|
||||
}
|
||||
}
|
||||
memoryCpp(CppSourceSet) {
|
||||
source {
|
||||
srcDirs 'src/main/native/thirdparty/memory/src', 'src/main/native/thirdparty/memory/include/wpi/memory'
|
||||
include '**/*.cpp'
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDirs 'src/main/native/thirdparty/memory/include'
|
||||
include '**/*.hpp'
|
||||
}
|
||||
}
|
||||
protobufCpp(CppSourceSet) {
|
||||
source {
|
||||
srcDirs 'src/main/native/thirdparty/protobuf/src'
|
||||
@@ -191,7 +181,6 @@ cppHeadersZip {
|
||||
'src/main/native/thirdparty/mpack/include',
|
||||
'src/main/native/thirdparty/nanopb/include',
|
||||
'src/main/native/thirdparty/sigslot/include',
|
||||
'src/main/native/thirdparty/memory/include',
|
||||
'src/main/native/thirdparty/protobuf/include'
|
||||
]
|
||||
|
||||
@@ -213,9 +202,6 @@ cppSourcesZip {
|
||||
from('src/main/native/thirdparty/llvm/cpp') {
|
||||
into '/'
|
||||
}
|
||||
from('src/main/native/thirdparty/memory/src') {
|
||||
into '/'
|
||||
}
|
||||
from('src/main/native/thirdparty/mpack/src') {
|
||||
into '/'
|
||||
}
|
||||
@@ -235,7 +221,7 @@ model {
|
||||
all {
|
||||
it.sources.each {
|
||||
it.exportedHeaders {
|
||||
srcDirs 'src/main/native/include', 'src/main/native/thirdparty/argparse/include/', 'src/main/native/thirdparty/debugging/include', 'src/main/native/thirdparty/expected/include', 'src/main/native/thirdparty/fmtlib/include', 'src/main/native/thirdparty/llvm/include', 'src/main/native/thirdparty/sigslot/include', 'src/main/native/thirdparty/json/include', 'src/main/native/thirdparty/memory/include', 'src/main/native/thirdparty/mpack/include', 'src/main/native/thirdparty/protobuf/include', 'src/main/native/thirdparty/nanopb/include'
|
||||
srcDirs 'src/main/native/include', 'src/main/native/thirdparty/argparse/include/', 'src/main/native/thirdparty/debugging/include', 'src/main/native/thirdparty/expected/include', 'src/main/native/thirdparty/fmtlib/include', 'src/main/native/thirdparty/llvm/include', 'src/main/native/thirdparty/sigslot/include', 'src/main/native/thirdparty/json/include', 'src/main/native/thirdparty/mpack/include', 'src/main/native/thirdparty/protobuf/include', 'src/main/native/thirdparty/nanopb/include'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,196 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_ALIGNED_ALLOCATOR_HPP_INCLUDED
|
||||
#define WPI_MEMORY_ALIGNED_ALLOCATOR_HPP_INCLUDED
|
||||
|
||||
/// \file
|
||||
/// Class \ref wpi::memory::aligned_allocator and related functions.
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "detail/assert.hpp"
|
||||
#include "detail/utility.hpp"
|
||||
#include "allocator_traits.hpp"
|
||||
#include "config.hpp"
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
/// A RawAllocator adapter that ensures a minimum alignment.
|
||||
/// It adjusts the alignment value so that it is always larger than the minimum and forwards to the specified allocator.
|
||||
/// \ingroup memory_adapter
|
||||
template <class RawAllocator>
|
||||
class aligned_allocator : WPI_EBO(allocator_traits<RawAllocator>::allocator_type)
|
||||
{
|
||||
using traits = allocator_traits<RawAllocator>;
|
||||
using composable_traits = composable_allocator_traits<RawAllocator>;
|
||||
using composable = is_composable_allocator<typename traits::allocator_type>;
|
||||
|
||||
public:
|
||||
using allocator_type = typename allocator_traits<RawAllocator>::allocator_type;
|
||||
using is_stateful = std::true_type;
|
||||
|
||||
/// \effects Creates it passing it the minimum alignment value and the allocator object.
|
||||
/// \requires \c min_alignment must be less than \c this->max_alignment().
|
||||
explicit aligned_allocator(std::size_t min_alignment, allocator_type&& alloc = {})
|
||||
: allocator_type(detail::move(alloc)), min_alignment_(min_alignment)
|
||||
{
|
||||
WPI_MEMORY_ASSERT(min_alignment_ <= max_alignment());
|
||||
}
|
||||
|
||||
/// @{
|
||||
/// \effects Moves the \c aligned_allocator object.
|
||||
/// It simply moves the underlying allocator.
|
||||
aligned_allocator(aligned_allocator&& other) noexcept
|
||||
: allocator_type(detail::move(other)), min_alignment_(other.min_alignment_)
|
||||
{
|
||||
}
|
||||
|
||||
aligned_allocator& operator=(aligned_allocator&& other) noexcept
|
||||
{
|
||||
allocator_type::operator=(detail::move(other));
|
||||
min_alignment_ = other.min_alignment_;
|
||||
return *this;
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \effects Forwards to the underlying allocator through the \ref allocator_traits.
|
||||
/// If the \c alignment is less than the \c min_alignment(), it is set to the minimum alignment.
|
||||
void* allocate_node(std::size_t size, std::size_t alignment)
|
||||
{
|
||||
if (min_alignment_ > alignment)
|
||||
alignment = min_alignment_;
|
||||
return traits::allocate_node(get_allocator(), size, alignment);
|
||||
}
|
||||
|
||||
void* allocate_array(std::size_t count, std::size_t size, std::size_t alignment)
|
||||
{
|
||||
if (min_alignment_ > alignment)
|
||||
alignment = min_alignment_;
|
||||
return traits::allocate_array(get_allocator(), count, size, alignment);
|
||||
}
|
||||
|
||||
void deallocate_node(void* ptr, std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
if (min_alignment_ > alignment)
|
||||
alignment = min_alignment_;
|
||||
traits::deallocate_node(get_allocator(), ptr, size, alignment);
|
||||
}
|
||||
|
||||
void deallocate_array(void* ptr, std::size_t count, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
{
|
||||
if (min_alignment_ > alignment)
|
||||
alignment = min_alignment_;
|
||||
traits::deallocate_array(get_allocator(), ptr, count, size, alignment);
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \effects Forwards to the underlying allocator through the \ref composable_allocator_traits.
|
||||
/// If the \c alignment is less than the \c min_alignment(), it is set to the minimum alignment.
|
||||
/// \requires The underyling allocator must be composable.
|
||||
WPI_ENABLE_IF(composable::value)
|
||||
void* try_allocate_node(std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
if (min_alignment_ > alignment)
|
||||
alignment = min_alignment_;
|
||||
return composable_traits::try_allocate_node(get_allocator(), size, alignment);
|
||||
}
|
||||
|
||||
WPI_ENABLE_IF(composable::value)
|
||||
void* try_allocate_array(std::size_t count, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
{
|
||||
if (min_alignment_ > alignment)
|
||||
alignment = min_alignment_;
|
||||
return composable_traits::try_allocate_array(get_allocator(), count, size,
|
||||
alignment);
|
||||
}
|
||||
|
||||
WPI_ENABLE_IF(composable::value)
|
||||
bool try_deallocate_node(void* ptr, std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
if (min_alignment_ > alignment)
|
||||
alignment = min_alignment_;
|
||||
return composable_traits::try_deallocate_node(get_allocator(), ptr, size,
|
||||
alignment);
|
||||
}
|
||||
|
||||
WPI_ENABLE_IF(composable::value)
|
||||
bool try_deallocate_array(void* ptr, std::size_t count, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
{
|
||||
if (min_alignment_ > alignment)
|
||||
alignment = min_alignment_;
|
||||
return composable_traits::try_deallocate_array(get_allocator(), ptr, count, size,
|
||||
alignment);
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \returns The value returned by the \ref allocator_traits for the underlying allocator.
|
||||
std::size_t max_node_size() const
|
||||
{
|
||||
return traits::max_node_size(get_allocator());
|
||||
}
|
||||
|
||||
std::size_t max_array_size() const
|
||||
{
|
||||
return traits::max_array_size(get_allocator());
|
||||
}
|
||||
|
||||
std::size_t max_alignment() const
|
||||
{
|
||||
return traits::max_alignment(get_allocator());
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \returns A reference to the underlying allocator.
|
||||
allocator_type& get_allocator() noexcept
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
const allocator_type& get_allocator() const noexcept
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// \returns The minimum alignment.
|
||||
std::size_t min_alignment() const noexcept
|
||||
{
|
||||
return min_alignment_;
|
||||
}
|
||||
|
||||
/// \effects Sets the minimum alignment to a new value.
|
||||
/// \requires \c min_alignment must be less than \c this->max_alignment().
|
||||
void set_min_alignment(std::size_t min_alignment)
|
||||
{
|
||||
WPI_MEMORY_ASSERT(min_alignment <= max_alignment());
|
||||
min_alignment_ = min_alignment;
|
||||
}
|
||||
|
||||
private:
|
||||
std::size_t min_alignment_;
|
||||
};
|
||||
|
||||
/// \returns A new \ref aligned_allocator created by forwarding the parameters to the constructor.
|
||||
/// \relates aligned_allocator
|
||||
template <class RawAllocator>
|
||||
auto make_aligned_allocator(std::size_t min_alignment, RawAllocator&& allocator) noexcept
|
||||
-> aligned_allocator<typename std::decay<RawAllocator>::type>
|
||||
{
|
||||
return aligned_allocator<
|
||||
typename std::decay<RawAllocator>::type>{min_alignment,
|
||||
detail::forward<RawAllocator>(allocator)};
|
||||
}
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_ALIGNED_ALLOCATOR_HPP_INCLUDED
|
||||
@@ -1,933 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_ALLOCATOR_STORAGE_HPP_INCLUDED
|
||||
#define WPI_MEMORY_ALLOCATOR_STORAGE_HPP_INCLUDED
|
||||
|
||||
/// \file
|
||||
/// Class template \ref wpi::memory::allocator_storage, some policies and resulting typedefs.
|
||||
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
|
||||
#include "detail/utility.hpp"
|
||||
#include "config.hpp"
|
||||
#include "allocator_traits.hpp"
|
||||
#include "threading.hpp"
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
template <class Alloc>
|
||||
void* try_allocate_node(std::true_type, Alloc& alloc, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
{
|
||||
return composable_allocator_traits<Alloc>::try_allocate_node(alloc, size,
|
||||
alignment);
|
||||
}
|
||||
|
||||
template <class Alloc>
|
||||
void* try_allocate_array(std::true_type, Alloc& alloc, std::size_t count,
|
||||
std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
return composable_allocator_traits<Alloc>::try_allocate_array(alloc, count, size,
|
||||
alignment);
|
||||
}
|
||||
|
||||
template <class Alloc>
|
||||
bool try_deallocate_node(std::true_type, Alloc& alloc, void* ptr, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
{
|
||||
return composable_allocator_traits<Alloc>::try_deallocate_node(alloc, ptr, size,
|
||||
alignment);
|
||||
}
|
||||
|
||||
template <class Alloc>
|
||||
bool try_deallocate_array(std::true_type, Alloc& alloc, void* ptr, std::size_t count,
|
||||
std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
return composable_allocator_traits<Alloc>::try_deallocate_array(alloc, ptr, count,
|
||||
size, alignment);
|
||||
}
|
||||
|
||||
template <class Alloc>
|
||||
void* try_allocate_node(std::false_type, Alloc&, std::size_t, std::size_t) noexcept
|
||||
{
|
||||
WPI_MEMORY_UNREACHABLE("Allocator is not compositioning");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <class Alloc>
|
||||
void* try_allocate_array(std::false_type, Alloc&, std::size_t, std::size_t,
|
||||
std::size_t) noexcept
|
||||
{
|
||||
WPI_MEMORY_UNREACHABLE("Allocator is not compositioning");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <class Alloc>
|
||||
bool try_deallocate_node(std::false_type, Alloc&, void*, std::size_t,
|
||||
std::size_t) noexcept
|
||||
{
|
||||
WPI_MEMORY_UNREACHABLE("Allocator is not compositioning");
|
||||
return false;
|
||||
}
|
||||
|
||||
template <class Alloc>
|
||||
bool try_deallocate_array(std::false_type, Alloc&, void*, std::size_t, std::size_t,
|
||||
std::size_t) noexcept
|
||||
{
|
||||
WPI_MEMORY_UNREACHABLE("Allocator is not compositioning");
|
||||
return false;
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
/// A RawAllocator that stores another allocator.
|
||||
/// The StoragePolicy defines the allocator type being stored and how it is stored.
|
||||
/// The \c Mutex controls synchronization of the access.
|
||||
/// \ingroup memory_storage
|
||||
template <class StoragePolicy, class Mutex>
|
||||
class allocator_storage
|
||||
: WPI_EBO(StoragePolicy,
|
||||
detail::mutex_storage<
|
||||
detail::mutex_for<typename StoragePolicy::allocator_type, Mutex>>)
|
||||
{
|
||||
using traits = allocator_traits<typename StoragePolicy::allocator_type>;
|
||||
using composable_traits =
|
||||
composable_allocator_traits<typename StoragePolicy::allocator_type>;
|
||||
using composable = is_composable_allocator<typename StoragePolicy::allocator_type>;
|
||||
using actual_mutex = const detail::mutex_storage<
|
||||
detail::mutex_for<typename StoragePolicy::allocator_type, Mutex>>;
|
||||
|
||||
public:
|
||||
using allocator_type = typename StoragePolicy::allocator_type;
|
||||
using storage_policy = StoragePolicy;
|
||||
using mutex = Mutex;
|
||||
using is_stateful = typename traits::is_stateful;
|
||||
|
||||
/// \effects Creates it by default-constructing the \c StoragePolicy.
|
||||
/// \requires The \c StoragePolicy must be default-constructible.
|
||||
/// \notes The default constructor may create an invalid allocator storage not associated with any allocator.
|
||||
/// If that is the case, it must not be used.
|
||||
allocator_storage() = default;
|
||||
|
||||
/// \effects Creates it by passing it an allocator.
|
||||
/// The allocator will be forwarded to the \c StoragePolicy, it decides whether it will be moved, its address stored or something else.
|
||||
/// \requires The expression <tt>new storage_policy(std::forward<Alloc>(alloc))</tt> must be well-formed,
|
||||
/// otherwise this constructor does not participate in overload resolution.
|
||||
template <
|
||||
class Alloc,
|
||||
// MSVC seems to ignore access rights in SFINAE below
|
||||
// use this to prevent this constructor being chosen instead of move for types inheriting from it
|
||||
WPI_REQUIRES(
|
||||
(!std::is_base_of<allocator_storage, typename std::decay<Alloc>::type>::value))>
|
||||
allocator_storage(Alloc&& alloc,
|
||||
WPI_SFINAE(new storage_policy(std::declval<Alloc>())))
|
||||
: storage_policy(detail::forward<Alloc>(alloc))
|
||||
{
|
||||
}
|
||||
|
||||
/// \effects Creates it by passing it another \c allocator_storage with a different \c StoragePolicy but the same \c Mutex type.
|
||||
/// Initializes it with the result of \c other.get_allocator().
|
||||
/// \requires The expression <tt>new storage_policy(other.get_allocator())</tt> must be well-formed,
|
||||
/// otherwise this constructor does not participate in overload resolution.
|
||||
template <class OtherPolicy>
|
||||
allocator_storage(
|
||||
const allocator_storage<OtherPolicy, Mutex>& other,
|
||||
WPI_SFINAE(new storage_policy(
|
||||
std::declval<const allocator_storage<OtherPolicy, Mutex>&>().get_allocator())))
|
||||
: storage_policy(other.get_allocator())
|
||||
{
|
||||
}
|
||||
|
||||
/// @{
|
||||
/// \effects Moves the \c allocator_storage object.
|
||||
/// A moved-out \c allocator_storage object must still store a valid allocator object.
|
||||
allocator_storage(allocator_storage&& other) noexcept
|
||||
: storage_policy(detail::move(other)),
|
||||
detail::mutex_storage<
|
||||
detail::mutex_for<typename StoragePolicy::allocator_type, Mutex>>(
|
||||
detail::move(other))
|
||||
{
|
||||
}
|
||||
|
||||
allocator_storage& operator=(allocator_storage&& other) noexcept
|
||||
{
|
||||
storage_policy:: operator=(detail::move(other));
|
||||
detail::mutex_storage<detail::mutex_for<typename StoragePolicy::allocator_type,
|
||||
Mutex>>::operator=(detail::move(other));
|
||||
return *this;
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \effects Copies the \c allocator_storage object.
|
||||
/// \requires The \c StoragePolicy must be copyable.
|
||||
allocator_storage(const allocator_storage&) = default;
|
||||
allocator_storage& operator=(const allocator_storage&) = default;
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \effects Calls the function on the stored allocator.
|
||||
/// The \c Mutex will be locked during the operation.
|
||||
void* allocate_node(std::size_t size, std::size_t alignment)
|
||||
{
|
||||
std::lock_guard<actual_mutex> lock(*this);
|
||||
auto&& alloc = get_allocator();
|
||||
return traits::allocate_node(alloc, size, alignment);
|
||||
}
|
||||
|
||||
void* allocate_array(std::size_t count, std::size_t size, std::size_t alignment)
|
||||
{
|
||||
std::lock_guard<actual_mutex> lock(*this);
|
||||
auto&& alloc = get_allocator();
|
||||
return traits::allocate_array(alloc, count, size, alignment);
|
||||
}
|
||||
|
||||
void deallocate_node(void* ptr, std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
std::lock_guard<actual_mutex> lock(*this);
|
||||
auto&& alloc = get_allocator();
|
||||
traits::deallocate_node(alloc, ptr, size, alignment);
|
||||
}
|
||||
|
||||
void deallocate_array(void* ptr, std::size_t count, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
{
|
||||
std::lock_guard<actual_mutex> lock(*this);
|
||||
auto&& alloc = get_allocator();
|
||||
traits::deallocate_array(alloc, ptr, count, size, alignment);
|
||||
}
|
||||
|
||||
std::size_t max_node_size() const
|
||||
{
|
||||
std::lock_guard<actual_mutex> lock(*this);
|
||||
auto&& alloc = get_allocator();
|
||||
return traits::max_node_size(alloc);
|
||||
}
|
||||
|
||||
std::size_t max_array_size() const
|
||||
{
|
||||
std::lock_guard<actual_mutex> lock(*this);
|
||||
auto&& alloc = get_allocator();
|
||||
return traits::max_array_size(alloc);
|
||||
}
|
||||
|
||||
std::size_t max_alignment() const
|
||||
{
|
||||
std::lock_guard<actual_mutex> lock(*this);
|
||||
auto&& alloc = get_allocator();
|
||||
return traits::max_alignment(alloc);
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \effects Calls the function on the stored composable allocator.
|
||||
/// The \c Mutex will be locked during the operation.
|
||||
/// \requires The allocator must be composable,
|
||||
/// i.e. \ref is_composable() must return `true`.
|
||||
/// \note This check is done at compile-time where possible,
|
||||
/// and at runtime in the case of type-erased storage.
|
||||
WPI_ENABLE_IF(composable::value)
|
||||
void* try_allocate_node(std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(is_composable());
|
||||
std::lock_guard<actual_mutex> lock(*this);
|
||||
auto&& alloc = get_allocator();
|
||||
return composable_traits::try_allocate_node(alloc, size, alignment);
|
||||
}
|
||||
|
||||
WPI_ENABLE_IF(composable::value)
|
||||
void* try_allocate_array(std::size_t count, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(is_composable());
|
||||
std::lock_guard<actual_mutex> lock(*this);
|
||||
auto&& alloc = get_allocator();
|
||||
return composable_traits::try_allocate_array(alloc, count, size, alignment);
|
||||
}
|
||||
|
||||
WPI_ENABLE_IF(composable::value)
|
||||
bool try_deallocate_node(void* ptr, std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(is_composable());
|
||||
std::lock_guard<actual_mutex> lock(*this);
|
||||
auto&& alloc = get_allocator();
|
||||
return composable_traits::try_deallocate_node(alloc, ptr, size, alignment);
|
||||
}
|
||||
|
||||
WPI_ENABLE_IF(composable::value)
|
||||
bool try_deallocate_array(void* ptr, std::size_t count, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(is_composable());
|
||||
std::lock_guard<actual_mutex> lock(*this);
|
||||
auto&& alloc = get_allocator();
|
||||
return composable_traits::try_deallocate_array(alloc, ptr, count, size, alignment);
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \effects Forwards to the \c StoragePolicy.
|
||||
/// \returns Returns a reference to the stored allocator.
|
||||
/// \note This does not lock the \c Mutex.
|
||||
auto get_allocator() noexcept
|
||||
-> decltype(std::declval<storage_policy>().get_allocator())
|
||||
{
|
||||
return storage_policy::get_allocator();
|
||||
}
|
||||
|
||||
auto get_allocator() const noexcept
|
||||
-> decltype(std::declval<const storage_policy>().get_allocator())
|
||||
{
|
||||
return storage_policy::get_allocator();
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \returns A proxy object that acts like a pointer to the stored allocator.
|
||||
/// It cannot be reassigned to point to another allocator object and only moving is supported, which is destructive.
|
||||
/// As long as the proxy object lives and is not moved from, the \c Mutex will be kept locked.
|
||||
auto lock() noexcept -> WPI_IMPL_DEFINED(decltype(detail::lock_allocator(
|
||||
std::declval<storage_policy>().get_allocator(), std::declval<actual_mutex&>())))
|
||||
{
|
||||
return detail::lock_allocator(get_allocator(), static_cast<actual_mutex&>(*this));
|
||||
}
|
||||
|
||||
auto lock() const noexcept -> WPI_IMPL_DEFINED(decltype(detail::lock_allocator(
|
||||
std::declval<const storage_policy>().get_allocator(),
|
||||
std::declval<actual_mutex&>())))
|
||||
{
|
||||
return detail::lock_allocator(get_allocator(), static_cast<actual_mutex&>(*this));
|
||||
}
|
||||
/// @}.
|
||||
|
||||
/// \returns Whether or not the stored allocator is composable,
|
||||
/// that is you can use the compositioning functions.
|
||||
/// \note Due to type-erased allocators,
|
||||
/// this function can not be `constexpr`.
|
||||
bool is_composable() const noexcept
|
||||
{
|
||||
return StoragePolicy::is_composable();
|
||||
}
|
||||
};
|
||||
|
||||
/// Tag type that enables type-erasure in \ref reference_storage.
|
||||
/// It can be used everywhere a \ref allocator_reference is used internally.
|
||||
/// \ingroup memory_storage
|
||||
struct any_allocator
|
||||
{
|
||||
};
|
||||
|
||||
/// A StoragePolicy that stores the allocator directly.
|
||||
/// It embeds the allocator inside it, i.e. moving the storage policy will move the allocator.
|
||||
/// \ingroup memory_storage
|
||||
template <class RawAllocator>
|
||||
class direct_storage : WPI_EBO(allocator_traits<RawAllocator>::allocator_type)
|
||||
{
|
||||
static_assert(!std::is_same<RawAllocator, any_allocator>::value,
|
||||
"cannot type-erase in direct_storage");
|
||||
|
||||
public:
|
||||
using allocator_type = typename allocator_traits<RawAllocator>::allocator_type;
|
||||
|
||||
/// \effects Creates it by default-constructing the allocator.
|
||||
/// \requires The \c RawAllcoator must be default constructible.
|
||||
direct_storage() = default;
|
||||
|
||||
/// \effects Creates it by moving in an allocator object.
|
||||
direct_storage(allocator_type&& allocator) noexcept
|
||||
: allocator_type(detail::move(allocator))
|
||||
{
|
||||
}
|
||||
|
||||
/// @{
|
||||
/// \effects Moves the \c direct_storage object.
|
||||
/// This will move the stored allocator.
|
||||
direct_storage(direct_storage&& other) noexcept : allocator_type(detail::move(other)) {}
|
||||
|
||||
direct_storage& operator=(direct_storage&& other) noexcept
|
||||
{
|
||||
allocator_type::operator=(detail::move(other));
|
||||
return *this;
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \returns A (\c const) reference to the stored allocator.
|
||||
allocator_type& get_allocator() noexcept
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
const allocator_type& get_allocator() const noexcept
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
/// @}
|
||||
|
||||
protected:
|
||||
~direct_storage() noexcept = default;
|
||||
|
||||
bool is_composable() const noexcept
|
||||
{
|
||||
return is_composable_allocator<allocator_type>::value;
|
||||
}
|
||||
};
|
||||
|
||||
/// An alias template for \ref allocator_storage using the \ref direct_storage policy without a mutex.
|
||||
/// It has the effect of giving any RawAllocator the interface with all member functions,
|
||||
/// avoiding the need to wrap it inside the \ref allocator_traits.
|
||||
/// \ingroup memory_storage
|
||||
template <class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(allocator_adapter,
|
||||
allocator_storage<direct_storage<RawAllocator>, no_mutex>);
|
||||
|
||||
/// \returns A new \ref allocator_adapter object created by forwarding to the constructor.
|
||||
/// \relates allocator_adapter
|
||||
template <class RawAllocator>
|
||||
auto make_allocator_adapter(RawAllocator&& allocator) noexcept
|
||||
-> allocator_adapter<typename std::decay<RawAllocator>::type>
|
||||
{
|
||||
return {detail::forward<RawAllocator>(allocator)};
|
||||
}
|
||||
|
||||
/// An alias template for \ref allocator_storage using the \ref direct_storage policy with a mutex.
|
||||
/// It has a similar effect as \ref allocator_adapter but performs synchronization.
|
||||
/// The \c Mutex will default to \c std::mutex if threading is supported,
|
||||
/// otherwise there is no default.
|
||||
/// \ingroup memory_storage
|
||||
#if WPI_HOSTED_IMPLEMENTATION
|
||||
template <class RawAllocator, class Mutex = std::mutex>
|
||||
WPI_ALIAS_TEMPLATE(thread_safe_allocator,
|
||||
allocator_storage<direct_storage<RawAllocator>, Mutex>);
|
||||
#else
|
||||
template <class RawAllocator, class Mutex>
|
||||
WPI_ALIAS_TEMPLATE(thread_safe_allocator,
|
||||
allocator_storage<direct_storage<RawAllocator>, Mutex>);
|
||||
#endif
|
||||
|
||||
#if WPI_HOSTED_IMPLEMENTATION
|
||||
/// \returns A new \ref thread_safe_allocator object created by forwarding to the constructor/
|
||||
/// \relates thread_safe_allocator
|
||||
template <class RawAllocator>
|
||||
auto make_thread_safe_allocator(RawAllocator&& allocator)
|
||||
-> thread_safe_allocator<typename std::decay<RawAllocator>::type>
|
||||
{
|
||||
return detail::forward<RawAllocator>(allocator);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// \returns A new \ref thread_safe_allocator object created by forwarding to the constructor,
|
||||
/// specifying a certain mutex type.
|
||||
/// \requires It requires threading support from the implementation.
|
||||
/// \relates thread_safe_allocator
|
||||
template <class Mutex, class RawAllocator>
|
||||
auto make_thread_safe_allocator(RawAllocator&& allocator)
|
||||
-> thread_safe_allocator<typename std::decay<RawAllocator>::type, Mutex>
|
||||
{
|
||||
return detail::forward<RawAllocator>(allocator);
|
||||
}
|
||||
|
||||
namespace detail
|
||||
{
|
||||
struct reference_stateful
|
||||
{
|
||||
};
|
||||
struct reference_stateless
|
||||
{
|
||||
};
|
||||
struct reference_shared
|
||||
{
|
||||
};
|
||||
|
||||
reference_stateful reference_type(std::true_type stateful, std::false_type shared);
|
||||
reference_stateless reference_type(std::false_type stateful, std::true_type shared);
|
||||
reference_stateless reference_type(std::false_type stateful, std::false_type shared);
|
||||
reference_shared reference_type(std::true_type stateful, std::true_type shared);
|
||||
|
||||
template <class RawAllocator, class Tag>
|
||||
class reference_storage_impl;
|
||||
|
||||
// reference to stateful: stores a pointer to an allocator
|
||||
template <class RawAllocator>
|
||||
class reference_storage_impl<RawAllocator, reference_stateful>
|
||||
{
|
||||
protected:
|
||||
reference_storage_impl() noexcept : alloc_(nullptr) {}
|
||||
|
||||
reference_storage_impl(RawAllocator& allocator) noexcept : alloc_(&allocator) {}
|
||||
|
||||
bool is_valid() const noexcept
|
||||
{
|
||||
return alloc_ != nullptr;
|
||||
}
|
||||
|
||||
RawAllocator& get_allocator() const noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(alloc_ != nullptr);
|
||||
return *alloc_;
|
||||
}
|
||||
|
||||
private:
|
||||
RawAllocator* alloc_;
|
||||
};
|
||||
|
||||
// reference to stateless: store in static storage
|
||||
template <class RawAllocator>
|
||||
class reference_storage_impl<RawAllocator, reference_stateless>
|
||||
{
|
||||
protected:
|
||||
reference_storage_impl() noexcept = default;
|
||||
|
||||
reference_storage_impl(const RawAllocator&) noexcept {}
|
||||
|
||||
bool is_valid() const noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
RawAllocator& get_allocator() const noexcept
|
||||
{
|
||||
static RawAllocator alloc;
|
||||
return alloc;
|
||||
}
|
||||
};
|
||||
|
||||
// reference to shared: stores RawAllocator directly
|
||||
template <class RawAllocator>
|
||||
class reference_storage_impl<RawAllocator, reference_shared>
|
||||
{
|
||||
protected:
|
||||
reference_storage_impl() noexcept = default;
|
||||
|
||||
reference_storage_impl(const RawAllocator& alloc) noexcept : alloc_(alloc) {}
|
||||
|
||||
bool is_valid() const noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
RawAllocator& get_allocator() const noexcept
|
||||
{
|
||||
return alloc_;
|
||||
}
|
||||
|
||||
private:
|
||||
mutable RawAllocator alloc_;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
/// Specifies whether or not a RawAllocator has shared semantics.
|
||||
/// It is shared, if - like \ref allocator_reference - if multiple objects refer to the same internal allocator and if it can be copied.
|
||||
/// This sharing is stateful, however, stateless allocators are not considered shared in the meaning of this traits. <br>
|
||||
/// If a \c RawAllocator is shared, it will be directly embedded inside \ref reference_storage since it already provides \ref allocator_reference like semantics, so there is no need to add them manually,<br>
|
||||
/// Specialize it for your own types, if they provide sharing semantics and can be copied.
|
||||
/// They also must provide an `operator==` to check whether two allocators refer to the same shared one.
|
||||
/// \note This makes no guarantees about the lifetime of the shared object, the sharing allocators can either own or refer to a shared object.
|
||||
/// \ingroup memory_storage
|
||||
template <class RawAllocator>
|
||||
struct is_shared_allocator : std::false_type
|
||||
{
|
||||
};
|
||||
|
||||
/// A StoragePolicy that stores a reference to an allocator.
|
||||
/// For stateful allocators it only stores a pointer to an allocator object and copying/moving only copies the pointer.
|
||||
/// For stateless allocators it does not store anything, an allocator will be constructed as needed.
|
||||
/// For allocators that are already shared (determined through \ref is_shared_allocator) it will store the allocator type directly.
|
||||
/// \note It does not take ownership over the allocator in the stateful case, the user has to ensure that the allocator object stays valid.
|
||||
/// In the other cases the lifetime does not matter.
|
||||
/// \ingroup memory_storage
|
||||
template <class RawAllocator>
|
||||
class reference_storage
|
||||
#ifndef DOXYGEN
|
||||
: WPI_EBO(detail::reference_storage_impl<
|
||||
typename allocator_traits<RawAllocator>::allocator_type,
|
||||
decltype(detail::reference_type(
|
||||
typename allocator_traits<RawAllocator>::is_stateful{},
|
||||
is_shared_allocator<RawAllocator>{}))>)
|
||||
#endif
|
||||
{
|
||||
using storage = detail::reference_storage_impl<
|
||||
typename allocator_traits<RawAllocator>::allocator_type,
|
||||
decltype(detail::reference_type(typename allocator_traits<
|
||||
RawAllocator>::is_stateful{},
|
||||
is_shared_allocator<RawAllocator>{}))>;
|
||||
|
||||
public:
|
||||
using allocator_type = typename allocator_traits<RawAllocator>::allocator_type;
|
||||
|
||||
/// Default constructor.
|
||||
/// \effects If the allocator is stateless, this has no effect and the object is usable as an allocator.
|
||||
/// If the allocator is stateful, creates an invalid reference without any associated allocator.
|
||||
/// Then it must not be used.
|
||||
/// If the allocator is shared, default constructs the shared allocator.
|
||||
/// If the shared allocator does not have a default constructor, this constructor is ill-formed.
|
||||
reference_storage() noexcept = default;
|
||||
|
||||
/// \effects Creates it from a stateless or shared allocator.
|
||||
/// It will not store anything, only creates the allocator as needed.
|
||||
/// \requires The \c RawAllocator is stateless or shared.
|
||||
reference_storage(const allocator_type& alloc) noexcept : storage(alloc) {}
|
||||
|
||||
/// \effects Creates it from a reference to a stateful allocator.
|
||||
/// It will store a pointer to this allocator object.
|
||||
/// \note The user has to take care that the lifetime of the reference does not exceed the allocator lifetime.
|
||||
reference_storage(allocator_type& alloc) noexcept : storage(alloc) {}
|
||||
|
||||
/// @{
|
||||
/// \effects Copies the \c allocator_reference object.
|
||||
/// Only copies the pointer to it in the stateful case.
|
||||
reference_storage(const reference_storage&) noexcept = default;
|
||||
reference_storage& operator=(const reference_storage&) noexcept = default;
|
||||
/// @}
|
||||
|
||||
/// \returns Whether or not the reference is valid.
|
||||
/// It is only invalid, if it was created by the default constructor and the allocator is stateful.
|
||||
explicit operator bool() const noexcept
|
||||
{
|
||||
return storage::is_valid();
|
||||
}
|
||||
|
||||
/// \returns Returns a reference to the allocator.
|
||||
/// \requires The reference must be valid.
|
||||
allocator_type& get_allocator() const noexcept
|
||||
{
|
||||
return storage::get_allocator();
|
||||
}
|
||||
|
||||
protected:
|
||||
~reference_storage() noexcept = default;
|
||||
|
||||
bool is_composable() const noexcept
|
||||
{
|
||||
return is_composable_allocator<allocator_type>::value;
|
||||
}
|
||||
};
|
||||
|
||||
/// Specialization of the class template \ref reference_storage that is type-erased.
|
||||
/// It is triggered by the tag type \ref any_allocator.
|
||||
/// The specialization can store a reference to any allocator type.
|
||||
/// \ingroup memory_storage
|
||||
template <>
|
||||
class reference_storage<any_allocator>
|
||||
{
|
||||
class base_allocator
|
||||
{
|
||||
public:
|
||||
using is_stateful = std::true_type;
|
||||
|
||||
virtual ~base_allocator() = default;
|
||||
|
||||
virtual void clone(void* storage) const noexcept = 0;
|
||||
|
||||
void* allocate_node(std::size_t size, std::size_t alignment)
|
||||
{
|
||||
return allocate_impl(1, size, alignment);
|
||||
}
|
||||
|
||||
void* allocate_array(std::size_t count, std::size_t size, std::size_t alignment)
|
||||
{
|
||||
return allocate_impl(count, size, alignment);
|
||||
}
|
||||
|
||||
void deallocate_node(void* node, std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
deallocate_impl(node, 1, size, alignment);
|
||||
}
|
||||
|
||||
void deallocate_array(void* array, std::size_t count, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
{
|
||||
deallocate_impl(array, count, size, alignment);
|
||||
}
|
||||
|
||||
void* try_allocate_node(std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
return try_allocate_impl(1, size, alignment);
|
||||
}
|
||||
|
||||
void* try_allocate_array(std::size_t count, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
{
|
||||
return try_allocate_impl(count, size, alignment);
|
||||
}
|
||||
|
||||
bool try_deallocate_node(void* node, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
{
|
||||
return try_deallocate_impl(node, 1, size, alignment);
|
||||
}
|
||||
|
||||
bool try_deallocate_array(void* array, std::size_t count, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
{
|
||||
return try_deallocate_impl(array, count, size, alignment);
|
||||
}
|
||||
|
||||
// count 1 means node
|
||||
virtual void* allocate_impl(std::size_t count, std::size_t size,
|
||||
std::size_t alignment) = 0;
|
||||
virtual void deallocate_impl(void* ptr, std::size_t count, std::size_t size,
|
||||
std::size_t alignment) noexcept = 0;
|
||||
|
||||
virtual void* try_allocate_impl(std::size_t count, std::size_t size,
|
||||
std::size_t alignment) noexcept = 0;
|
||||
|
||||
virtual bool try_deallocate_impl(void* ptr, std::size_t count, std::size_t size,
|
||||
std::size_t alignment) noexcept = 0;
|
||||
|
||||
std::size_t max_node_size() const
|
||||
{
|
||||
return max(query::node_size);
|
||||
}
|
||||
|
||||
std::size_t max_array_size() const
|
||||
{
|
||||
return max(query::array_size);
|
||||
}
|
||||
|
||||
std::size_t max_alignment() const
|
||||
{
|
||||
return max(query::alignment);
|
||||
}
|
||||
|
||||
virtual bool is_composable() const noexcept = 0;
|
||||
|
||||
protected:
|
||||
enum class query
|
||||
{
|
||||
node_size,
|
||||
array_size,
|
||||
alignment
|
||||
};
|
||||
|
||||
virtual std::size_t max(query q) const = 0;
|
||||
};
|
||||
|
||||
public:
|
||||
using allocator_type = WPI_IMPL_DEFINED(base_allocator);
|
||||
|
||||
/// \effects Creates it from a reference to any stateful RawAllocator.
|
||||
/// It will store a pointer to this allocator object.
|
||||
/// \note The user has to take care that the lifetime of the reference does not exceed the allocator lifetime.
|
||||
template <class RawAllocator>
|
||||
reference_storage(RawAllocator& alloc) noexcept
|
||||
{
|
||||
static_assert(sizeof(basic_allocator<RawAllocator>)
|
||||
<= sizeof(basic_allocator<default_instantiation>),
|
||||
"requires all instantiations to have certain maximum size");
|
||||
::new (static_cast<void*>(&storage_)) basic_allocator<RawAllocator>(alloc);
|
||||
}
|
||||
|
||||
// \effects Creates it from any stateless RawAllocator.
|
||||
/// It will not store anything, only creates the allocator as needed.
|
||||
/// \requires The \c RawAllocator is stateless.
|
||||
template <class RawAllocator>
|
||||
reference_storage(
|
||||
const RawAllocator& alloc,
|
||||
WPI_REQUIRES(!allocator_traits<RawAllocator>::is_stateful::value)) noexcept
|
||||
{
|
||||
static_assert(sizeof(basic_allocator<RawAllocator>)
|
||||
<= sizeof(basic_allocator<default_instantiation>),
|
||||
"requires all instantiations to have certain maximum size");
|
||||
::new (static_cast<void*>(&storage_)) basic_allocator<RawAllocator>(alloc);
|
||||
}
|
||||
|
||||
/// \effects Creates it from the internal base class for the type-erasure.
|
||||
/// Has the same effect as if the actual stored allocator were passed to the other constructor overloads.
|
||||
/// \note This constructor is used internally to avoid double-nesting.
|
||||
reference_storage(const WPI_IMPL_DEFINED(base_allocator) & alloc) noexcept
|
||||
{
|
||||
alloc.clone(&storage_);
|
||||
}
|
||||
|
||||
/// \effects Creates it from the internal base class for the type-erasure.
|
||||
/// Has the same effect as if the actual stored allocator were passed to the other constructor overloads.
|
||||
/// \note This constructor is used internally to avoid double-nesting.
|
||||
reference_storage(WPI_IMPL_DEFINED(base_allocator) & alloc) noexcept
|
||||
: reference_storage(static_cast<const base_allocator&>(alloc))
|
||||
{
|
||||
}
|
||||
|
||||
/// @{
|
||||
/// \effects Copies the \c reference_storage object.
|
||||
/// It only copies the pointer to the allocator.
|
||||
reference_storage(const reference_storage& other) noexcept
|
||||
{
|
||||
other.get_allocator().clone(&storage_);
|
||||
}
|
||||
|
||||
reference_storage& operator=(const reference_storage& other) noexcept
|
||||
{
|
||||
get_allocator().~allocator_type();
|
||||
other.get_allocator().clone(&storage_);
|
||||
return *this;
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// \returns A reference to the allocator.
|
||||
/// The actual type is implementation-defined since it is the base class used in the type-erasure,
|
||||
/// but it provides the full RawAllocator member functions.
|
||||
/// \note There is no way to access any custom member functions of the allocator type.
|
||||
allocator_type& get_allocator() const noexcept
|
||||
{
|
||||
auto mem = static_cast<void*>(&storage_);
|
||||
return *static_cast<base_allocator*>(mem);
|
||||
}
|
||||
|
||||
protected:
|
||||
~reference_storage() noexcept
|
||||
{
|
||||
get_allocator().~allocator_type();
|
||||
}
|
||||
|
||||
bool is_composable() const noexcept
|
||||
{
|
||||
return get_allocator().is_composable();
|
||||
}
|
||||
|
||||
private:
|
||||
template <class RawAllocator>
|
||||
class basic_allocator
|
||||
: public base_allocator,
|
||||
private detail::reference_storage_impl<
|
||||
typename allocator_traits<RawAllocator>::allocator_type,
|
||||
decltype(detail::reference_type(typename allocator_traits<
|
||||
RawAllocator>::is_stateful{},
|
||||
is_shared_allocator<RawAllocator>{}))>
|
||||
{
|
||||
using traits = allocator_traits<RawAllocator>;
|
||||
using composable = is_composable_allocator<typename traits::allocator_type>;
|
||||
using storage = detail::reference_storage_impl<
|
||||
typename allocator_traits<RawAllocator>::allocator_type,
|
||||
decltype(detail::reference_type(typename allocator_traits<
|
||||
RawAllocator>::is_stateful{},
|
||||
is_shared_allocator<RawAllocator>{}))>;
|
||||
|
||||
public:
|
||||
// non stateful
|
||||
basic_allocator(const RawAllocator& alloc) noexcept : storage(alloc) {}
|
||||
|
||||
// stateful
|
||||
basic_allocator(RawAllocator& alloc) noexcept : storage(alloc) {}
|
||||
|
||||
private:
|
||||
typename traits::allocator_type& get() const noexcept
|
||||
{
|
||||
return storage::get_allocator();
|
||||
}
|
||||
|
||||
void clone(void* storage) const noexcept override
|
||||
{
|
||||
::new (storage) basic_allocator(get());
|
||||
}
|
||||
|
||||
void* allocate_impl(std::size_t count, std::size_t size,
|
||||
std::size_t alignment) override
|
||||
{
|
||||
auto&& alloc = get();
|
||||
if (count == 1u)
|
||||
return traits::allocate_node(alloc, size, alignment);
|
||||
else
|
||||
return traits::allocate_array(alloc, count, size, alignment);
|
||||
}
|
||||
|
||||
void deallocate_impl(void* ptr, std::size_t count, std::size_t size,
|
||||
std::size_t alignment) noexcept override
|
||||
{
|
||||
auto&& alloc = get();
|
||||
if (count == 1u)
|
||||
traits::deallocate_node(alloc, ptr, size, alignment);
|
||||
else
|
||||
traits::deallocate_array(alloc, ptr, count, size, alignment);
|
||||
}
|
||||
|
||||
void* try_allocate_impl(std::size_t count, std::size_t size,
|
||||
std::size_t alignment) noexcept override
|
||||
{
|
||||
auto&& alloc = get();
|
||||
if (count == 1u)
|
||||
return detail::try_allocate_node(composable{}, alloc, size, alignment);
|
||||
else
|
||||
return detail::try_allocate_array(composable{}, alloc, count, size,
|
||||
alignment);
|
||||
}
|
||||
|
||||
bool try_deallocate_impl(void* ptr, std::size_t count, std::size_t size,
|
||||
std::size_t alignment) noexcept override
|
||||
{
|
||||
auto&& alloc = get();
|
||||
if (count == 1u)
|
||||
return detail::try_deallocate_node(composable{}, alloc, ptr, size,
|
||||
alignment);
|
||||
else
|
||||
return detail::try_deallocate_array(composable{}, alloc, ptr, count, size,
|
||||
alignment);
|
||||
}
|
||||
|
||||
bool is_composable() const noexcept override
|
||||
{
|
||||
return composable::value;
|
||||
}
|
||||
|
||||
std::size_t max(query q) const override
|
||||
{
|
||||
auto&& alloc = get();
|
||||
if (q == query::node_size)
|
||||
return traits::max_node_size(alloc);
|
||||
else if (q == query::array_size)
|
||||
return traits::max_array_size(alloc);
|
||||
return traits::max_alignment(alloc);
|
||||
}
|
||||
};
|
||||
|
||||
// use a stateful instantiation to determine size and alignment
|
||||
// base_allocator is stateful
|
||||
using default_instantiation = basic_allocator<base_allocator>;
|
||||
alignas(default_instantiation) mutable char storage_[sizeof(default_instantiation)];
|
||||
};
|
||||
|
||||
/// An alias template for \ref allocator_storage using the \ref reference_storage policy.
|
||||
/// It will store a reference to the given allocator type. The tag type \ref any_allocator enables type-erasure.
|
||||
/// Wrap the allocator in a \ref thread_safe_allocator if you want thread safety.
|
||||
/// \ingroup memory_storage
|
||||
template <class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(allocator_reference,
|
||||
allocator_storage<reference_storage<RawAllocator>, no_mutex>);
|
||||
|
||||
/// \returns A new \ref allocator_reference object by forwarding the allocator to the constructor.
|
||||
/// \relates allocator_reference
|
||||
template <class RawAllocator>
|
||||
auto make_allocator_reference(RawAllocator&& allocator) noexcept
|
||||
-> allocator_reference<typename std::decay<RawAllocator>::type>
|
||||
{
|
||||
return {detail::forward<RawAllocator>(allocator)};
|
||||
}
|
||||
|
||||
/// An alias for the \ref reference_storage specialization using type-erasure.
|
||||
/// \ingroup memory_storage
|
||||
using any_reference_storage = reference_storage<any_allocator>;
|
||||
|
||||
/// An alias for \ref allocator_storage using the \ref any_reference_storage.
|
||||
/// It will store a reference to any RawAllocator.
|
||||
/// This is the same as passing the tag type \ref any_allocator to the alias \ref allocator_reference.
|
||||
/// Wrap the allocator in a \ref thread_safe_allocator if you want thread safety.
|
||||
/// \ingroup memory_storage
|
||||
using any_allocator_reference = allocator_storage<any_reference_storage, no_mutex>;
|
||||
|
||||
/// \returns A new \ref any_allocator_reference object by forwarding the allocator to the constructor.
|
||||
/// \relates any_allocator_reference
|
||||
template <class RawAllocator>
|
||||
auto make_any_allocator_reference(RawAllocator&& allocator) noexcept
|
||||
-> any_allocator_reference
|
||||
{
|
||||
return {detail::forward<RawAllocator>(allocator)};
|
||||
}
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_ALLOCATOR_STORAGE_HPP_INCLUDED
|
||||
@@ -1,601 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_ALLOCATOR_TRAITS_HPP_INCLUDED
|
||||
#define WPI_MEMORY_ALLOCATOR_TRAITS_HPP_INCLUDED
|
||||
|
||||
/// \file
|
||||
/// The default specialization of the \ref wpi::memory::allocator_traits.
|
||||
|
||||
#include <cstddef>
|
||||
#include <type_traits>
|
||||
|
||||
#include "detail/align.hpp"
|
||||
#include "detail/utility.hpp"
|
||||
#include "config.hpp"
|
||||
|
||||
#if WPI_HOSTED_IMPLEMENTATION
|
||||
#include <memory>
|
||||
#endif
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
template <class Allocator>
|
||||
std::true_type has_construct(int, WPI_SFINAE(std::declval<Allocator>().construct(
|
||||
std::declval<typename Allocator::pointer>(),
|
||||
std::declval<typename Allocator::value_type>())));
|
||||
|
||||
template <class Allocator>
|
||||
std::false_type has_construct(short);
|
||||
|
||||
template <class Allocator>
|
||||
std::true_type has_destroy(int, WPI_SFINAE(std::declval<Allocator>().destroy(
|
||||
std::declval<typename Allocator::pointer>())));
|
||||
|
||||
template <class Allocator>
|
||||
std::false_type has_destroy(short);
|
||||
|
||||
template <class Allocator>
|
||||
struct check_standard_allocator
|
||||
{
|
||||
using custom_construct = decltype(has_construct<Allocator>(0));
|
||||
using custom_destroy = decltype(has_destroy<Allocator>(0));
|
||||
|
||||
using valid = std::integral_constant<bool, !custom_construct::value
|
||||
&& !custom_destroy::value>;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
/// Traits class that checks whether or not a standard \c Allocator can be used as RawAllocator.
|
||||
/// It checks the existence of a custom \c construct(), \c destroy() function, if provided,
|
||||
/// it cannot be used since it would not be called.<br>
|
||||
/// Specialize it for custom \c Allocator types to override this check.
|
||||
/// \ingroup memory_core
|
||||
template <class Allocator>
|
||||
struct allocator_is_raw_allocator
|
||||
: WPI_EBO(detail::check_standard_allocator<Allocator>::valid)
|
||||
{
|
||||
};
|
||||
|
||||
/// Specialization of \ref allocator_is_raw_allocator that allows \c std::allocator again.
|
||||
/// \ingroup memory_core
|
||||
template <typename T>
|
||||
struct allocator_is_raw_allocator<std::allocator<T>> : std::true_type
|
||||
{
|
||||
};
|
||||
|
||||
namespace traits_detail // use seperate namespace to avoid name clashes
|
||||
{
|
||||
// full_concept has the best conversion rank, error the lowest
|
||||
// used to give priority to the functions
|
||||
struct error
|
||||
{
|
||||
operator void*() const noexcept
|
||||
{
|
||||
WPI_MEMORY_UNREACHABLE(
|
||||
"this is just to hide an error and move static_assert to the front");
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
struct std_concept : error
|
||||
{
|
||||
};
|
||||
struct min_concept : std_concept
|
||||
{
|
||||
};
|
||||
struct full_concept : min_concept
|
||||
{
|
||||
};
|
||||
|
||||
// used to delay assert in handle_error() until instantiation
|
||||
template <typename T>
|
||||
struct invalid_allocator_concept
|
||||
{
|
||||
static const bool error = false;
|
||||
};
|
||||
|
||||
//=== allocator_type ===//
|
||||
// if Allocator has a member template `rebind`, use that to rebind to `char`
|
||||
// else if Allocator has a member `value_type`, rebind by changing argument
|
||||
// else does nothing
|
||||
template <class Allocator>
|
||||
auto rebind_impl(int) -> typename Allocator::template rebind<char>::other&;
|
||||
|
||||
template <class Allocator, typename T>
|
||||
struct allocator_rebinder
|
||||
{
|
||||
using type = Allocator&;
|
||||
};
|
||||
|
||||
template <template <typename, typename...> class Alloc, typename U, typename... Args,
|
||||
typename T>
|
||||
struct allocator_rebinder<Alloc<U, Args...>, T>
|
||||
{
|
||||
using type = Alloc<T, Args...>&;
|
||||
};
|
||||
|
||||
template <class Allocator, typename = typename Allocator::value_type>
|
||||
auto rebind_impl(char) -> typename allocator_rebinder<Allocator, char>::type;
|
||||
|
||||
template <class Allocator>
|
||||
auto rebind_impl(...) -> Allocator&;
|
||||
|
||||
template <class Allocator>
|
||||
struct allocator_type_impl // required for MSVC
|
||||
{
|
||||
using type = decltype(rebind_impl<Allocator>(0));
|
||||
};
|
||||
|
||||
template <class Allocator>
|
||||
using allocator_type =
|
||||
typename std::decay<typename allocator_type_impl<Allocator>::type>::type;
|
||||
|
||||
//=== is_stateful ===//
|
||||
// first try to access Allocator::is_stateful,
|
||||
// then use whether or not the type is empty
|
||||
template <class Allocator>
|
||||
auto is_stateful(full_concept) -> decltype(typename Allocator::is_stateful{});
|
||||
|
||||
template <class Allocator, bool IsEmpty>
|
||||
struct is_stateful_impl;
|
||||
|
||||
template <class Allocator>
|
||||
struct is_stateful_impl<Allocator, true>
|
||||
{
|
||||
static_assert(std::is_default_constructible<Allocator>::value,
|
||||
"RawAllocator is empty but not default constructible ."
|
||||
"This means it is not a stateless allocator. "
|
||||
"If this is actually intended provide the appropriate is_stateful "
|
||||
"typedef in your class.");
|
||||
using type = std::false_type;
|
||||
};
|
||||
|
||||
template <class Allocator>
|
||||
struct is_stateful_impl<Allocator, false>
|
||||
{
|
||||
using type = std::true_type;
|
||||
};
|
||||
|
||||
template <class Allocator>
|
||||
auto is_stateful(min_concept) ->
|
||||
typename is_stateful_impl<Allocator, std::is_empty<Allocator>::value>::type;
|
||||
|
||||
//=== allocate_node() ===//
|
||||
// first try Allocator::allocate_node
|
||||
// then assume std_allocator and call Allocator::allocate
|
||||
// then error
|
||||
template <class Allocator>
|
||||
auto allocate_node(full_concept, Allocator& alloc, std::size_t size,
|
||||
std::size_t alignment)
|
||||
-> WPI_AUTO_RETURN_TYPE(alloc.allocate_node(size, alignment), void*)
|
||||
|
||||
template <class Allocator>
|
||||
auto allocate_node(std_concept, Allocator& alloc, std::size_t size, std::size_t)
|
||||
-> WPI_AUTO_RETURN(static_cast<void*>(alloc.allocate(size)))
|
||||
|
||||
template <class Allocator>
|
||||
error allocate_node(error, Allocator&, std::size_t, std::size_t)
|
||||
{
|
||||
static_assert(invalid_allocator_concept<Allocator>::error,
|
||||
"type is not a RawAllocator as it does not provide: void* "
|
||||
"allocate_node(std::size_t, "
|
||||
"std::size_t)");
|
||||
return {};
|
||||
}
|
||||
|
||||
//=== deallocate_node() ===//
|
||||
// first try Allocator::deallocate_node
|
||||
// then assume std_allocator and call Allocator::deallocate
|
||||
// then error
|
||||
template <class Allocator>
|
||||
auto deallocate_node(full_concept, Allocator& alloc, void* ptr, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
-> WPI_AUTO_RETURN_TYPE(alloc.deallocate_node(ptr, size, alignment), void)
|
||||
|
||||
template <class Allocator>
|
||||
auto deallocate_node(std_concept, Allocator& alloc, void* ptr, std::size_t size,
|
||||
std::size_t) noexcept
|
||||
-> WPI_AUTO_RETURN_TYPE(alloc.deallocate(static_cast<char*>(ptr), size), void)
|
||||
|
||||
template <class Allocator>
|
||||
error deallocate_node(error, Allocator&, void*, std::size_t, std::size_t)
|
||||
{
|
||||
static_assert(invalid_allocator_concept<Allocator>::error,
|
||||
"type is not a RawAllocator as it does not provide: void "
|
||||
"deallocate_node(void*, std::size_t, "
|
||||
"std::size_t)");
|
||||
return error{};
|
||||
}
|
||||
|
||||
//=== allocate_array() ===//
|
||||
// first try Allocator::allocate_array
|
||||
// then forward to allocate_node()
|
||||
template <class Allocator>
|
||||
auto allocate_array(full_concept, Allocator& alloc, std::size_t count, std::size_t size,
|
||||
std::size_t alignment)
|
||||
-> WPI_AUTO_RETURN_TYPE(alloc.allocate_array(count, size, alignment), void*)
|
||||
|
||||
template <class Allocator>
|
||||
void* allocate_array(min_concept, Allocator& alloc, std::size_t count,
|
||||
std::size_t size, std::size_t alignment)
|
||||
{
|
||||
return allocate_node(full_concept{}, alloc, count * size, alignment);
|
||||
}
|
||||
|
||||
//=== deallocate_array() ===//
|
||||
// first try Allocator::deallocate_array
|
||||
// then forward to deallocate_node()
|
||||
template <class Allocator>
|
||||
auto deallocate_array(full_concept, Allocator& alloc, void* ptr, std::size_t count,
|
||||
std::size_t size, std::size_t alignment) noexcept
|
||||
-> WPI_AUTO_RETURN_TYPE(alloc.deallocate_array(ptr, count, size, alignment),
|
||||
void)
|
||||
|
||||
template <class Allocator>
|
||||
void deallocate_array(min_concept, Allocator& alloc, void* ptr,
|
||||
std::size_t count, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
{
|
||||
deallocate_node(full_concept{}, alloc, ptr, count * size, alignment);
|
||||
}
|
||||
|
||||
//=== max_node_size() ===//
|
||||
// first try Allocator::max_node_size()
|
||||
// then return maximum value
|
||||
template <class Allocator>
|
||||
auto max_node_size(full_concept, const Allocator& alloc)
|
||||
-> WPI_AUTO_RETURN_TYPE(alloc.max_node_size(), std::size_t)
|
||||
|
||||
template <class Allocator>
|
||||
std::size_t max_node_size(min_concept, const Allocator&) noexcept
|
||||
{
|
||||
return std::size_t(-1);
|
||||
}
|
||||
|
||||
//=== max_node_size() ===//
|
||||
// first try Allocator::max_array_size()
|
||||
// then forward to max_node_size()
|
||||
template <class Allocator>
|
||||
auto max_array_size(full_concept, const Allocator& alloc)
|
||||
-> WPI_AUTO_RETURN_TYPE(alloc.max_array_size(), std::size_t)
|
||||
|
||||
template <class Allocator>
|
||||
std::size_t max_array_size(min_concept, const Allocator& alloc)
|
||||
{
|
||||
return max_node_size(full_concept{}, alloc);
|
||||
}
|
||||
|
||||
//=== max_alignment() ===//
|
||||
// first try Allocator::max_alignment()
|
||||
// then return detail::max_alignment
|
||||
template <class Allocator>
|
||||
auto max_alignment(full_concept, const Allocator& alloc)
|
||||
-> WPI_AUTO_RETURN_TYPE(alloc.max_alignment(), std::size_t)
|
||||
|
||||
template <class Allocator>
|
||||
std::size_t max_alignment(min_concept, const Allocator&)
|
||||
{
|
||||
return detail::max_alignment;
|
||||
}
|
||||
} // namespace traits_detail
|
||||
|
||||
/// The default specialization of the allocator_traits for a RawAllocator.
|
||||
/// See the last link for the requirements on types that do not specialize this class and the interface documentation.
|
||||
/// Any specialization must provide the same interface.
|
||||
/// \ingroup memory_core
|
||||
template <class Allocator>
|
||||
class allocator_traits
|
||||
{
|
||||
public:
|
||||
using allocator_type = traits_detail::allocator_type<Allocator>;
|
||||
using is_stateful =
|
||||
decltype(traits_detail::is_stateful<Allocator>(traits_detail::full_concept{}));
|
||||
|
||||
static void* allocate_node(allocator_type& state, std::size_t size,
|
||||
std::size_t alignment)
|
||||
{
|
||||
static_assert(allocator_is_raw_allocator<Allocator>::value,
|
||||
"Allocator cannot be used as RawAllocator because it provides custom "
|
||||
"construct()/destroy()");
|
||||
return traits_detail::allocate_node(traits_detail::full_concept{}, state, size,
|
||||
alignment);
|
||||
}
|
||||
|
||||
static void* allocate_array(allocator_type& state, std::size_t count, std::size_t size,
|
||||
std::size_t alignment)
|
||||
{
|
||||
static_assert(allocator_is_raw_allocator<Allocator>::value,
|
||||
"Allocator cannot be used as RawAllocator because it provides custom "
|
||||
"construct()/destroy()");
|
||||
return traits_detail::allocate_array(traits_detail::full_concept{}, state, count,
|
||||
size, alignment);
|
||||
}
|
||||
|
||||
static void deallocate_node(allocator_type& state, void* node, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
{
|
||||
static_assert(allocator_is_raw_allocator<Allocator>::value,
|
||||
"Allocator cannot be used as RawAllocator because it provides custom "
|
||||
"construct()/destroy()");
|
||||
traits_detail::deallocate_node(traits_detail::full_concept{}, state, node, size,
|
||||
alignment);
|
||||
}
|
||||
|
||||
static void deallocate_array(allocator_type& state, void* array, std::size_t count,
|
||||
std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
static_assert(allocator_is_raw_allocator<Allocator>::value,
|
||||
"Allocator cannot be used as RawAllocator because it provides custom "
|
||||
"construct()/destroy()");
|
||||
traits_detail::deallocate_array(traits_detail::full_concept{}, state, array, count,
|
||||
size, alignment);
|
||||
}
|
||||
|
||||
static std::size_t max_node_size(const allocator_type& state)
|
||||
{
|
||||
static_assert(allocator_is_raw_allocator<Allocator>::value,
|
||||
"Allocator cannot be used as RawAllocator because it provides custom "
|
||||
"construct()/destroy()");
|
||||
return traits_detail::max_node_size(traits_detail::full_concept{}, state);
|
||||
}
|
||||
|
||||
static std::size_t max_array_size(const allocator_type& state)
|
||||
{
|
||||
static_assert(allocator_is_raw_allocator<Allocator>::value,
|
||||
"Allocator cannot be used as RawAllocator because it provides custom "
|
||||
"construct()/destroy()");
|
||||
return traits_detail::max_array_size(traits_detail::full_concept{}, state);
|
||||
}
|
||||
|
||||
static std::size_t max_alignment(const allocator_type& state)
|
||||
{
|
||||
static_assert(allocator_is_raw_allocator<Allocator>::value,
|
||||
"Allocator cannot be used as RawAllocator because it provides custom "
|
||||
"construct()/destroy()");
|
||||
return traits_detail::max_alignment(traits_detail::full_concept{}, state);
|
||||
}
|
||||
|
||||
#if !defined(DOXYGEN)
|
||||
using foonathan_memory_default_traits = std::true_type;
|
||||
#endif
|
||||
};
|
||||
|
||||
namespace detail
|
||||
{
|
||||
template <class RawAllocator>
|
||||
typename allocator_traits<RawAllocator>::foonathan_memory_default_traits
|
||||
alloc_uses_default_traits(RawAllocator&);
|
||||
|
||||
std::false_type alloc_uses_default_traits(...);
|
||||
|
||||
template <typename T>
|
||||
struct has_invalid_alloc_function
|
||||
: std::is_same<
|
||||
decltype(traits_detail::allocate_node(traits_detail::full_concept{},
|
||||
std::declval<typename allocator_traits<
|
||||
T>::allocator_type&>(),
|
||||
0, 0)),
|
||||
traits_detail::error>
|
||||
{
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct has_invalid_dealloc_function
|
||||
: std::is_same<
|
||||
decltype(traits_detail::deallocate_node(traits_detail::full_concept{},
|
||||
std::declval<typename allocator_traits<
|
||||
T>::allocator_type&>(),
|
||||
nullptr, 0, 0)),
|
||||
traits_detail::error>
|
||||
{
|
||||
};
|
||||
|
||||
template <typename T, class DefaultTraits>
|
||||
struct is_raw_allocator : std::true_type
|
||||
{
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct is_raw_allocator<T, std::integral_constant<bool, true>>
|
||||
: std::integral_constant<bool, allocator_is_raw_allocator<T>::value
|
||||
&& !(has_invalid_alloc_function<T>::value
|
||||
|| has_invalid_dealloc_function<T>::value)>
|
||||
{
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
/// Traits that check whether a type models concept RawAllocator.<br>
|
||||
/// It must either provide the necessary functions for the default traits specialization or has specialized it.
|
||||
/// \ingroup memory_core
|
||||
template <typename T>
|
||||
struct is_raw_allocator
|
||||
: detail::is_raw_allocator<T,
|
||||
decltype(detail::alloc_uses_default_traits(std::declval<T&>()))>
|
||||
{
|
||||
};
|
||||
|
||||
namespace traits_detail
|
||||
{
|
||||
//=== try_allocate_node() ===//
|
||||
// try Allocator::try_allocate_node
|
||||
// otherwise error
|
||||
template <class Allocator>
|
||||
auto try_allocate_node(full_concept, Allocator& alloc, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
-> WPI_AUTO_RETURN_TYPE(alloc.try_allocate_node(size, alignment), void*)
|
||||
|
||||
template <class Allocator>
|
||||
error try_allocate_node(error, Allocator&, std::size_t, std::size_t)
|
||||
{
|
||||
static_assert(invalid_allocator_concept<Allocator>::error,
|
||||
"type is not a composable RawAllocator as it does not provide: void* "
|
||||
"try_allocate_node(std::size_t, "
|
||||
"std::size_t)");
|
||||
return {};
|
||||
}
|
||||
|
||||
//=== try_deallocate_node() ===//
|
||||
// try Allocator::try_deallocate_node
|
||||
// otherwise error
|
||||
template <class Allocator>
|
||||
auto try_deallocate_node(full_concept, Allocator& alloc, void* ptr, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
-> WPI_AUTO_RETURN_TYPE(alloc.try_deallocate_node(ptr, size, alignment), bool)
|
||||
|
||||
template <class Allocator>
|
||||
error try_deallocate_node(error, Allocator&, void*, std::size_t, std::size_t)
|
||||
{
|
||||
static_assert(invalid_allocator_concept<Allocator>::error,
|
||||
"type is not a composable RawAllocator as it does not provide: bool "
|
||||
"try_deallocate_node(void*, std::size_t, "
|
||||
"std::size_t)");
|
||||
return error{};
|
||||
}
|
||||
|
||||
//=== try_allocate_array() ===//
|
||||
// first try Allocator::try_allocate_array
|
||||
// then forward to try_allocate_node()
|
||||
template <class Allocator>
|
||||
auto try_allocate_array(full_concept, Allocator& alloc, std::size_t count,
|
||||
std::size_t size, std::size_t alignment) noexcept
|
||||
-> WPI_AUTO_RETURN_TYPE(alloc.try_allocate_array(count, size, alignment),
|
||||
void*)
|
||||
|
||||
template <class Allocator>
|
||||
void* try_allocate_array(min_concept, Allocator& alloc, std::size_t count,
|
||||
std::size_t size, std::size_t alignment)
|
||||
{
|
||||
return try_allocate_node(full_concept{}, alloc, count * size, alignment);
|
||||
}
|
||||
|
||||
//=== try_deallocate_array() ===//
|
||||
// first try Allocator::try_deallocate_array
|
||||
// then forward to try_deallocate_node()
|
||||
template <class Allocator>
|
||||
auto try_deallocate_array(full_concept, Allocator& alloc, void* ptr, std::size_t count,
|
||||
std::size_t size, std::size_t alignment) noexcept
|
||||
-> WPI_AUTO_RETURN_TYPE(alloc.try_deallocate_array(ptr, count, size,
|
||||
alignment),
|
||||
bool)
|
||||
|
||||
template <class Allocator>
|
||||
bool try_deallocate_array(min_concept, Allocator& alloc, void* ptr,
|
||||
std::size_t count, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
{
|
||||
return try_deallocate_node(full_concept{}, alloc, ptr, count * size, alignment);
|
||||
}
|
||||
} // namespace traits_detail
|
||||
|
||||
/// The default specialization of the composable_allocator_traits for a ComposableAllocator.
|
||||
/// See the last link for the requirements on types that do not specialize this class and the interface documentation.
|
||||
/// Any specialization must provide the same interface.
|
||||
/// \ingroup memory_core
|
||||
template <class Allocator>
|
||||
class composable_allocator_traits
|
||||
{
|
||||
public:
|
||||
using allocator_type = typename allocator_traits<Allocator>::allocator_type;
|
||||
|
||||
static void* try_allocate_node(allocator_type& state, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
{
|
||||
static_assert(is_raw_allocator<Allocator>::value,
|
||||
"ComposableAllocator must be RawAllocator");
|
||||
return traits_detail::try_allocate_node(traits_detail::full_concept{}, state, size,
|
||||
alignment);
|
||||
}
|
||||
|
||||
static void* try_allocate_array(allocator_type& state, std::size_t count,
|
||||
std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
static_assert(is_raw_allocator<Allocator>::value,
|
||||
"ComposableAllocator must be RawAllocator");
|
||||
return traits_detail::try_allocate_array(traits_detail::full_concept{}, state,
|
||||
count, size, alignment);
|
||||
}
|
||||
|
||||
static bool try_deallocate_node(allocator_type& state, void* node, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
{
|
||||
static_assert(is_raw_allocator<Allocator>::value,
|
||||
"ComposableAllocator must be RawAllocator");
|
||||
return traits_detail::try_deallocate_node(traits_detail::full_concept{}, state,
|
||||
node, size, alignment);
|
||||
}
|
||||
|
||||
static bool try_deallocate_array(allocator_type& state, void* array, std::size_t count,
|
||||
std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
static_assert(is_raw_allocator<Allocator>::value,
|
||||
"ComposableAllocator must be RawAllocator");
|
||||
return traits_detail::try_deallocate_array(traits_detail::full_concept{}, state,
|
||||
array, count, size, alignment);
|
||||
}
|
||||
|
||||
#if !defined(DOXYGEN)
|
||||
using foonathan_memory_default_traits = std::true_type;
|
||||
#endif
|
||||
};
|
||||
|
||||
namespace detail
|
||||
{
|
||||
template <class RawAllocator>
|
||||
typename composable_allocator_traits<RawAllocator>::foonathan_memory_default_traits
|
||||
composable_alloc_uses_default_traits(RawAllocator&);
|
||||
|
||||
std::false_type composable_alloc_uses_default_traits(...);
|
||||
|
||||
template <typename T>
|
||||
struct has_invalid_try_alloc_function
|
||||
: std::is_same<
|
||||
decltype(traits_detail::try_allocate_node(traits_detail::full_concept{},
|
||||
std::declval<typename allocator_traits<
|
||||
T>::allocator_type&>(),
|
||||
0, 0)),
|
||||
traits_detail::error>
|
||||
{
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct has_invalid_try_dealloc_function
|
||||
: std::is_same<decltype(traits_detail::
|
||||
try_deallocate_node(traits_detail::full_concept{},
|
||||
std::declval<typename allocator_traits<
|
||||
T>::allocator_type&>(),
|
||||
nullptr, 0, 0)),
|
||||
traits_detail::error>
|
||||
{
|
||||
};
|
||||
|
||||
template <typename T, class DefaultTraits>
|
||||
struct is_composable_allocator : memory::is_raw_allocator<T>
|
||||
{
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct is_composable_allocator<T, std::integral_constant<bool, true>>
|
||||
: std::integral_constant<bool, memory::is_raw_allocator<T>::value
|
||||
&& !(has_invalid_try_alloc_function<T>::value
|
||||
|| has_invalid_try_dealloc_function<T>::value)>
|
||||
{
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
/// Traits that check whether a type models concept ComposableAllocator.<br>
|
||||
/// It must be a RawAllocator and either provide the necessary functions for the default traits specialization or has specialized it.
|
||||
/// \ingroup memory_core
|
||||
template <typename T>
|
||||
struct is_composable_allocator
|
||||
: detail::is_composable_allocator<T, decltype(detail::composable_alloc_uses_default_traits(
|
||||
std::declval<T&>()))>
|
||||
{
|
||||
};
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_ALLOCATOR_TRAITS_HPP_INCLUDED
|
||||
@@ -1,147 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
/// \file
|
||||
/// Configuration macros.
|
||||
|
||||
#ifndef WPI_MEMORY_CONFIG_HPP_INCLUDED
|
||||
#define WPI_MEMORY_CONFIG_HPP_INCLUDED
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#if !defined(DOXYGEN)
|
||||
#define WPI_MEMORY_IMPL_IN_CONFIG_HPP
|
||||
#include "config_impl.hpp"
|
||||
#undef WPI_MEMORY_IMPL_IN_CONFIG_HPP
|
||||
#endif
|
||||
|
||||
// exception support
|
||||
#ifndef WPI_HAS_EXCEPTION_SUPPORT
|
||||
#if defined(__GNUC__) && !defined(__EXCEPTIONS)
|
||||
#define WPI_HAS_EXCEPTION_SUPPORT 0
|
||||
#elif defined(_MSC_VER) && !_HAS_EXCEPTIONS
|
||||
#define WPI_HAS_EXCEPTION_SUPPORT 0
|
||||
#else
|
||||
#define WPI_HAS_EXCEPTION_SUPPORT 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if WPI_HAS_EXCEPTION_SUPPORT
|
||||
#define WPI_THROW(Ex) throw(Ex)
|
||||
#else
|
||||
#include <cstdlib>
|
||||
#define WPI_THROW(Ex) ((Ex), std::abort())
|
||||
#endif
|
||||
|
||||
// hosted implementation
|
||||
#ifndef WPI_HOSTED_IMPLEMENTATION
|
||||
#if !_MSC_VER && !__STDC_HOSTED__
|
||||
#define WPI_HOSTED_IMPLEMENTATION 0
|
||||
#else
|
||||
#define WPI_HOSTED_IMPLEMENTATION 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// log prefix
|
||||
#define WPI_MEMORY_LOG_PREFIX "wpi::memory"
|
||||
|
||||
// version
|
||||
#define WPI_MEMORY_VERSION \
|
||||
(WPI_MEMORY_VERSION_MAJOR * 100 + WPI_MEMORY_VERSION_MINOR)
|
||||
|
||||
// use this macro to mark implementation-defined types
|
||||
// gives it more semantics and useful with doxygen
|
||||
// add PREDEFINED: WPI_IMPL_DEFINED():=implementation_defined
|
||||
#ifndef WPI_IMPL_DEFINED
|
||||
#define WPI_IMPL_DEFINED(...) __VA_ARGS__
|
||||
#endif
|
||||
|
||||
// use this macro to mark base class which only purpose is EBO
|
||||
// gives it more semantics and useful with doxygen
|
||||
// add PREDEFINED: WPI_EBO():=
|
||||
#ifndef WPI_EBO
|
||||
#define WPI_EBO(...) __VA_ARGS__
|
||||
#endif
|
||||
|
||||
#ifndef WPI_ALIAS_TEMPLATE
|
||||
// defines a template alias
|
||||
// usage:
|
||||
// template <typename T>
|
||||
// WPI_ALIAS_TEMPLATE(bar, foo<T, int>);
|
||||
// useful for doxygen
|
||||
#ifdef DOXYGEN
|
||||
#define WPI_ALIAS_TEMPLATE(Name, ...) \
|
||||
class Name : public __VA_ARGS__ \
|
||||
{ \
|
||||
}
|
||||
#else
|
||||
#define WPI_ALIAS_TEMPLATE(Name, ...) using Name = __VA_ARGS__
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef DOXYGEN
|
||||
// dummy definitions of config macros for doxygen
|
||||
|
||||
/// The major version number.
|
||||
/// \ingroup memory_core
|
||||
#define WPI_MEMORY_VERSION_MAJOR 1
|
||||
|
||||
/// The minor version number.
|
||||
/// \ingroup memory_core
|
||||
#define WPI_MEMORY_VERSION_MINOR 1
|
||||
|
||||
/// The total version number of the form \c Mmm.
|
||||
/// \ingroup memory_core
|
||||
#define WPI_MEMORY_VERSION \
|
||||
(WPI_MEMORY_VERSION_MAJOR * 100 + WPI_MEMORY_VERSION_MINOR)
|
||||
|
||||
/// Whether or not the allocation size will be checked,
|
||||
/// i.e. the \ref wpi::memory::bad_allocation_size thrown.
|
||||
/// \ingroup memory_core
|
||||
#define WPI_MEMORY_CHECK_ALLOCATION_SIZE 1
|
||||
|
||||
/// Whether or not internal assertions in the library are enabled.
|
||||
/// \ingroup memory_core
|
||||
#define WPI_MEMORY_DEBUG_ASSERT 1
|
||||
|
||||
/// Whether or not allocated memory will be filled with special values.
|
||||
/// \ingroup memory_core
|
||||
#define WPI_MEMORY_DEBUG_FILL 1
|
||||
|
||||
/// The size of the fence memory, it has no effect if \ref WPI_MEMORY_DEBUG_FILL is \c false.
|
||||
/// \note For most allocators, the actual value doesn't matter and they use appropriate defaults to ensure alignment etc.
|
||||
/// \ingroup memory_core
|
||||
#define WPI_MEMORY_DEBUG_FENCE 1
|
||||
|
||||
/// Whether or not leak checking is enabled.
|
||||
/// \ingroup memory_core
|
||||
#define WPI_MEMORY_DEBUG_LEAK_CHECK 1
|
||||
|
||||
/// Whether or not the deallocation functions will check for pointers that were never allocated by an allocator.
|
||||
/// \ingroup memory_core
|
||||
#define WPI_MEMORY_DEBUG_POINTER_CHECK 1
|
||||
|
||||
/// Whether or not the deallocation functions will check for double free errors.
|
||||
/// This option makes no sense if \ref WPI_MEMORY_DEBUG_POINTER_CHECK is \c false.
|
||||
/// \ingroup memory_core
|
||||
#define WPI_MEMORY_DEBUG_DOUBLE_DEALLOC_CHECK 1
|
||||
|
||||
/// Whether or not everything is in namespace <tt>wpi::memory</tt>.
|
||||
/// If \c false, a namespace alias <tt>namespace memory = wpi::memory</tt> is automatically inserted into each header,
|
||||
/// allowing to qualify everything with <tt>wpi::</tt>.
|
||||
/// \note This option breaks in combination with using <tt>using namespace wpi;</tt>.
|
||||
/// \ingroup memory_core
|
||||
#define WPI_MEMORY_NAMESPACE_PREFIX 1
|
||||
|
||||
/// The mode of the automatic \ref wpi::memory::temporary_stack creation.
|
||||
/// Set to `2` to enable automatic lifetime management of the per-thread stack through nifty counter.
|
||||
/// Then all memory will be freed upon program termination automatically.
|
||||
/// Set to `1` to disable automatic lifetime managment of the per-thread stack,
|
||||
/// requires managing it through the \ref wpi::memory::temporary_stack_initializer.
|
||||
/// Set to `0` to disable the per-thread stack completely.
|
||||
/// \ref wpi::memory::get_temporary_stack() will abort the program upon call.
|
||||
/// \ingroup memory_allocator
|
||||
#define WPI_MEMORY_TEMPORARY_STACK_MODE 2
|
||||
#endif
|
||||
|
||||
#endif // WPI_MEMORY_CONFIG_HPP_INCLUDED
|
||||
@@ -1,34 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
// Copyright (C) 2015-2020 Jonathan Müller <jonathanmueller.dev@gmail.com>
|
||||
// This file is subject to the license terms in the LICENSE file
|
||||
// found in the top-level directory of this distribution.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
//=== options ===//
|
||||
#define WPI_MEMORY_CHECK_ALLOCATION_SIZE 1
|
||||
#define WPI_MEMORY_IMPL_DEFAULT_ALLOCATOR heap_allocator
|
||||
#ifdef NDEBUG
|
||||
#define WPI_MEMORY_DEBUG_ASSERT 0
|
||||
#define WPI_MEMORY_DEBUG_FILL 0
|
||||
#define WPI_MEMORY_DEBUG_FENCE 0
|
||||
#define WPI_MEMORY_DEBUG_LEAK_CHECK 0
|
||||
#define WPI_MEMORY_DEBUG_POINTER_CHECK 0
|
||||
#define WPI_MEMORY_DEBUG_DOUBLE_DEALLOC_CHECK 0
|
||||
#else
|
||||
#define WPI_MEMORY_DEBUG_ASSERT 1
|
||||
#define WPI_MEMORY_DEBUG_FILL 1
|
||||
#define WPI_MEMORY_DEBUG_FENCE 8
|
||||
#define WPI_MEMORY_DEBUG_LEAK_CHECK 1
|
||||
#define WPI_MEMORY_DEBUG_POINTER_CHECK 1
|
||||
#define WPI_MEMORY_DEBUG_DOUBLE_DEALLOC_CHECK 1
|
||||
#endif
|
||||
#define WPI_MEMORY_EXTERN_TEMPLATE 1
|
||||
#define WPI_MEMORY_TEMPORARY_STACK_MODE 2
|
||||
|
||||
#define WPI_MEMORY_NO_NODE_SIZE 1
|
||||
@@ -1,367 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_CONTAINER_HPP_INCLUDED
|
||||
#define WPI_MEMORY_CONTAINER_HPP_INCLUDED
|
||||
|
||||
/// \file
|
||||
/// Aliasas for STL containers using a certain RawAllocator.
|
||||
/// \note Only available on a hosted implementation.
|
||||
|
||||
#include "config.hpp"
|
||||
#if !WPI_HOSTED_IMPLEMENTATION
|
||||
#error "This header is only available for a hosted implementation."
|
||||
#endif
|
||||
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
|
||||
#include <deque>
|
||||
#include <forward_list>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <queue>
|
||||
#include <scoped_allocator>
|
||||
#include <set>
|
||||
#include <stack>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "std_allocator.hpp"
|
||||
#include "threading.hpp"
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
/// \ingroup memory_adapter
|
||||
/// @{
|
||||
|
||||
/// Alias template for an STL container that uses a certain
|
||||
/// RawAllocator. It is just a shorthand for a passing in the \c
|
||||
/// RawAllocator wrapped in a \ref wpi::memory::std_allocator.
|
||||
template <typename T, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(vector, std::vector<T, std_allocator<T, RawAllocator>>);
|
||||
|
||||
/// Same as above but uses \c std::scoped_allocator_adaptor so the allocator is inherited by all
|
||||
/// nested containers.
|
||||
template <typename T, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(
|
||||
vector_scoped_alloc,
|
||||
std::vector<T, std::scoped_allocator_adaptor<std_allocator<T, RawAllocator>>>);
|
||||
|
||||
/// \copydoc vector
|
||||
template <typename T, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(deque, std::deque<T, std_allocator<T, RawAllocator>>);
|
||||
/// \copydoc vector_scoped_alloc
|
||||
template <typename T, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(
|
||||
deque_scoped_alloc,
|
||||
std::deque<T, std::scoped_allocator_adaptor<std_allocator<T, RawAllocator>>>);
|
||||
|
||||
/// \copydoc vector
|
||||
template <typename T, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(list, std::list<T, std_allocator<T, RawAllocator>>);
|
||||
/// \copydoc vector_scoped_alloc
|
||||
template <typename T, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(
|
||||
list_scoped_alloc,
|
||||
std::list<T, std::scoped_allocator_adaptor<std_allocator<T, RawAllocator>>>);
|
||||
|
||||
/// \copydoc vector
|
||||
template <typename T, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(forward_list,
|
||||
std::forward_list<T, std_allocator<T, RawAllocator>>);
|
||||
/// \copydoc vector_scoped_alloc
|
||||
template <typename T, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(
|
||||
forward_list_scoped_alloc,
|
||||
std::forward_list<T, std::scoped_allocator_adaptor<std_allocator<T, RawAllocator>>>);
|
||||
|
||||
/// \copydoc vector
|
||||
template <typename T, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(set, std::set<T, std::less<T>, std_allocator<T, RawAllocator>>);
|
||||
/// \copydoc vector_scoped_alloc
|
||||
template <typename T, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(
|
||||
set_scoped_alloc,
|
||||
std::set<T, std::less<T>,
|
||||
std::scoped_allocator_adaptor<std_allocator<T, RawAllocator>>>);
|
||||
|
||||
/// \copydoc vector
|
||||
template <typename T, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(multiset,
|
||||
std::multiset<T, std::less<T>, std_allocator<T, RawAllocator>>);
|
||||
/// \copydoc vector_scoped_alloc
|
||||
template <typename T, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(
|
||||
multiset_scoped_alloc,
|
||||
std::multiset<T, std::less<T>,
|
||||
std::scoped_allocator_adaptor<std_allocator<T, RawAllocator>>>);
|
||||
|
||||
/// \copydoc vector
|
||||
template <typename Key, typename Value, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(
|
||||
map, std::map<Key, Value, std::less<Key>,
|
||||
std_allocator<std::pair<const Key, Value>, RawAllocator>>);
|
||||
/// \copydoc vector_scoped_alloc
|
||||
template <typename Key, typename Value, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(
|
||||
map_scoped_alloc,
|
||||
std::map<Key, Value, std::less<Key>,
|
||||
std::scoped_allocator_adaptor<
|
||||
std_allocator<std::pair<const Key, Value>, RawAllocator>>>);
|
||||
|
||||
/// \copydoc vector
|
||||
template <typename Key, typename Value, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(
|
||||
multimap, std::multimap<Key, Value, std::less<Key>,
|
||||
std_allocator<std::pair<const Key, Value>, RawAllocator>>);
|
||||
/// \copydoc vector_scoped_alloc
|
||||
template <typename Key, typename Value, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(
|
||||
multimap_scoped_alloc,
|
||||
std::multimap<Key, Value, std::less<Key>,
|
||||
std::scoped_allocator_adaptor<
|
||||
std_allocator<std::pair<const Key, Value>, RawAllocator>>>);
|
||||
|
||||
/// \copydoc vector
|
||||
template <typename T, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(
|
||||
unordered_set,
|
||||
std::unordered_set<T, std::hash<T>, std::equal_to<T>, std_allocator<T, RawAllocator>>);
|
||||
/// \copydoc vector_scoped_alloc
|
||||
template <typename T, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(
|
||||
unordered_set_scoped_alloc,
|
||||
std::unordered_set<T, std::hash<T>, std::equal_to<T>,
|
||||
std::scoped_allocator_adaptor<std_allocator<T, RawAllocator>>>);
|
||||
|
||||
/// \copydoc vector
|
||||
template <typename T, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(unordered_multiset,
|
||||
std::unordered_multiset<T, std::hash<T>, std::equal_to<T>,
|
||||
std_allocator<T, RawAllocator>>);
|
||||
/// \copydoc vector_scoped_alloc
|
||||
template <typename T, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(
|
||||
unordered_multiset_scoped_alloc,
|
||||
std::unordered_multiset<T, std::hash<T>, std::equal_to<T>,
|
||||
std::scoped_allocator_adaptor<std_allocator<T, RawAllocator>>>);
|
||||
|
||||
/// \copydoc vector
|
||||
template <typename Key, typename Value, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(
|
||||
unordered_map,
|
||||
std::unordered_map<Key, Value, std::hash<Key>, std::equal_to<Key>,
|
||||
std_allocator<std::pair<const Key, Value>, RawAllocator>>);
|
||||
/// \copydoc vector_scoped_alloc
|
||||
template <typename Key, typename Value, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(
|
||||
unordered_map_scoped_alloc,
|
||||
std::unordered_map<Key, Value, std::hash<Key>, std::equal_to<Key>,
|
||||
std::scoped_allocator_adaptor<
|
||||
std_allocator<std::pair<const Key, Value>, RawAllocator>>>);
|
||||
|
||||
/// \copydoc vector
|
||||
template <typename Key, typename Value, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(
|
||||
unordered_multimap,
|
||||
std::unordered_multimap<Key, Value, std::hash<Key>, std::equal_to<Key>,
|
||||
std_allocator<std::pair<const Key, Value>, RawAllocator>>);
|
||||
/// \copydoc vector_scoped_alloc
|
||||
template <typename Key, typename Value, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(
|
||||
unordered_multimap_scoped_alloc,
|
||||
std::unordered_multimap<Key, Value, std::hash<Key>, std::equal_to<Key>,
|
||||
std::scoped_allocator_adaptor<
|
||||
std_allocator<std::pair<const Key, Value>, RawAllocator>>>);
|
||||
|
||||
/// \copydoc vector
|
||||
template <typename T, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(stack, std::stack<T, deque<T, RawAllocator>>);
|
||||
/// \copydoc vector_scoped_alloc
|
||||
template <typename T, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(stack_scoped_alloc,
|
||||
std::stack<T, deque_scoped_alloc<T, RawAllocator>>);
|
||||
|
||||
/// \copydoc vector
|
||||
template <typename T, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(queue, std::queue<T, deque<T, RawAllocator>>);
|
||||
/// \copydoc vector_scoped_alloc
|
||||
template <typename T, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(queue_scoped_alloc,
|
||||
std::queue<T, deque_scoped_alloc<T, RawAllocator>>);
|
||||
|
||||
/// \copydoc vector
|
||||
template <typename T, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(priority_queue, std::priority_queue<T, deque<T, RawAllocator>>);
|
||||
/// \copydoc vector_scoped_alloc
|
||||
template <typename T, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(priority_queue_scoped_alloc,
|
||||
std::priority_queue<T, deque_scoped_alloc<T, RawAllocator>>);
|
||||
|
||||
/// \copydoc vector
|
||||
template <class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(
|
||||
string,
|
||||
std::basic_string<char, std::char_traits<char>, std_allocator<char, RawAllocator>>);
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// Convenience function to create a container adapter using a certain
|
||||
/// RawAllocator. \returns An empty adapter with an
|
||||
/// implementation container using a reference to a given allocator. \ingroup memory_adapter
|
||||
template <typename T, class RawAllocator, class Container = deque<T, RawAllocator>>
|
||||
std::stack<T, Container> make_stack(RawAllocator& allocator)
|
||||
{
|
||||
return std::stack<T, Container>{Container(allocator)};
|
||||
}
|
||||
|
||||
/// \copydoc make_stack
|
||||
template <typename T, class RawAllocator, class Container = deque<T, RawAllocator>>
|
||||
std::queue<T, Container> make_queue(RawAllocator& allocator)
|
||||
{
|
||||
return std::queue<T, Container>{Container(allocator)};
|
||||
}
|
||||
|
||||
/// \copydoc make_stack
|
||||
template <typename T, class RawAllocator, class Container = deque<T, RawAllocator>,
|
||||
class Compare = std::less<T>>
|
||||
std::priority_queue<T, Container, Compare> make_priority_queue(RawAllocator& allocator,
|
||||
Compare comp = {})
|
||||
{
|
||||
return std::priority_queue<T, Container, Compare>{detail::move(comp),
|
||||
Container(allocator)};
|
||||
}
|
||||
/// @}
|
||||
|
||||
#if !defined(DOXYGEN)
|
||||
|
||||
#include "detail/container_node_sizes.hpp"
|
||||
|
||||
#if !defined(WPI_MEMORY_NO_NODE_SIZE)
|
||||
/// \exclude
|
||||
namespace detail
|
||||
{
|
||||
template <typename T, class StdAllocator>
|
||||
struct shared_ptr_node_size
|
||||
{
|
||||
static_assert(sizeof(T) != sizeof(T), "unsupported allocator type");
|
||||
};
|
||||
|
||||
template <typename T, class RawAllocator>
|
||||
struct shared_ptr_node_size<T, std_allocator<T, RawAllocator>>
|
||||
: std::conditional<allocator_traits<RawAllocator>::is_stateful::value,
|
||||
memory::shared_ptr_stateful_node_size<T>,
|
||||
memory::shared_ptr_stateless_node_size<T>>::type
|
||||
{
|
||||
static_assert(sizeof(std_allocator<T, RawAllocator>) <= sizeof(void*),
|
||||
"fix node size debugger");
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename T, class StdAllocator>
|
||||
struct shared_ptr_node_size : detail::shared_ptr_node_size<T, StdAllocator>
|
||||
{
|
||||
};
|
||||
#endif
|
||||
|
||||
#else
|
||||
/// \ingroup memory_adapter
|
||||
/// @{
|
||||
|
||||
/// Contains the node size of a node based STL container with a specific type.
|
||||
///
|
||||
/// This trait is auto-generated and may not be available depending on the build configuration,
|
||||
/// especially when doing cross compilation.
|
||||
template <typename T>
|
||||
struct forward_list_node_size : std::integral_constant<std::size_t, implementation_defined>
|
||||
{
|
||||
};
|
||||
|
||||
/// \copydoc forward_list_node_size
|
||||
template <typename T>
|
||||
struct list_node_size : std::integral_constant<std::size_t, implementation_defined>
|
||||
{
|
||||
};
|
||||
|
||||
/// \copydoc forward_list_node_size
|
||||
template <typename T>
|
||||
struct set_node_size : std::integral_constant<std::size_t, implementation_defined>
|
||||
{
|
||||
};
|
||||
|
||||
/// \copydoc forward_list_node_size
|
||||
template <typename T>
|
||||
struct multiset_node_size : std::integral_constant<std::size_t, implementation_defined>
|
||||
{
|
||||
};
|
||||
|
||||
/// \copydoc forward_list_node_size
|
||||
template <typename T>
|
||||
struct unordered_set_node_size : std::integral_constant<std::size_t, implementation_defined>
|
||||
{
|
||||
};
|
||||
|
||||
/// \copydoc forward_list_node_size
|
||||
template <typename T>
|
||||
struct unordered_multiset_node_size
|
||||
: std::integral_constant<std::size_t, implementation_defined>
|
||||
{
|
||||
};
|
||||
|
||||
/// Contains the node size of a node based STL container with a specific type.
|
||||
///
|
||||
/// This trait is auto-generated and may not be available depending on the build configuration,
|
||||
/// especially when doing cross compilation.
|
||||
///
|
||||
/// \notes `T` is always the `value_type` of the container, e.g. `std::pair<const Key, Value>`.
|
||||
template <typename T>
|
||||
struct map_node_size : std::integral_constant<std::size_t, implementation_defined>
|
||||
{
|
||||
};
|
||||
|
||||
/// \copydoc map_node_size
|
||||
template <typename T>
|
||||
struct multimap_node_size : std::integral_constant<std::size_t, implementation_defined>
|
||||
{
|
||||
};
|
||||
|
||||
/// \copydoc map_node_size
|
||||
template <typename T>
|
||||
struct unordered_map_node_size : std::integral_constant<std::size_t, implementation_defined>
|
||||
{
|
||||
};
|
||||
|
||||
/// \copydoc map_node_size
|
||||
template <typename T>
|
||||
struct unordered_multimap_node_size
|
||||
: std::integral_constant<std::size_t, implementation_defined>
|
||||
{
|
||||
};
|
||||
|
||||
/// \copydoc forward_list_node_size
|
||||
template <typename T, class StdAllocator>
|
||||
struct shared_ptr_node_size : std::integral_constant<std::size_t, implementation_defined>
|
||||
{
|
||||
};
|
||||
/// @}
|
||||
#endif
|
||||
|
||||
#if !defined(WPI_MEMORY_NO_NODE_SIZE)
|
||||
/// The node size required by \ref allocate_shared.
|
||||
/// \note This is similar to \ref shared_ptr_node_size but takes a
|
||||
/// RawAllocator instead.
|
||||
template <typename T, class RawAllocator>
|
||||
struct allocate_shared_node_size : shared_ptr_node_size<T, std_allocator<T, RawAllocator>>
|
||||
{
|
||||
};
|
||||
#endif
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_CONTAINER_HPP_INCLUDED
|
||||
@@ -1,113 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_DEBUGGING_HPP_INCLUDED
|
||||
#define WPI_MEMORY_DEBUGGING_HPP_INCLUDED
|
||||
|
||||
/// \file
|
||||
/// Debugging facilities.
|
||||
|
||||
#include "config.hpp"
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
struct allocator_info;
|
||||
|
||||
/// The magic values that are used for debug filling.
|
||||
/// If \ref WPI_MEMORY_DEBUG_FILL is \c true, memory will be filled to help detect use-after-free or missing initialization errors.
|
||||
/// These are the constants for the different types.
|
||||
/// \ingroup memory_core
|
||||
enum class debug_magic : unsigned char
|
||||
{
|
||||
/// Marks internal memory used by the allocator - "allocated block".
|
||||
internal_memory = 0xAB,
|
||||
/// Marks internal memory currently not used by the allocator - "freed block".
|
||||
internal_freed_memory = 0xFB,
|
||||
/// Marks allocated, but not yet used memory - "clean memory".
|
||||
new_memory = 0xCD,
|
||||
/// Marks freed memory - "dead memory".
|
||||
freed_memory = 0xDD,
|
||||
/// Marks buffer memory used to ensure proper alignment.
|
||||
/// This memory can also serve as \ref debug_magic::fence_memory.
|
||||
alignment_memory = 0xED,
|
||||
/// Marks buffer memory used to protect against overflow - "fence memory".
|
||||
/// The option \ref WPI_MEMORY_DEBUG_FENCE controls the size of a memory fence that will be placed before or after a memory block.
|
||||
/// It helps catching buffer overflows.
|
||||
fence_memory = 0xFD
|
||||
};
|
||||
|
||||
/// The type of the handler called when a memory leak is detected.
|
||||
/// Leak checking can be controlled via the option \ref WPI_MEMORY_DEBUG_LEAK_CHECK
|
||||
/// and only affects calls through the \ref allocator_traits, not direct calls.
|
||||
/// The handler gets the \ref allocator_info and the amount of memory leaked.
|
||||
/// This can also be negative, meaning that more memory has been freed than allocated.
|
||||
/// \requiredbe A leak handler shall log the leak, abort the program, do nothing or anything else that seems appropriate.
|
||||
/// It must not throw any exceptions since it is called in the cleanup process.
|
||||
/// \defaultbe On a hosted implementation it logs the leak to \c stderr and returns, continuing execution.
|
||||
/// On a freestanding implementation it does nothing.
|
||||
/// \ingroup memory_core
|
||||
using leak_handler = void (*)(const allocator_info& info, std::ptrdiff_t amount);
|
||||
|
||||
/// Exchanges the \ref leak_handler.
|
||||
/// \effects Sets \c h as the new \ref leak_handler in an atomic operation.
|
||||
/// A \c nullptr sets the default \ref leak_handler.
|
||||
/// \returns The previous \ref leak_handler. This is never \c nullptr.
|
||||
/// \ingroup memory_core
|
||||
leak_handler set_leak_handler(leak_handler h);
|
||||
|
||||
/// Returns the \ref leak_handler.
|
||||
/// \returns The current \ref leak_handler. This is never \c nullptr.
|
||||
/// \ingroup memory_core
|
||||
leak_handler get_leak_handler();
|
||||
|
||||
/// The type of the handler called when an invalid pointer is passed to a deallocation function.
|
||||
/// Pointer checking can be controlled via the options \ref WPI_MEMORY_DEBUG_POINTER_CHECK and \ref WPI_MEMORY_DEBUG_DOUBLE_DEALLOC_CHECK.
|
||||
/// The handler gets the \ref allocator_info and the invalid pointer.
|
||||
/// \requiredbe An invalid pointer handler shall terminate the program.
|
||||
/// It must not throw any exceptions since it might be called in the cleanup process.
|
||||
/// \defaultbe On a hosted implementation it logs the information to \c stderr and calls \c std::abort().
|
||||
/// On a freestanding implementation it only calls \c std::abort().
|
||||
/// \ingroup memory_core
|
||||
using invalid_pointer_handler = void (*)(const allocator_info& info, const void* ptr);
|
||||
|
||||
/// Exchanges the \ref invalid_pointer_handler.
|
||||
/// \effects Sets \c h as the new \ref invalid_pointer_handler in an atomic operation.
|
||||
/// A \c nullptr sets the default \ref invalid_pointer_handler.
|
||||
/// \returns The previous \ref invalid_pointer_handler. This is never \c nullptr.
|
||||
/// \ingroup memory_core
|
||||
invalid_pointer_handler set_invalid_pointer_handler(invalid_pointer_handler h);
|
||||
|
||||
/// Returns the \ref invalid_pointer_handler.
|
||||
/// \returns The current \ref invalid_pointer_handler. This is never \c nullptr.
|
||||
/// \ingroup memory_core
|
||||
invalid_pointer_handler get_invalid_pointer_handler();
|
||||
|
||||
/// The type of the handler called when a buffer under/overflow is detected.
|
||||
/// If \ref WPI_MEMORY_DEBUG_FILL is \c true and \ref WPI_MEMORY_DEBUG_FENCE has a non-zero value
|
||||
/// the allocator classes check if a write into the fence has occured upon deallocation.
|
||||
/// The handler gets the memory block belonging to the corrupted fence, its size and the exact address.
|
||||
/// \requiredbe A buffer overflow handler shall terminate the program.
|
||||
/// It must not throw any exceptions since it me be called in the cleanup process.
|
||||
/// \defaultbe On a hosted implementation it logs the information to \c stderr and calls \c std::abort().
|
||||
/// On a freestanding implementation it only calls \c std::abort().
|
||||
/// \ingroup memory_core
|
||||
using buffer_overflow_handler = void (*)(const void* memory, std::size_t size,
|
||||
const void* write_ptr);
|
||||
|
||||
/// Exchanges the \ref buffer_overflow_handler.
|
||||
/// \effects Sets \c h as the new \ref buffer_overflow_handler in an atomic operation.
|
||||
/// A \c nullptr sets the default \ref buffer_overflow_handler.
|
||||
/// \returns The previous \ref buffer_overflow_handler. This is never \c nullptr.
|
||||
/// \ingroup memory_core
|
||||
buffer_overflow_handler set_buffer_overflow_handler(buffer_overflow_handler h);
|
||||
|
||||
/// Returns the \ref buffer_overflow_handler.
|
||||
/// \returns The current \ref buffer_overflow_handler. This is never \c nullptr.
|
||||
/// \ingroup memory_core
|
||||
buffer_overflow_handler get_buffer_overflow_handler();
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_DEBUGGING_HPP_INCLUDED
|
||||
@@ -1,36 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_DEFAULT_ALLOCATOR_HPP_INCLUDED
|
||||
#define WPI_MEMORY_DEFAULT_ALLOCATOR_HPP_INCLUDED
|
||||
|
||||
/// \file
|
||||
/// The typedef \ref wpi::memory::default_allocator.
|
||||
|
||||
#include "config.hpp"
|
||||
#include "heap_allocator.hpp"
|
||||
#include "new_allocator.hpp"
|
||||
#include "static_allocator.hpp"
|
||||
#include "virtual_memory.hpp"
|
||||
|
||||
#if WPI_HOSTED_IMPLEMENTATION
|
||||
#include "malloc_allocator.hpp"
|
||||
#endif
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
/// The default RawAllocator that will be used as BlockAllocator in memory arenas.
|
||||
/// Arena allocators like \ref memory_stack or \ref memory_pool allocate memory by subdividing a huge block.
|
||||
/// They get a BlockAllocator that will be used for their internal allocation,
|
||||
/// this type is the default value.
|
||||
/// \requiredbe Its type can be changed via the CMake option \c WPI_MEMORY_DEFAULT_ALLCOATOR,
|
||||
/// but it must be one of the following: \ref heap_allocator, \ref new_allocator, \ref malloc_allocator, \ref static_allocator, \ref virtual_memory_allocator.
|
||||
/// \defaultbe The default is \ref heap_allocator.
|
||||
/// \ingroup memory_allocator
|
||||
using default_allocator = WPI_IMPL_DEFINED(WPI_MEMORY_IMPL_DEFAULT_ALLOCATOR);
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_DEFAULT_ALLOCATOR_HPP_INCLUDED
|
||||
@@ -1,307 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_DELETER_HPP_INCLUDED
|
||||
#define WPI_MEMORY_DELETER_HPP_INCLUDED
|
||||
|
||||
/// \file
|
||||
/// \c Deleter classes using a RawAllocator.
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "allocator_storage.hpp"
|
||||
#include "config.hpp"
|
||||
#include "threading.hpp"
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
/// A deleter class that deallocates the memory through a specified RawAllocator.
|
||||
///
|
||||
/// It deallocates memory for a specified type but does not call its destructors.
|
||||
/// \ingroup memory_adapter
|
||||
template <typename Type, class RawAllocator>
|
||||
class allocator_deallocator : WPI_EBO(allocator_reference<RawAllocator>)
|
||||
{
|
||||
static_assert(!std::is_abstract<Type>::value,
|
||||
"use allocator_polymorphic_deallocator for storing base classes");
|
||||
|
||||
public:
|
||||
using allocator_type = typename allocator_reference<RawAllocator>::allocator_type;
|
||||
using value_type = Type;
|
||||
|
||||
/// \effects Creates it without any associated allocator.
|
||||
/// The deallocator must not be used if that is the case.
|
||||
/// \notes This functions is useful if you have want to create an empty smart pointer without giving it an allocator.
|
||||
allocator_deallocator() noexcept = default;
|
||||
|
||||
/// \effects Creates it by passing it an \ref allocator_reference.
|
||||
/// It will store the reference to it and uses the referenced allocator object for the deallocation.
|
||||
allocator_deallocator(allocator_reference<RawAllocator> alloc) noexcept
|
||||
: allocator_reference<RawAllocator>(alloc)
|
||||
{
|
||||
}
|
||||
|
||||
/// \effects Deallocates the memory given to it.
|
||||
/// Calls \c deallocate_node(pointer, sizeof(value_type), alignof(value_type)) on the referenced allocator object.
|
||||
/// \requires The deallocator must not have been created by the default constructor.
|
||||
void operator()(value_type* pointer) noexcept
|
||||
{
|
||||
this->deallocate_node(pointer, sizeof(value_type), alignof(value_type));
|
||||
}
|
||||
|
||||
/// \returns The reference to the allocator.
|
||||
/// It has the same type as the call to \ref allocator_reference::get_allocator().
|
||||
/// \requires The deallocator must not be created by the default constructor.
|
||||
auto get_allocator() const noexcept
|
||||
-> decltype(std::declval<allocator_reference<allocator_type>>().get_allocator())
|
||||
{
|
||||
return this->allocator_reference<allocator_type>::get_allocator();
|
||||
}
|
||||
};
|
||||
|
||||
/// Specialization of \ref allocator_deallocator for array types.
|
||||
/// Otherwise the same behavior.
|
||||
/// \ingroup memory_adapter
|
||||
template <typename Type, class RawAllocator>
|
||||
class allocator_deallocator<Type[], RawAllocator>
|
||||
: WPI_EBO(allocator_reference<RawAllocator>)
|
||||
{
|
||||
static_assert(!std::is_abstract<Type>::value, "must not create polymorphic arrays");
|
||||
|
||||
public:
|
||||
using allocator_type = typename allocator_reference<RawAllocator>::allocator_type;
|
||||
using value_type = Type;
|
||||
|
||||
/// \effects Creates it without any associated allocator.
|
||||
/// The deallocator must not be used if that is the case.
|
||||
/// \notes This functions is useful if you have want to create an empty smart pointer without giving it an allocator.
|
||||
allocator_deallocator() noexcept : size_(0u) {}
|
||||
|
||||
/// \effects Creates it by passing it an \ref allocator_reference and the size of the array that will be deallocated.
|
||||
/// It will store the reference to the allocator and uses the referenced allocator object for the deallocation.
|
||||
allocator_deallocator(allocator_reference<RawAllocator> alloc,
|
||||
std::size_t size) noexcept
|
||||
: allocator_reference<RawAllocator>(alloc), size_(size)
|
||||
{
|
||||
}
|
||||
|
||||
/// \effects Deallocates the memory given to it.
|
||||
/// Calls \c deallocate_array(pointer, size, sizeof(value_type), alignof(value_type))
|
||||
/// on the referenced allocator object with the size given in the constructor.
|
||||
/// \requires The deallocator must not have been created by the default constructor.
|
||||
void operator()(value_type* pointer) noexcept
|
||||
{
|
||||
this->deallocate_array(pointer, size_, sizeof(value_type), alignof(value_type));
|
||||
}
|
||||
|
||||
/// \returns The reference to the allocator.
|
||||
/// It has the same type as the call to \ref allocator_reference::get_allocator().
|
||||
/// \requires The deallocator must not have been created by the default constructor.
|
||||
auto get_allocator() const noexcept
|
||||
-> decltype(std::declval<allocator_reference<allocator_type>>().get_allocator())
|
||||
{
|
||||
return this->allocator_reference<allocator_type>::get_allocator();
|
||||
}
|
||||
|
||||
/// \returns The size of the array that will be deallocated.
|
||||
/// This is the same value as passed in the constructor, or `0` if it was created by the default constructor.
|
||||
std::size_t array_size() const noexcept
|
||||
{
|
||||
return size_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::size_t size_;
|
||||
};
|
||||
|
||||
/// A deleter class that deallocates the memory of a derived type through a specified RawAllocator.
|
||||
///
|
||||
/// It can only be created from a \ref allocator_deallocator and thus must only be used for smart pointers initialized by derived-to-base conversion of the pointer.
|
||||
/// \ingroup memory_adapter
|
||||
template <typename BaseType, class RawAllocator>
|
||||
class allocator_polymorphic_deallocator : WPI_EBO(allocator_reference<RawAllocator>)
|
||||
{
|
||||
public:
|
||||
using allocator_type = typename allocator_reference<RawAllocator>::allocator_type;
|
||||
using value_type = BaseType;
|
||||
|
||||
/// \effects Creates it from a deallocator for a derived type.
|
||||
/// It will deallocate the memory as if done by the derived type.
|
||||
template <typename T, WPI_REQUIRES((std::is_base_of<BaseType, T>::value))>
|
||||
allocator_polymorphic_deallocator(allocator_deallocator<T, RawAllocator> dealloc)
|
||||
: allocator_reference<RawAllocator>(dealloc.get_allocator()),
|
||||
derived_size_(sizeof(T)),
|
||||
derived_alignment_(alignof(T))
|
||||
{
|
||||
}
|
||||
|
||||
/// \effects Deallocates the memory given to it.
|
||||
/// Calls \c deallocate_node(pointer, size, alignment) on the referenced allocator object,
|
||||
/// where \c size and \c alignment are the values of the type it was created with.
|
||||
void operator()(value_type* pointer) noexcept
|
||||
{
|
||||
this->deallocate_node(pointer, derived_size_, derived_alignment_);
|
||||
}
|
||||
|
||||
/// \returns The reference to the allocator.
|
||||
/// It has the same type as the call to \ref allocator_reference::get_allocator().
|
||||
auto get_allocator() const noexcept
|
||||
-> decltype(std::declval<allocator_reference<allocator_type>>().get_allocator())
|
||||
{
|
||||
return this->allocator_reference<allocator_type>::get_allocator();
|
||||
}
|
||||
|
||||
private:
|
||||
std::size_t derived_size_, derived_alignment_;
|
||||
};
|
||||
|
||||
/// Similar to \ref allocator_deallocator but calls the destructors of the object.
|
||||
/// Otherwise behaves the same.
|
||||
/// \ingroup memory_adapter
|
||||
template <typename Type, class RawAllocator>
|
||||
class allocator_deleter : WPI_EBO(allocator_reference<RawAllocator>)
|
||||
{
|
||||
static_assert(!std::is_abstract<Type>::value,
|
||||
"use allocator_polymorphic_deleter for storing base classes");
|
||||
|
||||
public:
|
||||
using allocator_type = typename allocator_reference<RawAllocator>::allocator_type;
|
||||
using value_type = Type;
|
||||
|
||||
/// \effects Creates it without any associated allocator.
|
||||
/// The deleter must not be used if that is the case.
|
||||
/// \notes This functions is useful if you have want to create an empty smart pointer without giving it an allocator.
|
||||
allocator_deleter() noexcept = default;
|
||||
|
||||
/// \effects Creates it by passing it an \ref allocator_reference.
|
||||
/// It will store the reference to it and uses the referenced allocator object for the deallocation.
|
||||
allocator_deleter(allocator_reference<RawAllocator> alloc) noexcept
|
||||
: allocator_reference<RawAllocator>(alloc)
|
||||
{
|
||||
}
|
||||
|
||||
/// \effects Calls the destructor and deallocates the memory given to it.
|
||||
/// Calls \c deallocate_node(pointer, sizeof(value_type), alignof(value_type))
|
||||
/// on the referenced allocator object for the deallocation.
|
||||
/// \requires The deleter must not have been created by the default constructor.
|
||||
void operator()(value_type* pointer) noexcept
|
||||
{
|
||||
pointer->~value_type();
|
||||
this->deallocate_node(pointer, sizeof(value_type), alignof(value_type));
|
||||
}
|
||||
|
||||
/// \returns The reference to the allocator.
|
||||
/// It has the same type as the call to \ref allocator_reference::get_allocator().
|
||||
auto get_allocator() const noexcept
|
||||
-> decltype(std::declval<allocator_reference<allocator_type>>().get_allocator())
|
||||
{
|
||||
return this->allocator_reference<allocator_type>::get_allocator();
|
||||
}
|
||||
};
|
||||
|
||||
/// Specialization of \ref allocator_deleter for array types.
|
||||
/// Otherwise the same behavior.
|
||||
/// \ingroup memory_adapter
|
||||
template <typename Type, class RawAllocator>
|
||||
class allocator_deleter<Type[], RawAllocator>
|
||||
: WPI_EBO(allocator_reference<RawAllocator>)
|
||||
{
|
||||
static_assert(!std::is_abstract<Type>::value, "must not create polymorphic arrays");
|
||||
|
||||
public:
|
||||
using allocator_type = typename allocator_reference<RawAllocator>::allocator_type;
|
||||
using value_type = Type;
|
||||
|
||||
/// \effects Creates it without any associated allocator.
|
||||
/// The deleter must not be used if that is the case.
|
||||
/// \notes This functions is useful if you have want to create an empty smart pointer without giving it an allocator.
|
||||
allocator_deleter() noexcept : size_(0u) {}
|
||||
|
||||
/// \effects Creates it by passing it an \ref allocator_reference and the size of the array that will be deallocated.
|
||||
/// It will store the reference to the allocator and uses the referenced allocator object for the deallocation.
|
||||
allocator_deleter(allocator_reference<RawAllocator> alloc, std::size_t size) noexcept
|
||||
: allocator_reference<RawAllocator>(alloc), size_(size)
|
||||
{
|
||||
}
|
||||
|
||||
/// \effects Calls the destructors and deallocates the memory given to it.
|
||||
/// Calls \c deallocate_array(pointer, size, sizeof(value_type), alignof(value_type))
|
||||
/// on the referenced allocator object with the size given in the constructor for the deallocation.
|
||||
/// \requires The deleter must not have been created by the default constructor.
|
||||
void operator()(value_type* pointer) noexcept
|
||||
{
|
||||
for (auto cur = pointer; cur != pointer + size_; ++cur)
|
||||
cur->~value_type();
|
||||
this->deallocate_array(pointer, size_, sizeof(value_type), alignof(value_type));
|
||||
}
|
||||
|
||||
/// \returns The reference to the allocator.
|
||||
/// It has the same type as the call to \ref allocator_reference::get_allocator().
|
||||
/// \requires The deleter must not be created by the default constructor.
|
||||
auto get_allocator() const noexcept
|
||||
-> decltype(std::declval<allocator_reference<allocator_type>>().get_allocator())
|
||||
{
|
||||
return this->allocator_reference<allocator_type>::get_allocator();
|
||||
}
|
||||
|
||||
/// \returns The size of the array that will be deallocated.
|
||||
/// This is the same value as passed in the constructor, or `0` if it was created by the default constructor.
|
||||
std::size_t array_size() const noexcept
|
||||
{
|
||||
return size_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::size_t size_;
|
||||
};
|
||||
|
||||
/// Similar to \ref allocator_polymorphic_deallocator but calls the destructors of the object.
|
||||
/// Otherwise behaves the same.
|
||||
/// \note It has a relatively high space overhead, so only use it if you have to.
|
||||
/// \ingroup memory_adapter
|
||||
template <typename BaseType, class RawAllocator>
|
||||
class allocator_polymorphic_deleter : WPI_EBO(allocator_reference<RawAllocator>)
|
||||
{
|
||||
public:
|
||||
using allocator_type = typename allocator_reference<RawAllocator>::allocator_type;
|
||||
using value_type = BaseType;
|
||||
|
||||
/// \effects Creates it from a deleter for a derived type.
|
||||
/// It will deallocate the memory as if done by the derived type.
|
||||
template <typename T, WPI_REQUIRES((std::is_base_of<BaseType, T>::value))>
|
||||
allocator_polymorphic_deleter(allocator_deleter<T, RawAllocator> deleter)
|
||||
: allocator_reference<RawAllocator>(deleter.get_allocator()),
|
||||
derived_size_(sizeof(T)),
|
||||
derived_alignment_(alignof(T))
|
||||
{
|
||||
WPI_MEMORY_ASSERT(std::size_t(derived_size_) == sizeof(T)
|
||||
&& std::size_t(derived_alignment_) == alignof(T));
|
||||
}
|
||||
|
||||
/// \effects Deallocates the memory given to it.
|
||||
/// Calls \c deallocate_node(pointer, size, alignment) on the referenced allocator object,
|
||||
/// where \c size and \c alignment are the values of the type it was created with.
|
||||
void operator()(value_type* pointer) noexcept
|
||||
{
|
||||
pointer->~value_type();
|
||||
this->deallocate_node(pointer, derived_size_, derived_alignment_);
|
||||
}
|
||||
|
||||
/// \returns The reference to the allocator.
|
||||
/// It has the same type as the call to \ref allocator_reference::get_allocator().
|
||||
auto get_allocator() const noexcept
|
||||
-> decltype(std::declval<allocator_reference<allocator_type>>().get_allocator())
|
||||
{
|
||||
return this->allocator_reference<allocator_type>::get_allocator();
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned short derived_size_,
|
||||
derived_alignment_; // use unsigned short here to save space
|
||||
};
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif //WPI_MEMORY_DELETER_HPP_INCLUDED
|
||||
@@ -1,51 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_DETAIL_ALIGN_HPP_INCLUDED
|
||||
#define WPI_MEMORY_DETAIL_ALIGN_HPP_INCLUDED
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "../config.hpp"
|
||||
#include "assert.hpp"
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
// whether or not an alignment is valid, i.e. a power of two not zero
|
||||
constexpr bool is_valid_alignment(std::size_t alignment) noexcept
|
||||
{
|
||||
return alignment && (alignment & (alignment - 1)) == 0u;
|
||||
}
|
||||
|
||||
// returns the offset needed to align ptr for given alignment
|
||||
// alignment must be valid
|
||||
inline std::size_t align_offset(std::uintptr_t address, std::size_t alignment) noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(is_valid_alignment(alignment));
|
||||
auto misaligned = address & (alignment - 1);
|
||||
return misaligned != 0 ? (alignment - misaligned) : 0;
|
||||
}
|
||||
inline std::size_t align_offset(void* ptr, std::size_t alignment) noexcept
|
||||
{
|
||||
return align_offset(reinterpret_cast<std::uintptr_t>(ptr), alignment);
|
||||
}
|
||||
|
||||
// whether or not the pointer is aligned for given alignment
|
||||
// alignment must be valid
|
||||
bool is_aligned(void* ptr, std::size_t alignment) noexcept;
|
||||
|
||||
// maximum alignment value
|
||||
constexpr std::size_t max_alignment = alignof(std::max_align_t);
|
||||
static_assert(is_valid_alignment(max_alignment), "ehm..?");
|
||||
|
||||
// returns the minimum alignment required for a node of given size
|
||||
std::size_t alignment_for(std::size_t size) noexcept;
|
||||
} // namespace detail
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_DETAIL_ALIGN_HPP_INCLUDED
|
||||
@@ -1,56 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_DETAIL_ASSERT_HPP_INCLUDED
|
||||
#define WPI_MEMORY_DETAIL_ASSERT_HPP_INCLUDED
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "../config.hpp"
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
// handles a failed assertion
|
||||
void handle_failed_assert(const char* msg, const char* file, int line,
|
||||
const char* fnc) noexcept;
|
||||
|
||||
void handle_warning(const char* msg, const char* file, int line,
|
||||
const char* fnc) noexcept;
|
||||
|
||||
// note: debug assertion macros don't use fully qualified name
|
||||
// because they should only be used in this library, where the whole namespace is available
|
||||
// can be override via command line definitions
|
||||
#if WPI_MEMORY_DEBUG_ASSERT && !defined(WPI_MEMORY_ASSERT)
|
||||
#define WPI_MEMORY_ASSERT(Expr) \
|
||||
static_cast<void>((Expr) \
|
||||
|| (detail::handle_failed_assert("Assertion \"" #Expr "\" failed", __FILE__, \
|
||||
__LINE__, __func__), \
|
||||
true))
|
||||
|
||||
#define WPI_MEMORY_ASSERT_MSG(Expr, Msg) \
|
||||
static_cast<void>((Expr) \
|
||||
|| (detail::handle_failed_assert("Assertion \"" #Expr "\" failed: " Msg, \
|
||||
__FILE__, __LINE__, __func__), \
|
||||
true))
|
||||
|
||||
#define WPI_MEMORY_UNREACHABLE(Msg) \
|
||||
detail::handle_failed_assert("Unreachable code reached: " Msg, __FILE__, __LINE__, __func__)
|
||||
|
||||
#define WPI_MEMORY_WARNING(Msg) detail::handle_warning(Msg, __FILE__, __LINE__, __func__)
|
||||
|
||||
#elif !defined(WPI_MEMORY_ASSERT)
|
||||
#define WPI_MEMORY_ASSERT(Expr)
|
||||
#define WPI_MEMORY_ASSERT_MSG(Expr, Msg)
|
||||
#define WPI_MEMORY_UNREACHABLE(Msg) std::abort()
|
||||
#define WPI_MEMORY_WARNING(Msg)
|
||||
#endif
|
||||
} // namespace detail
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_DETAIL_ASSERT_HPP_INCLUDED
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_DETAIL_CONTAINER_NODE_SIZES_HPP_INCLUDED
|
||||
#define WPI_MEMORY_DETAIL_CONTAINER_NODE_SIZES_HPP_INCLUDED
|
||||
|
||||
#include "container_node_sizes_impl.hpp"
|
||||
|
||||
#endif //WPI_MEMORY_DETAIL_CONTAINER_NODE_SIZES_HPP_INCLUDED
|
||||
@@ -1,234 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_DEBUG_HELPERS_HPP_INCLUDED
|
||||
#define WPI_MEMORY_DEBUG_HELPERS_HPP_INCLUDED
|
||||
|
||||
#include <atomic>
|
||||
#include <type_traits>
|
||||
|
||||
#include "../config.hpp"
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
enum class debug_magic : unsigned char;
|
||||
struct allocator_info;
|
||||
|
||||
namespace detail
|
||||
{
|
||||
using debug_fill_enabled = std::integral_constant<bool, WPI_MEMORY_DEBUG_FILL>;
|
||||
constexpr std::size_t debug_fence_size =
|
||||
WPI_MEMORY_DEBUG_FILL ? WPI_MEMORY_DEBUG_FENCE : 0u;
|
||||
|
||||
#if WPI_MEMORY_DEBUG_FILL
|
||||
// fills size bytes of memory with debug_magic
|
||||
void debug_fill(void* memory, std::size_t size, debug_magic m) noexcept;
|
||||
|
||||
// returns nullptr if memory is filled with debug_magic
|
||||
// else returns pointer to mismatched byte
|
||||
void* debug_is_filled(void* memory, std::size_t size, debug_magic m) noexcept;
|
||||
|
||||
// fills fence, new and fence
|
||||
// returns after fence
|
||||
void* debug_fill_new(void* memory, std::size_t node_size,
|
||||
std::size_t fence_size = debug_fence_size) noexcept;
|
||||
|
||||
// fills free memory and returns memory starting at fence
|
||||
void* debug_fill_free(void* memory, std::size_t node_size,
|
||||
std::size_t fence_size = debug_fence_size) noexcept;
|
||||
|
||||
// fills internal memory
|
||||
void debug_fill_internal(void* memory, std::size_t size, bool free) noexcept;
|
||||
#else
|
||||
inline void debug_fill(void*, std::size_t, debug_magic) noexcept {}
|
||||
|
||||
inline void* debug_is_filled(void*, std::size_t, debug_magic) noexcept
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline void* debug_fill_new(void* memory, std::size_t, std::size_t) noexcept
|
||||
{
|
||||
return memory;
|
||||
}
|
||||
|
||||
inline void* debug_fill_free(void* memory, std::size_t, std::size_t) noexcept
|
||||
{
|
||||
return static_cast<char*>(memory);
|
||||
}
|
||||
|
||||
inline void debug_fill_internal(void*, std::size_t, bool) noexcept {}
|
||||
#endif
|
||||
|
||||
void debug_handle_invalid_ptr(const allocator_info& info, void* ptr);
|
||||
|
||||
// validates given ptr by evaluating the Functor
|
||||
// if the Functor returns false, calls the debug_leak_checker
|
||||
// note: ptr is just used as the information passed to the invalid ptr handler
|
||||
template <class Functor>
|
||||
void debug_check_pointer(Functor condition, const allocator_info& info, void* ptr)
|
||||
{
|
||||
#if WPI_MEMORY_DEBUG_POINTER_CHECK
|
||||
if (!condition())
|
||||
debug_handle_invalid_ptr(info, ptr);
|
||||
#else
|
||||
(void)ptr;
|
||||
(void)condition;
|
||||
(void)info;
|
||||
#endif
|
||||
}
|
||||
|
||||
// validates ptr by using a more expensive double-dealloc check
|
||||
template <class Functor>
|
||||
void debug_check_double_dealloc(Functor condition, const allocator_info& info,
|
||||
void* ptr)
|
||||
{
|
||||
#if WPI_MEMORY_DEBUG_DOUBLE_DEALLOC_CHECK
|
||||
debug_check_pointer(condition, info, ptr);
|
||||
#else
|
||||
(void)condition;
|
||||
(void)info;
|
||||
(void)ptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
void debug_handle_memory_leak(const allocator_info& info, std::ptrdiff_t amount);
|
||||
|
||||
// does no leak checking, null overhead
|
||||
template <class Handler>
|
||||
class no_leak_checker
|
||||
{
|
||||
public:
|
||||
no_leak_checker() noexcept {}
|
||||
no_leak_checker(no_leak_checker&&) noexcept {}
|
||||
~no_leak_checker() noexcept {}
|
||||
|
||||
no_leak_checker& operator=(no_leak_checker&&) noexcept
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
void on_allocate(std::size_t) noexcept {}
|
||||
void on_deallocate(std::size_t) noexcept {}
|
||||
};
|
||||
|
||||
// does leak checking per-object
|
||||
// leak is detected upon destructor
|
||||
template <class Handler>
|
||||
class object_leak_checker : Handler
|
||||
{
|
||||
public:
|
||||
object_leak_checker() noexcept : allocated_(0) {}
|
||||
|
||||
object_leak_checker(object_leak_checker&& other) noexcept
|
||||
: allocated_(other.allocated_)
|
||||
{
|
||||
other.allocated_ = 0;
|
||||
}
|
||||
|
||||
~object_leak_checker() noexcept
|
||||
{
|
||||
if (allocated_ != 0)
|
||||
this->operator()(allocated_);
|
||||
}
|
||||
|
||||
object_leak_checker& operator=(object_leak_checker&& other) noexcept
|
||||
{
|
||||
allocated_ = other.allocated_;
|
||||
other.allocated_ = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void on_allocate(std::size_t size) noexcept
|
||||
{
|
||||
allocated_ += std::ptrdiff_t(size);
|
||||
}
|
||||
|
||||
void on_deallocate(std::size_t size) noexcept
|
||||
{
|
||||
allocated_ -= std::ptrdiff_t(size);
|
||||
}
|
||||
|
||||
private:
|
||||
std::ptrdiff_t allocated_;
|
||||
};
|
||||
|
||||
// does leak checking on a global basis
|
||||
// call macro WPI_MEMORY_GLOBAL_LEAK_CHECKER(handler, var_name) in the header
|
||||
// when last counter gets destroyed, leak is detected
|
||||
template <class Handler>
|
||||
class global_leak_checker_impl
|
||||
{
|
||||
public:
|
||||
struct counter : Handler
|
||||
{
|
||||
counter()
|
||||
{
|
||||
++no_counter_objects_;
|
||||
}
|
||||
|
||||
~counter()
|
||||
{
|
||||
--no_counter_objects_;
|
||||
if (no_counter_objects_ == 0u && allocated_ != 0u)
|
||||
this->operator()(allocated_);
|
||||
}
|
||||
};
|
||||
|
||||
global_leak_checker_impl() noexcept {}
|
||||
global_leak_checker_impl(global_leak_checker_impl&&) noexcept {}
|
||||
~global_leak_checker_impl() noexcept {}
|
||||
|
||||
global_leak_checker_impl& operator=(global_leak_checker_impl&&) noexcept
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
void on_allocate(std::size_t size) noexcept
|
||||
{
|
||||
allocated_ += std::ptrdiff_t(size);
|
||||
}
|
||||
|
||||
void on_deallocate(std::size_t size) noexcept
|
||||
{
|
||||
allocated_ -= std::ptrdiff_t(size);
|
||||
}
|
||||
|
||||
private:
|
||||
static std::atomic<std::size_t> no_counter_objects_;
|
||||
static std::atomic<std::ptrdiff_t> allocated_;
|
||||
};
|
||||
|
||||
template <class Handler>
|
||||
std::atomic<std::size_t> global_leak_checker_impl<Handler>::no_counter_objects_(0u);
|
||||
|
||||
template <class Handler>
|
||||
std::atomic<std::ptrdiff_t> global_leak_checker_impl<Handler>::allocated_(0);
|
||||
|
||||
#if WPI_MEMORY_DEBUG_LEAK_CHECK
|
||||
template <class Handler>
|
||||
using global_leak_checker = global_leak_checker_impl<Handler>;
|
||||
|
||||
#define WPI_MEMORY_GLOBAL_LEAK_CHECKER(handler, var_name) \
|
||||
static wpi::memory::detail::global_leak_checker<handler>::counter var_name;
|
||||
#else
|
||||
template <class Handler>
|
||||
using global_leak_checker = no_leak_checker<int>; // only one instantiation
|
||||
|
||||
#define WPI_MEMORY_GLOBAL_LEAK_CHECKER(handler, var_name)
|
||||
#endif
|
||||
|
||||
#if WPI_MEMORY_DEBUG_LEAK_CHECK
|
||||
template <class Handler>
|
||||
using default_leak_checker = object_leak_checker<Handler>;
|
||||
#else
|
||||
template <class Handler>
|
||||
using default_leak_checker = no_leak_checker<Handler>;
|
||||
#endif
|
||||
} // namespace detail
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_DEBUG_HELPERS_HPP_INCLUDED
|
||||
@@ -1,41 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_DETAIL_EBO_STORAGE_HPP_INCLUDED
|
||||
#define WPI_MEMORY_DETAIL_EBO_STORAGE_HPP_INCLUDED
|
||||
|
||||
#include "utility.hpp"
|
||||
#include "../config.hpp"
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
template <int Tag, typename T>
|
||||
class ebo_storage : T
|
||||
{
|
||||
protected:
|
||||
ebo_storage(const T& t) : T(t) {}
|
||||
|
||||
ebo_storage(T&& t) noexcept(std::is_nothrow_move_constructible<T>::value)
|
||||
: T(detail::move(t))
|
||||
{
|
||||
}
|
||||
|
||||
T& get() noexcept
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
const T& get() const noexcept
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
} // namespace detail
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_DETAIL_EBO_STORAGE_HPP_INCLUDED
|
||||
@@ -1,227 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_DETAILL_FREE_LIST_HPP_INCLUDED
|
||||
#define WPI_MEMORY_DETAILL_FREE_LIST_HPP_INCLUDED
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "align.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "../config.hpp"
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
// stores free blocks for a memory pool
|
||||
// memory blocks are fragmented and stored in a list
|
||||
// debug: fills memory and uses a bigger node_size for fence memory
|
||||
class free_memory_list
|
||||
{
|
||||
public:
|
||||
// minimum element size
|
||||
static constexpr auto min_element_size = sizeof(char*);
|
||||
// alignment
|
||||
static constexpr auto min_element_alignment = alignof(char*);
|
||||
|
||||
// minimal size of the block that needs to be inserted
|
||||
static constexpr std::size_t min_block_size(std::size_t node_size,
|
||||
std::size_t number_of_nodes)
|
||||
{
|
||||
return (node_size < min_element_size ? min_element_size : node_size)
|
||||
* number_of_nodes;
|
||||
}
|
||||
|
||||
//=== constructor ===//
|
||||
free_memory_list(std::size_t node_size) noexcept;
|
||||
|
||||
// calls other constructor plus insert
|
||||
free_memory_list(std::size_t node_size, void* mem, std::size_t size) noexcept;
|
||||
|
||||
free_memory_list(free_memory_list&& other) noexcept;
|
||||
~free_memory_list() noexcept = default;
|
||||
|
||||
free_memory_list& operator=(free_memory_list&& other) noexcept;
|
||||
|
||||
friend void swap(free_memory_list& a, free_memory_list& b) noexcept;
|
||||
|
||||
//=== insert/allocation/deallocation ===//
|
||||
// inserts a new memory block, by splitting it up and setting the links
|
||||
// does not own memory!
|
||||
// mem must be aligned for alignment()
|
||||
// pre: size != 0
|
||||
void insert(void* mem, std::size_t size) noexcept;
|
||||
|
||||
// returns the usable size
|
||||
// i.e. how many memory will be actually inserted and usable on a call to insert()
|
||||
std::size_t usable_size(std::size_t size) const noexcept
|
||||
{
|
||||
// Round down to next multiple of node size.
|
||||
return (size / node_size_) * node_size_;
|
||||
}
|
||||
|
||||
// returns a single block from the list
|
||||
// pre: !empty()
|
||||
void* allocate() noexcept;
|
||||
|
||||
// returns a memory block big enough for n bytes
|
||||
// might fail even if capacity is sufficient
|
||||
void* allocate(std::size_t n) noexcept;
|
||||
|
||||
// deallocates a single block
|
||||
void deallocate(void* ptr) noexcept;
|
||||
|
||||
// deallocates multiple blocks with n bytes total
|
||||
void deallocate(void* ptr, std::size_t n) noexcept;
|
||||
|
||||
//=== getter ===//
|
||||
std::size_t node_size() const noexcept
|
||||
{
|
||||
return node_size_;
|
||||
}
|
||||
|
||||
// alignment of all nodes
|
||||
std::size_t alignment() const noexcept;
|
||||
|
||||
// number of nodes remaining
|
||||
std::size_t capacity() const noexcept
|
||||
{
|
||||
return capacity_;
|
||||
}
|
||||
|
||||
bool empty() const noexcept
|
||||
{
|
||||
return first_ == nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
void insert_impl(void* mem, std::size_t size) noexcept;
|
||||
|
||||
char* first_;
|
||||
std::size_t node_size_, capacity_;
|
||||
};
|
||||
|
||||
void swap(free_memory_list& a, free_memory_list& b) noexcept;
|
||||
|
||||
// same as above but keeps the nodes ordered
|
||||
// this allows array allocations, that is, consecutive nodes
|
||||
// debug: fills memory and uses a bigger node_size for fence memory
|
||||
class ordered_free_memory_list
|
||||
{
|
||||
public:
|
||||
// minimum element size
|
||||
static constexpr auto min_element_size = sizeof(char*);
|
||||
// alignment
|
||||
static constexpr auto min_element_alignment = alignof(char*);
|
||||
|
||||
// minimal size of the block that needs to be inserted
|
||||
static constexpr std::size_t min_block_size(std::size_t node_size,
|
||||
std::size_t number_of_nodes)
|
||||
{
|
||||
return (node_size < min_element_size ? min_element_size : node_size)
|
||||
* number_of_nodes;
|
||||
}
|
||||
|
||||
//=== constructor ===//
|
||||
ordered_free_memory_list(std::size_t node_size) noexcept;
|
||||
|
||||
ordered_free_memory_list(std::size_t node_size, void* mem,
|
||||
std::size_t size) noexcept
|
||||
: ordered_free_memory_list(node_size)
|
||||
{
|
||||
insert(mem, size);
|
||||
}
|
||||
|
||||
ordered_free_memory_list(ordered_free_memory_list&& other) noexcept;
|
||||
|
||||
~ordered_free_memory_list() noexcept = default;
|
||||
|
||||
ordered_free_memory_list& operator=(ordered_free_memory_list&& other) noexcept
|
||||
{
|
||||
ordered_free_memory_list tmp(detail::move(other));
|
||||
swap(*this, tmp);
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend void swap(ordered_free_memory_list& a, ordered_free_memory_list& b) noexcept;
|
||||
|
||||
//=== insert/allocation/deallocation ===//
|
||||
// inserts a new memory block, by splitting it up and setting the links
|
||||
// does not own memory!
|
||||
// mem must be aligned for alignment()
|
||||
// pre: size != 0
|
||||
void insert(void* mem, std::size_t size) noexcept;
|
||||
|
||||
// returns the usable size
|
||||
// i.e. how many memory will be actually inserted and usable on a call to insert()
|
||||
std::size_t usable_size(std::size_t size) const noexcept
|
||||
{
|
||||
// Round down to next multiple of node size.
|
||||
return (size / node_size_) * node_size_;
|
||||
}
|
||||
|
||||
// returns a single block from the list
|
||||
// pre: !empty()
|
||||
void* allocate() noexcept;
|
||||
|
||||
// returns a memory block big enough for n bytes (!, not nodes)
|
||||
// might fail even if capacity is sufficient
|
||||
void* allocate(std::size_t n) noexcept;
|
||||
|
||||
// deallocates a single block
|
||||
void deallocate(void* ptr) noexcept;
|
||||
|
||||
// deallocates multiple blocks with n bytes total
|
||||
void deallocate(void* ptr, std::size_t n) noexcept;
|
||||
|
||||
//=== getter ===//
|
||||
std::size_t node_size() const noexcept
|
||||
{
|
||||
return node_size_;
|
||||
}
|
||||
|
||||
// alignment of all nodes
|
||||
std::size_t alignment() const noexcept;
|
||||
|
||||
// number of nodes remaining
|
||||
std::size_t capacity() const noexcept
|
||||
{
|
||||
return capacity_;
|
||||
}
|
||||
|
||||
bool empty() const noexcept
|
||||
{
|
||||
return capacity_ == 0u;
|
||||
}
|
||||
|
||||
private:
|
||||
// returns previous pointer
|
||||
char* insert_impl(void* mem, std::size_t size) noexcept;
|
||||
|
||||
char* begin_node() noexcept;
|
||||
char* end_node() noexcept;
|
||||
|
||||
std::uintptr_t begin_proxy_, end_proxy_;
|
||||
std::size_t node_size_, capacity_;
|
||||
char * last_dealloc_, *last_dealloc_prev_;
|
||||
};
|
||||
|
||||
void swap(ordered_free_memory_list& a, ordered_free_memory_list& b) noexcept;
|
||||
|
||||
#if WPI_MEMORY_DEBUG_DOUBLE_DEALLOC_CHECK
|
||||
// use ordered version to allow pointer check
|
||||
using node_free_memory_list = ordered_free_memory_list;
|
||||
using array_free_memory_list = ordered_free_memory_list;
|
||||
#else
|
||||
using node_free_memory_list = free_memory_list;
|
||||
using array_free_memory_list = ordered_free_memory_list;
|
||||
#endif
|
||||
} // namespace detail
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_DETAILL_FREE_LIST_HPP_INCLUDED
|
||||
@@ -1,125 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_DETAIL_FREE_LIST_ARRAY_HPP
|
||||
#define WPI_MEMORY_DETAIL_FREE_LIST_ARRAY_HPP
|
||||
|
||||
#include "align.hpp"
|
||||
#include "assert.hpp"
|
||||
#include "memory_stack.hpp"
|
||||
#include "../config.hpp"
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
// an array of free_memory_list types
|
||||
// indexed via size, AccessPolicy does necessary conversions
|
||||
// requires trivial destructible FreeList type
|
||||
template <class FreeList, class AccessPolicy>
|
||||
class free_list_array
|
||||
{
|
||||
// not supported on GCC 4.7
|
||||
//static_assert(std::is_trivially_destructible<FreeList>::value,
|
||||
// "free list must be trivially destructible");
|
||||
public:
|
||||
// creates sufficient elements to support up to given maximum node size
|
||||
// all lists are initially empty
|
||||
// actual number is calculated via policy
|
||||
// memory is taken from fixed_memory_stack, it must be sufficient
|
||||
free_list_array(fixed_memory_stack& stack, const char* end,
|
||||
std::size_t max_node_size) noexcept
|
||||
: no_elements_(AccessPolicy::index_from_size(max_node_size) - min_size_index + 1)
|
||||
{
|
||||
array_ = static_cast<FreeList*>(
|
||||
stack.allocate(end, no_elements_ * sizeof(FreeList), alignof(FreeList)));
|
||||
WPI_MEMORY_ASSERT_MSG(array_, "insufficient memory for free lists");
|
||||
for (std::size_t i = 0u; i != no_elements_; ++i)
|
||||
{
|
||||
auto node_size = AccessPolicy::size_from_index(i + min_size_index);
|
||||
::new (static_cast<void*>(array_ + i)) FreeList(node_size);
|
||||
}
|
||||
}
|
||||
|
||||
// move constructor, does not actually move the elements, just the pointer
|
||||
free_list_array(free_list_array&& other) noexcept
|
||||
: array_(other.array_), no_elements_(other.no_elements_)
|
||||
{
|
||||
other.array_ = nullptr;
|
||||
other.no_elements_ = 0u;
|
||||
}
|
||||
|
||||
// destructor, does nothing, list must be trivially destructible!
|
||||
~free_list_array() noexcept = default;
|
||||
|
||||
free_list_array& operator=(free_list_array&& other) noexcept
|
||||
{
|
||||
array_ = other.array_;
|
||||
no_elements_ = other.no_elements_;
|
||||
|
||||
other.array_ = nullptr;
|
||||
other.no_elements_ = 0u;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// access free list for given size
|
||||
FreeList& get(std::size_t node_size) const noexcept
|
||||
{
|
||||
auto i = AccessPolicy::index_from_size(node_size);
|
||||
if (i < min_size_index)
|
||||
i = min_size_index;
|
||||
return array_[i - min_size_index];
|
||||
}
|
||||
|
||||
// number of free lists
|
||||
std::size_t size() const noexcept
|
||||
{
|
||||
return no_elements_;
|
||||
}
|
||||
|
||||
// maximum supported node size
|
||||
std::size_t max_node_size() const noexcept
|
||||
{
|
||||
return AccessPolicy::size_from_index(no_elements_ + min_size_index - 1);
|
||||
}
|
||||
|
||||
private:
|
||||
static const std::size_t min_size_index;
|
||||
|
||||
FreeList* array_;
|
||||
std::size_t no_elements_;
|
||||
};
|
||||
|
||||
template <class FL, class AP>
|
||||
const std::size_t free_list_array<FL, AP>::min_size_index =
|
||||
AP::index_from_size(FL::min_element_size);
|
||||
|
||||
// AccessPolicy that maps size to indices 1:1
|
||||
// creates a free list for each size!
|
||||
struct identity_access_policy
|
||||
{
|
||||
static std::size_t index_from_size(std::size_t size) noexcept
|
||||
{
|
||||
return size;
|
||||
}
|
||||
|
||||
static std::size_t size_from_index(std::size_t index) noexcept
|
||||
{
|
||||
return index;
|
||||
}
|
||||
};
|
||||
|
||||
// AccessPolicy that maps sizes to the integral log2
|
||||
// this creates more nodes and never wastes more than half the size
|
||||
struct log2_access_policy
|
||||
{
|
||||
static std::size_t index_from_size(std::size_t size) noexcept;
|
||||
static std::size_t size_from_index(std::size_t index) noexcept;
|
||||
};
|
||||
} // namespace detail
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif //WPI_MEMORY_DETAIL_FREE_LIST_ARRAY_HPP
|
||||
@@ -1,68 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_DETAIL_ILOG2_HPP_INCLUDED
|
||||
#define WPI_MEMORY_DETAIL_ILOG2_HPP_INCLUDED
|
||||
|
||||
#include <climits>
|
||||
#include <cstdint>
|
||||
|
||||
#include "../config.hpp"
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
// undefined for 0
|
||||
template <typename UInt>
|
||||
constexpr bool is_power_of_two(UInt x)
|
||||
{
|
||||
return (x & (x - 1)) == 0;
|
||||
}
|
||||
|
||||
inline std::size_t ilog2_base(std::uint64_t x)
|
||||
{
|
||||
#if defined(__GNUC__)
|
||||
unsigned long long value = x;
|
||||
return sizeof(value) * CHAR_BIT - static_cast<unsigned>(__builtin_clzll(value));
|
||||
#else
|
||||
// Adapted from https://stackoverflow.com/a/40943402
|
||||
std::size_t clz = 64;
|
||||
std::size_t c = 32;
|
||||
do
|
||||
{
|
||||
auto tmp = x >> c;
|
||||
if (tmp != 0)
|
||||
{
|
||||
clz -= c;
|
||||
x = tmp;
|
||||
}
|
||||
c = c >> 1;
|
||||
} while (c != 0);
|
||||
clz -= x ? 1 : 0;
|
||||
|
||||
return 64 - clz;
|
||||
#endif
|
||||
}
|
||||
|
||||
// ilog2() implementation, cuts part after the comma
|
||||
// e.g. 1 -> 0, 2 -> 1, 3 -> 1, 4 -> 2, 5 -> 2
|
||||
inline std::size_t ilog2(std::uint64_t x)
|
||||
{
|
||||
return ilog2_base(x) - 1;
|
||||
}
|
||||
|
||||
// ceiling ilog2() implementation, adds one if part after comma
|
||||
// e.g. 1 -> 0, 2 -> 1, 3 -> 2, 4 -> 2, 5 -> 3
|
||||
inline std::size_t ilog2_ceil(std::uint64_t x)
|
||||
{
|
||||
// only subtract one if power of two
|
||||
return ilog2_base(x) - std::size_t(is_power_of_two(x));
|
||||
}
|
||||
} // namespace detail
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif
|
||||
@@ -1,85 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_DETAIL_LOWLEVEL_ALLOCATOR_HPP_INCLUDED
|
||||
#define WPI_MEMORY_DETAIL_LOWLEVEL_ALLOCATOR_HPP_INCLUDED
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "../config.hpp"
|
||||
#include "../error.hpp"
|
||||
#include "align.hpp"
|
||||
#include "debug_helpers.hpp"
|
||||
#include "assert.hpp"
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
template <class Functor>
|
||||
struct lowlevel_allocator_leak_handler
|
||||
{
|
||||
void operator()(std::ptrdiff_t amount)
|
||||
{
|
||||
debug_handle_memory_leak(Functor::info(), amount);
|
||||
}
|
||||
};
|
||||
|
||||
// Functor controls low-level allocation:
|
||||
// static allocator_info info()
|
||||
// static void* allocate(std::size_t size, std::size_t alignment);
|
||||
// static void deallocate(void *memory, std::size_t size, std::size_t alignment);
|
||||
// static std::size_t max_node_size();
|
||||
template <class Functor>
|
||||
class lowlevel_allocator : global_leak_checker<lowlevel_allocator_leak_handler<Functor>>
|
||||
{
|
||||
public:
|
||||
using is_stateful = std::false_type;
|
||||
|
||||
lowlevel_allocator() noexcept {}
|
||||
lowlevel_allocator(lowlevel_allocator&&) noexcept {}
|
||||
~lowlevel_allocator() noexcept {}
|
||||
|
||||
lowlevel_allocator& operator=(lowlevel_allocator&&) noexcept
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
void* allocate_node(std::size_t size, std::size_t alignment)
|
||||
{
|
||||
auto actual_size = size + (debug_fence_size ? 2 * max_alignment : 0u);
|
||||
|
||||
auto memory = Functor::allocate(actual_size, alignment);
|
||||
if (!memory)
|
||||
WPI_THROW(out_of_memory(Functor::info(), actual_size));
|
||||
|
||||
this->on_allocate(actual_size);
|
||||
|
||||
return debug_fill_new(memory, size, max_alignment);
|
||||
}
|
||||
|
||||
void deallocate_node(void* node, std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
auto actual_size = size + (debug_fence_size ? 2 * max_alignment : 0u);
|
||||
|
||||
auto memory = debug_fill_free(node, size, max_alignment);
|
||||
Functor::deallocate(memory, actual_size, alignment);
|
||||
|
||||
this->on_deallocate(actual_size);
|
||||
}
|
||||
|
||||
std::size_t max_node_size() const noexcept
|
||||
{
|
||||
return Functor::max_node_size();
|
||||
}
|
||||
};
|
||||
|
||||
#define WPI_MEMORY_LL_ALLOCATOR_LEAK_CHECKER(functor, var_name) \
|
||||
WPI_MEMORY_GLOBAL_LEAK_CHECKER(lowlevel_allocator_leak_handler<functor>, var_name)
|
||||
} // namespace detail
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_DETAIL_LOWLEVEL_ALLOCATOR_HPP_INCLUDED
|
||||
@@ -1,119 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_DETAIL_MEMORY_STACK_HPP_INCLUDED
|
||||
#define WPI_MEMORY_DETAIL_MEMORY_STACK_HPP_INCLUDED
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "../config.hpp"
|
||||
#include "align.hpp"
|
||||
#include "debug_helpers.hpp"
|
||||
#include "../debugging.hpp"
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
// simple memory stack implementation that does not support growing
|
||||
class fixed_memory_stack
|
||||
{
|
||||
public:
|
||||
fixed_memory_stack() noexcept : fixed_memory_stack(nullptr) {}
|
||||
|
||||
// gives it the current pointer, the end pointer must be maintained seperataly
|
||||
explicit fixed_memory_stack(void* memory) noexcept
|
||||
: cur_(static_cast<char*>(memory))
|
||||
{
|
||||
}
|
||||
|
||||
fixed_memory_stack(fixed_memory_stack&& other) noexcept : cur_(other.cur_)
|
||||
{
|
||||
other.cur_ = nullptr;
|
||||
}
|
||||
|
||||
~fixed_memory_stack() noexcept = default;
|
||||
|
||||
fixed_memory_stack& operator=(fixed_memory_stack&& other) noexcept
|
||||
{
|
||||
cur_ = other.cur_;
|
||||
other.cur_ = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// bumps the top pointer without filling it
|
||||
void bump(std::size_t offset) noexcept
|
||||
{
|
||||
cur_ += offset;
|
||||
}
|
||||
|
||||
// bumps the top pointer by offset and fills
|
||||
void bump(std::size_t offset, debug_magic m) noexcept
|
||||
{
|
||||
detail::debug_fill(cur_, offset, m);
|
||||
bump(offset);
|
||||
}
|
||||
|
||||
// same as bump(offset, m) but returns old value
|
||||
void* bump_return(std::size_t offset,
|
||||
debug_magic m = debug_magic::new_memory) noexcept
|
||||
{
|
||||
auto memory = cur_;
|
||||
detail::debug_fill(memory, offset, m);
|
||||
cur_ += offset;
|
||||
return memory;
|
||||
}
|
||||
|
||||
// allocates memory by advancing the stack, returns nullptr if insufficient
|
||||
// debug: mark memory as new_memory, put fence in front and back
|
||||
void* allocate(const char* end, std::size_t size, std::size_t alignment,
|
||||
std::size_t fence_size = debug_fence_size) noexcept
|
||||
{
|
||||
if (cur_ == nullptr)
|
||||
return nullptr;
|
||||
|
||||
auto remaining = std::size_t(end - cur_);
|
||||
auto offset = align_offset(cur_ + fence_size, alignment);
|
||||
if (fence_size + offset + size + fence_size > remaining)
|
||||
return nullptr;
|
||||
|
||||
return allocate_unchecked(size, offset, fence_size);
|
||||
}
|
||||
|
||||
// same as allocate() but does not check the size
|
||||
// note: pass it the align OFFSET, not the alignment
|
||||
void* allocate_unchecked(std::size_t size, std::size_t align_offset,
|
||||
std::size_t fence_size = debug_fence_size) noexcept
|
||||
{
|
||||
bump(fence_size, debug_magic::fence_memory);
|
||||
bump(align_offset, debug_magic::alignment_memory);
|
||||
auto mem = bump_return(size);
|
||||
bump(fence_size, debug_magic::fence_memory);
|
||||
return mem;
|
||||
}
|
||||
|
||||
// unwindws the stack to a certain older position
|
||||
// debug: marks memory from new top to old top as freed
|
||||
// doesn't check for invalid pointer
|
||||
void unwind(char* top) noexcept
|
||||
{
|
||||
debug_fill(top, std::size_t(cur_ - top), debug_magic::freed_memory);
|
||||
cur_ = top;
|
||||
}
|
||||
|
||||
// returns the current top
|
||||
char* top() const noexcept
|
||||
{
|
||||
return cur_;
|
||||
}
|
||||
|
||||
private:
|
||||
char* cur_;
|
||||
};
|
||||
} // namespace detail
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_DETAIL_MEMORY_STACK_HPP_INCLUDED
|
||||
@@ -1,163 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_DETAIL_SMALL_FREE_LIST_HPP_INCLUDED
|
||||
#define WPI_MEMORY_DETAIL_SMALL_FREE_LIST_HPP_INCLUDED
|
||||
|
||||
#include <cstddef>
|
||||
#include <climits>
|
||||
|
||||
#include "../config.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "align.hpp"
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
struct chunk_base
|
||||
{
|
||||
chunk_base* prev = this;
|
||||
chunk_base* next = this;
|
||||
|
||||
unsigned char first_free = 0; // first free node for the linked list
|
||||
unsigned char capacity = 0; // total number of free nodes available
|
||||
unsigned char no_nodes = 0; // total number of nodes in memory
|
||||
|
||||
chunk_base() noexcept = default;
|
||||
|
||||
chunk_base(unsigned char no) noexcept : capacity(no), no_nodes(no) {}
|
||||
};
|
||||
|
||||
constexpr std::size_t chunk_memory_offset =
|
||||
sizeof(chunk_base) % detail::max_alignment == 0 ?
|
||||
sizeof(chunk_base) :
|
||||
(sizeof(chunk_base) / detail::max_alignment + 1) * detail::max_alignment;
|
||||
constexpr std::size_t chunk_max_nodes = UCHAR_MAX;
|
||||
|
||||
struct chunk;
|
||||
|
||||
// the same as free_memory_list but optimized for small node sizes
|
||||
// it is slower and does not support arrays
|
||||
// but has very small overhead
|
||||
// debug: allocate() and deallocate() mark memory as new and freed, respectively
|
||||
// node_size is increased via two times fence size and fence is put in front and after
|
||||
class small_free_memory_list
|
||||
{
|
||||
static constexpr std::size_t chunk_count(std::size_t number_of_nodes)
|
||||
{
|
||||
return number_of_nodes / chunk_max_nodes
|
||||
+ (number_of_nodes % chunk_max_nodes == 0 ? 0 : 1);
|
||||
}
|
||||
|
||||
public:
|
||||
// minimum element size
|
||||
static constexpr std::size_t min_element_size = 1;
|
||||
// alignment
|
||||
static constexpr std::size_t min_element_alignment = 1;
|
||||
|
||||
// minimal size of the block that needs to be inserted
|
||||
static constexpr std::size_t min_block_size(std::size_t node_size,
|
||||
std::size_t number_of_nodes)
|
||||
{
|
||||
return chunk_count(number_of_nodes)
|
||||
* (chunk_memory_offset + chunk_max_nodes * node_size);
|
||||
}
|
||||
|
||||
//=== constructor ===//
|
||||
small_free_memory_list(std::size_t node_size) noexcept;
|
||||
|
||||
// does not own memory!
|
||||
small_free_memory_list(std::size_t node_size, void* mem, std::size_t size) noexcept;
|
||||
|
||||
small_free_memory_list(small_free_memory_list&& other) noexcept;
|
||||
|
||||
~small_free_memory_list() noexcept = default;
|
||||
|
||||
small_free_memory_list& operator=(small_free_memory_list&& other) noexcept
|
||||
{
|
||||
small_free_memory_list tmp(detail::move(other));
|
||||
swap(*this, tmp);
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend void swap(small_free_memory_list& a, small_free_memory_list& b) noexcept;
|
||||
|
||||
//=== insert/alloc/dealloc ===//
|
||||
// inserts new memory of given size into the free list
|
||||
// mem must be aligned for maximum alignment
|
||||
void insert(void* mem, std::size_t size) noexcept;
|
||||
|
||||
// returns the usable size
|
||||
// i.e. how many memory will be actually inserted and usable on a call to insert()
|
||||
std::size_t usable_size(std::size_t size) const noexcept;
|
||||
|
||||
// allocates a node big enough for the node size
|
||||
// pre: !empty()
|
||||
void* allocate() noexcept;
|
||||
|
||||
// always returns nullptr, because array allocations are not supported
|
||||
void* allocate(std::size_t) noexcept
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// deallocates the node previously allocated via allocate()
|
||||
void deallocate(void* node) noexcept;
|
||||
|
||||
// forwards to insert()
|
||||
void deallocate(void* mem, std::size_t size) noexcept
|
||||
{
|
||||
insert(mem, size);
|
||||
}
|
||||
|
||||
// hint for allocate() to be prepared to allocate n nodes
|
||||
// it searches for a chunk that has n nodes free
|
||||
// returns false, if there is none like that
|
||||
// never fails for n == 1 if not empty()
|
||||
// pre: capacity() >= n * node_size()
|
||||
bool find_chunk(std::size_t n) noexcept
|
||||
{
|
||||
return find_chunk_impl(n) != nullptr;
|
||||
}
|
||||
|
||||
//=== getter ===//
|
||||
std::size_t node_size() const noexcept
|
||||
{
|
||||
return node_size_;
|
||||
}
|
||||
|
||||
// the alignment of all nodes
|
||||
std::size_t alignment() const noexcept;
|
||||
|
||||
// number of nodes remaining
|
||||
std::size_t capacity() const noexcept
|
||||
{
|
||||
return capacity_;
|
||||
}
|
||||
|
||||
bool empty() const noexcept
|
||||
{
|
||||
return capacity_ == 0u;
|
||||
}
|
||||
|
||||
private:
|
||||
chunk* find_chunk_impl(std::size_t n = 1) noexcept;
|
||||
chunk* find_chunk_impl(unsigned char* node, chunk_base* first,
|
||||
chunk_base* last) noexcept;
|
||||
chunk* find_chunk_impl(unsigned char* node) noexcept;
|
||||
|
||||
chunk_base base_;
|
||||
std::size_t node_size_, capacity_;
|
||||
chunk_base *alloc_chunk_, *dealloc_chunk_;
|
||||
};
|
||||
|
||||
// for some reason, this is required in order to define it
|
||||
void swap(small_free_memory_list& a, small_free_memory_list& b) noexcept;
|
||||
} // namespace detail
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_DETAIL_SMALL_FREE_LIST_HPP_INCLUDED
|
||||
@@ -1,117 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_DETAIL_UTILITY_HPP
|
||||
#define WPI_MEMORY_DETAIL_UTILITY_HPP
|
||||
|
||||
// implementation of some functions from <utility> to prevent dependencies on it
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "../config.hpp"
|
||||
|
||||
#if WPI_HOSTED_IMPLEMENTATION
|
||||
#include <utility>
|
||||
#endif
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
// move - taken from http://stackoverflow.com/a/7518365
|
||||
template <typename T>
|
||||
typename std::remove_reference<T>::type&& move(T&& arg) noexcept
|
||||
{
|
||||
return static_cast<typename std::remove_reference<T>::type&&>(arg);
|
||||
}
|
||||
// forward - taken from http://stackoverflow.com/a/27501759
|
||||
template <class T>
|
||||
T&& forward(typename std::remove_reference<T>::type& t) noexcept
|
||||
{
|
||||
return static_cast<T&&>(t);
|
||||
}
|
||||
template <class T>
|
||||
T&& forward(typename std::remove_reference<T>::type&& t) noexcept
|
||||
{
|
||||
static_assert(!std::is_lvalue_reference<T>::value,
|
||||
"Can not forward an rvalue as an lvalue.");
|
||||
return static_cast<T&&>(t);
|
||||
}
|
||||
|
||||
namespace swap_
|
||||
{
|
||||
#if WPI_HOSTED_IMPLEMENTATION
|
||||
using std::swap;
|
||||
#else
|
||||
template <typename T>
|
||||
void swap(T& a, T& b)
|
||||
{
|
||||
T tmp = move(a);
|
||||
a = move(b);
|
||||
b = move(tmp);
|
||||
}
|
||||
#endif
|
||||
} // namespace swap_
|
||||
|
||||
// ADL aware swap
|
||||
template <typename T>
|
||||
void adl_swap(T& a, T& b) noexcept
|
||||
{
|
||||
using swap_::swap;
|
||||
swap(a, b);
|
||||
}
|
||||
|
||||
// fancier syntax for enable_if
|
||||
// used as (template) parameter
|
||||
// also useful for doxygen
|
||||
// define PREDEFINED: WPI_REQUIRES(x):=
|
||||
#define WPI_REQUIRES(Expr) typename std::enable_if<(Expr), int>::type = 0
|
||||
|
||||
// same as above, but as return type
|
||||
// also useful for doxygen:
|
||||
// defined PREDEFINED: WPI_REQUIRES_RET(x,r):=r
|
||||
#define WPI_REQUIRES_RET(Expr, ...) typename std::enable_if<(Expr), __VA_ARGS__>::type
|
||||
|
||||
// fancier syntax for enable_if on non-templated member function
|
||||
#define WPI_ENABLE_IF(Expr) \
|
||||
template <typename Dummy = std::true_type, WPI_REQUIRES(Dummy::value && (Expr))>
|
||||
|
||||
// fancier syntax for general expression SFINAE
|
||||
// used as (template) parameter
|
||||
// also useful for doxygen:
|
||||
// define PREDEFINED: WPI_SFINAE(x):=
|
||||
#define WPI_SFINAE(Expr) decltype((Expr), int()) = 0
|
||||
|
||||
// avoids code repetition for one-line forwarding functions
|
||||
#define WPI_AUTO_RETURN(Expr) \
|
||||
decltype(Expr) \
|
||||
{ \
|
||||
return Expr; \
|
||||
}
|
||||
|
||||
// same as above, but requires certain type
|
||||
#define WPI_AUTO_RETURN_TYPE(Expr, T) \
|
||||
decltype(Expr) \
|
||||
{ \
|
||||
static_assert(std::is_same<decltype(Expr), T>::value, \
|
||||
#Expr " does not have the return type " #T); \
|
||||
return Expr; \
|
||||
}
|
||||
|
||||
// whether or not a type is an instantiation of a template
|
||||
template <template <typename...> class Template, typename T>
|
||||
struct is_instantiation_of : std::false_type
|
||||
{
|
||||
};
|
||||
|
||||
template <template <typename...> class Template, typename... Args>
|
||||
struct is_instantiation_of<Template, Template<Args...>> : std::true_type
|
||||
{
|
||||
};
|
||||
} // namespace detail
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif //WPI_MEMORY_DETAIL_UTILITY_HPP
|
||||
@@ -1,288 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
/// \file
|
||||
/// The exception classes.
|
||||
|
||||
#ifndef WPI_MEMORY_ERROR_HPP_INCLUDED
|
||||
#define WPI_MEMORY_ERROR_HPP_INCLUDED
|
||||
|
||||
#include <cstddef>
|
||||
#include <new>
|
||||
|
||||
#include "config.hpp"
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
/// Contains information about an allocator.
|
||||
/// It can be used for logging in the various handler functions.
|
||||
/// \ingroup memory_core
|
||||
struct allocator_info
|
||||
{
|
||||
/// The name of the allocator.
|
||||
/// It is a NTBS whose lifetime is not managed by this object,
|
||||
/// it must be stored elsewhere or be a string literal.
|
||||
const char* name;
|
||||
|
||||
/// A pointer representing an allocator.
|
||||
/// It does not necessarily point to the beginning of the allocator object,
|
||||
/// the only guarantee is that different allocator objects result in a different pointer value.
|
||||
/// For stateless allocators it is sometimes \c nullptr.
|
||||
/// \note The pointer must not be cast back to any allocator type.
|
||||
const void* allocator;
|
||||
|
||||
/// \effects Creates it by giving it the name of the allocator and a pointer.
|
||||
constexpr allocator_info(const char* n, const void* alloc) noexcept
|
||||
: name(n), allocator(alloc)
|
||||
{
|
||||
}
|
||||
|
||||
/// @{
|
||||
/// \effects Compares two \ref allocator_info objects, they are equal, if the \ref allocator is the same.
|
||||
/// \returns The result of the comparision.
|
||||
friend constexpr bool operator==(const allocator_info& a,
|
||||
const allocator_info& b) noexcept
|
||||
{
|
||||
return a.allocator == b.allocator;
|
||||
}
|
||||
|
||||
friend constexpr bool operator!=(const allocator_info& a,
|
||||
const allocator_info& b) noexcept
|
||||
{
|
||||
return a.allocator != b.allocator;
|
||||
}
|
||||
/// @}
|
||||
};
|
||||
|
||||
/// The exception class thrown when a low level allocator runs out of memory.
|
||||
/// It is derived from \c std::bad_alloc.
|
||||
/// This can happen if a low level allocation function like \c std::malloc() runs out of memory.
|
||||
/// Throwing can be prohibited by the handler function.
|
||||
/// \ingroup memory_core
|
||||
class out_of_memory : public std::bad_alloc
|
||||
{
|
||||
public:
|
||||
/// The type of the handler called in the constructor of \ref out_of_memory.
|
||||
/// When an out of memory situation is encountered and the exception class created,
|
||||
/// this handler gets called.
|
||||
/// It is especially useful if exception support is disabled.
|
||||
/// It gets the \ref allocator_info and the amount of memory that was tried to be allocated.
|
||||
/// \requiredbe It can log the error, throw a different exception derived from \c std::bad_alloc or abort the program.
|
||||
/// If it returns, this exception object will be created and thrown.
|
||||
/// \defaultbe On a hosted implementation it logs the error on \c stderr and continues execution,
|
||||
/// leading to this exception being thrown.
|
||||
/// On a freestanding implementation it does nothing.
|
||||
/// \note It is different from \c std::new_handler; it will not be called in a loop trying to allocate memory
|
||||
/// or something like that. Its only job is to report the error.
|
||||
using handler = void (*)(const allocator_info& info, std::size_t amount);
|
||||
|
||||
/// \effects Sets \c h as the new \ref handler in an atomic operation.
|
||||
/// A \c nullptr sets the default \ref handler.
|
||||
/// \returns The previous \ref handler. This is never \c nullptr.
|
||||
static handler set_handler(handler h);
|
||||
|
||||
/// \returns The current \ref handler. This is never \c nullptr.
|
||||
static handler get_handler();
|
||||
|
||||
/// \effects Creates it by passing it the \ref allocator_info and the amount of memory failed to be allocated.
|
||||
/// It also calls the \ref handler to control whether or not it will be thrown.
|
||||
out_of_memory(const allocator_info& info, std::size_t amount);
|
||||
|
||||
/// \returns A static NTBS that describes the error.
|
||||
/// It does not contain any specific information since there is no memory for formatting.
|
||||
const char* what() const noexcept override;
|
||||
|
||||
/// \returns The \ref allocator_info passed to it in the constructor.
|
||||
const allocator_info& allocator() const noexcept
|
||||
{
|
||||
return info_;
|
||||
}
|
||||
|
||||
/// \returns The amount of memory that was tried to be allocated.
|
||||
/// This is the value passed in the constructor.
|
||||
std::size_t failed_allocation_size() const noexcept
|
||||
{
|
||||
return amount_;
|
||||
}
|
||||
|
||||
private:
|
||||
allocator_info info_;
|
||||
std::size_t amount_;
|
||||
};
|
||||
|
||||
/// A special case of \ref out_of_memory errors
|
||||
/// thrown when a low-level allocator with a fixed size runs out of memory.
|
||||
/// For example, thrown by \ref fixed_block_allocator or \ref static_allocator.<br>
|
||||
/// It is derived from \ref out_of_memory but does not provide its own handler.
|
||||
/// \ingroup memory_core
|
||||
class out_of_fixed_memory : public out_of_memory
|
||||
{
|
||||
public:
|
||||
/// \effects Just forwards to \ref out_of_memory.
|
||||
out_of_fixed_memory(const allocator_info& info, std::size_t amount)
|
||||
: out_of_memory(info, amount)
|
||||
{
|
||||
}
|
||||
|
||||
/// \returns A static NTBS that describes the error.
|
||||
/// It does not contain any specific information since there is no memory for formatting.
|
||||
const char* what() const noexcept override;
|
||||
};
|
||||
|
||||
/// The exception class thrown when an allocation size is bigger than the supported maximum.
|
||||
/// This size is either the node, array or alignment parameter in a call to an allocation function.
|
||||
/// If those exceed the supported maximum returned by \c max_node_size(), \c max_array_size() or \c max_alignment(),
|
||||
/// one of its derived classes will be thrown or this class if in a situation where the type is unknown.
|
||||
/// It is derived from \c std::bad_alloc.
|
||||
/// Throwing can be prohibited by the handler function.
|
||||
/// \note Even if all parameters are less than the maximum, \ref out_of_memory or a similar exception can be thrown,
|
||||
/// because the maximum functions return an upper bound and not the actual supported maximum size,
|
||||
/// since it always depends on fence memory, alignment buffer and the like.
|
||||
/// \note A user should only \c catch for \c bad_allocation_size, not the derived classes.
|
||||
/// \note Most checks will only be done if \ref WPI_MEMORY_CHECK_ALLOCATION_SIZE is \c true.
|
||||
/// \ingroup memory_core
|
||||
class bad_allocation_size : public std::bad_alloc
|
||||
{
|
||||
public:
|
||||
/// The type of the handler called in the constructor of \ref bad_allocation_size.
|
||||
/// When a bad allocation size is detected and the exception object created,
|
||||
/// this handler gets called.
|
||||
/// It is especially useful if exception support is disabled.
|
||||
/// It gets the \ref allocator_info, the size passed to the function and the supported size
|
||||
/// (the latter is still an upper bound).
|
||||
/// \requiredbe It can log the error, throw a different exception derived from \c std::bad_alloc or abort the program.
|
||||
/// If it returns, this exception object will be created and thrown.
|
||||
/// \defaultbe On a hosted implementation it logs the error on \c stderr and continues execution,
|
||||
/// leading to this exception being thrown.
|
||||
/// On a freestanding implementation it does nothing.
|
||||
using handler = void (*)(const allocator_info& info, std::size_t passed,
|
||||
std::size_t supported);
|
||||
|
||||
/// \effects Sets \c h as the new \ref handler in an atomic operation.
|
||||
/// A \c nullptr sets the default \ref handler.
|
||||
/// \returns The previous \ref handler. This is never \c nullptr.
|
||||
static handler set_handler(handler h);
|
||||
|
||||
/// \returns The current \ref handler. This is never \c nullptr.
|
||||
static handler get_handler();
|
||||
|
||||
/// \effects Creates it by passing it the \ref allocator_info, the size passed to the allocation function
|
||||
/// and an upper bound on the supported size.
|
||||
/// It also calls the \ref handler to control whether or not it will be thrown.
|
||||
bad_allocation_size(const allocator_info& info, std::size_t passed,
|
||||
std::size_t supported);
|
||||
|
||||
/// \returns A static NTBS that describes the error.
|
||||
/// It does not contain any specific information since there is no memory for formatting.
|
||||
const char* what() const noexcept override;
|
||||
|
||||
/// \returns The \ref allocator_info passed to it in the constructor.
|
||||
const allocator_info& allocator() const noexcept
|
||||
{
|
||||
return info_;
|
||||
}
|
||||
|
||||
/// \returns The size or alignment value that was passed to the allocation function
|
||||
/// which was too big. This is the same value passed to the constructor.
|
||||
std::size_t passed_value() const noexcept
|
||||
{
|
||||
return passed_;
|
||||
}
|
||||
|
||||
/// \returns An upper bound on the maximum supported size/alignment.
|
||||
/// It is only an upper bound, values below can fail, but values above will always fail.
|
||||
std::size_t supported_value() const noexcept
|
||||
{
|
||||
return supported_;
|
||||
}
|
||||
|
||||
private:
|
||||
allocator_info info_;
|
||||
std::size_t passed_, supported_;
|
||||
};
|
||||
|
||||
/// The exception class thrown when the node size exceeds the supported maximum,
|
||||
/// i.e. it is bigger than \c max_node_size().
|
||||
/// It is derived from \ref bad_allocation_size but does not override the handler.
|
||||
/// \ingroup memory_core
|
||||
class bad_node_size : public bad_allocation_size
|
||||
{
|
||||
public:
|
||||
/// \effects Just forwards to \ref bad_allocation_size.
|
||||
bad_node_size(const allocator_info& info, std::size_t passed, std::size_t supported)
|
||||
: bad_allocation_size(info, passed, supported)
|
||||
{
|
||||
}
|
||||
|
||||
/// \returns A static NTBS that describes the error.
|
||||
/// It does not contain any specific information since there is no memory for formatting.
|
||||
const char* what() const noexcept override;
|
||||
};
|
||||
|
||||
/// The exception class thrown when the array size exceeds the supported maximum,
|
||||
/// i.e. it is bigger than \c max_array_size().
|
||||
/// It is derived from \ref bad_allocation_size but does not override the handler.
|
||||
/// \ingroup memory_core
|
||||
class bad_array_size : public bad_allocation_size
|
||||
{
|
||||
public:
|
||||
/// \effects Just forwards to \ref bad_allocation_size.
|
||||
bad_array_size(const allocator_info& info, std::size_t passed, std::size_t supported)
|
||||
: bad_allocation_size(info, passed, supported)
|
||||
{
|
||||
}
|
||||
|
||||
/// \returns A static NTBS that describes the error.
|
||||
/// It does not contain any specific information since there is no memory for formatting.
|
||||
const char* what() const noexcept override;
|
||||
};
|
||||
|
||||
/// The exception class thrown when the alignment exceeds the supported maximum,
|
||||
/// i.e. it is bigger than \c max_alignment().
|
||||
/// It is derived from \ref bad_allocation_size but does not override the handler.
|
||||
/// \ingroup memory_core
|
||||
class bad_alignment : public bad_allocation_size
|
||||
{
|
||||
public:
|
||||
/// \effects Just forwards to \ref bad_allocation_size.
|
||||
/// \c passed is <tt>count * size</tt>, \c supported the size in bytes.
|
||||
bad_alignment(const allocator_info& info, std::size_t passed, std::size_t supported)
|
||||
: bad_allocation_size(info, passed, supported)
|
||||
{
|
||||
}
|
||||
|
||||
/// \returns A static NTBS that describes the error.
|
||||
/// It does not contain any specific information since there is no memory for formatting.
|
||||
const char* what() const noexcept override;
|
||||
};
|
||||
|
||||
namespace detail
|
||||
{
|
||||
template <class Ex, typename Func>
|
||||
void check_allocation_size(std::size_t passed, Func f, const allocator_info& info)
|
||||
{
|
||||
#if WPI_MEMORY_CHECK_ALLOCATION_SIZE
|
||||
auto supported = f();
|
||||
if (passed > supported)
|
||||
WPI_THROW(Ex(info, passed, supported));
|
||||
#else
|
||||
(void)passed;
|
||||
(void)f;
|
||||
(void)info;
|
||||
#endif
|
||||
}
|
||||
|
||||
template <class Ex>
|
||||
void check_allocation_size(std::size_t passed, std::size_t supported,
|
||||
const allocator_info& info)
|
||||
{
|
||||
check_allocation_size<Ex>(
|
||||
passed, [&] { return supported; }, info);
|
||||
}
|
||||
} // namespace detail
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_ERROR_HPP_INCLUDED
|
||||
@@ -1,211 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_FALLBACK_ALLOCATOR_HPP_INCLUDED
|
||||
#define WPI_MEMORY_FALLBACK_ALLOCATOR_HPP_INCLUDED
|
||||
|
||||
/// \file
|
||||
//// Class template \ref wpi::memory::fallback_allocator.
|
||||
|
||||
#include "detail/ebo_storage.hpp"
|
||||
#include "detail/utility.hpp"
|
||||
#include "allocator_traits.hpp"
|
||||
#include "config.hpp"
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
/// A RawAllocator with a fallback.
|
||||
/// Allocation first tries `Default`, if it fails,
|
||||
/// it uses `Fallback`.
|
||||
/// \requires `Default` must be a composable RawAllocator,
|
||||
/// `Fallback` must be a RawAllocator.
|
||||
/// \ingroup memory_adapter
|
||||
template <class Default, class Fallback>
|
||||
class fallback_allocator
|
||||
: WPI_EBO(detail::ebo_storage<0, typename allocator_traits<Default>::allocator_type>),
|
||||
WPI_EBO(detail::ebo_storage<1, typename allocator_traits<Fallback>::allocator_type>)
|
||||
{
|
||||
using default_traits = allocator_traits<Default>;
|
||||
using default_composable_traits = composable_allocator_traits<Default>;
|
||||
using fallback_traits = allocator_traits<Fallback>;
|
||||
using fallback_composable_traits = composable_allocator_traits<Fallback>;
|
||||
using fallback_composable =
|
||||
is_composable_allocator<typename fallback_traits::allocator_type>;
|
||||
|
||||
public:
|
||||
using default_allocator_type = typename allocator_traits<Default>::allocator_type;
|
||||
using fallback_allocator_type = typename allocator_traits<Fallback>::allocator_type;
|
||||
|
||||
using is_stateful =
|
||||
std::integral_constant<bool, default_traits::is_stateful::value
|
||||
|| fallback_traits::is_stateful::value>;
|
||||
|
||||
/// \effects Default constructs both allocators.
|
||||
/// \notes This function only participates in overload resolution, if both allocators are not stateful.
|
||||
WPI_ENABLE_IF(!is_stateful::value)
|
||||
fallback_allocator()
|
||||
: detail::ebo_storage<0, default_allocator_type>({}),
|
||||
detail::ebo_storage<1, fallback_allocator_type>({})
|
||||
{
|
||||
}
|
||||
|
||||
/// \effects Constructs the allocator by passing in the two allocators it has.
|
||||
explicit fallback_allocator(default_allocator_type&& default_alloc,
|
||||
fallback_allocator_type&& fallback_alloc = {})
|
||||
: detail::ebo_storage<0, default_allocator_type>(detail::move(default_alloc)),
|
||||
detail::ebo_storage<1, fallback_allocator_type>(detail::move(fallback_alloc))
|
||||
{
|
||||
}
|
||||
|
||||
/// @{
|
||||
/// \effects First calls the compositioning (de)allocation function on the `default_allocator_type`.
|
||||
/// If that fails, uses the non-compositioning function of the `fallback_allocator_type`.
|
||||
void* allocate_node(std::size_t size, std::size_t alignment)
|
||||
{
|
||||
auto ptr = default_composable_traits::try_allocate_node(get_default_allocator(),
|
||||
size, alignment);
|
||||
if (!ptr)
|
||||
ptr = fallback_traits::allocate_node(get_fallback_allocator(), size, alignment);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void* allocate_array(std::size_t count, std::size_t size, std::size_t alignment)
|
||||
{
|
||||
auto ptr = default_composable_traits::try_allocate_array(get_default_allocator(),
|
||||
count, size, alignment);
|
||||
if (!ptr)
|
||||
ptr = fallback_traits::allocate_array(get_fallback_allocator(), count, size,
|
||||
alignment);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void deallocate_node(void* ptr, std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
auto res = default_composable_traits::try_deallocate_node(get_default_allocator(),
|
||||
ptr, size, alignment);
|
||||
if (!res)
|
||||
fallback_traits::deallocate_node(get_fallback_allocator(), ptr, size,
|
||||
alignment);
|
||||
}
|
||||
|
||||
void deallocate_array(void* ptr, std::size_t count, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
{
|
||||
auto res =
|
||||
default_composable_traits::try_deallocate_array(get_default_allocator(), ptr,
|
||||
count, size, alignment);
|
||||
if (!res)
|
||||
fallback_traits::deallocate_array(get_fallback_allocator(), ptr, count, size,
|
||||
alignment);
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \effects First calls the compositioning (de)allocation function on the `default_allocator_type`.
|
||||
/// If that fails, uses the compositioning function of the `fallback_allocator_type`.
|
||||
/// \requires The `fallback_allocator_type` msut be composable.
|
||||
WPI_ENABLE_IF(fallback_composable::value)
|
||||
void* try_allocate_node(std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
auto ptr = default_composable_traits::try_allocate_node(get_default_allocator(),
|
||||
size, alignment);
|
||||
if (!ptr)
|
||||
ptr = fallback_composable_traits::try_allocate_node(get_fallback_allocator(),
|
||||
size, alignment);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
WPI_ENABLE_IF(fallback_composable::value)
|
||||
void* allocate_array(std::size_t count, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
{
|
||||
auto ptr = default_composable_traits::try_allocate_array(get_default_allocator(),
|
||||
count, size, alignment);
|
||||
if (!ptr)
|
||||
ptr = fallback_composable_traits::try_allocate_array(get_fallback_allocator(),
|
||||
count, size, alignment);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
WPI_ENABLE_IF(fallback_composable::value)
|
||||
bool try_deallocate_node(void* ptr, std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
auto res = default_composable_traits::try_deallocate_node(get_default_allocator(),
|
||||
ptr, size, alignment);
|
||||
if (!res)
|
||||
res = fallback_composable_traits::try_deallocate_node(get_fallback_allocator(),
|
||||
ptr, size, alignment);
|
||||
return res;
|
||||
}
|
||||
|
||||
WPI_ENABLE_IF(fallback_composable::value)
|
||||
bool try_deallocate_array(void* ptr, std::size_t count, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
{
|
||||
auto res =
|
||||
default_composable_traits::try_deallocate_array(get_default_allocator(), ptr,
|
||||
count, size, alignment);
|
||||
if (!res)
|
||||
res = fallback_composable_traits::try_deallocate_array(get_fallback_allocator(),
|
||||
ptr, count, size,
|
||||
alignment);
|
||||
return res;
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \returns The maximum of the two values from both allocators.
|
||||
std::size_t max_node_size() const
|
||||
{
|
||||
auto def = default_traits::max_node_size(get_default_allocator());
|
||||
auto fallback = fallback_traits::max_node_size(get_fallback_allocator());
|
||||
return fallback > def ? fallback : def;
|
||||
}
|
||||
|
||||
std::size_t max_array_size() const
|
||||
{
|
||||
auto def = default_traits::max_array_size(get_default_allocator());
|
||||
auto fallback = fallback_traits::max_array_size(get_fallback_allocator());
|
||||
return fallback > def ? fallback : def;
|
||||
}
|
||||
|
||||
std::size_t max_alignment() const
|
||||
{
|
||||
auto def = default_traits::max_alignment(get_default_allocator());
|
||||
auto fallback = fallback_traits::max_alignment(get_fallback_allocator());
|
||||
return fallback > def ? fallback : def;
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \returns A (`const`) reference to the default allocator.
|
||||
default_allocator_type& get_default_allocator() noexcept
|
||||
{
|
||||
return detail::ebo_storage<0, default_allocator_type>::get();
|
||||
}
|
||||
|
||||
const default_allocator_type& get_default_allocator() const noexcept
|
||||
{
|
||||
return detail::ebo_storage<0, default_allocator_type>::get();
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \returns A (`const`) reference to the fallback allocator.
|
||||
fallback_allocator_type& get_fallback_allocator() noexcept
|
||||
{
|
||||
return detail::ebo_storage<1, fallback_allocator_type>::get();
|
||||
}
|
||||
|
||||
const fallback_allocator_type& get_fallback_allocator() const noexcept
|
||||
{
|
||||
return detail::ebo_storage<1, fallback_allocator_type>::get();
|
||||
}
|
||||
/// @}
|
||||
};
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_FALLBACK_ALLOCATOR_HPP_INCLUDED
|
||||
@@ -1,82 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_HEAP_ALLOCATOR_HPP_INCLUDED
|
||||
#define WPI_MEMORY_HEAP_ALLOCATOR_HPP_INCLUDED
|
||||
|
||||
/// \file
|
||||
/// Class \ref wpi::memory::heap_allocator and related functions.
|
||||
|
||||
#include "detail/lowlevel_allocator.hpp"
|
||||
#include "config.hpp"
|
||||
|
||||
#if WPI_MEMORY_EXTERN_TEMPLATE
|
||||
#include "allocator_traits.hpp"
|
||||
#endif
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
struct allocator_info;
|
||||
|
||||
/// Allocates heap memory.
|
||||
/// This function is used by the \ref heap_allocator to allocate the heap memory.
|
||||
/// It is not defined on a freestanding implementation, a definition must be provided by the library user.
|
||||
/// \requiredbe This function shall return a block of uninitialized memory that is aligned for \c max_align_t and has the given size.
|
||||
/// The size parameter will not be zero.
|
||||
/// It shall return a \c nullptr if no memory is available.
|
||||
/// It must be thread safe.
|
||||
/// \defaultbe On a hosted implementation this function uses OS specific facilities, \c std::malloc is used as fallback.
|
||||
/// \ingroup memory_allocator
|
||||
void* heap_alloc(std::size_t size) noexcept;
|
||||
|
||||
/// Deallocates heap memory.
|
||||
/// This function is used by the \ref heap_allocator to allocate the heap memory.
|
||||
/// It is not defined on a freestanding implementation, a definition must be provided by the library user.
|
||||
/// \requiredbe This function gets a pointer from a previous call to \ref heap_alloc with the same size.
|
||||
/// It shall free the memory.
|
||||
/// The pointer will not be zero.
|
||||
/// It must be thread safe.
|
||||
/// \defaultbe On a hosted implementation this function uses OS specific facilities, \c std::free is used as fallback.
|
||||
/// \ingroup memory_allocator
|
||||
void heap_dealloc(void* ptr, std::size_t size) noexcept;
|
||||
|
||||
namespace detail
|
||||
{
|
||||
struct heap_allocator_impl
|
||||
{
|
||||
static allocator_info info() noexcept;
|
||||
|
||||
static void* allocate(std::size_t size, std::size_t) noexcept
|
||||
{
|
||||
return heap_alloc(size);
|
||||
}
|
||||
|
||||
static void deallocate(void* ptr, std::size_t size, std::size_t) noexcept
|
||||
{
|
||||
heap_dealloc(ptr, size);
|
||||
}
|
||||
|
||||
static std::size_t max_node_size() noexcept;
|
||||
};
|
||||
|
||||
WPI_MEMORY_LL_ALLOCATOR_LEAK_CHECKER(heap_allocator_impl,
|
||||
heap_alloator_leak_checker)
|
||||
} // namespace detail
|
||||
|
||||
/// A stateless RawAllocator that allocates memory from the heap.
|
||||
/// It uses the two functions \ref heap_alloc and \ref heap_dealloc for the allocation,
|
||||
/// which default to \c std::malloc and \c std::free.
|
||||
/// \ingroup memory_allocator
|
||||
using heap_allocator =
|
||||
WPI_IMPL_DEFINED(detail::lowlevel_allocator<detail::heap_allocator_impl>);
|
||||
|
||||
#if WPI_MEMORY_EXTERN_TEMPLATE
|
||||
extern template class detail::lowlevel_allocator<detail::heap_allocator_impl>;
|
||||
extern template class allocator_traits<heap_allocator>;
|
||||
#endif
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_HEAP_ALLOCATOR_HPP_INCLUDED
|
||||
@@ -1,304 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_ITERATION_ALLOCATOR_HPP_INCLUDED
|
||||
#define WPI_MEMORY_ITERATION_ALLOCATOR_HPP_INCLUDED
|
||||
|
||||
/// \file
|
||||
/// Class template \ref wpi::memory::iteration_allocator.
|
||||
|
||||
#include "detail/debug_helpers.hpp"
|
||||
#include "detail/memory_stack.hpp"
|
||||
#include "default_allocator.hpp"
|
||||
#include "error.hpp"
|
||||
#include "memory_arena.hpp"
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
template <class BlockOrRawAllocator>
|
||||
using iteration_block_allocator =
|
||||
make_block_allocator_t<BlockOrRawAllocator, fixed_block_allocator>;
|
||||
} // namespace detail
|
||||
|
||||
/// A stateful RawAllocator that is designed for allocations in a loop.
|
||||
/// It uses `N` stacks for the allocation, one of them is always active.
|
||||
/// Allocation uses the currently active stack.
|
||||
/// Calling \ref iteration_allocator::next_iteration() at the end of the loop,
|
||||
/// will make the next stack active for allocation,
|
||||
/// effectively releasing all of its memory.
|
||||
/// Any memory allocated will thus be usable for `N` iterations of the loop.
|
||||
/// This type of allocator is a generalization of the double frame allocator.
|
||||
/// \ingroup memory_allocator
|
||||
template <std::size_t N, class BlockOrRawAllocator = default_allocator>
|
||||
class iteration_allocator
|
||||
: WPI_EBO(detail::iteration_block_allocator<BlockOrRawAllocator>)
|
||||
{
|
||||
public:
|
||||
using allocator_type = detail::iteration_block_allocator<BlockOrRawAllocator>;
|
||||
|
||||
/// \effects Creates it with a given initial block size and and other constructor arguments for the BlockAllocator.
|
||||
/// It will allocate the first (and only) block and evenly divide it on all the stacks it uses.
|
||||
template <typename... Args>
|
||||
explicit iteration_allocator(std::size_t block_size, Args&&... args)
|
||||
: allocator_type(block_size, detail::forward<Args>(args)...), cur_(0u)
|
||||
{
|
||||
block_ = get_allocator().allocate_block();
|
||||
auto cur = static_cast<char*>(block_.memory);
|
||||
auto size_each = block_.size / N;
|
||||
for (auto i = 0u; i != N; ++i)
|
||||
{
|
||||
stacks_[i] = detail::fixed_memory_stack(cur);
|
||||
cur += size_each;
|
||||
}
|
||||
}
|
||||
|
||||
iteration_allocator(iteration_allocator&& other) noexcept
|
||||
: allocator_type(detail::move(other)),
|
||||
block_(other.block_),
|
||||
cur_(detail::move(other.cur_))
|
||||
{
|
||||
for (auto i = 0u; i != N; ++i)
|
||||
stacks_[i] = detail::move(other.stacks_[i]);
|
||||
|
||||
other.cur_ = N;
|
||||
}
|
||||
|
||||
~iteration_allocator() noexcept
|
||||
{
|
||||
if (cur_ < N)
|
||||
get_allocator().deallocate_block(block_);
|
||||
}
|
||||
|
||||
iteration_allocator& operator=(iteration_allocator&& other) noexcept
|
||||
{
|
||||
allocator_type::operator=(detail::move(other));
|
||||
block_ = other.block_;
|
||||
cur_ = other.cur_;
|
||||
|
||||
for (auto i = 0u; i != N; ++i)
|
||||
stacks_[i] = detail::move(other.stacks_[i]);
|
||||
|
||||
other.cur_ = N;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// \effects Allocates a memory block of given size and alignment.
|
||||
/// It simply moves the top marker of the currently active stack.
|
||||
/// \returns A node with given size and alignment.
|
||||
/// \throws \ref out_of_fixed_memory if the current stack does not have any memory left.
|
||||
/// \requires \c size and \c alignment must be valid.
|
||||
void* allocate(std::size_t size, std::size_t alignment)
|
||||
{
|
||||
auto& stack = stacks_[cur_];
|
||||
|
||||
auto fence = detail::debug_fence_size;
|
||||
auto offset = detail::align_offset(stack.top() + fence, alignment);
|
||||
if (!stack.top()
|
||||
|| (fence + offset + size + fence > std::size_t(block_end(cur_) - stack.top())))
|
||||
WPI_THROW(out_of_fixed_memory(info(), size));
|
||||
return stack.allocate_unchecked(size, offset);
|
||||
}
|
||||
|
||||
/// \effects Allocates a memory block of given size and alignment
|
||||
/// similar to \ref allocate().
|
||||
/// \returns A node with given size and alignment
|
||||
/// or `nullptr` if the current stack does not have any memory left.
|
||||
void* try_allocate(std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
auto& stack = stacks_[cur_];
|
||||
return stack.allocate(block_end(cur_), size, alignment);
|
||||
}
|
||||
|
||||
/// \effects Goes to the next internal stack.
|
||||
/// This will clear the stack whose \ref max_iterations() lifetime has reached,
|
||||
/// and use it for all allocations in this iteration.
|
||||
/// \note This function should be called at the end of the loop.
|
||||
void next_iteration() noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT_MSG(cur_ != N, "moved-from allocator");
|
||||
cur_ = (cur_ + 1) % N;
|
||||
stacks_[cur_].unwind(block_start(cur_));
|
||||
}
|
||||
|
||||
/// \returns The number of iteration each allocation will live.
|
||||
/// This is the template parameter `N`.
|
||||
static std::size_t max_iterations() noexcept
|
||||
{
|
||||
return N;
|
||||
}
|
||||
|
||||
/// \returns The index of the current iteration.
|
||||
/// This is modulo \ref max_iterations().
|
||||
std::size_t cur_iteration() const noexcept
|
||||
{
|
||||
return cur_;
|
||||
}
|
||||
|
||||
/// \returns A reference to the BlockAllocator used for managing the memory.
|
||||
/// \requires It is undefined behavior to move this allocator out into another object.
|
||||
allocator_type& get_allocator() noexcept
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// \returns The amount of memory remaining in the stack with the given index.
|
||||
/// This is the number of bytes that are available for allocation.
|
||||
std::size_t capacity_left(std::size_t i) const noexcept
|
||||
{
|
||||
return std::size_t(block_end(i) - stacks_[i].top());
|
||||
}
|
||||
|
||||
/// \returns The amount of memory remaining in the currently active stack.
|
||||
std::size_t capacity_left() const noexcept
|
||||
{
|
||||
return capacity_left(cur_iteration());
|
||||
}
|
||||
|
||||
private:
|
||||
allocator_info info() const noexcept
|
||||
{
|
||||
return {WPI_MEMORY_LOG_PREFIX "::iteration_allocator", this};
|
||||
}
|
||||
|
||||
char* block_start(std::size_t i) const noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT_MSG(i <= N, "moved from state");
|
||||
auto ptr = static_cast<char*>(block_.memory);
|
||||
return ptr + (i * block_.size / N);
|
||||
}
|
||||
|
||||
char* block_end(std::size_t i) const noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT_MSG(i < N, "moved from state");
|
||||
return block_start(i + 1);
|
||||
}
|
||||
|
||||
detail::fixed_memory_stack stacks_[N];
|
||||
memory_block block_;
|
||||
std::size_t cur_;
|
||||
|
||||
friend allocator_traits<iteration_allocator<N, BlockOrRawAllocator>>;
|
||||
friend composable_allocator_traits<iteration_allocator<N, BlockOrRawAllocator>>;
|
||||
};
|
||||
|
||||
/// An alias for \ref iteration_allocator for two iterations.
|
||||
/// \ingroup memory_allocator
|
||||
template <class BlockOrRawAllocator = default_allocator>
|
||||
WPI_ALIAS_TEMPLATE(double_frame_allocator,
|
||||
iteration_allocator<2, BlockOrRawAllocator>);
|
||||
|
||||
#if WPI_MEMORY_EXTERN_TEMPLATE
|
||||
extern template class iteration_allocator<2>;
|
||||
#endif
|
||||
|
||||
/// Specialization of the \ref allocator_traits for \ref iteration_allocator.
|
||||
/// \note It is not allowed to mix calls through the specialization and through the member functions,
|
||||
/// i.e. \ref memory_stack::allocate() and this \c allocate_node().
|
||||
/// \ingroup memory_allocator
|
||||
template <std::size_t N, class BlockAllocator>
|
||||
class allocator_traits<iteration_allocator<N, BlockAllocator>>
|
||||
{
|
||||
public:
|
||||
using allocator_type = iteration_allocator<N, BlockAllocator>;
|
||||
using is_stateful = std::true_type;
|
||||
|
||||
/// \returns The result of \ref iteration_allocator::allocate().
|
||||
static void* allocate_node(allocator_type& state, std::size_t size,
|
||||
std::size_t alignment)
|
||||
{
|
||||
return state.allocate(size, alignment);
|
||||
}
|
||||
|
||||
/// \returns The result of \ref memory_stack::allocate().
|
||||
static void* allocate_array(allocator_type& state, std::size_t count, std::size_t size,
|
||||
std::size_t alignment)
|
||||
{
|
||||
return allocate_node(state, count * size, alignment);
|
||||
}
|
||||
|
||||
/// @{
|
||||
/// \effects Does nothing.
|
||||
/// Actual deallocation can only be done via \ref memory_stack::unwind().
|
||||
static void deallocate_node(allocator_type&, void*, std::size_t, std::size_t) noexcept
|
||||
{
|
||||
}
|
||||
|
||||
static void deallocate_array(allocator_type&, void*, std::size_t, std::size_t,
|
||||
std::size_t) noexcept
|
||||
{
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \returns The maximum size which is \ref iteration_allocator::capacity_left().
|
||||
static std::size_t max_node_size(const allocator_type& state) noexcept
|
||||
{
|
||||
return state.capacity_left();
|
||||
}
|
||||
|
||||
static std::size_t max_array_size(const allocator_type& state) noexcept
|
||||
{
|
||||
return state.capacity_left();
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// \returns The maximum possible value since there is no alignment restriction
|
||||
/// (except indirectly through \ref memory_stack::next_capacity()).
|
||||
static std::size_t max_alignment(const allocator_type&) noexcept
|
||||
{
|
||||
return std::size_t(-1);
|
||||
}
|
||||
};
|
||||
|
||||
/// Specialization of the \ref composable_allocator_traits for \ref iteration_allocator classes.
|
||||
/// \ingroup memory_allocator
|
||||
template <std::size_t N, class BlockAllocator>
|
||||
class composable_allocator_traits<iteration_allocator<N, BlockAllocator>>
|
||||
{
|
||||
public:
|
||||
using allocator_type = iteration_allocator<N, BlockAllocator>;
|
||||
|
||||
/// \returns The result of \ref memory_stack::try_allocate().
|
||||
static void* try_allocate_node(allocator_type& state, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
{
|
||||
return state.try_allocate(size, alignment);
|
||||
}
|
||||
|
||||
/// \returns The result of \ref memory_stack::try_allocate().
|
||||
static void* try_allocate_array(allocator_type& state, std::size_t count,
|
||||
std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
return state.try_allocate(count * size, alignment);
|
||||
}
|
||||
|
||||
/// @{
|
||||
/// \effects Does nothing.
|
||||
/// \returns Whether the memory will be deallocated by \ref memory_stack::unwind().
|
||||
static bool try_deallocate_node(allocator_type& state, void* ptr, std::size_t,
|
||||
std::size_t) noexcept
|
||||
{
|
||||
return state.block_.contains(ptr);
|
||||
}
|
||||
|
||||
static bool try_deallocate_array(allocator_type& state, void* ptr, std::size_t count,
|
||||
std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
return try_deallocate_node(state, ptr, count * size, alignment);
|
||||
}
|
||||
/// @}
|
||||
};
|
||||
|
||||
#if WPI_MEMORY_EXTERN_TEMPLATE
|
||||
extern template class allocator_traits<iteration_allocator<2>>;
|
||||
extern template class composable_allocator_traits<iteration_allocator<2>>;
|
||||
#endif
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_ITERATION_ALLOCATOR_HPP_INCLUDED
|
||||
@@ -1,926 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_JOINT_ALLOCATOR_HPP_INCLUDED
|
||||
#define WPI_MEMORY_JOINT_ALLOCATOR_HPP_INCLUDED
|
||||
|
||||
/// \file
|
||||
/// Class template \ref wpi::memory::joint_ptr, \ref wpi::memory::joint_allocator and related.
|
||||
|
||||
#include <initializer_list>
|
||||
#include <new>
|
||||
|
||||
#include "detail/align.hpp"
|
||||
#include "detail/memory_stack.hpp"
|
||||
#include "detail/utility.hpp"
|
||||
#include "allocator_storage.hpp"
|
||||
#include "config.hpp"
|
||||
#include "default_allocator.hpp"
|
||||
#include "error.hpp"
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
template <typename T, class RawAllocator>
|
||||
class joint_ptr;
|
||||
|
||||
template <typename T>
|
||||
class joint_type;
|
||||
|
||||
namespace detail
|
||||
{
|
||||
// the stack that allocates the joint memory
|
||||
class joint_stack
|
||||
{
|
||||
public:
|
||||
joint_stack(void* mem, std::size_t cap) noexcept
|
||||
: stack_(static_cast<char*>(mem)), end_(static_cast<char*>(mem) + cap)
|
||||
{
|
||||
}
|
||||
|
||||
void* allocate(std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
return stack_.allocate(end_, size, alignment, 0u);
|
||||
}
|
||||
|
||||
bool bump(std::size_t offset) noexcept
|
||||
{
|
||||
if (offset > std::size_t(end_ - stack_.top()))
|
||||
return false;
|
||||
stack_.bump(offset);
|
||||
return true;
|
||||
}
|
||||
|
||||
char* top() noexcept
|
||||
{
|
||||
return stack_.top();
|
||||
}
|
||||
|
||||
const char* top() const noexcept
|
||||
{
|
||||
return stack_.top();
|
||||
}
|
||||
|
||||
void unwind(void* ptr) noexcept
|
||||
{
|
||||
stack_.unwind(static_cast<char*>(ptr));
|
||||
}
|
||||
|
||||
std::size_t capacity(const char* mem) const noexcept
|
||||
{
|
||||
return std::size_t(end_ - mem);
|
||||
}
|
||||
|
||||
std::size_t capacity_left() const noexcept
|
||||
{
|
||||
return std::size_t(end_ - top());
|
||||
}
|
||||
|
||||
std::size_t capacity_used(const char* mem) const noexcept
|
||||
{
|
||||
return std::size_t(top() - mem);
|
||||
}
|
||||
|
||||
private:
|
||||
detail::fixed_memory_stack stack_;
|
||||
char* end_;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
detail::joint_stack& get_stack(joint_type<T>& obj) noexcept;
|
||||
|
||||
template <typename T>
|
||||
const detail::joint_stack& get_stack(const joint_type<T>& obj) noexcept;
|
||||
} // namespace detail
|
||||
|
||||
/// Tag type that can't be created.
|
||||
///
|
||||
/// It isued by \ref joint_ptr.
|
||||
/// \ingroup memory_allocator
|
||||
class joint
|
||||
{
|
||||
joint(std::size_t cap) noexcept : capacity(cap) {}
|
||||
|
||||
std::size_t capacity;
|
||||
|
||||
template <typename T, class RawAllocator>
|
||||
friend class joint_ptr;
|
||||
template <typename T>
|
||||
friend class joint_type;
|
||||
};
|
||||
|
||||
/// Tag type to make the joint size more explicit.
|
||||
///
|
||||
/// It is used by \ref joint_ptr.
|
||||
/// \ingroup memory_allocator
|
||||
struct joint_size
|
||||
{
|
||||
std::size_t size;
|
||||
|
||||
explicit joint_size(std::size_t s) noexcept : size(s) {}
|
||||
};
|
||||
|
||||
/// CRTP base class for all objects that want to use joint memory.
|
||||
///
|
||||
/// This will disable default copy/move operations
|
||||
/// and inserts additional members for the joint memory management.
|
||||
/// \ingroup memory_allocator
|
||||
template <typename T>
|
||||
class joint_type
|
||||
{
|
||||
protected:
|
||||
/// \effects Creates the base class,
|
||||
/// the tag type cannot be created by the user.
|
||||
/// \note This ensures that you cannot create joint types yourself.
|
||||
joint_type(joint j) noexcept;
|
||||
|
||||
joint_type(const joint_type&) = delete;
|
||||
joint_type(joint_type&&) = delete;
|
||||
|
||||
private:
|
||||
detail::joint_stack stack_;
|
||||
|
||||
template <typename U>
|
||||
friend detail::joint_stack& detail::get_stack(joint_type<U>& obj) noexcept;
|
||||
template <typename U>
|
||||
friend const detail::joint_stack& detail::get_stack(const joint_type<U>& obj) noexcept;
|
||||
};
|
||||
|
||||
namespace detail
|
||||
{
|
||||
template <typename T>
|
||||
detail::joint_stack& get_stack(joint_type<T>& obj) noexcept
|
||||
{
|
||||
return obj.stack_;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const detail::joint_stack& get_stack(const joint_type<T>& obj) noexcept
|
||||
{
|
||||
return obj.stack_;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
char* get_memory(joint_type<T>& obj) noexcept
|
||||
{
|
||||
auto mem = static_cast<void*>(&obj);
|
||||
return static_cast<char*>(mem) + sizeof(T);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const char* get_memory(const joint_type<T>& obj) noexcept
|
||||
{
|
||||
auto mem = static_cast<const void*>(&obj);
|
||||
return static_cast<const char*>(mem) + sizeof(T);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename T>
|
||||
joint_type<T>::joint_type(joint j) noexcept : stack_(detail::get_memory(*this), j.capacity)
|
||||
{
|
||||
WPI_MEMORY_ASSERT(stack_.top() == detail::get_memory(*this));
|
||||
WPI_MEMORY_ASSERT(stack_.capacity_left() == j.capacity);
|
||||
}
|
||||
|
||||
/// A pointer to an object where all allocations are joint.
|
||||
///
|
||||
/// It can either own an object or not (be `nullptr`).
|
||||
/// When it owns an object, it points to a memory block.
|
||||
/// This memory block contains both the actual object (of the type `T`)
|
||||
/// and space for allocations of `T`s members.
|
||||
///
|
||||
/// The type `T` must be derived from \ref joint_type and every constructor must take \ref joint
|
||||
/// as first parameter.
|
||||
/// This prevents that you create joint objects yourself,
|
||||
/// without the additional storage.
|
||||
/// The default copy and move constructors are also deleted,
|
||||
/// you need to write them yourself.
|
||||
///
|
||||
/// You can only access the object through the pointer,
|
||||
/// use \ref joint_allocator or \ref joint_array as members of `T`,
|
||||
/// to enable the memory sharing.
|
||||
/// If you are using \ref joint_allocator inside STL containers,
|
||||
/// make sure that you do not call their regular copy/move constructors,
|
||||
/// but instead the version where you pass an allocator.
|
||||
///
|
||||
/// The memory block will be managed by the given RawAllocator,
|
||||
/// it is stored in an \ref allocator_reference and not owned by the pointer directly.
|
||||
/// \ingroup memory_allocator
|
||||
template <typename T, class RawAllocator>
|
||||
class joint_ptr : WPI_EBO(allocator_reference<RawAllocator>)
|
||||
{
|
||||
static_assert(std::is_base_of<joint_type<T>, T>::value,
|
||||
"T must be derived of joint_type<T>");
|
||||
|
||||
public:
|
||||
using element_type = T;
|
||||
using allocator_type = typename allocator_reference<RawAllocator>::allocator_type;
|
||||
|
||||
//=== constructors/destructor/assignment ===//
|
||||
/// @{
|
||||
/// \effects Creates it with a RawAllocator, but does not own a new object.
|
||||
explicit joint_ptr(allocator_type& alloc) noexcept
|
||||
: allocator_reference<RawAllocator>(alloc), ptr_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
explicit joint_ptr(const allocator_type& alloc) noexcept
|
||||
: allocator_reference<RawAllocator>(alloc), ptr_(nullptr)
|
||||
{
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \effects Reserves memory for the object and the additional size,
|
||||
/// and creates the object by forwarding the arguments to its constructor.
|
||||
/// The RawAllocator will be used for the allocation.
|
||||
template <typename... Args>
|
||||
joint_ptr(allocator_type& alloc, joint_size additional_size, Args&&... args)
|
||||
: joint_ptr(alloc)
|
||||
{
|
||||
create(additional_size.size, detail::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
joint_ptr(const allocator_type& alloc, joint_size additional_size, Args&&... args)
|
||||
: joint_ptr(alloc)
|
||||
{
|
||||
create(additional_size.size, detail::forward<Args>(args)...);
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// \effects Move-constructs the pointer.
|
||||
/// Ownership will be transferred from `other` to the new object.
|
||||
joint_ptr(joint_ptr&& other) noexcept
|
||||
: allocator_reference<RawAllocator>(detail::move(other)), ptr_(other.ptr_)
|
||||
{
|
||||
other.ptr_ = nullptr;
|
||||
}
|
||||
|
||||
/// \effects Destroys the object and deallocates its storage.
|
||||
~joint_ptr() noexcept
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
/// \effects Move-assings the pointer.
|
||||
/// The previously owned object will be destroyed,
|
||||
/// and ownership of `other` transferred.
|
||||
joint_ptr& operator=(joint_ptr&& other) noexcept
|
||||
{
|
||||
joint_ptr tmp(detail::move(other));
|
||||
swap(*this, tmp);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// \effects Same as `reset()`.
|
||||
joint_ptr& operator=(std::nullptr_t) noexcept
|
||||
{
|
||||
reset();
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// \effects Swaps to pointers and their ownership and allocator.
|
||||
friend void swap(joint_ptr& a, joint_ptr& b) noexcept
|
||||
{
|
||||
detail::adl_swap(static_cast<allocator_reference<RawAllocator>&>(a),
|
||||
static_cast<allocator_reference<RawAllocator>&>(b));
|
||||
detail::adl_swap(a.ptr_, b.ptr_);
|
||||
}
|
||||
|
||||
//=== modifiers ===//
|
||||
/// \effects Destroys the object it refers to,
|
||||
/// if there is any.
|
||||
void reset() noexcept
|
||||
{
|
||||
if (ptr_)
|
||||
{
|
||||
(**this).~element_type();
|
||||
this->deallocate_node(ptr_,
|
||||
sizeof(element_type)
|
||||
+ detail::get_stack(*ptr_).capacity(
|
||||
detail::get_memory(*ptr_)),
|
||||
alignof(element_type));
|
||||
ptr_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
//=== accessors ===//
|
||||
/// \returns `true` if the pointer does own an object,
|
||||
/// `false` otherwise.
|
||||
explicit operator bool() const noexcept
|
||||
{
|
||||
return ptr_ != nullptr;
|
||||
}
|
||||
|
||||
/// \returns A reference to the object it owns.
|
||||
/// \requires The pointer must own an object,
|
||||
/// i.e. `operator bool()` must return `true`.
|
||||
element_type& operator*() const noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(ptr_);
|
||||
return *get();
|
||||
}
|
||||
|
||||
/// \returns A pointer to the object it owns.
|
||||
/// \requires The pointer must own an object,
|
||||
/// i.e. `operator bool()` must return `true`.
|
||||
element_type* operator->() const noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(ptr_);
|
||||
return get();
|
||||
}
|
||||
|
||||
/// \returns A pointer to the object it owns
|
||||
/// or `nullptr`, if it does not own any object.
|
||||
element_type* get() const noexcept
|
||||
{
|
||||
return static_cast<element_type*>(ptr_);
|
||||
}
|
||||
|
||||
/// \returns A reference to the allocator it will use for the deallocation.
|
||||
auto get_allocator() const noexcept
|
||||
-> decltype(std::declval<allocator_reference<allocator_type>>().get_allocator())
|
||||
{
|
||||
return this->allocator_reference<allocator_type>::get_allocator();
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename... Args>
|
||||
void create(std::size_t additional_size, Args&&... args)
|
||||
{
|
||||
auto mem = this->allocate_node(sizeof(element_type) + additional_size,
|
||||
alignof(element_type));
|
||||
|
||||
element_type* ptr = nullptr;
|
||||
#if WPI_HAS_EXCEPTION_SUPPORT
|
||||
try
|
||||
{
|
||||
ptr = ::new (mem)
|
||||
element_type(joint(additional_size), detail::forward<Args>(args)...);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
this->deallocate_node(mem, sizeof(element_type) + additional_size,
|
||||
alignof(element_type));
|
||||
throw;
|
||||
}
|
||||
#else
|
||||
ptr = ::new (mem)
|
||||
element_type(joint(additional_size), detail::forward<Args>(args)...);
|
||||
#endif
|
||||
ptr_ = ptr;
|
||||
}
|
||||
|
||||
joint_type<T>* ptr_;
|
||||
|
||||
friend class joint_allocator;
|
||||
};
|
||||
|
||||
/// @{
|
||||
/// \returns `!ptr`,
|
||||
/// i.e. if `ptr` does not own anything.
|
||||
/// \relates joint_ptr
|
||||
template <typename T, class RawAllocator>
|
||||
bool operator==(const joint_ptr<T, RawAllocator>& ptr, std::nullptr_t)
|
||||
{
|
||||
return !ptr;
|
||||
}
|
||||
|
||||
template <typename T, class RawAllocator>
|
||||
bool operator==(std::nullptr_t, const joint_ptr<T, RawAllocator>& ptr)
|
||||
{
|
||||
return ptr == nullptr;
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \returns `ptr.get() == p`,
|
||||
/// i.e. if `ptr` ownws the object referred to by `p`.
|
||||
/// \relates joint_ptr
|
||||
template <typename T, class RawAllocator>
|
||||
bool operator==(const joint_ptr<T, RawAllocator>& ptr, T* p)
|
||||
{
|
||||
return ptr.get() == p;
|
||||
}
|
||||
|
||||
template <typename T, class RawAllocator>
|
||||
bool operator==(T* p, const joint_ptr<T, RawAllocator>& ptr)
|
||||
{
|
||||
return ptr == p;
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \returns `!(ptr == nullptr)`,
|
||||
/// i.e. if `ptr` does own something.
|
||||
/// \relates joint_ptr
|
||||
template <typename T, class RawAllocator>
|
||||
bool operator!=(const joint_ptr<T, RawAllocator>& ptr, std::nullptr_t)
|
||||
{
|
||||
return !(ptr == nullptr);
|
||||
}
|
||||
|
||||
template <typename T, class RawAllocator>
|
||||
bool operator!=(std::nullptr_t, const joint_ptr<T, RawAllocator>& ptr)
|
||||
{
|
||||
return ptr != nullptr;
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \returns `!(ptr == p)`,
|
||||
/// i.e. if `ptr` does not ownw the object referred to by `p`.
|
||||
/// \relates joint_ptr
|
||||
template <typename T, class RawAllocator>
|
||||
bool operator!=(const joint_ptr<T, RawAllocator>& ptr, T* p)
|
||||
{
|
||||
return !(ptr == p);
|
||||
}
|
||||
|
||||
template <typename T, class RawAllocator>
|
||||
bool operator!=(T* p, const joint_ptr<T, RawAllocator>& ptr)
|
||||
{
|
||||
return ptr != p;
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \returns A new \ref joint_ptr as if created with the same arguments passed to the constructor.
|
||||
/// \relatesalso joint_ptr
|
||||
/// \ingroup memory_allocator
|
||||
template <typename T, class RawAllocator, typename... Args>
|
||||
auto allocate_joint(RawAllocator& alloc, joint_size additional_size, Args&&... args)
|
||||
-> joint_ptr<T, RawAllocator>
|
||||
{
|
||||
return joint_ptr<T, RawAllocator>(alloc, additional_size,
|
||||
detail::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename T, class RawAllocator, typename... Args>
|
||||
auto allocate_joint(const RawAllocator& alloc, joint_size additional_size, Args&&... args)
|
||||
-> joint_ptr<T, RawAllocator>
|
||||
{
|
||||
return joint_ptr<T, RawAllocator>(alloc, additional_size,
|
||||
detail::forward<Args>(args)...);
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \returns A new \ref joint_ptr that points to a copy of `joint`.
|
||||
/// It will allocate as much memory as needed and forward to the copy constructor.
|
||||
/// \ingroup memory_allocator
|
||||
template <class RawAllocator, typename T>
|
||||
auto clone_joint(RawAllocator& alloc, const joint_type<T>& joint)
|
||||
-> joint_ptr<T, RawAllocator>
|
||||
{
|
||||
return joint_ptr<T, RawAllocator>(alloc,
|
||||
joint_size(detail::get_stack(joint).capacity_used(
|
||||
detail::get_memory(joint))),
|
||||
static_cast<const T&>(joint));
|
||||
}
|
||||
|
||||
template <class RawAllocator, typename T>
|
||||
auto clone_joint(const RawAllocator& alloc, const joint_type<T>& joint)
|
||||
-> joint_ptr<T, RawAllocator>
|
||||
{
|
||||
return joint_ptr<T, RawAllocator>(alloc,
|
||||
joint_size(detail::get_stack(joint).capacity_used(
|
||||
detail::get_memory(joint))),
|
||||
static_cast<const T&>(joint));
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// A RawAllocator that uses the additional joint memory for its allocation.
|
||||
///
|
||||
/// It is somewhat limited and allows only allocation once.
|
||||
/// All joint allocators for an object share the joint memory and must not be used in multiple threads.
|
||||
/// The memory it returns is owned by a \ref joint_ptr and will be destroyed through it.
|
||||
/// \ingroup memory_allocator
|
||||
class joint_allocator
|
||||
{
|
||||
public:
|
||||
#if defined(__GNUC__) && (!defined(_GLIBCXX_USE_CXX11_ABI) || _GLIBCXX_USE_CXX11_ABI == 0)
|
||||
// std::string requires default constructor for the small string optimization when using gcc's old ABI
|
||||
// so add one, but it must never be used for allocation
|
||||
joint_allocator() noexcept : stack_(nullptr) {}
|
||||
#endif
|
||||
|
||||
/// \effects Creates it using the joint memory of the given object.
|
||||
template <typename T>
|
||||
joint_allocator(joint_type<T>& j) noexcept : stack_(&detail::get_stack(j))
|
||||
{
|
||||
}
|
||||
|
||||
joint_allocator(const joint_allocator& other) noexcept = default;
|
||||
joint_allocator& operator=(const joint_allocator& other) noexcept = default;
|
||||
|
||||
/// \effects Allocates a node with given properties.
|
||||
/// \returns A pointer to the new node.
|
||||
/// \throws \ref out_of_fixed_memory exception if this function has been called for a second time
|
||||
/// or the joint memory block is exhausted.
|
||||
void* allocate_node(std::size_t size, std::size_t alignment)
|
||||
{
|
||||
WPI_MEMORY_ASSERT(stack_);
|
||||
auto mem = stack_->allocate(size, alignment);
|
||||
if (!mem)
|
||||
WPI_THROW(out_of_fixed_memory(info(), size));
|
||||
return mem;
|
||||
}
|
||||
|
||||
/// \effects Deallocates the node, if possible.
|
||||
/// \note It is only possible if it was the last allocation.
|
||||
void deallocate_node(void* ptr, std::size_t size, std::size_t) noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(stack_);
|
||||
auto end = static_cast<char*>(ptr) + size;
|
||||
if (end == stack_->top())
|
||||
stack_->unwind(ptr);
|
||||
}
|
||||
|
||||
private:
|
||||
allocator_info info() const noexcept
|
||||
{
|
||||
return allocator_info(WPI_MEMORY_LOG_PREFIX "::joint_allocator", this);
|
||||
}
|
||||
|
||||
detail::joint_stack* stack_;
|
||||
|
||||
friend bool operator==(const joint_allocator& lhs, const joint_allocator& rhs) noexcept;
|
||||
};
|
||||
|
||||
/// @{
|
||||
/// \returns Whether `lhs` and `rhs` use the same joint memory for the allocation.
|
||||
/// \relates joint_allocator
|
||||
inline bool operator==(const joint_allocator& lhs, const joint_allocator& rhs) noexcept
|
||||
{
|
||||
return lhs.stack_ == rhs.stack_;
|
||||
}
|
||||
|
||||
inline bool operator!=(const joint_allocator& lhs, const joint_allocator& rhs) noexcept
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// Specialization of \ref is_shared_allocator to mark \ref joint_allocator as shared.
|
||||
/// This allows using it as \ref allocator_reference directly.
|
||||
/// \ingroup memory_allocator
|
||||
template <>
|
||||
struct is_shared_allocator<joint_allocator> : std::true_type
|
||||
{
|
||||
};
|
||||
|
||||
/// Specialization of \ref is_thread_safe_allocator to mark \ref joint_allocator as thread safe.
|
||||
/// This is an optimization to get rid of the mutex in \ref allocator_storage,
|
||||
/// as joint allocator must not be shared between threads.
|
||||
/// \note The allocator is *not* thread safe, it just must not be shared.
|
||||
template <>
|
||||
struct is_thread_safe_allocator<joint_allocator> : std::true_type
|
||||
{
|
||||
};
|
||||
|
||||
#if !defined(DOXYGEN)
|
||||
template <class RawAllocator>
|
||||
struct propagation_traits;
|
||||
#endif
|
||||
|
||||
/// Specialization of the \ref propagation_traits for the \ref joint_allocator.
|
||||
/// A joint allocator does not propagate on assignment
|
||||
/// and it is not allowed to use the regular copy/move constructor of allocator aware containers,
|
||||
/// instead it needs the copy/move constructor with allocator.
|
||||
/// \note This is required because the container constructor will end up copying/moving the allocator.
|
||||
/// But this is not allowed as you need the allocator with the correct joined memory.
|
||||
/// Copying can be customized (i.e. forbidden), but sadly not move, so keep that in mind.
|
||||
/// \ingroup memory_allocator
|
||||
template <>
|
||||
struct propagation_traits<joint_allocator>
|
||||
{
|
||||
using propagate_on_container_swap = std::false_type;
|
||||
using propagate_on_container_move_assignment = std::false_type;
|
||||
using propagate_on_container_copy_assignment = std::false_type;
|
||||
|
||||
template <class AllocReference>
|
||||
static AllocReference select_on_container_copy_construction(const AllocReference&)
|
||||
{
|
||||
static_assert(always_false<AllocReference>::value,
|
||||
"you must not use the regular copy constructor");
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
struct always_false : std::false_type
|
||||
{
|
||||
};
|
||||
};
|
||||
|
||||
/// A zero overhead dynamic array using joint memory.
|
||||
///
|
||||
/// If you use, e.g. `std::vector` with \ref joint_allocator,
|
||||
/// this has a slight additional overhead.
|
||||
/// This type is joint memory aware and has no overhead.
|
||||
///
|
||||
/// It has a dynamic, but fixed size,
|
||||
/// it cannot grow after it has been created.
|
||||
/// \ingroup memory_allocator
|
||||
template <typename T>
|
||||
class joint_array
|
||||
{
|
||||
public:
|
||||
using value_type = T;
|
||||
using iterator = value_type*;
|
||||
using const_iterator = const value_type*;
|
||||
|
||||
//=== constructors ===//
|
||||
/// \effects Creates with `size` default-constructed objects using the specified joint memory.
|
||||
/// \throws \ref out_of_fixed_memory if `size` is too big
|
||||
/// and anything thrown by `T`s constructor.
|
||||
/// If an allocation is thrown, the memory will be released directly.
|
||||
template <typename JointType>
|
||||
joint_array(std::size_t size, joint_type<JointType>& j)
|
||||
: joint_array(detail::get_stack(j), size)
|
||||
{
|
||||
}
|
||||
|
||||
/// \effects Creates with `size` copies of `val` using the specified joint memory.
|
||||
/// \throws \ref out_of_fixed_memory if `size` is too big
|
||||
/// and anything thrown by `T`s constructor.
|
||||
/// If an allocation is thrown, the memory will be released directly.
|
||||
template <typename JointType>
|
||||
joint_array(std::size_t size, const value_type& val, joint_type<JointType>& j)
|
||||
: joint_array(detail::get_stack(j), size, val)
|
||||
{
|
||||
}
|
||||
|
||||
/// \effects Creates with the copies of the objects in the initializer list using the specified joint memory.
|
||||
/// \throws \ref out_of_fixed_memory if the size is too big
|
||||
/// and anything thrown by `T`s constructor.
|
||||
/// If an allocation is thrown, the memory will be released directly.
|
||||
template <typename JointType>
|
||||
joint_array(std::initializer_list<value_type> ilist, joint_type<JointType>& j)
|
||||
: joint_array(detail::get_stack(j), ilist)
|
||||
{
|
||||
}
|
||||
|
||||
/// \effects Creates it by forwarding each element of the range to `T`s constructor using the specified joint memory.
|
||||
/// \throws \ref out_of_fixed_memory if the size is too big
|
||||
/// and anything thrown by `T`s constructor.
|
||||
/// If an allocation is thrown, the memory will be released directly.
|
||||
template <typename InIter, typename JointType,
|
||||
typename = decltype(*std::declval<InIter&>()++)>
|
||||
joint_array(InIter begin, InIter end, joint_type<JointType>& j)
|
||||
: joint_array(detail::get_stack(j), begin, end)
|
||||
{
|
||||
}
|
||||
|
||||
joint_array(const joint_array&) = delete;
|
||||
|
||||
/// \effects Copy constructs each element from `other` into the storage of the specified joint memory.
|
||||
/// \throws \ref out_of_fixed_memory if the size is too big
|
||||
/// and anything thrown by `T`s constructor.
|
||||
/// If an allocation is thrown, the memory will be released directly.
|
||||
template <typename JointType>
|
||||
joint_array(const joint_array& other, joint_type<JointType>& j)
|
||||
: joint_array(detail::get_stack(j), other)
|
||||
{
|
||||
}
|
||||
|
||||
joint_array(joint_array&&) = delete;
|
||||
|
||||
/// \effects Move constructs each element from `other` into the storage of the specified joint memory.
|
||||
/// \throws \ref out_of_fixed_memory if the size is too big
|
||||
/// and anything thrown by `T`s constructor.
|
||||
/// If an allocation is thrown, the memory will be released directly.
|
||||
template <typename JointType>
|
||||
joint_array(joint_array&& other, joint_type<JointType>& j)
|
||||
: joint_array(detail::get_stack(j), detail::move(other))
|
||||
{
|
||||
}
|
||||
|
||||
/// \effects Destroys all objects,
|
||||
/// but does not release the storage.
|
||||
~joint_array() noexcept
|
||||
{
|
||||
for (std::size_t i = 0u; i != size_; ++i)
|
||||
ptr_[i].~T();
|
||||
}
|
||||
|
||||
joint_array& operator=(const joint_array&) = delete;
|
||||
joint_array& operator=(joint_array&&) = delete;
|
||||
|
||||
//=== accessors ===//
|
||||
/// @{
|
||||
/// \returns A reference to the `i`th object.
|
||||
/// \requires `i < size()`.
|
||||
value_type& operator[](std::size_t i) noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(i < size_);
|
||||
return ptr_[i];
|
||||
}
|
||||
|
||||
const value_type& operator[](std::size_t i) const noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(i < size_);
|
||||
return ptr_[i];
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \returns A pointer to the first object.
|
||||
/// It points to contiguous memory and can be used to access the objects directly.
|
||||
value_type* data() noexcept
|
||||
{
|
||||
return ptr_;
|
||||
}
|
||||
|
||||
const value_type* data() const noexcept
|
||||
{
|
||||
return ptr_;
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \returns A random access iterator to the first element.
|
||||
iterator begin() noexcept
|
||||
{
|
||||
return ptr_;
|
||||
}
|
||||
|
||||
const_iterator begin() const noexcept
|
||||
{
|
||||
return ptr_;
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \returns A random access iterator one past the last element.
|
||||
iterator end() noexcept
|
||||
{
|
||||
return ptr_ + size_;
|
||||
}
|
||||
|
||||
const_iterator end() const noexcept
|
||||
{
|
||||
return ptr_ + size_;
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// \returns The number of elements in the array.
|
||||
std::size_t size() const noexcept
|
||||
{
|
||||
return size_;
|
||||
}
|
||||
|
||||
/// \returns `true` if the array is empty, `false` otherwise.
|
||||
bool empty() const noexcept
|
||||
{
|
||||
return size_ == 0u;
|
||||
}
|
||||
|
||||
private:
|
||||
// allocate only
|
||||
struct allocate_only
|
||||
{
|
||||
};
|
||||
joint_array(allocate_only, detail::joint_stack& stack, std::size_t size)
|
||||
: ptr_(nullptr), size_(0u)
|
||||
{
|
||||
ptr_ = static_cast<T*>(stack.allocate(size * sizeof(T), alignof(T)));
|
||||
if (!ptr_)
|
||||
WPI_THROW(out_of_fixed_memory(info(), size * sizeof(T)));
|
||||
}
|
||||
|
||||
class builder
|
||||
{
|
||||
public:
|
||||
builder(detail::joint_stack& stack, T* ptr) noexcept
|
||||
: stack_(&stack), objects_(ptr), size_(0u)
|
||||
{
|
||||
}
|
||||
|
||||
~builder() noexcept
|
||||
{
|
||||
for (std::size_t i = 0u; i != size_; ++i)
|
||||
objects_[i].~T();
|
||||
|
||||
if (size_)
|
||||
stack_->unwind(objects_);
|
||||
}
|
||||
|
||||
builder(builder&&) = delete;
|
||||
builder& operator=(builder&&) = delete;
|
||||
|
||||
template <typename... Args>
|
||||
T* create(Args&&... args)
|
||||
{
|
||||
auto ptr = ::new (static_cast<void*>(&objects_[size_]))
|
||||
T(detail::forward<Args>(args)...);
|
||||
++size_;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
std::size_t size() const noexcept
|
||||
{
|
||||
return size_;
|
||||
}
|
||||
|
||||
std::size_t release() noexcept
|
||||
{
|
||||
auto res = size_;
|
||||
size_ = 0u;
|
||||
return res;
|
||||
}
|
||||
|
||||
private:
|
||||
detail::joint_stack* stack_;
|
||||
T* objects_;
|
||||
std::size_t size_;
|
||||
};
|
||||
|
||||
joint_array(detail::joint_stack& stack, std::size_t size)
|
||||
: joint_array(allocate_only{}, stack, size)
|
||||
{
|
||||
builder b(stack, ptr_);
|
||||
for (auto i = 0u; i != size; ++i)
|
||||
b.create();
|
||||
size_ = b.release();
|
||||
}
|
||||
|
||||
joint_array(detail::joint_stack& stack, std::size_t size, const value_type& value)
|
||||
: joint_array(allocate_only{}, stack, size)
|
||||
{
|
||||
builder b(stack, ptr_);
|
||||
for (auto i = 0u; i != size; ++i)
|
||||
b.create(value);
|
||||
size_ = b.release();
|
||||
}
|
||||
|
||||
joint_array(detail::joint_stack& stack, std::initializer_list<value_type> ilist)
|
||||
: joint_array(allocate_only{}, stack, ilist.size())
|
||||
{
|
||||
builder b(stack, ptr_);
|
||||
for (auto& elem : ilist)
|
||||
b.create(elem);
|
||||
size_ = b.release();
|
||||
}
|
||||
|
||||
joint_array(detail::joint_stack& stack, const joint_array& other)
|
||||
: joint_array(allocate_only{}, stack, other.size())
|
||||
{
|
||||
builder b(stack, ptr_);
|
||||
for (auto& elem : other)
|
||||
b.create(elem);
|
||||
size_ = b.release();
|
||||
}
|
||||
|
||||
joint_array(detail::joint_stack& stack, joint_array&& other)
|
||||
: joint_array(allocate_only{}, stack, other.size())
|
||||
{
|
||||
builder b(stack, ptr_);
|
||||
for (auto& elem : other)
|
||||
b.create(detail::move(elem));
|
||||
size_ = b.release();
|
||||
}
|
||||
|
||||
template <typename InIter>
|
||||
joint_array(detail::joint_stack& stack, InIter begin, InIter end)
|
||||
: ptr_(nullptr), size_(0u)
|
||||
{
|
||||
if (begin == end)
|
||||
return;
|
||||
|
||||
ptr_ = static_cast<T*>(stack.allocate(sizeof(T), alignof(T)));
|
||||
if (!ptr_)
|
||||
WPI_THROW(out_of_fixed_memory(info(), sizeof(T)));
|
||||
|
||||
builder b(stack, ptr_);
|
||||
b.create(*begin++);
|
||||
|
||||
for (auto last = ptr_; begin != end; ++begin)
|
||||
{
|
||||
// just bump stack to get more memory
|
||||
if (!stack.bump(sizeof(T)))
|
||||
WPI_THROW(out_of_fixed_memory(info(), b.size() * sizeof(T)));
|
||||
|
||||
auto cur = b.create(*begin);
|
||||
WPI_MEMORY_ASSERT(last + 1 == cur);
|
||||
last = cur;
|
||||
}
|
||||
|
||||
size_ = b.release();
|
||||
}
|
||||
|
||||
allocator_info info() const noexcept
|
||||
{
|
||||
return {WPI_MEMORY_LOG_PREFIX "::joint_array", this};
|
||||
}
|
||||
|
||||
value_type* ptr_;
|
||||
std::size_t size_;
|
||||
};
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_JOINT_ALLOCATOR_HPP_INCLUDED
|
||||
@@ -1,70 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_MALLOC_ALLOCATOR_HPP_INCLUDED
|
||||
#define WPI_MEMORY_MALLOC_ALLOCATOR_HPP_INCLUDED
|
||||
|
||||
/// \file
|
||||
/// Class \ref wpi::memory::malloc_allocator.
|
||||
/// \note Only available on a hosted implementation.
|
||||
|
||||
#include "config.hpp"
|
||||
#if !WPI_HOSTED_IMPLEMENTATION
|
||||
#error "This header is only available for a hosted implementation."
|
||||
#endif
|
||||
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
|
||||
#include "detail/lowlevel_allocator.hpp"
|
||||
|
||||
#if WPI_MEMORY_EXTERN_TEMPLATE
|
||||
#include "allocator_traits.hpp"
|
||||
#endif
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
struct allocator_info;
|
||||
|
||||
namespace detail
|
||||
{
|
||||
struct malloc_allocator_impl
|
||||
{
|
||||
static allocator_info info() noexcept;
|
||||
|
||||
static void* allocate(std::size_t size, std::size_t) noexcept
|
||||
{
|
||||
return std::malloc(size);
|
||||
}
|
||||
|
||||
static void deallocate(void* ptr, std::size_t, std::size_t) noexcept
|
||||
{
|
||||
std::free(ptr);
|
||||
}
|
||||
|
||||
static std::size_t max_node_size() noexcept
|
||||
{
|
||||
return std::allocator_traits<std::allocator<char>>::max_size({});
|
||||
}
|
||||
};
|
||||
|
||||
WPI_MEMORY_LL_ALLOCATOR_LEAK_CHECKER(malloc_allocator_impl,
|
||||
malloc_alloator_leak_checker)
|
||||
} // namespace detail
|
||||
|
||||
/// A stateless RawAllocator that allocates memory using <tt>std::malloc()</tt>.
|
||||
/// It throws \ref out_of_memory when the allocation fails.
|
||||
/// \ingroup memory_allocator
|
||||
using malloc_allocator =
|
||||
WPI_IMPL_DEFINED(detail::lowlevel_allocator<detail::malloc_allocator_impl>);
|
||||
|
||||
#if WPI_MEMORY_EXTERN_TEMPLATE
|
||||
extern template class detail::lowlevel_allocator<detail::malloc_allocator_impl>;
|
||||
extern template class allocator_traits<malloc_allocator>;
|
||||
#endif
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif //WPI_MEMORY_MALLOC_ALLOCATOR_HPP_INCLUDED
|
||||
@@ -1,692 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_MEMORY_ARENA_HPP_INCLUDED
|
||||
#define WPI_MEMORY_MEMORY_ARENA_HPP_INCLUDED
|
||||
|
||||
/// \file
|
||||
/// Class \ref wpi::memory::memory_arena and related functionality regarding BlockAllocators.
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "detail/debug_helpers.hpp"
|
||||
#include "detail/assert.hpp"
|
||||
#include "detail/utility.hpp"
|
||||
#include "allocator_traits.hpp"
|
||||
#include "config.hpp"
|
||||
#include "default_allocator.hpp"
|
||||
#include "error.hpp"
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
/// A memory block.
|
||||
/// It is defined by its starting address and size.
|
||||
/// \ingroup memory_core
|
||||
struct memory_block
|
||||
{
|
||||
void* memory; ///< The address of the memory block (might be \c nullptr).
|
||||
std::size_t size; ///< The size of the memory block (might be \c 0).
|
||||
|
||||
/// \effects Creates an invalid memory block with starting address \c nullptr and size \c 0.
|
||||
memory_block() noexcept : memory_block(nullptr, std::size_t(0)) {}
|
||||
|
||||
/// \effects Creates a memory block from a given starting address and size.
|
||||
memory_block(void* mem, std::size_t s) noexcept : memory(mem), size(s) {}
|
||||
|
||||
/// \effects Creates a memory block from a [begin,end) range.
|
||||
memory_block(void* begin, void* end) noexcept
|
||||
: memory_block(begin, static_cast<std::size_t>(static_cast<char*>(end)
|
||||
- static_cast<char*>(begin)))
|
||||
{
|
||||
}
|
||||
|
||||
/// \returns Whether or not a pointer is inside the memory.
|
||||
bool contains(const void* address) const noexcept
|
||||
{
|
||||
auto mem = static_cast<const char*>(memory);
|
||||
auto addr = static_cast<const char*>(address);
|
||||
return addr >= mem && addr < mem + size;
|
||||
}
|
||||
};
|
||||
|
||||
namespace detail
|
||||
{
|
||||
template <class BlockAllocator>
|
||||
std::true_type is_block_allocator_impl(
|
||||
int,
|
||||
WPI_SFINAE(std::declval<memory_block&>() =
|
||||
std::declval<BlockAllocator&>().allocate_block()),
|
||||
WPI_SFINAE(std::declval<std::size_t&>() =
|
||||
std::declval<BlockAllocator&>().next_block_size()),
|
||||
WPI_SFINAE(std::declval<BlockAllocator>().deallocate_block(memory_block{})));
|
||||
|
||||
template <typename T>
|
||||
std::false_type is_block_allocator_impl(short);
|
||||
} // namespace detail
|
||||
|
||||
/// Traits that check whether a type models concept BlockAllocator.
|
||||
/// \ingroup memory_core
|
||||
template <typename T>
|
||||
struct is_block_allocator : decltype(detail::is_block_allocator_impl<T>(0))
|
||||
{
|
||||
};
|
||||
|
||||
#if !defined(DOXYGEN)
|
||||
template <class BlockAllocator, bool Cached = true>
|
||||
class memory_arena;
|
||||
#endif
|
||||
|
||||
/// @{
|
||||
/// Controls the caching of \ref memory_arena.
|
||||
/// By default, deallocated blocks are put onto a cache, so they can be reused later;
|
||||
/// this tag value enable/disable it..<br>
|
||||
/// This can be useful, e.g. if there will never be blocks available for deallocation.
|
||||
/// The (tiny) overhead for the cache can then be disabled.
|
||||
/// An example is \ref memory_pool.
|
||||
/// \ingroup memory_core
|
||||
constexpr bool cached_arena = true;
|
||||
constexpr bool uncached_arena = false;
|
||||
/// @}
|
||||
|
||||
namespace detail
|
||||
{
|
||||
// stores memory block in an intrusive linked list and allows LIFO access
|
||||
class memory_block_stack
|
||||
{
|
||||
public:
|
||||
memory_block_stack() noexcept : head_(nullptr) {}
|
||||
|
||||
~memory_block_stack() noexcept {}
|
||||
|
||||
memory_block_stack(memory_block_stack&& other) noexcept : head_(other.head_)
|
||||
{
|
||||
other.head_ = nullptr;
|
||||
}
|
||||
|
||||
memory_block_stack& operator=(memory_block_stack&& other) noexcept
|
||||
{
|
||||
memory_block_stack tmp(detail::move(other));
|
||||
swap(*this, tmp);
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend void swap(memory_block_stack& a, memory_block_stack& b) noexcept
|
||||
{
|
||||
detail::adl_swap(a.head_, b.head_);
|
||||
}
|
||||
|
||||
// the raw allocated block returned from an allocator
|
||||
using allocated_mb = memory_block;
|
||||
|
||||
// the inserted block slightly smaller to allow for the fixup value
|
||||
using inserted_mb = memory_block;
|
||||
|
||||
// how much an inserted block is smaller
|
||||
static constexpr std::size_t implementation_offset() noexcept
|
||||
{
|
||||
// node size rounded up to the next multiple of max_alignment.
|
||||
return (sizeof(node) / max_alignment + (sizeof(node) % max_alignment != 0))
|
||||
* max_alignment;
|
||||
}
|
||||
|
||||
// pushes a memory block
|
||||
void push(allocated_mb block) noexcept;
|
||||
|
||||
// pops a memory block and returns the original block
|
||||
allocated_mb pop() noexcept;
|
||||
|
||||
// steals the top block from another stack
|
||||
void steal_top(memory_block_stack& other) noexcept;
|
||||
|
||||
// returns the last pushed() inserted memory block
|
||||
inserted_mb top() const noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(head_);
|
||||
auto mem = static_cast<void*>(head_);
|
||||
return {static_cast<char*>(mem) + implementation_offset(), head_->usable_size};
|
||||
}
|
||||
|
||||
bool empty() const noexcept
|
||||
{
|
||||
return head_ == nullptr;
|
||||
}
|
||||
|
||||
bool owns(const void* ptr) const noexcept;
|
||||
|
||||
// O(n) size
|
||||
std::size_t size() const noexcept;
|
||||
|
||||
private:
|
||||
struct node
|
||||
{
|
||||
node* prev;
|
||||
std::size_t usable_size;
|
||||
|
||||
node(node* p, std::size_t size) noexcept : prev(p), usable_size(size) {}
|
||||
};
|
||||
|
||||
node* head_;
|
||||
};
|
||||
|
||||
template <bool Cached>
|
||||
class memory_arena_cache;
|
||||
|
||||
template <>
|
||||
class memory_arena_cache<cached_arena>
|
||||
{
|
||||
protected:
|
||||
bool cache_empty() const noexcept
|
||||
{
|
||||
return cached_.empty();
|
||||
}
|
||||
|
||||
std::size_t cache_size() const noexcept
|
||||
{
|
||||
return cached_.size();
|
||||
}
|
||||
|
||||
std::size_t cached_block_size() const noexcept
|
||||
{
|
||||
return cached_.top().size;
|
||||
}
|
||||
|
||||
bool take_from_cache(detail::memory_block_stack& used) noexcept
|
||||
{
|
||||
if (cached_.empty())
|
||||
return false;
|
||||
used.steal_top(cached_);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class BlockAllocator>
|
||||
void do_deallocate_block(BlockAllocator&, detail::memory_block_stack& used) noexcept
|
||||
{
|
||||
cached_.steal_top(used);
|
||||
}
|
||||
|
||||
template <class BlockAllocator>
|
||||
void do_shrink_to_fit(BlockAllocator& alloc) noexcept
|
||||
{
|
||||
detail::memory_block_stack to_dealloc;
|
||||
// pop from cache and push to temporary stack
|
||||
// this revers order
|
||||
while (!cached_.empty())
|
||||
to_dealloc.steal_top(cached_);
|
||||
// now dealloc everything
|
||||
while (!to_dealloc.empty())
|
||||
alloc.deallocate_block(to_dealloc.pop());
|
||||
}
|
||||
|
||||
private:
|
||||
detail::memory_block_stack cached_;
|
||||
};
|
||||
|
||||
template <>
|
||||
class memory_arena_cache<uncached_arena>
|
||||
{
|
||||
protected:
|
||||
bool cache_empty() const noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
std::size_t cache_size() const noexcept
|
||||
{
|
||||
return 0u;
|
||||
}
|
||||
|
||||
std::size_t cached_block_size() const noexcept
|
||||
{
|
||||
return 0u;
|
||||
}
|
||||
|
||||
bool take_from_cache(detail::memory_block_stack&) noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
template <class BlockAllocator>
|
||||
void do_deallocate_block(BlockAllocator& alloc,
|
||||
detail::memory_block_stack& used) noexcept
|
||||
{
|
||||
alloc.deallocate_block(used.pop());
|
||||
}
|
||||
|
||||
template <class BlockAllocator>
|
||||
void do_shrink_to_fit(BlockAllocator&) noexcept
|
||||
{
|
||||
}
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
/// A memory arena that manages huge memory blocks for a higher-level allocator.
|
||||
/// Some allocators like \ref memory_stack work on huge memory blocks,
|
||||
/// this class manages them fro those allocators.
|
||||
/// It uses a BlockAllocator for the allocation of those blocks.
|
||||
/// The memory blocks in use are put onto a stack like structure, deallocation will pop from the top,
|
||||
/// so it is only possible to deallocate the last allocated block of the arena.
|
||||
/// By default, blocks are not really deallocated but stored in a cache.
|
||||
/// This can be disabled with the second template parameter,
|
||||
/// passing it \ref uncached_arena (or \c false) disables it,
|
||||
/// \ref cached_arena (or \c true) enables it explicitly.
|
||||
/// \ingroup memory_core
|
||||
template <class BlockAllocator, bool Cached /* = true */>
|
||||
class memory_arena : WPI_EBO(BlockAllocator),
|
||||
WPI_EBO(detail::memory_arena_cache<Cached>)
|
||||
{
|
||||
static_assert(is_block_allocator<BlockAllocator>::value,
|
||||
"BlockAllocator is not a BlockAllocator!");
|
||||
using cache = detail::memory_arena_cache<Cached>;
|
||||
|
||||
public:
|
||||
using allocator_type = BlockAllocator;
|
||||
using is_cached = std::integral_constant<bool, Cached>;
|
||||
|
||||
/// \returns The minimum block size required for an arena containing the given amount of memory.
|
||||
/// If an arena is created with the result of `min_block_size(n)`, the resulting capacity will be exactly `n`.
|
||||
/// \requires `byte_size` must be a positive number.
|
||||
static constexpr std::size_t min_block_size(std::size_t byte_size) noexcept
|
||||
{
|
||||
return detail::memory_block_stack::implementation_offset() + byte_size;
|
||||
}
|
||||
|
||||
/// \effects Creates it by giving it the size and other arguments for the BlockAllocator.
|
||||
/// It forwards these arguments to its constructor.
|
||||
/// \requires \c block_size must be greater than \c min_block_size(0) and other requirements depending on the BlockAllocator.
|
||||
/// \throws Anything thrown by the constructor of the \c BlockAllocator.
|
||||
template <typename... Args>
|
||||
explicit memory_arena(std::size_t block_size, Args&&... args)
|
||||
: allocator_type(block_size, detail::forward<Args>(args)...)
|
||||
{
|
||||
WPI_MEMORY_ASSERT(block_size > min_block_size(0));
|
||||
}
|
||||
|
||||
/// \effects Deallocates all memory blocks that where requested back to the BlockAllocator.
|
||||
~memory_arena() noexcept
|
||||
{
|
||||
// clear cache
|
||||
shrink_to_fit();
|
||||
// now deallocate everything
|
||||
while (!used_.empty())
|
||||
allocator_type::deallocate_block(used_.pop());
|
||||
}
|
||||
|
||||
/// @{
|
||||
/// \effects Moves the arena.
|
||||
/// The new arena takes ownership over all the memory blocks from the other arena object,
|
||||
/// which is empty after that.
|
||||
/// This does not invalidate any memory blocks.
|
||||
memory_arena(memory_arena&& other) noexcept
|
||||
: allocator_type(detail::move(other)),
|
||||
cache(detail::move(other)),
|
||||
used_(detail::move(other.used_))
|
||||
{
|
||||
}
|
||||
|
||||
memory_arena& operator=(memory_arena&& other) noexcept
|
||||
{
|
||||
memory_arena tmp(detail::move(other));
|
||||
swap(*this, tmp);
|
||||
return *this;
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// \effects Swaps to memory arena objects.
|
||||
/// This does not invalidate any memory blocks.
|
||||
friend void swap(memory_arena& a, memory_arena& b) noexcept
|
||||
{
|
||||
detail::adl_swap(static_cast<allocator_type&>(a), static_cast<allocator_type&>(b));
|
||||
detail::adl_swap(static_cast<cache&>(a), static_cast<cache&>(b));
|
||||
detail::adl_swap(a.used_, b.used_);
|
||||
}
|
||||
|
||||
/// \effects Allocates a new memory block.
|
||||
/// It first uses a cache of previously deallocated blocks, if caching is enabled,
|
||||
/// if it is empty, allocates a new one.
|
||||
/// \returns The new \ref memory_block.
|
||||
/// \throws Anything thrown by the BlockAllocator allocation function.
|
||||
memory_block allocate_block()
|
||||
{
|
||||
if (!this->take_from_cache(used_))
|
||||
used_.push(allocator_type::allocate_block());
|
||||
|
||||
auto block = used_.top();
|
||||
detail::debug_fill_internal(block.memory, block.size, false);
|
||||
return block;
|
||||
}
|
||||
|
||||
/// \returns The current memory block.
|
||||
/// This is the memory block that will be deallocated by the next call to \ref deallocate_block().
|
||||
memory_block current_block() const noexcept
|
||||
{
|
||||
return used_.top();
|
||||
}
|
||||
|
||||
/// \effects Deallocates the current memory block.
|
||||
/// The current memory block is the block on top of the stack of blocks.
|
||||
/// If caching is enabled, it does not really deallocate it but puts it onto a cache for later use,
|
||||
/// use \ref shrink_to_fit() to purge that cache.
|
||||
void deallocate_block() noexcept
|
||||
{
|
||||
auto block = used_.top();
|
||||
detail::debug_fill_internal(block.memory, block.size, true);
|
||||
this->do_deallocate_block(get_allocator(), used_);
|
||||
}
|
||||
|
||||
/// \returns If `ptr` is in memory owned by the arena.
|
||||
bool owns(const void* ptr) const noexcept
|
||||
{
|
||||
return used_.owns(ptr);
|
||||
}
|
||||
|
||||
/// \effects Purges the cache of unused memory blocks by returning them.
|
||||
/// The memory blocks will be deallocated in reversed order of allocation.
|
||||
/// Does nothing if caching is disabled.
|
||||
void shrink_to_fit() noexcept
|
||||
{
|
||||
this->do_shrink_to_fit(get_allocator());
|
||||
}
|
||||
|
||||
/// \returns The capacity of the arena, i.e. how many blocks are used and cached.
|
||||
std::size_t capacity() const noexcept
|
||||
{
|
||||
return size() + cache_size();
|
||||
}
|
||||
|
||||
/// \returns The size of the cache, i.e. how many blocks can be allocated without allocation.
|
||||
std::size_t cache_size() const noexcept
|
||||
{
|
||||
return cache::cache_size();
|
||||
}
|
||||
|
||||
/// \returns The size of the arena, i.e. how many blocks are in use.
|
||||
/// It is always smaller or equal to the \ref capacity().
|
||||
std::size_t size() const noexcept
|
||||
{
|
||||
return used_.size();
|
||||
}
|
||||
|
||||
/// \returns The size of the next memory block,
|
||||
/// i.e. of the next call to \ref allocate_block().
|
||||
/// If there are blocks in the cache, returns size of the next one.
|
||||
/// Otherwise forwards to the BlockAllocator and subtracts an implementation offset.
|
||||
std::size_t next_block_size() const noexcept
|
||||
{
|
||||
return this->cache_empty() ?
|
||||
allocator_type::next_block_size()
|
||||
- detail::memory_block_stack::implementation_offset() :
|
||||
this->cached_block_size();
|
||||
}
|
||||
|
||||
/// \returns A reference of the BlockAllocator object.
|
||||
/// \requires It is undefined behavior to move this allocator out into another object.
|
||||
allocator_type& get_allocator() noexcept
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
detail::memory_block_stack used_;
|
||||
};
|
||||
|
||||
#if WPI_MEMORY_EXTERN_TEMPLATE
|
||||
extern template class memory_arena<static_block_allocator, true>;
|
||||
extern template class memory_arena<static_block_allocator, false>;
|
||||
extern template class memory_arena<virtual_block_allocator, true>;
|
||||
extern template class memory_arena<virtual_block_allocator, false>;
|
||||
#endif
|
||||
|
||||
/// A BlockAllocator that uses a given RawAllocator for allocating the blocks.
|
||||
/// It calls the \c allocate_array() function with a node of size \c 1 and maximum alignment on the used allocator for the block allocation.
|
||||
/// The size of the next memory block will grow by a given factor after each allocation,
|
||||
/// allowing an amortized constant allocation time in the higher level allocator.
|
||||
/// The factor can be given as rational in the template parameter, default is \c 2.
|
||||
/// \ingroup memory_adapter
|
||||
template <class RawAllocator = default_allocator, unsigned Num = 2, unsigned Den = 1>
|
||||
class growing_block_allocator
|
||||
: WPI_EBO(allocator_traits<RawAllocator>::allocator_type)
|
||||
{
|
||||
static_assert(float(Num) / Den >= 1.0, "invalid growth factor");
|
||||
|
||||
using traits = allocator_traits<RawAllocator>;
|
||||
|
||||
public:
|
||||
using allocator_type = typename traits::allocator_type;
|
||||
|
||||
/// \effects Creates it by giving it the initial block size, the allocator object and the growth factor.
|
||||
/// By default, it uses a default-constructed allocator object and a growth factor of \c 2.
|
||||
/// \requires \c block_size must be greater than 0.
|
||||
explicit growing_block_allocator(std::size_t block_size,
|
||||
allocator_type alloc = allocator_type()) noexcept
|
||||
: allocator_type(detail::move(alloc)), block_size_(block_size)
|
||||
{
|
||||
}
|
||||
|
||||
/// \effects Allocates a new memory block and increases the block size for the next allocation.
|
||||
/// \returns The new \ref memory_block.
|
||||
/// \throws Anything thrown by the \c allocate_array() function of the RawAllocator.
|
||||
memory_block allocate_block()
|
||||
{
|
||||
auto memory =
|
||||
traits::allocate_array(get_allocator(), block_size_, 1, detail::max_alignment);
|
||||
memory_block block(memory, block_size_);
|
||||
block_size_ = grow_block_size(block_size_);
|
||||
return block;
|
||||
}
|
||||
|
||||
/// \effects Deallocates a previously allocated memory block.
|
||||
/// This does not decrease the block size.
|
||||
/// \requires \c block must be previously returned by a call to \ref allocate_block().
|
||||
void deallocate_block(memory_block block) noexcept
|
||||
{
|
||||
traits::deallocate_array(get_allocator(), block.memory, block.size, 1,
|
||||
detail::max_alignment);
|
||||
}
|
||||
|
||||
/// \returns The size of the memory block returned by the next call to \ref allocate_block().
|
||||
std::size_t next_block_size() const noexcept
|
||||
{
|
||||
return block_size_;
|
||||
}
|
||||
|
||||
/// \returns A reference to the used RawAllocator object.
|
||||
allocator_type& get_allocator() noexcept
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// \returns The growth factor.
|
||||
static float growth_factor() noexcept
|
||||
{
|
||||
static constexpr auto factor = float(Num) / Den;
|
||||
return factor;
|
||||
}
|
||||
|
||||
static std::size_t grow_block_size(std::size_t block_size) noexcept
|
||||
{
|
||||
return block_size * Num / Den;
|
||||
}
|
||||
|
||||
private:
|
||||
std::size_t block_size_;
|
||||
};
|
||||
|
||||
#if WPI_MEMORY_EXTERN_TEMPLATE
|
||||
extern template class growing_block_allocator<>;
|
||||
extern template class memory_arena<growing_block_allocator<>, true>;
|
||||
extern template class memory_arena<growing_block_allocator<>, false>;
|
||||
#endif
|
||||
|
||||
/// A BlockAllocator that allows only one block allocation.
|
||||
/// It can be used to prevent higher-level allocators from expanding.
|
||||
/// The one block allocation is performed through the \c allocate_array() function of the given RawAllocator.
|
||||
/// \ingroup memory_adapter
|
||||
template <class RawAllocator = default_allocator>
|
||||
class fixed_block_allocator : WPI_EBO(allocator_traits<RawAllocator>::allocator_type)
|
||||
{
|
||||
using traits = allocator_traits<RawAllocator>;
|
||||
|
||||
public:
|
||||
using allocator_type = typename traits::allocator_type;
|
||||
|
||||
/// \effects Creates it by passing it the size of the block and the allocator object.
|
||||
/// \requires \c block_size must be greater than 0,
|
||||
explicit fixed_block_allocator(std::size_t block_size,
|
||||
allocator_type alloc = allocator_type()) noexcept
|
||||
: allocator_type(detail::move(alloc)), block_size_(block_size)
|
||||
{
|
||||
}
|
||||
|
||||
/// \effects Allocates a new memory block or throws an exception if there was already one allocation.
|
||||
/// \returns The new \ref memory_block.
|
||||
/// \throws Anything thrown by the \c allocate_array() function of the RawAllocator or \ref out_of_memory if this is not the first call.
|
||||
memory_block allocate_block()
|
||||
{
|
||||
if (block_size_)
|
||||
{
|
||||
auto mem = traits::allocate_array(get_allocator(), block_size_, 1,
|
||||
detail::max_alignment);
|
||||
memory_block block(mem, block_size_);
|
||||
block_size_ = 0u;
|
||||
return block;
|
||||
}
|
||||
WPI_THROW(out_of_fixed_memory(info(), block_size_));
|
||||
}
|
||||
|
||||
/// \effects Deallocates the previously allocated memory block.
|
||||
/// It also resets and allows a new call again.
|
||||
void deallocate_block(memory_block block) noexcept
|
||||
{
|
||||
detail::debug_check_pointer([&] { return block_size_ == 0u; }, info(),
|
||||
block.memory);
|
||||
traits::deallocate_array(get_allocator(), block.memory, block.size, 1,
|
||||
detail::max_alignment);
|
||||
block_size_ = block.size;
|
||||
}
|
||||
|
||||
/// \returns The size of the next block which is either the initial size or \c 0.
|
||||
std::size_t next_block_size() const noexcept
|
||||
{
|
||||
return block_size_;
|
||||
}
|
||||
|
||||
/// \returns A reference to the used RawAllocator object.
|
||||
allocator_type& get_allocator() noexcept
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
allocator_info info() noexcept
|
||||
{
|
||||
return {WPI_MEMORY_LOG_PREFIX "::fixed_block_allocator", this};
|
||||
}
|
||||
|
||||
std::size_t block_size_;
|
||||
};
|
||||
|
||||
#if WPI_MEMORY_EXTERN_TEMPLATE
|
||||
extern template class fixed_block_allocator<>;
|
||||
extern template class memory_arena<fixed_block_allocator<>, true>;
|
||||
extern template class memory_arena<fixed_block_allocator<>, false>;
|
||||
#endif
|
||||
|
||||
namespace detail
|
||||
{
|
||||
template <class RawAlloc>
|
||||
using default_block_wrapper = growing_block_allocator<RawAlloc>;
|
||||
|
||||
template <template <class...> class Wrapper, class BlockAllocator, typename... Args>
|
||||
BlockAllocator make_block_allocator(std::true_type, std::size_t block_size,
|
||||
Args&&... args)
|
||||
{
|
||||
return BlockAllocator(block_size, detail::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <template <class...> class Wrapper, class RawAlloc>
|
||||
auto make_block_allocator(std::false_type, std::size_t block_size,
|
||||
RawAlloc alloc = RawAlloc()) -> Wrapper<RawAlloc>
|
||||
{
|
||||
return Wrapper<RawAlloc>(block_size, detail::move(alloc));
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
/// Takes either a BlockAllocator or a RawAllocator.
|
||||
/// In the first case simply aliases the type unchanged, in the second to \ref growing_block_allocator (or the template in `BlockAllocator`) with the RawAllocator.
|
||||
/// Using this allows passing normal RawAllocators as BlockAllocators.
|
||||
/// \ingroup memory_core
|
||||
template <class BlockOrRawAllocator,
|
||||
template <typename...> class BlockAllocator = detail::default_block_wrapper>
|
||||
using make_block_allocator_t = WPI_IMPL_DEFINED(
|
||||
typename std::conditional<is_block_allocator<BlockOrRawAllocator>::value,
|
||||
BlockOrRawAllocator,
|
||||
BlockAllocator<BlockOrRawAllocator>>::type);
|
||||
|
||||
/// @{
|
||||
/// Helper function make a BlockAllocator.
|
||||
/// \returns A BlockAllocator of the given type created with the given arguments.
|
||||
/// \requires Same requirements as the constructor.
|
||||
/// \ingroup memory_core
|
||||
template <class BlockOrRawAllocator, typename... Args>
|
||||
make_block_allocator_t<BlockOrRawAllocator> make_block_allocator(std::size_t block_size,
|
||||
Args&&... args)
|
||||
{
|
||||
return detail::make_block_allocator<
|
||||
detail::default_block_wrapper,
|
||||
BlockOrRawAllocator>(is_block_allocator<BlockOrRawAllocator>{}, block_size,
|
||||
detail::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <template <class...> class BlockAllocator, class BlockOrRawAllocator,
|
||||
typename... Args>
|
||||
make_block_allocator_t<BlockOrRawAllocator, BlockAllocator> make_block_allocator(
|
||||
std::size_t block_size, Args&&... args)
|
||||
{
|
||||
return detail::make_block_allocator<
|
||||
BlockAllocator, BlockOrRawAllocator>(is_block_allocator<BlockOrRawAllocator>{},
|
||||
block_size, detail::forward<Args>(args)...);
|
||||
}
|
||||
/// @}
|
||||
|
||||
namespace literals
|
||||
{
|
||||
/// Syntax sugar to express sizes with unit prefixes.
|
||||
/// \returns The number of bytes `value` is in the given unit.
|
||||
/// \ingroup memory_core
|
||||
/// @{
|
||||
constexpr std::size_t operator""_KiB(unsigned long long value) noexcept
|
||||
{
|
||||
return std::size_t(value * 1024);
|
||||
}
|
||||
|
||||
constexpr std::size_t operator""_KB(unsigned long long value) noexcept
|
||||
{
|
||||
return std::size_t(value * 1000);
|
||||
}
|
||||
|
||||
constexpr std::size_t operator""_MiB(unsigned long long value) noexcept
|
||||
{
|
||||
return std::size_t(value * 1024 * 1024);
|
||||
}
|
||||
|
||||
constexpr std::size_t operator""_MB(unsigned long long value) noexcept
|
||||
{
|
||||
return std::size_t(value * 1000 * 1000);
|
||||
}
|
||||
|
||||
constexpr std::size_t operator""_GiB(unsigned long long value) noexcept
|
||||
{
|
||||
return std::size_t(value * 1024 * 1024 * 1024);
|
||||
}
|
||||
|
||||
constexpr std::size_t operator""_GB(unsigned long long value) noexcept
|
||||
{
|
||||
return std::size_t(value * 1000 * 1000 * 1000);
|
||||
}
|
||||
} // namespace literals
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_MEMORY_ARENA_HPP_INCLUDED
|
||||
@@ -1,432 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_MEMORY_POOL_HPP_INCLUDED
|
||||
#define WPI_MEMORY_MEMORY_POOL_HPP_INCLUDED
|
||||
|
||||
// Inform that wpi::memory::memory_pool::min_block_size API is available
|
||||
#define WPI_MEMORY_MEMORY_POOL_HAS_MIN_BLOCK_SIZE
|
||||
|
||||
/// \file
|
||||
/// Class \ref wpi::memory::memory_pool and its \ref wpi::memory::allocator_traits specialization.
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "detail/align.hpp"
|
||||
#include "detail/debug_helpers.hpp"
|
||||
#include "detail/assert.hpp"
|
||||
#include "config.hpp"
|
||||
#include "error.hpp"
|
||||
#include "memory_arena.hpp"
|
||||
#include "memory_pool_type.hpp"
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
struct memory_pool_leak_handler
|
||||
{
|
||||
void operator()(std::ptrdiff_t amount);
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
/// A stateful RawAllocator that manages nodes of fixed size.
|
||||
/// It uses a \ref memory_arena with a given \c BlockOrRawAllocator defaulting to \ref growing_block_allocator,
|
||||
/// subdivides them in small nodes of given size and puts them onto a free list.
|
||||
/// Allocation and deallocation simply remove or add nodes from this list and are thus fast.
|
||||
/// The way the list is maintained can be controlled via the \c PoolType
|
||||
/// which is either \ref node_pool, \ref array_pool or \ref small_node_pool.<br>
|
||||
/// This kind of allocator is ideal for fixed size allocations and deallocations in any order,
|
||||
/// for example in a node based container like \c std::list.
|
||||
/// It is not so good for different allocation sizes and has some drawbacks for arrays
|
||||
/// as described in \ref memory_pool_type.hpp.
|
||||
/// \ingroup memory_allocator
|
||||
template <typename PoolType = node_pool, class BlockOrRawAllocator = default_allocator>
|
||||
class memory_pool
|
||||
: WPI_EBO(detail::default_leak_checker<detail::memory_pool_leak_handler>)
|
||||
{
|
||||
using free_list = typename PoolType::type;
|
||||
using leak_checker = detail::default_leak_checker<detail::memory_pool_leak_handler>;
|
||||
|
||||
public:
|
||||
using allocator_type = make_block_allocator_t<BlockOrRawAllocator>;
|
||||
using pool_type = PoolType;
|
||||
|
||||
static constexpr std::size_t min_node_size =
|
||||
WPI_IMPL_DEFINED(free_list::min_element_size);
|
||||
|
||||
/// \returns The minimum block size required for certain number of node.
|
||||
/// \requires \c node_size must be a valid node size
|
||||
/// and \c number_of_nodes must be a non-zero value.
|
||||
/// \note MSVC's implementation of \c std::list for example is never empty and always allocates proxy nodes.
|
||||
/// To get enough memory for \c N elements of a list, \c number_of_nodes needs to include the proxy count in addition to \c N.
|
||||
static constexpr std::size_t min_block_size(std::size_t node_size,
|
||||
std::size_t number_of_nodes) noexcept
|
||||
{
|
||||
return detail::memory_block_stack::implementation_offset()
|
||||
+ free_list::min_block_size(node_size, number_of_nodes);
|
||||
}
|
||||
|
||||
/// \effects Creates it by specifying the size each node will have,
|
||||
/// the initial block size for the arena and other constructor arguments for the BlockAllocator.
|
||||
/// If the \c node_size is less than the \c min_node_size, the \c min_node_size will be the actual node size.
|
||||
/// It will allocate an initial memory block with given size from the BlockAllocator
|
||||
/// and puts it onto the free list.
|
||||
/// \requires \c node_size must be a valid node size
|
||||
/// and \c block_size must be at least \c min_block_size(node_size, 1).
|
||||
template <typename... Args>
|
||||
memory_pool(std::size_t node_size, std::size_t block_size, Args&&... args)
|
||||
: arena_(block_size, detail::forward<Args>(args)...), free_list_(node_size)
|
||||
{
|
||||
allocate_block();
|
||||
}
|
||||
|
||||
/// \effects Destroys the \ref memory_pool by returning all memory blocks,
|
||||
/// regardless of properly deallocated back to the BlockAllocator.
|
||||
~memory_pool() noexcept {}
|
||||
|
||||
/// @{
|
||||
/// \effects Moving a \ref memory_pool object transfers ownership over the free list,
|
||||
/// i.e. the moved from pool is completely empty and the new one has all its memory.
|
||||
/// That means that it is not allowed to call \ref deallocate_node() on a moved-from allocator
|
||||
/// even when passing it memory that was previously allocated by this object.
|
||||
memory_pool(memory_pool&& other) noexcept
|
||||
: leak_checker(detail::move(other)),
|
||||
arena_(detail::move(other.arena_)),
|
||||
free_list_(detail::move(other.free_list_))
|
||||
{
|
||||
}
|
||||
|
||||
memory_pool& operator=(memory_pool&& other) noexcept
|
||||
{
|
||||
leak_checker::operator=(detail::move(other));
|
||||
arena_ = detail::move(other.arena_);
|
||||
free_list_ = detail::move(other.free_list_);
|
||||
return *this;
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// \effects Allocates a single node by removing it from the free list.
|
||||
/// If the free list is empty, a new memory block will be allocated from the arena and put onto it.
|
||||
/// The new block size will be \ref next_capacity() big.
|
||||
/// \returns A node of size \ref node_size() suitable aligned,
|
||||
/// i.e. suitable for any type where <tt>sizeof(T) < node_size()</tt>.
|
||||
/// \throws Anything thrown by the used BlockAllocator's allocation function if a growth is needed.
|
||||
void* allocate_node()
|
||||
{
|
||||
if (free_list_.empty())
|
||||
allocate_block();
|
||||
WPI_MEMORY_ASSERT(!free_list_.empty());
|
||||
return free_list_.allocate();
|
||||
}
|
||||
|
||||
/// \effects Allocates a single node similar to \ref allocate_node().
|
||||
/// But if the free list is empty, a new block will *not* be allocated.
|
||||
/// \returns A suitable aligned node of size \ref node_size() or `nullptr`.
|
||||
void* try_allocate_node() noexcept
|
||||
{
|
||||
return free_list_.empty() ? nullptr : free_list_.allocate();
|
||||
}
|
||||
|
||||
/// \effects Allocates an array of nodes by searching for \c n continuous nodes on the list and removing them.
|
||||
/// Depending on the \c PoolType this can be a slow operation or not allowed at all.
|
||||
/// This can sometimes lead to a growth, even if technically there is enough continuous memory on the free list.
|
||||
/// \returns An array of \c n nodes of size \ref node_size() suitable aligned.
|
||||
/// \throws Anything thrown by the used BlockAllocator's allocation function if a growth is needed,
|
||||
/// or \ref bad_array_size if <tt>n * node_size()</tt> is too big.
|
||||
/// \requires \c n must be valid array count.
|
||||
void* allocate_array(std::size_t n)
|
||||
{
|
||||
detail::check_allocation_size<bad_array_size>(
|
||||
n * node_size(), [&] { return pool_type::value ? next_capacity() : 0; },
|
||||
info());
|
||||
return allocate_array(n, node_size());
|
||||
}
|
||||
|
||||
/// \effects Allocates an array of nodes similar to \ref allocate_array().
|
||||
/// But it will never allocate a new memory block.
|
||||
/// \returns An array of \c n nodes of size \ref node_size() suitable aligned
|
||||
/// or `nullptr`.
|
||||
void* try_allocate_array(std::size_t n) noexcept
|
||||
{
|
||||
return try_allocate_array(n, node_size());
|
||||
}
|
||||
|
||||
/// \effects Deallocates a single node by putting it back onto the free list.
|
||||
/// \requires \c ptr must be a result from a previous call to \ref allocate_node() on the same free list,
|
||||
/// i.e. either this allocator object or a new object created by moving this to it.
|
||||
void deallocate_node(void* ptr) noexcept
|
||||
{
|
||||
free_list_.deallocate(ptr);
|
||||
}
|
||||
|
||||
/// \effects Deallocates a single node but it does not be a result of a previous call to \ref allocate_node().
|
||||
/// \returns `true` if the node could be deallocated, `false` otherwise.
|
||||
/// \note Some free list implementations can deallocate any memory,
|
||||
/// doesn't matter where it is coming from.
|
||||
bool try_deallocate_node(void* ptr) noexcept
|
||||
{
|
||||
if (!arena_.owns(ptr))
|
||||
return false;
|
||||
free_list_.deallocate(ptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// \effects Deallocates an array by putting it back onto the free list.
|
||||
/// \requires \c ptr must be a result from a previous call to \ref allocate_array() with the same \c n on the same free list,
|
||||
/// i.e. either this allocator object or a new object created by moving this to it.
|
||||
void deallocate_array(void* ptr, std::size_t n) noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT_MSG(pool_type::value, "does not support array allocations");
|
||||
free_list_.deallocate(ptr, n * node_size());
|
||||
}
|
||||
|
||||
/// \effects Deallocates an array but it does not be a result of a previous call to \ref allocate_array().
|
||||
/// \returns `true` if the node could be deallocated, `false` otherwise.
|
||||
/// \note Some free list implementations can deallocate any memory,
|
||||
/// doesn't matter where it is coming from.
|
||||
bool try_deallocate_array(void* ptr, std::size_t n) noexcept
|
||||
{
|
||||
return try_deallocate_array(ptr, n, node_size());
|
||||
}
|
||||
|
||||
/// \returns The size of each node in the pool,
|
||||
/// this is either the same value as in the constructor or \c min_node_size if the value was too small.
|
||||
std::size_t node_size() const noexcept
|
||||
{
|
||||
return free_list_.node_size();
|
||||
}
|
||||
|
||||
/// \effects Returns the total amount of bytes remaining on the free list.
|
||||
/// Divide it by \ref node_size() to get the number of nodes that can be allocated without growing the arena.
|
||||
/// \note Array allocations may lead to a growth even if the capacity_left left is big enough.
|
||||
std::size_t capacity_left() const noexcept
|
||||
{
|
||||
return free_list_.capacity() * node_size();
|
||||
}
|
||||
|
||||
/// \returns The size of the next memory block after the free list gets empty and the arena grows.
|
||||
/// \ref capacity_left() will increase by this amount.
|
||||
/// \note Due to fence memory in debug mode this cannot be just divided by the \ref node_size() to get the number of nodes.
|
||||
std::size_t next_capacity() const noexcept
|
||||
{
|
||||
return free_list_.usable_size(arena_.next_block_size());
|
||||
}
|
||||
|
||||
/// \returns A reference to the BlockAllocator used for managing the arena.
|
||||
/// \requires It is undefined behavior to move this allocator out into another object.
|
||||
allocator_type& get_allocator() noexcept
|
||||
{
|
||||
return arena_.get_allocator();
|
||||
}
|
||||
|
||||
private:
|
||||
allocator_info info() const noexcept
|
||||
{
|
||||
return {WPI_MEMORY_LOG_PREFIX "::memory_pool", this};
|
||||
}
|
||||
|
||||
void allocate_block()
|
||||
{
|
||||
auto mem = arena_.allocate_block();
|
||||
free_list_.insert(static_cast<char*>(mem.memory), mem.size);
|
||||
}
|
||||
|
||||
void* allocate_array(std::size_t n, std::size_t node_size)
|
||||
{
|
||||
auto mem = free_list_.empty() ? nullptr : free_list_.allocate(n * node_size);
|
||||
if (!mem)
|
||||
{
|
||||
allocate_block();
|
||||
mem = free_list_.allocate(n * node_size);
|
||||
if (!mem)
|
||||
WPI_THROW(bad_array_size(info(), n * node_size, capacity_left()));
|
||||
}
|
||||
return mem;
|
||||
}
|
||||
|
||||
void* try_allocate_array(std::size_t n, std::size_t node_size) noexcept
|
||||
{
|
||||
return !pool_type::value || free_list_.empty() ? nullptr :
|
||||
free_list_.allocate(n * node_size);
|
||||
}
|
||||
|
||||
bool try_deallocate_array(void* ptr, std::size_t n, std::size_t node_size) noexcept
|
||||
{
|
||||
if (!pool_type::value || !arena_.owns(ptr))
|
||||
return false;
|
||||
free_list_.deallocate(ptr, n * node_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
memory_arena<allocator_type, false> arena_;
|
||||
free_list free_list_;
|
||||
|
||||
friend allocator_traits<memory_pool<PoolType, BlockOrRawAllocator>>;
|
||||
friend composable_allocator_traits<memory_pool<PoolType, BlockOrRawAllocator>>;
|
||||
};
|
||||
|
||||
#if WPI_MEMORY_EXTERN_TEMPLATE
|
||||
extern template class memory_pool<node_pool>;
|
||||
extern template class memory_pool<array_pool>;
|
||||
extern template class memory_pool<small_node_pool>;
|
||||
#endif
|
||||
|
||||
template <class Type, class Alloc>
|
||||
constexpr std::size_t memory_pool<Type, Alloc>::min_node_size;
|
||||
|
||||
/// Specialization of the \ref allocator_traits for \ref memory_pool classes.
|
||||
/// \note It is not allowed to mix calls through the specialization and through the member functions,
|
||||
/// i.e. \ref memory_pool::allocate_node() and this \c allocate_node().
|
||||
/// \ingroup memory_allocator
|
||||
template <typename PoolType, class ImplRawAllocator>
|
||||
class allocator_traits<memory_pool<PoolType, ImplRawAllocator>>
|
||||
{
|
||||
public:
|
||||
using allocator_type = memory_pool<PoolType, ImplRawAllocator>;
|
||||
using is_stateful = std::true_type;
|
||||
|
||||
/// \returns The result of \ref memory_pool::allocate_node().
|
||||
/// \throws Anything thrown by the pool allocation function
|
||||
/// or a \ref bad_allocation_size exception.
|
||||
static void* allocate_node(allocator_type& state, std::size_t size,
|
||||
std::size_t alignment)
|
||||
{
|
||||
detail::check_allocation_size<bad_node_size>(size, max_node_size(state),
|
||||
state.info());
|
||||
detail::check_allocation_size<bad_alignment>(
|
||||
alignment, [&] { return max_alignment(state); }, state.info());
|
||||
auto mem = state.allocate_node();
|
||||
state.on_allocate(size);
|
||||
return mem;
|
||||
}
|
||||
|
||||
/// \effects Forwards to \ref memory_pool::allocate_array()
|
||||
/// with the number of nodes adjusted to be the minimum,
|
||||
/// i.e. when the \c size is less than the \ref memory_pool::node_size().
|
||||
/// \returns A array with specified properties.
|
||||
/// \requires The \ref memory_pool has to support array allocations.
|
||||
/// \throws Anything thrown by the pool allocation function.
|
||||
static void* allocate_array(allocator_type& state, std::size_t count, std::size_t size,
|
||||
std::size_t alignment)
|
||||
{
|
||||
detail::check_allocation_size<bad_node_size>(size, max_node_size(state),
|
||||
state.info());
|
||||
detail::check_allocation_size<bad_alignment>(
|
||||
alignment, [&] { return max_alignment(state); }, state.info());
|
||||
detail::check_allocation_size<bad_array_size>(count * size, max_array_size(state),
|
||||
state.info());
|
||||
auto mem = state.allocate_array(count, size);
|
||||
state.on_allocate(count * size);
|
||||
return mem;
|
||||
}
|
||||
|
||||
/// \effects Just forwards to \ref memory_pool::deallocate_node().
|
||||
static void deallocate_node(allocator_type& state, void* node, std::size_t size,
|
||||
std::size_t) noexcept
|
||||
{
|
||||
state.deallocate_node(node);
|
||||
state.on_deallocate(size);
|
||||
}
|
||||
|
||||
/// \effects Forwards to \ref memory_pool::deallocate_array() with the same size adjustment.
|
||||
static void deallocate_array(allocator_type& state, void* array, std::size_t count,
|
||||
std::size_t size, std::size_t) noexcept
|
||||
{
|
||||
state.free_list_.deallocate(array, count * size);
|
||||
state.on_deallocate(count * size);
|
||||
}
|
||||
|
||||
/// \returns The maximum size of each node which is \ref memory_pool::node_size().
|
||||
static std::size_t max_node_size(const allocator_type& state) noexcept
|
||||
{
|
||||
return state.node_size();
|
||||
}
|
||||
|
||||
/// \returns An upper bound on the maximum array size which is \ref memory_pool::next_capacity().
|
||||
static std::size_t max_array_size(const allocator_type& state) noexcept
|
||||
{
|
||||
return state.next_capacity();
|
||||
}
|
||||
|
||||
/// \returns The maximum alignment which is the next bigger power of two if less than \c alignof(std::max_align_t)
|
||||
/// or the maximum alignment itself otherwise.
|
||||
static std::size_t max_alignment(const allocator_type& state) noexcept
|
||||
{
|
||||
return state.free_list_.alignment();
|
||||
}
|
||||
};
|
||||
|
||||
/// Specialization of the \ref composable_allocator_traits for \ref memory_pool classes.
|
||||
/// \ingroup memory_allocator
|
||||
template <typename PoolType, class BlockOrRawAllocator>
|
||||
class composable_allocator_traits<memory_pool<PoolType, BlockOrRawAllocator>>
|
||||
{
|
||||
using traits = allocator_traits<memory_pool<PoolType, BlockOrRawAllocator>>;
|
||||
|
||||
public:
|
||||
using allocator_type = memory_pool<PoolType, BlockOrRawAllocator>;
|
||||
|
||||
/// \returns The result of \ref memory_pool::try_allocate_node()
|
||||
/// or `nullptr` if the allocation size was too big.
|
||||
static void* try_allocate_node(allocator_type& state, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
{
|
||||
if (size > traits::max_node_size(state) || alignment > traits::max_alignment(state))
|
||||
return nullptr;
|
||||
return state.try_allocate_node();
|
||||
}
|
||||
|
||||
/// \effects Forwards to \ref memory_pool::try_allocate_array()
|
||||
/// with the number of nodes adjusted to be the minimum,
|
||||
/// if the \c size is less than the \ref memory_pool::node_size().
|
||||
/// \returns A array with specified properties
|
||||
/// or `nullptr` if it was unable to allocate.
|
||||
static void* try_allocate_array(allocator_type& state, std::size_t count,
|
||||
std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
if (size > traits::max_node_size(state)
|
||||
|| count * size > traits::max_array_size(state)
|
||||
|| alignment > traits::max_alignment(state))
|
||||
return nullptr;
|
||||
return state.try_allocate_array(count, size);
|
||||
}
|
||||
|
||||
/// \effects Just forwards to \ref memory_pool::try_deallocate_node().
|
||||
/// \returns Whether the deallocation was successful.
|
||||
static bool try_deallocate_node(allocator_type& state, void* node, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
{
|
||||
if (size > traits::max_node_size(state) || alignment > traits::max_alignment(state))
|
||||
return false;
|
||||
return state.try_deallocate_node(node);
|
||||
}
|
||||
|
||||
/// \effects Forwards to \ref memory_pool::deallocate_array() with the same size adjustment.
|
||||
/// \returns Whether the deallocation was successful.
|
||||
static bool try_deallocate_array(allocator_type& state, void* array, std::size_t count,
|
||||
std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
if (size > traits::max_node_size(state)
|
||||
|| count * size > traits::max_array_size(state)
|
||||
|| alignment > traits::max_alignment(state))
|
||||
return false;
|
||||
return state.try_deallocate_array(array, count, size);
|
||||
}
|
||||
};
|
||||
|
||||
#if WPI_MEMORY_EXTERN_TEMPLATE
|
||||
extern template class allocator_traits<memory_pool<node_pool>>;
|
||||
extern template class allocator_traits<memory_pool<array_pool>>;
|
||||
extern template class allocator_traits<memory_pool<small_node_pool>>;
|
||||
|
||||
extern template class composable_allocator_traits<memory_pool<node_pool>>;
|
||||
extern template class composable_allocator_traits<memory_pool<array_pool>>;
|
||||
extern template class composable_allocator_traits<memory_pool<small_node_pool>>;
|
||||
#endif
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_MEMORY_POOL_HPP_INCLUDED
|
||||
@@ -1,568 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_MEMORY_POOL_COLLECTION_HPP_INCLUDED
|
||||
#define WPI_MEMORY_MEMORY_POOL_COLLECTION_HPP_INCLUDED
|
||||
|
||||
/// \file
|
||||
/// Class \ref wpi::memory::memory_pool_collection and related classes.
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "detail/align.hpp"
|
||||
#include "detail/assert.hpp"
|
||||
#include "detail/memory_stack.hpp"
|
||||
#include "detail/free_list_array.hpp"
|
||||
#include "config.hpp"
|
||||
#include "debugging.hpp"
|
||||
#include "error.hpp"
|
||||
#include "memory_arena.hpp"
|
||||
#include "memory_pool_type.hpp"
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
struct memory_pool_collection_leak_handler
|
||||
{
|
||||
void operator()(std::ptrdiff_t amount);
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
/// A \c BucketDistribution for \ref memory_pool_collection defining that there is a bucket, i.e. pool, for each size.
|
||||
/// That means that for each possible size up to an upper bound there will be a seperate free list.
|
||||
/// Allocating a node will not waste any memory.
|
||||
/// \ingroup memory_allocator
|
||||
struct identity_buckets
|
||||
{
|
||||
using type = detail::identity_access_policy;
|
||||
};
|
||||
|
||||
/// A \c BucketDistribution for \ref memory_pool_collection defining that there is a bucket, i.e. pool, for each power of two.
|
||||
/// That means for each power of two up to an upper bound there will be a separate free list.
|
||||
/// Allocating a node will only waste half of the memory.
|
||||
/// \ingroup memory_allocator
|
||||
struct log2_buckets
|
||||
{
|
||||
using type = detail::log2_access_policy;
|
||||
};
|
||||
|
||||
/// A stateful RawAllocator that behaves as a collection of multiple \ref memory_pool objects.
|
||||
/// It maintains a list of multiple free lists, whose types are controlled via the \c PoolType tags defined in \ref memory_pool_type.hpp,
|
||||
/// each of a different size as defined in the \c BucketDistribution (\ref identity_buckets or \ref log2_buckets).
|
||||
/// Allocating a node of given size will use the appropriate free list.<br>
|
||||
/// This allocator is ideal for node allocations in any order but with a predefined set of sizes,
|
||||
/// not only one size like \ref memory_pool.
|
||||
/// \ingroup memory_allocator
|
||||
template <class PoolType, class BucketDistribution,
|
||||
class BlockOrRawAllocator = default_allocator>
|
||||
class memory_pool_collection
|
||||
: WPI_EBO(detail::default_leak_checker<detail::memory_pool_collection_leak_handler>)
|
||||
{
|
||||
using free_list_array =
|
||||
detail::free_list_array<typename PoolType::type, typename BucketDistribution::type>;
|
||||
using leak_checker =
|
||||
detail::default_leak_checker<detail::memory_pool_collection_leak_handler>;
|
||||
|
||||
public:
|
||||
using allocator_type = make_block_allocator_t<BlockOrRawAllocator>;
|
||||
using pool_type = PoolType;
|
||||
using bucket_distribution = BucketDistribution;
|
||||
|
||||
/// \effects Creates it by giving it the maximum node size it should be able to allocate,
|
||||
/// the size of the initial memory block and other constructor arguments for the BlockAllocator.
|
||||
/// The \c BucketDistribution controls how many free lists are created,
|
||||
/// but unlike in \ref memory_pool all free lists are initially empty and the first memory block queued.
|
||||
/// \requires \c block_size must be non-zero and \c max_node_size must be a valid node size and smaller than \c block_size divided by the number of pools.
|
||||
template <typename... Args>
|
||||
memory_pool_collection(std::size_t max_node_size, std::size_t block_size,
|
||||
Args&&... args)
|
||||
: arena_(block_size, detail::forward<Args>(args)...),
|
||||
stack_(allocate_block()),
|
||||
pools_(stack_, block_end(), max_node_size)
|
||||
{
|
||||
detail::check_allocation_size<bad_node_size>(max_node_size, def_capacity(), info());
|
||||
}
|
||||
|
||||
/// \effects Destroys the \ref memory_pool_collection by returning all memory blocks,
|
||||
/// regardless of properly deallocated back to the BlockAllocator.
|
||||
~memory_pool_collection() noexcept = default;
|
||||
|
||||
/// @{
|
||||
/// \effects Moving a \ref memory_pool_collection object transfers ownership over the free lists,
|
||||
/// i.e. the moved from pool is completely empty and the new one has all its memory.
|
||||
/// That means that it is not allowed to call \ref deallocate_node() on a moved-from allocator
|
||||
/// even when passing it memory that was previously allocated by this object.
|
||||
memory_pool_collection(memory_pool_collection&& other) noexcept
|
||||
: leak_checker(detail::move(other)),
|
||||
arena_(detail::move(other.arena_)),
|
||||
stack_(detail::move(other.stack_)),
|
||||
pools_(detail::move(other.pools_))
|
||||
{
|
||||
}
|
||||
|
||||
memory_pool_collection& operator=(memory_pool_collection&& other) noexcept
|
||||
{
|
||||
leak_checker::operator=(detail::move(other));
|
||||
arena_ = detail::move(other.arena_);
|
||||
stack_ = detail::move(other.stack_);
|
||||
pools_ = detail::move(other.pools_);
|
||||
return *this;
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// \effects Allocates a node of given size.
|
||||
/// It first finds the appropriate free list as defined in the \c BucketDistribution.
|
||||
/// If it is empty, it will use an implementation defined amount of memory from the arena
|
||||
/// and inserts it in it.
|
||||
/// If the arena is empty too, it will request a new memory block from the BlockAllocator
|
||||
/// of size \ref next_capacity() and puts part of it onto this free list.
|
||||
/// Then it removes a node from it.
|
||||
/// \returns A node of given size suitable aligned,
|
||||
/// i.e. suitable for any type where <tt>sizeof(T) < node_size</tt>.
|
||||
/// \throws Anything thrown by the BlockAllocator if a growth is needed or a \ref bad_node_size exception if the node size is too big.
|
||||
void* allocate_node(std::size_t node_size)
|
||||
{
|
||||
detail::check_allocation_size<bad_node_size>(
|
||||
node_size, [&] { return max_node_size(); }, info());
|
||||
auto& pool = pools_.get(node_size);
|
||||
if (pool.empty())
|
||||
{
|
||||
auto block = reserve_memory(pool, def_capacity());
|
||||
pool.insert(block.memory, block.size);
|
||||
}
|
||||
|
||||
auto mem = pool.allocate();
|
||||
WPI_MEMORY_ASSERT(mem);
|
||||
return mem;
|
||||
}
|
||||
|
||||
/// \effects Allocates a node of given size.
|
||||
/// It is similar to \ref allocate_node() but will return `nullptr` on any failure,
|
||||
/// instead of growing the arnea and possibly throwing.
|
||||
/// \returns A node of given size suitable aligned
|
||||
/// or `nullptr` in case of failure.
|
||||
void* try_allocate_node(std::size_t node_size) noexcept
|
||||
{
|
||||
if (node_size > max_node_size())
|
||||
return nullptr;
|
||||
auto& pool = pools_.get(node_size);
|
||||
if (pool.empty())
|
||||
{
|
||||
try_reserve_memory(pool, def_capacity());
|
||||
return pool.empty() ? nullptr : pool.allocate();
|
||||
}
|
||||
else
|
||||
return pool.allocate();
|
||||
}
|
||||
|
||||
/// \effects Allocates an array of nodes by searching for \c n continuous nodes on the appropriate free list and removing them.
|
||||
/// Depending on the \c PoolType this can be a slow operation or not allowed at all.
|
||||
/// This can sometimes lead to a growth on the free list, even if technically there is enough continuous memory on the free list.
|
||||
/// Otherwise has the same behavior as \ref allocate_node().
|
||||
/// \returns An array of \c n nodes of size \c node_size suitable aligned.
|
||||
/// \throws Anything thrown by the used BlockAllocator's allocation function if a growth is needed,
|
||||
/// or a \ref bad_allocation_size exception.
|
||||
/// \requires \c count must be valid array count and
|
||||
/// \c node_size must be valid node size.
|
||||
void* allocate_array(std::size_t count, std::size_t node_size)
|
||||
{
|
||||
detail::check_allocation_size<bad_node_size>(
|
||||
node_size, [&] { return max_node_size(); }, info());
|
||||
|
||||
auto& pool = pools_.get(node_size);
|
||||
|
||||
// try allocating if not empty
|
||||
// for pools without array allocation support, allocate() will always return nullptr
|
||||
auto mem = pool.empty() ? nullptr : pool.allocate(count * node_size);
|
||||
if (!mem)
|
||||
{
|
||||
// reserve more memory
|
||||
auto block = reserve_memory(pool, def_capacity());
|
||||
pool.insert(block.memory, block.size);
|
||||
|
||||
mem = pool.allocate(count * node_size);
|
||||
if (!mem)
|
||||
{
|
||||
// reserve more then the default capacity if that didn't work either
|
||||
detail::check_allocation_size<bad_array_size>(
|
||||
count * node_size,
|
||||
[&] { return next_capacity() - pool.alignment() + 1; }, info());
|
||||
|
||||
block = reserve_memory(pool, count * node_size);
|
||||
pool.insert(block.memory, block.size);
|
||||
|
||||
mem = pool.allocate(count * node_size);
|
||||
WPI_MEMORY_ASSERT(mem);
|
||||
}
|
||||
}
|
||||
|
||||
return mem;
|
||||
}
|
||||
|
||||
/// \effects Allocates a array of given size.
|
||||
/// It is similar to \ref allocate_node() but will return `nullptr` on any failure,
|
||||
/// instead of growing the arnea and possibly throwing.
|
||||
/// \returns A array of given size suitable aligned
|
||||
/// or `nullptr` in case of failure.
|
||||
void* try_allocate_array(std::size_t count, std::size_t node_size) noexcept
|
||||
{
|
||||
if (!pool_type::value || node_size > max_node_size())
|
||||
return nullptr;
|
||||
auto& pool = pools_.get(node_size);
|
||||
if (pool.empty())
|
||||
{
|
||||
try_reserve_memory(pool, def_capacity());
|
||||
return pool.empty() ? nullptr : pool.allocate(count * node_size);
|
||||
}
|
||||
else
|
||||
return pool.allocate(count * node_size);
|
||||
}
|
||||
|
||||
/// \effects Deallocates a node by putting it back onto the appropriate free list.
|
||||
/// \requires \c ptr must be a result from a previous call to \ref allocate_node() with the same size on the same free list,
|
||||
/// i.e. either this allocator object or a new object created by moving this to it.
|
||||
void deallocate_node(void* ptr, std::size_t node_size) noexcept
|
||||
{
|
||||
pools_.get(node_size).deallocate(ptr);
|
||||
}
|
||||
|
||||
/// \effects Deallocates a node similar to \ref deallocate_node().
|
||||
/// But it checks if it can deallocate this memory.
|
||||
/// \returns `true` if the node could be deallocated,
|
||||
/// `false` otherwise.
|
||||
bool try_deallocate_node(void* ptr, std::size_t node_size) noexcept
|
||||
{
|
||||
if (node_size > max_node_size() || !arena_.owns(ptr))
|
||||
return false;
|
||||
pools_.get(node_size).deallocate(ptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// \effects Deallocates an array by putting it back onto the free list.
|
||||
/// \requires \c ptr must be a result from a previous call to \ref allocate_array() with the same sizes on the same free list,
|
||||
/// i.e. either this allocator object or a new object created by moving this to it.
|
||||
void deallocate_array(void* ptr, std::size_t count, std::size_t node_size) noexcept
|
||||
{
|
||||
pools_.get(node_size).deallocate(ptr, count * node_size);
|
||||
}
|
||||
|
||||
/// \effects Deallocates a array similar to \ref deallocate_array().
|
||||
/// But it checks if it can deallocate this memory.
|
||||
/// \returns `true` if the array could be deallocated,
|
||||
/// `false` otherwise.
|
||||
bool try_deallocate_array(void* ptr, std::size_t count, std::size_t node_size) noexcept
|
||||
{
|
||||
if (!pool_type::value || node_size > max_node_size() || !arena_.owns(ptr))
|
||||
return false;
|
||||
pools_.get(node_size).deallocate(ptr, count * node_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// \effects Inserts more memory on the free list for nodes of given size.
|
||||
/// It will try to put \c capacity_left bytes from the arena onto the free list defined over the \c BucketDistribution,
|
||||
/// if the arena is empty, a new memory block is requested from the BlockAllocator
|
||||
/// and it will be used.
|
||||
/// \throws Anything thrown by the BlockAllocator if a growth is needed.
|
||||
/// \requires \c node_size must be valid node size less than or equal to \ref max_node_size(),
|
||||
/// \c capacity_left must be less than \ref next_capacity().
|
||||
void reserve(std::size_t node_size, std::size_t capacity)
|
||||
{
|
||||
WPI_MEMORY_ASSERT_MSG(node_size <= max_node_size(), "node_size too big");
|
||||
auto& pool = pools_.get(node_size);
|
||||
reserve_memory(pool, capacity);
|
||||
}
|
||||
|
||||
/// \returns The maximum node size for which is a free list.
|
||||
/// This is the value passed to it in the constructor.
|
||||
std::size_t max_node_size() const noexcept
|
||||
{
|
||||
return pools_.max_node_size();
|
||||
}
|
||||
|
||||
/// \returns The amount of nodes available in the free list for nodes of given size
|
||||
/// as defined over the \c BucketDistribution.
|
||||
/// This is the number of nodes that can be allocated without the free list requesting more memory from the arena.
|
||||
/// \note Array allocations may lead to a growth even if the capacity_left is big enough.
|
||||
std::size_t pool_capacity_left(std::size_t node_size) const noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT_MSG(node_size <= max_node_size(), "node_size too big");
|
||||
return pools_.get(node_size).capacity();
|
||||
}
|
||||
|
||||
/// \returns The amount of memory available in the arena not inside the free lists.
|
||||
/// This is the number of bytes that can be inserted into the free lists
|
||||
/// without requesting more memory from the BlockAllocator.
|
||||
/// \note Array allocations may lead to a growth even if the capacity is big enough.
|
||||
std::size_t capacity_left() const noexcept
|
||||
{
|
||||
return std::size_t(block_end() - stack_.top());
|
||||
}
|
||||
|
||||
/// \returns The size of the next memory block after \ref capacity_left() arena grows.
|
||||
/// This is the amount of memory that can be distributed in the pools.
|
||||
/// \note If the `PoolType` is \ref small_node_pool, the exact usable memory is lower than that.
|
||||
std::size_t next_capacity() const noexcept
|
||||
{
|
||||
return arena_.next_block_size();
|
||||
}
|
||||
|
||||
/// \returns A reference to the BlockAllocator used for managing the arena.
|
||||
/// \requires It is undefined behavior to move this allocator out into another object.
|
||||
allocator_type& get_allocator() noexcept
|
||||
{
|
||||
return arena_.get_allocator();
|
||||
}
|
||||
|
||||
private:
|
||||
allocator_info info() const noexcept
|
||||
{
|
||||
return {WPI_MEMORY_LOG_PREFIX "::memory_pool_collection", this};
|
||||
}
|
||||
|
||||
std::size_t def_capacity() const noexcept
|
||||
{
|
||||
return arena_.next_block_size() / pools_.size();
|
||||
}
|
||||
|
||||
detail::fixed_memory_stack allocate_block()
|
||||
{
|
||||
return detail::fixed_memory_stack(arena_.allocate_block().memory);
|
||||
}
|
||||
|
||||
const char* block_end() const noexcept
|
||||
{
|
||||
auto block = arena_.current_block();
|
||||
return static_cast<const char*>(block.memory) + block.size;
|
||||
}
|
||||
|
||||
bool insert_rest(typename pool_type::type& pool) noexcept
|
||||
{
|
||||
if (auto remaining = std::size_t(block_end() - stack_.top()))
|
||||
{
|
||||
auto offset = detail::align_offset(stack_.top(), detail::max_alignment);
|
||||
if (offset < remaining)
|
||||
{
|
||||
detail::debug_fill(stack_.top(), offset, debug_magic::alignment_memory);
|
||||
pool.insert(stack_.top() + offset, remaining - offset);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void try_reserve_memory(typename pool_type::type& pool, std::size_t capacity) noexcept
|
||||
{
|
||||
auto mem = stack_.allocate(block_end(), capacity, detail::max_alignment);
|
||||
if (!mem)
|
||||
insert_rest(pool);
|
||||
else
|
||||
pool.insert(mem, capacity);
|
||||
}
|
||||
|
||||
memory_block reserve_memory(typename pool_type::type& pool, std::size_t capacity)
|
||||
{
|
||||
auto mem = stack_.allocate(block_end(), capacity, detail::max_alignment);
|
||||
if (!mem)
|
||||
{
|
||||
insert_rest(pool);
|
||||
// get new block
|
||||
stack_ = allocate_block();
|
||||
|
||||
// allocate ensuring alignment
|
||||
mem = stack_.allocate(block_end(), capacity, detail::max_alignment);
|
||||
WPI_MEMORY_ASSERT(mem);
|
||||
}
|
||||
return {mem, capacity};
|
||||
}
|
||||
|
||||
memory_arena<allocator_type, false> arena_;
|
||||
detail::fixed_memory_stack stack_;
|
||||
free_list_array pools_;
|
||||
|
||||
friend allocator_traits<memory_pool_collection>;
|
||||
};
|
||||
|
||||
#if WPI_MEMORY_EXTERN_TEMPLATE
|
||||
extern template class memory_pool_collection<node_pool, identity_buckets>;
|
||||
extern template class memory_pool_collection<array_pool, identity_buckets>;
|
||||
extern template class memory_pool_collection<small_node_pool, identity_buckets>;
|
||||
|
||||
extern template class memory_pool_collection<node_pool, log2_buckets>;
|
||||
extern template class memory_pool_collection<array_pool, log2_buckets>;
|
||||
extern template class memory_pool_collection<small_node_pool, log2_buckets>;
|
||||
#endif
|
||||
|
||||
/// An alias for \ref memory_pool_collection using the \ref identity_buckets policy
|
||||
/// and a \c PoolType defaulting to \ref node_pool.
|
||||
/// \ingroup memory_allocator
|
||||
template <class PoolType = node_pool, class ImplAllocator = default_allocator>
|
||||
WPI_ALIAS_TEMPLATE(bucket_allocator,
|
||||
memory_pool_collection<PoolType, identity_buckets, ImplAllocator>);
|
||||
|
||||
template <class Allocator>
|
||||
class allocator_traits;
|
||||
|
||||
/// Specialization of the \ref allocator_traits for \ref memory_pool_collection classes.
|
||||
/// \note It is not allowed to mix calls through the specialization and through the member functions,
|
||||
/// i.e. \ref memory_pool_collection::allocate_node() and this \c allocate_node().
|
||||
/// \ingroup memory_allocator
|
||||
template <class Pool, class BucketDist, class RawAllocator>
|
||||
class allocator_traits<memory_pool_collection<Pool, BucketDist, RawAllocator>>
|
||||
{
|
||||
public:
|
||||
using allocator_type = memory_pool_collection<Pool, BucketDist, RawAllocator>;
|
||||
using is_stateful = std::true_type;
|
||||
|
||||
/// \returns The result of \ref memory_pool_collection::allocate_node().
|
||||
/// \throws Anything thrown by the pool allocation function
|
||||
/// or a \ref bad_allocation_size exception if \c size / \c alignment exceeds \ref max_node_size() / the suitable alignment value,
|
||||
/// i.e. the node is over-aligned.
|
||||
static void* allocate_node(allocator_type& state, std::size_t size,
|
||||
std::size_t alignment)
|
||||
{
|
||||
// node already checked
|
||||
detail::check_allocation_size<bad_alignment>(
|
||||
alignment, [&] { return detail::alignment_for(size); }, state.info());
|
||||
auto mem = state.allocate_node(size);
|
||||
state.on_allocate(size);
|
||||
return mem;
|
||||
}
|
||||
|
||||
/// \returns The result of \ref memory_pool_collection::allocate_array().
|
||||
/// \throws Anything thrown by the pool allocation function or a \ref bad_allocation_size exception.
|
||||
/// \requires The \ref memory_pool_collection has to support array allocations.
|
||||
static void* allocate_array(allocator_type& state, std::size_t count, std::size_t size,
|
||||
std::size_t alignment)
|
||||
{
|
||||
// node and array already checked
|
||||
detail::check_allocation_size<bad_alignment>(
|
||||
alignment, [&] { return detail::alignment_for(size); }, state.info());
|
||||
auto mem = state.allocate_array(count, size);
|
||||
state.on_allocate(count * size);
|
||||
return mem;
|
||||
}
|
||||
|
||||
/// \effects Calls \ref memory_pool_collection::deallocate_node().
|
||||
static void deallocate_node(allocator_type& state, void* node, std::size_t size,
|
||||
std::size_t) noexcept
|
||||
{
|
||||
state.deallocate_node(node, size);
|
||||
state.on_deallocate(size);
|
||||
}
|
||||
|
||||
/// \effects Calls \ref memory_pool_collection::deallocate_array().
|
||||
/// \requires The \ref memory_pool_collection has to support array allocations.
|
||||
static void deallocate_array(allocator_type& state, void* array, std::size_t count,
|
||||
std::size_t size, std::size_t) noexcept
|
||||
{
|
||||
state.deallocate_array(array, count, size);
|
||||
state.on_deallocate(count * size);
|
||||
}
|
||||
|
||||
/// \returns The maximum size of each node which is \ref memory_pool_collection::max_node_size().
|
||||
static std::size_t max_node_size(const allocator_type& state) noexcept
|
||||
{
|
||||
return state.max_node_size();
|
||||
}
|
||||
|
||||
/// \returns An upper bound on the maximum array size which is \ref memory_pool::next_capacity().
|
||||
static std::size_t max_array_size(const allocator_type& state) noexcept
|
||||
{
|
||||
return state.next_capacity();
|
||||
}
|
||||
|
||||
/// \returns Just \c alignof(std::max_align_t) since the actual maximum alignment depends on the node size,
|
||||
/// the nodes must not be over-aligned.
|
||||
static std::size_t max_alignment(const allocator_type&) noexcept
|
||||
{
|
||||
return detail::max_alignment;
|
||||
}
|
||||
};
|
||||
|
||||
/// Specialization of the \ref composable_allocator_traits for \ref memory_pool_collection classes.
|
||||
/// \ingroup memory_allocator
|
||||
template <class Pool, class BucketDist, class RawAllocator>
|
||||
class composable_allocator_traits<memory_pool_collection<Pool, BucketDist, RawAllocator>>
|
||||
{
|
||||
using traits = allocator_traits<memory_pool_collection<Pool, BucketDist, RawAllocator>>;
|
||||
|
||||
public:
|
||||
using allocator_type = memory_pool_collection<Pool, BucketDist, RawAllocator>;
|
||||
|
||||
/// \returns The result of \ref memory_pool_collection::try_allocate_node()
|
||||
/// or `nullptr` if the allocation size was too big.
|
||||
static void* try_allocate_node(allocator_type& state, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
{
|
||||
if (alignment > traits::max_alignment(state))
|
||||
return nullptr;
|
||||
return state.try_allocate_node(size);
|
||||
}
|
||||
|
||||
/// \returns The result of \ref memory_pool_collection::try_allocate_array()
|
||||
/// or `nullptr` if the allocation size was too big.
|
||||
static void* try_allocate_array(allocator_type& state, std::size_t count,
|
||||
std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
if (count * size > traits::max_array_size(state)
|
||||
|| alignment > traits::max_alignment(state))
|
||||
return nullptr;
|
||||
return state.try_allocate_array(count, size);
|
||||
}
|
||||
|
||||
/// \effects Just forwards to \ref memory_pool_collection::try_deallocate_node().
|
||||
/// \returns Whether the deallocation was successful.
|
||||
static bool try_deallocate_node(allocator_type& state, void* node, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
{
|
||||
if (alignment > traits::max_alignment(state))
|
||||
return false;
|
||||
return state.try_deallocate_node(node, size);
|
||||
}
|
||||
|
||||
/// \effects Forwards to \ref memory_pool_collection::deallocate_array().
|
||||
/// \returns Whether the deallocation was successful.
|
||||
static bool try_deallocate_array(allocator_type& state, void* array, std::size_t count,
|
||||
std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
if (count * size > traits::max_array_size(state)
|
||||
|| alignment > traits::max_alignment(state))
|
||||
return false;
|
||||
return state.try_deallocate_array(array, count, size);
|
||||
}
|
||||
};
|
||||
|
||||
#if WPI_MEMORY_EXTERN_TEMPLATE
|
||||
extern template class allocator_traits<memory_pool_collection<node_pool, identity_buckets>>;
|
||||
extern template class allocator_traits<
|
||||
memory_pool_collection<array_pool, identity_buckets>>;
|
||||
extern template class allocator_traits<
|
||||
memory_pool_collection<small_node_pool, identity_buckets>>;
|
||||
|
||||
extern template class allocator_traits<memory_pool_collection<node_pool, log2_buckets>>;
|
||||
extern template class allocator_traits<memory_pool_collection<array_pool, log2_buckets>>;
|
||||
extern template class allocator_traits<
|
||||
memory_pool_collection<small_node_pool, log2_buckets>>;
|
||||
|
||||
extern template class composable_allocator_traits<
|
||||
memory_pool_collection<node_pool, identity_buckets>>;
|
||||
extern template class composable_allocator_traits<
|
||||
memory_pool_collection<array_pool, identity_buckets>>;
|
||||
extern template class composable_allocator_traits<
|
||||
memory_pool_collection<small_node_pool, identity_buckets>>;
|
||||
|
||||
extern template class composable_allocator_traits<
|
||||
memory_pool_collection<node_pool, log2_buckets>>;
|
||||
extern template class composable_allocator_traits<
|
||||
memory_pool_collection<array_pool, log2_buckets>>;
|
||||
extern template class composable_allocator_traits<
|
||||
memory_pool_collection<small_node_pool, log2_buckets>>;
|
||||
#endif
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_MEMORY_POOL_COLLECTION_HPP_INCLUDED
|
||||
@@ -1,52 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_MEMORY_POOL_TYPE_HPP_INCLUDED
|
||||
#define WPI_MEMORY_MEMORY_POOL_TYPE_HPP_INCLUDED
|
||||
|
||||
/// \file
|
||||
/// The \c PoolType tag types.
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "detail/free_list.hpp"
|
||||
#include "detail/small_free_list.hpp"
|
||||
#include "config.hpp"
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
/// Tag type defining a memory pool optimized for nodes.
|
||||
/// It does not support array allocations that great and may trigger a growth even if there is enough memory.
|
||||
/// But it is the fastest pool type.
|
||||
/// \ingroup memory_allocator
|
||||
struct node_pool : WPI_EBO(std::true_type)
|
||||
{
|
||||
using type = detail::node_free_memory_list;
|
||||
};
|
||||
|
||||
/// Tag type defining a memory pool optimized for arrays.
|
||||
/// It keeps the nodes ordered inside the free list and searches the list for an appropriate memory block.
|
||||
/// Array allocations are still pretty slow, if the array gets big enough it can get slower than \c new.
|
||||
/// Node allocations are still fast, unless there is deallocation in random order.
|
||||
/// \note Use this tag type only if you really need to have a memory pool!
|
||||
/// \ingroup memory_allocator
|
||||
struct array_pool : WPI_EBO(std::true_type)
|
||||
{
|
||||
using type = detail::array_free_memory_list;
|
||||
};
|
||||
|
||||
/// Tag type defining a memory pool optimized for small nodes.
|
||||
/// The free list is intrusive and thus requires that each node has at least the size of a pointer.
|
||||
/// This tag type does not have this requirement and thus allows zero-memory-overhead allocations of small nodes.
|
||||
/// It is a little bit slower than \ref node_pool and does not support arrays.
|
||||
/// \ingroup memory_allocator
|
||||
struct small_node_pool : WPI_EBO(std::false_type)
|
||||
{
|
||||
using type = detail::small_free_memory_list;
|
||||
};
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_MEMORY_POOL_TYPE_HPP_INCLUDED
|
||||
@@ -1,238 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_MEMORY_RESOURCE_ADAPTER_HPP_INCLUDED
|
||||
#define WPI_MEMORY_MEMORY_RESOURCE_ADAPTER_HPP_INCLUDED
|
||||
|
||||
/// \file
|
||||
/// Class \ref wpi::memory::memory_resource_adapter and \ref wpi::memory::memory_resource_allocator to allow usage of PMRs.
|
||||
|
||||
#include "detail/assert.hpp"
|
||||
#include "detail/utility.hpp"
|
||||
#include "config.hpp"
|
||||
#include "allocator_traits.hpp"
|
||||
|
||||
#if defined(__has_include) && __has_include(<memory_resource>)
|
||||
|
||||
#if !defined(__GNUC__) || __cplusplus >= 201703L
|
||||
// The experimental/memory_resource header lacks a check for C++17 on older GCC,
|
||||
// so we have to do it for them.
|
||||
#include <memory_resource>
|
||||
#endif
|
||||
|
||||
#elif defined(__has_include) && __has_include(<experimental/memory_resource>)
|
||||
|
||||
#if !defined(__GNUC__) || __cplusplus >= 201402L
|
||||
// The experimental/memory_resource header lacks a check for C++14 on older GCC,
|
||||
// so we have to do it for them.
|
||||
#include <experimental/memory_resource>
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(__cpp_lib_memory_resource)
|
||||
|
||||
// We use std::pmr::memory_resource.
|
||||
namespace wpi_memory_pmr = std::pmr;
|
||||
|
||||
#elif defined(__cpp_lib_experimental_memory_resources)
|
||||
|
||||
// We use std::experimental::pmr::memory_resource.
|
||||
namespace wpi_memory_pmr = std::experimental::pmr;
|
||||
|
||||
#else
|
||||
|
||||
// We use our own implementation.
|
||||
namespace wpi_memory_pmr
|
||||
{
|
||||
// see N3916 for documentation
|
||||
class memory_resource
|
||||
{
|
||||
static const std::size_t max_alignment = alignof(std::max_align_t);
|
||||
|
||||
public:
|
||||
virtual ~memory_resource() noexcept {}
|
||||
void* allocate(std::size_t bytes, std::size_t alignment = max_alignment)
|
||||
{
|
||||
return do_allocate(bytes, alignment);
|
||||
}
|
||||
void deallocate(void* p, std::size_t bytes, std::size_t alignment = max_alignment)
|
||||
{
|
||||
do_deallocate(p, bytes, alignment);
|
||||
}
|
||||
bool is_equal(const memory_resource& other) const noexcept
|
||||
{
|
||||
return do_is_equal(other);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void* do_allocate(std::size_t bytes, std::size_t alignment) = 0;
|
||||
virtual void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) = 0;
|
||||
virtual bool do_is_equal(const memory_resource& other) const noexcept = 0;
|
||||
};
|
||||
inline bool operator==(const memory_resource& a, const memory_resource& b) noexcept
|
||||
{
|
||||
return &a == &b || a.is_equal(b);
|
||||
}
|
||||
inline bool operator!=(const memory_resource& a, const memory_resource& b) noexcept
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
} // namespace wpi_memory_pmr
|
||||
|
||||
#endif
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
/// The \c memory_resource abstract base class used in the implementation.
|
||||
/// \ingroup memory_adapter
|
||||
WPI_ALIAS_TEMPLATE(memory_resource, foonathan_memory_pmr::memory_resource);
|
||||
|
||||
/// Wraps a RawAllocator and makes it a \ref memory_resource.
|
||||
/// \ingroup memory_adapter
|
||||
template <class RawAllocator>
|
||||
class memory_resource_adapter
|
||||
: public memory_resource,
|
||||
WPI_EBO(allocator_traits<RawAllocator>::allocator_type)
|
||||
{
|
||||
public:
|
||||
using allocator_type = typename allocator_traits<RawAllocator>::allocator_type;
|
||||
|
||||
/// \effects Creates the resource by moving in the allocator.
|
||||
memory_resource_adapter(allocator_type&& other) noexcept
|
||||
: allocator_type(detail::move(other))
|
||||
{
|
||||
}
|
||||
|
||||
/// @{
|
||||
/// \returns A reference to the wrapped allocator.
|
||||
allocator_type& get_allocator() noexcept
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
const allocator_type& get_allocator() const noexcept
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
/// @}
|
||||
|
||||
protected:
|
||||
using traits_type = allocator_traits<RawAllocator>;
|
||||
|
||||
/// \effects Allocates raw memory with given size and alignment.
|
||||
/// It forwards to \c allocate_node() or \c allocate_array() depending on the size.
|
||||
/// \returns The new memory as returned by the RawAllocator.
|
||||
/// \throws Anything thrown by the allocation function.
|
||||
void* do_allocate(std::size_t bytes, std::size_t alignment) override
|
||||
{
|
||||
auto max = traits_type::max_node_size(*this);
|
||||
if (bytes <= max)
|
||||
return traits_type::allocate_node(*this, bytes, alignment);
|
||||
auto div = bytes / max;
|
||||
auto mod = bytes % max;
|
||||
auto n = div + (mod != 0);
|
||||
return traits_type::allocate_array(*this, n, max, alignment);
|
||||
}
|
||||
|
||||
/// \effects Deallocates memory previously allocated by \ref do_allocate.
|
||||
/// It forwards to \c deallocate_node() or \c deallocate_array() depending on the size.
|
||||
/// \throws Nothing.
|
||||
void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override
|
||||
{
|
||||
auto max = traits_type::max_node_size(*this);
|
||||
if (bytes <= max)
|
||||
traits_type::deallocate_node(*this, p, bytes, alignment);
|
||||
else
|
||||
{
|
||||
auto div = bytes / max;
|
||||
auto mod = bytes % max;
|
||||
auto n = div + (mod != 0);
|
||||
traits_type::deallocate_array(*this, p, n, max, alignment);
|
||||
}
|
||||
}
|
||||
|
||||
/// \returns Whether or not \c *this is equal to \c other
|
||||
/// by comparing the addresses.
|
||||
bool do_is_equal(const memory_resource& other) const noexcept override
|
||||
{
|
||||
return this == &other;
|
||||
}
|
||||
};
|
||||
|
||||
/// Wraps a \ref memory_resource and makes it a RawAllocator.
|
||||
/// \ingroup memory_adapter
|
||||
class memory_resource_allocator
|
||||
{
|
||||
public:
|
||||
/// \effects Creates it by giving it a pointer to the \ref memory_resource.
|
||||
/// \requires \c ptr must not be \c nullptr.
|
||||
memory_resource_allocator(memory_resource* ptr) noexcept : ptr_(ptr)
|
||||
{
|
||||
WPI_MEMORY_ASSERT(ptr);
|
||||
}
|
||||
|
||||
/// \effects Allocates a node by forwarding to the \c allocate() function.
|
||||
/// \returns The node as returned by the \ref memory_resource.
|
||||
/// \throws Anything thrown by the \c allocate() function.
|
||||
void* allocate_node(std::size_t size, std::size_t alignment)
|
||||
{
|
||||
return ptr_->allocate(size, alignment);
|
||||
}
|
||||
|
||||
/// \effects Deallocates a node by forwarding to the \c deallocate() function.
|
||||
void deallocate_node(void* ptr, std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
ptr_->deallocate(ptr, size, alignment);
|
||||
}
|
||||
|
||||
/// \returns The maximum alignment which is the maximum value of type \c std::size_t.
|
||||
std::size_t max_alignment() const noexcept
|
||||
{
|
||||
return std::size_t(-1);
|
||||
}
|
||||
|
||||
/// \returns A pointer to the used \ref memory_resource, this is never \c nullptr.
|
||||
memory_resource* resource() const noexcept
|
||||
{
|
||||
return ptr_;
|
||||
}
|
||||
|
||||
private:
|
||||
memory_resource* ptr_;
|
||||
};
|
||||
|
||||
/// @{
|
||||
/// \returns Whether `lhs` and `rhs` share the same resource.
|
||||
/// \relates memory_resource_allocator
|
||||
inline bool operator==(const memory_resource_allocator& lhs,
|
||||
const memory_resource_allocator& rhs) noexcept
|
||||
{
|
||||
return lhs.resource() == rhs.resource();
|
||||
}
|
||||
|
||||
inline bool operator!=(const memory_resource_allocator& lhs,
|
||||
const memory_resource_allocator& rhs) noexcept
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
/// @}
|
||||
|
||||
#if !defined(DOXYGEN)
|
||||
template <class RawAllocator>
|
||||
struct is_shared_allocator;
|
||||
#endif
|
||||
|
||||
/// Specialization of \ref is_shared_allocator to mark \ref memory_resource_allocator as shared.
|
||||
/// This allows using it as \ref allocator_reference directly.
|
||||
/// \ingroup memory_adapter
|
||||
template <>
|
||||
struct is_shared_allocator<memory_resource_allocator> : std::true_type
|
||||
{
|
||||
};
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_MEMORY_RESOURCE_ADAPTER_HPP_INCLUDED
|
||||
@@ -1,489 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_MEMORY_STACK_HPP_INCLUDED
|
||||
#define WPI_MEMORY_MEMORY_STACK_HPP_INCLUDED
|
||||
|
||||
/// \file
|
||||
/// Class \ref wpi::memory::memory_stack and its \ref wpi::memory::allocator_traits specialization.
|
||||
|
||||
// Inform that wpi::memory::memory_stack::min_block_size API is available
|
||||
#define WPI_MEMORY_MEMORY_STACK_HAS_MIN_BLOCK_SIZE
|
||||
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
#include "detail/assert.hpp"
|
||||
#include "detail/memory_stack.hpp"
|
||||
#include "config.hpp"
|
||||
#include "error.hpp"
|
||||
#include "memory_arena.hpp"
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
#if !defined(DOXYGEN)
|
||||
template <class Impl>
|
||||
class memory_stack;
|
||||
#endif
|
||||
|
||||
namespace detail
|
||||
{
|
||||
class stack_marker
|
||||
{
|
||||
std::size_t index;
|
||||
char* top;
|
||||
const char* end;
|
||||
|
||||
stack_marker(std::size_t i, const detail::fixed_memory_stack& s,
|
||||
const char* e) noexcept
|
||||
: index(i), top(s.top()), end(e)
|
||||
{
|
||||
}
|
||||
|
||||
friend bool operator==(const stack_marker& lhs, const stack_marker& rhs) noexcept
|
||||
{
|
||||
if (lhs.index != rhs.index)
|
||||
return false;
|
||||
WPI_MEMORY_ASSERT_MSG(lhs.end == rhs.end, "you must not compare two "
|
||||
"stack markers from different "
|
||||
"stacks");
|
||||
return lhs.top == rhs.top;
|
||||
}
|
||||
|
||||
friend bool operator!=(const stack_marker& lhs, const stack_marker& rhs) noexcept
|
||||
{
|
||||
return !(rhs == lhs);
|
||||
}
|
||||
|
||||
friend bool operator<(const stack_marker& lhs, const stack_marker& rhs) noexcept
|
||||
{
|
||||
if (lhs.index != rhs.index)
|
||||
return lhs.index < rhs.index;
|
||||
WPI_MEMORY_ASSERT_MSG(lhs.end == rhs.end, "you must not compare two "
|
||||
"stack markers from different "
|
||||
"stacks");
|
||||
return lhs.top < rhs.top;
|
||||
}
|
||||
|
||||
friend bool operator>(const stack_marker& lhs, const stack_marker& rhs) noexcept
|
||||
{
|
||||
return rhs < lhs;
|
||||
}
|
||||
|
||||
friend bool operator<=(const stack_marker& lhs, const stack_marker& rhs) noexcept
|
||||
{
|
||||
return !(rhs < lhs);
|
||||
}
|
||||
|
||||
friend bool operator>=(const stack_marker& lhs, const stack_marker& rhs) noexcept
|
||||
{
|
||||
return !(lhs < rhs);
|
||||
}
|
||||
|
||||
template <class Impl>
|
||||
friend class memory::memory_stack;
|
||||
};
|
||||
|
||||
struct memory_stack_leak_handler
|
||||
{
|
||||
void operator()(std::ptrdiff_t amount);
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
/// A stateful RawAllocator that provides stack-like (LIFO) allocations.
|
||||
/// It uses a \ref memory_arena with a given \c BlockOrRawAllocator defaulting to \ref growing_block_allocator to allocate huge blocks
|
||||
/// and saves a marker to the current top.
|
||||
/// Allocation simply moves this marker by the appropriate number of bytes and returns the pointer at the old marker position,
|
||||
/// deallocation is not directly supported, only setting the marker to a previously queried position.
|
||||
/// \ingroup memory_allocator
|
||||
template <class BlockOrRawAllocator = default_allocator>
|
||||
class memory_stack
|
||||
: WPI_EBO(detail::default_leak_checker<detail::memory_stack_leak_handler>)
|
||||
{
|
||||
public:
|
||||
using allocator_type = make_block_allocator_t<BlockOrRawAllocator>;
|
||||
|
||||
/// \returns The minimum block size required for a stack containing the given amount of memory.
|
||||
/// If a stack is created with the result of `min_block_size(n)`, the resulting capacity will be exactly `n`.
|
||||
/// \requires `byte_size` must be a positive number.
|
||||
/// \note Due to debug fence sizes, the actual amount of usable memory can vary.
|
||||
/// However, this is impossible to compute without knowing the exact allocation pattern before,
|
||||
/// so this is just a rough estimate.
|
||||
static constexpr std::size_t min_block_size(std::size_t byte_size) noexcept
|
||||
{
|
||||
return detail::memory_block_stack::implementation_offset() + byte_size;
|
||||
}
|
||||
|
||||
/// \effects Creates it with a given initial block size and and other constructor arguments for the BlockAllocator.
|
||||
/// It will allocate the first block and sets the top to its beginning.
|
||||
/// \requires \c block_size must be at least \c min_block_size(1).
|
||||
template <typename... Args>
|
||||
explicit memory_stack(std::size_t block_size, Args&&... args)
|
||||
: arena_(block_size, detail::forward<Args>(args)...),
|
||||
stack_(arena_.allocate_block().memory)
|
||||
{
|
||||
}
|
||||
|
||||
/// \effects Allocates a memory block of given size and alignment.
|
||||
/// It simply moves the top marker.
|
||||
/// If there is not enough space on the current memory block,
|
||||
/// a new one will be allocated by the BlockAllocator or taken from a cache
|
||||
/// and used for the allocation.
|
||||
/// \returns A node with given size and alignment.
|
||||
/// \throws Anything thrown by the BlockAllocator on growth
|
||||
/// or \ref bad_allocation_size if \c size is too big.
|
||||
/// \requires \c size and \c alignment must be valid.
|
||||
void* allocate(std::size_t size, std::size_t alignment)
|
||||
{
|
||||
auto fence = detail::debug_fence_size;
|
||||
auto offset = detail::align_offset(stack_.top() + fence, alignment);
|
||||
|
||||
if (!stack_.top()
|
||||
|| fence + offset + size + fence > std::size_t(block_end() - stack_.top()))
|
||||
{
|
||||
// need to grow
|
||||
auto block = arena_.allocate_block();
|
||||
stack_ = detail::fixed_memory_stack(block.memory);
|
||||
|
||||
// new alignment required for over-aligned types
|
||||
offset = detail::align_offset(stack_.top() + fence, alignment);
|
||||
|
||||
auto needed = fence + offset + size + fence;
|
||||
detail::check_allocation_size<bad_allocation_size>(needed, block.size, info());
|
||||
}
|
||||
|
||||
return stack_.allocate_unchecked(size, offset);
|
||||
}
|
||||
|
||||
/// \effects Allocates a memory block of given size and alignment,
|
||||
/// similar to \ref allocate().
|
||||
/// But it does not attempt a growth if the arena is empty.
|
||||
/// \returns A node with given size and alignment
|
||||
/// or `nullptr` if there wasn't enough memory available.
|
||||
void* try_allocate(std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
return stack_.allocate(block_end(), size, alignment);
|
||||
}
|
||||
|
||||
/// The marker type that is used for unwinding.
|
||||
/// The exact type is implementation defined,
|
||||
/// it is only required that it is efficiently copyable
|
||||
/// and has all the comparision operators defined for two markers on the same stack.
|
||||
/// Two markers are equal, if they are copies or created from two `top()` calls without a call to `unwind()` or `allocate()`.
|
||||
/// A marker `a` is less than marker `b`, if after `a` was obtained, there was one or more call to `allocate()` and no call to `unwind()`.
|
||||
using marker = WPI_IMPL_DEFINED(detail::stack_marker);
|
||||
|
||||
/// \returns A marker to the current top of the stack.
|
||||
marker top() const noexcept
|
||||
{
|
||||
return {arena_.size() - 1, stack_, block_end()};
|
||||
}
|
||||
|
||||
/// \effects Unwinds the stack to a certain marker position.
|
||||
/// This sets the top pointer of the stack to the position described by the marker
|
||||
/// and has the effect of deallocating all memory allocated since the marker was obtained.
|
||||
/// If any memory blocks are unused after the operation,
|
||||
/// they are not deallocated but put in a cache for later use,
|
||||
/// call \ref shrink_to_fit() to actually deallocate them.
|
||||
/// \requires The marker must point to memory that is still in use and was the whole time,
|
||||
/// i.e. it must have been pointed below the top at all time.
|
||||
void unwind(marker m) noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(m <= top());
|
||||
detail::debug_check_pointer([&] { return m.index <= arena_.size() - 1; }, info(),
|
||||
m.top);
|
||||
|
||||
if (std::size_t to_deallocate = (arena_.size() - 1) - m.index) // different index
|
||||
{
|
||||
arena_.deallocate_block();
|
||||
for (std::size_t i = 1; i != to_deallocate; ++i)
|
||||
arena_.deallocate_block();
|
||||
|
||||
detail::debug_check_pointer(
|
||||
[&]
|
||||
{
|
||||
auto cur = arena_.current_block();
|
||||
return m.end == static_cast<char*>(cur.memory) + cur.size;
|
||||
},
|
||||
info(), m.top);
|
||||
|
||||
// mark memory from new top to end of the block as freed
|
||||
detail::debug_fill_free(m.top, std::size_t(m.end - m.top), 0);
|
||||
stack_ = detail::fixed_memory_stack(m.top);
|
||||
}
|
||||
else // same index
|
||||
{
|
||||
detail::debug_check_pointer([&] { return stack_.top() >= m.top; }, info(),
|
||||
m.top);
|
||||
stack_.unwind(m.top);
|
||||
}
|
||||
}
|
||||
|
||||
/// \effects \ref unwind() does not actually do any deallocation of blocks on the BlockAllocator,
|
||||
/// unused memory is stored in a cache for later reuse.
|
||||
/// This function clears that cache.
|
||||
void shrink_to_fit() noexcept
|
||||
{
|
||||
arena_.shrink_to_fit();
|
||||
}
|
||||
|
||||
/// \returns The amount of memory remaining in the current block.
|
||||
/// This is the number of bytes that are available for allocation
|
||||
/// before the cache or BlockAllocator needs to be used.
|
||||
std::size_t capacity_left() const noexcept
|
||||
{
|
||||
return std::size_t(block_end() - stack_.top());
|
||||
}
|
||||
|
||||
/// \returns The size of the next memory block after the current block is exhausted and the arena grows.
|
||||
/// This function just forwards to the \ref memory_arena.
|
||||
/// \note All of it is available for the stack to use, but due to fences and alignment buffers,
|
||||
/// this may not be the exact amount of memory usable for the user.
|
||||
std::size_t next_capacity() const noexcept
|
||||
{
|
||||
return arena_.next_block_size();
|
||||
}
|
||||
|
||||
/// \returns A reference to the BlockAllocator used for managing the arena.
|
||||
/// \requires It is undefined behavior to move this allocator out into another object.
|
||||
allocator_type& get_allocator() noexcept
|
||||
{
|
||||
return arena_.get_allocator();
|
||||
}
|
||||
|
||||
private:
|
||||
allocator_info info() const noexcept
|
||||
{
|
||||
return {WPI_MEMORY_LOG_PREFIX "::memory_stack", this};
|
||||
}
|
||||
|
||||
const char* block_end() const noexcept
|
||||
{
|
||||
auto block = arena_.current_block();
|
||||
return static_cast<const char*>(block.memory) + block.size;
|
||||
}
|
||||
|
||||
memory_arena<allocator_type> arena_;
|
||||
detail::fixed_memory_stack stack_;
|
||||
|
||||
friend allocator_traits<memory_stack<BlockOrRawAllocator>>;
|
||||
friend composable_allocator_traits<memory_stack<BlockOrRawAllocator>>;
|
||||
};
|
||||
|
||||
/// Simple utility that automatically unwinds a `Stack` to a previously saved location.
|
||||
/// A `Stack` is anything that provides a `marker`, a `top()` function returning a `marker`
|
||||
/// and an `unwind()` function to unwind to a `marker`,
|
||||
/// like a \ref wpi::memory::memory_stack
|
||||
/// \ingroup memory_allocator
|
||||
template <class Stack = memory_stack<>>
|
||||
class memory_stack_raii_unwind
|
||||
{
|
||||
public:
|
||||
using stack_type = Stack;
|
||||
using marker_type = typename stack_type::marker;
|
||||
|
||||
/// \effects Same as `memory_stack_raii_unwind(stack, stack.top())`.
|
||||
explicit memory_stack_raii_unwind(stack_type& stack) noexcept
|
||||
: memory_stack_raii_unwind(stack, stack.top())
|
||||
{
|
||||
}
|
||||
|
||||
/// \effects Creates the unwinder by giving it the stack and the marker.
|
||||
/// \requires The stack must live longer than this object.
|
||||
memory_stack_raii_unwind(stack_type& stack, marker_type marker) noexcept
|
||||
: marker_(marker), stack_(&stack)
|
||||
{
|
||||
}
|
||||
|
||||
/// \effects Move constructs the unwinder by taking the saved position from `other`.
|
||||
/// `other.will_unwind()` will return `false` after it.
|
||||
memory_stack_raii_unwind(memory_stack_raii_unwind&& other) noexcept
|
||||
: marker_(other.marker_), stack_(other.stack_)
|
||||
{
|
||||
other.stack_ = nullptr;
|
||||
}
|
||||
|
||||
/// \effects Unwinds to the previously saved location,
|
||||
/// if there is any, by calling `unwind()`.
|
||||
~memory_stack_raii_unwind() noexcept
|
||||
{
|
||||
if (stack_)
|
||||
stack_->unwind(marker_);
|
||||
}
|
||||
|
||||
/// \effects Move assigns the unwinder by taking the saved position from `other`.
|
||||
/// `other.will_unwind()` will return `false` after it.
|
||||
memory_stack_raii_unwind& operator=(memory_stack_raii_unwind&& other) noexcept
|
||||
{
|
||||
if (stack_)
|
||||
stack_->unwind(marker_);
|
||||
|
||||
marker_ = other.marker_;
|
||||
stack_ = other.stack_;
|
||||
|
||||
other.stack_ = nullptr;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// \effects Removes the location without unwinding it.
|
||||
/// `will_unwind()` will return `false`.
|
||||
void release() noexcept
|
||||
{
|
||||
stack_ = nullptr;
|
||||
}
|
||||
|
||||
/// \effects Unwinds to the saved location explictly.
|
||||
/// \requires `will_unwind()` must return `true`.
|
||||
void unwind() noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(will_unwind());
|
||||
stack_->unwind(marker_);
|
||||
}
|
||||
|
||||
/// \returns Whether or not the unwinder will actually unwind.
|
||||
/// \note It will not unwind if it is in the moved-from state.
|
||||
bool will_unwind() const noexcept
|
||||
{
|
||||
return stack_ != nullptr;
|
||||
}
|
||||
|
||||
/// \returns The saved marker, if there is any.
|
||||
/// \requires `will_unwind()` must return `true`.
|
||||
marker_type get_marker() const noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(will_unwind());
|
||||
return marker_;
|
||||
}
|
||||
|
||||
/// \returns The stack it will unwind.
|
||||
/// \requires `will_unwind()` must return `true`.
|
||||
stack_type& get_stack() const noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(will_unwind());
|
||||
return *stack_;
|
||||
}
|
||||
|
||||
private:
|
||||
marker_type marker_;
|
||||
stack_type* stack_;
|
||||
};
|
||||
|
||||
#if WPI_MEMORY_EXTERN_TEMPLATE
|
||||
extern template class memory_stack<>;
|
||||
extern template class memory_stack_raii_unwind<memory_stack<>>;
|
||||
#endif
|
||||
|
||||
/// Specialization of the \ref allocator_traits for \ref memory_stack classes.
|
||||
/// \note It is not allowed to mix calls through the specialization and through the member functions,
|
||||
/// i.e. \ref memory_stack::allocate() and this \c allocate_node().
|
||||
/// \ingroup memory_allocator
|
||||
template <class BlockAllocator>
|
||||
class allocator_traits<memory_stack<BlockAllocator>>
|
||||
{
|
||||
public:
|
||||
using allocator_type = memory_stack<BlockAllocator>;
|
||||
using is_stateful = std::true_type;
|
||||
|
||||
/// \returns The result of \ref memory_stack::allocate().
|
||||
static void* allocate_node(allocator_type& state, std::size_t size,
|
||||
std::size_t alignment)
|
||||
{
|
||||
auto mem = state.allocate(size, alignment);
|
||||
state.on_allocate(size);
|
||||
return mem;
|
||||
}
|
||||
|
||||
/// \returns The result of \ref memory_stack::allocate().
|
||||
static void* allocate_array(allocator_type& state, std::size_t count, std::size_t size,
|
||||
std::size_t alignment)
|
||||
{
|
||||
return allocate_node(state, count * size, alignment);
|
||||
}
|
||||
|
||||
/// @{
|
||||
/// \effects Does nothing besides bookmarking for leak checking, if that is enabled.
|
||||
/// Actual deallocation can only be done via \ref memory_stack::unwind().
|
||||
static void deallocate_node(allocator_type& state, void*, std::size_t size,
|
||||
std::size_t) noexcept
|
||||
{
|
||||
state.on_deallocate(size);
|
||||
}
|
||||
|
||||
static void deallocate_array(allocator_type& state, void* ptr, std::size_t count,
|
||||
std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
deallocate_node(state, ptr, count * size, alignment);
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \returns The maximum size which is \ref memory_stack::next_capacity().
|
||||
static std::size_t max_node_size(const allocator_type& state) noexcept
|
||||
{
|
||||
return state.next_capacity();
|
||||
}
|
||||
|
||||
static std::size_t max_array_size(const allocator_type& state) noexcept
|
||||
{
|
||||
return state.next_capacity();
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// \returns The maximum possible value since there is no alignment restriction
|
||||
/// (except indirectly through \ref memory_stack::next_capacity()).
|
||||
static std::size_t max_alignment(const allocator_type&) noexcept
|
||||
{
|
||||
return std::size_t(-1);
|
||||
}
|
||||
};
|
||||
|
||||
/// Specialization of the \ref composable_allocator_traits for \ref memory_stack classes.
|
||||
/// \ingroup memory_allocator
|
||||
template <class BlockAllocator>
|
||||
class composable_allocator_traits<memory_stack<BlockAllocator>>
|
||||
{
|
||||
public:
|
||||
using allocator_type = memory_stack<BlockAllocator>;
|
||||
|
||||
/// \returns The result of \ref memory_stack::try_allocate().
|
||||
static void* try_allocate_node(allocator_type& state, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
{
|
||||
return state.try_allocate(size, alignment);
|
||||
}
|
||||
|
||||
/// \returns The result of \ref memory_stack::try_allocate().
|
||||
static void* try_allocate_array(allocator_type& state, std::size_t count,
|
||||
std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
return state.try_allocate(count * size, alignment);
|
||||
}
|
||||
|
||||
/// @{
|
||||
/// \effects Does nothing.
|
||||
/// \returns Whether the memory will be deallocated by \ref memory_stack::unwind().
|
||||
static bool try_deallocate_node(allocator_type& state, void* ptr, std::size_t,
|
||||
std::size_t) noexcept
|
||||
{
|
||||
return state.arena_.owns(ptr);
|
||||
}
|
||||
|
||||
static bool try_deallocate_array(allocator_type& state, void* ptr, std::size_t count,
|
||||
std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
return try_deallocate_node(state, ptr, count * size, alignment);
|
||||
}
|
||||
/// @}
|
||||
};
|
||||
|
||||
#if WPI_MEMORY_EXTERN_TEMPLATE
|
||||
extern template class allocator_traits<memory_stack<>>;
|
||||
extern template class composable_allocator_traits<memory_stack<>>;
|
||||
#endif
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_MEMORY_STACK_HPP_INCLUDED
|
||||
@@ -1,41 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_NAMESPACE_ALIAS_HPP_INCLUDED
|
||||
#define WPI_MEMORY_NAMESPACE_ALIAS_HPP_INCLUDED
|
||||
|
||||
/// \file
|
||||
/// Convenient namespace alias.
|
||||
|
||||
/// \defgroup memory Memory Allocator Library
|
||||
/// @{
|
||||
|
||||
/// \defgroup memory_core Core components
|
||||
|
||||
/// \defgroup memory_allocator Allocator implementations
|
||||
|
||||
/// \defgroup memory_adapter Adapters and Wrappers
|
||||
|
||||
/// \defgroup memory_storage Allocator storage
|
||||
|
||||
/// @}
|
||||
|
||||
/// \namespace wpi
|
||||
/// Foonathan namespace.
|
||||
|
||||
/// \namespace wpi::memory
|
||||
/// Memory namespace.
|
||||
|
||||
/// \namespace wpi::memory::literals
|
||||
/// Literals namespace.
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
}
|
||||
} // namespace wpi
|
||||
|
||||
namespace memory = wpi::memory;
|
||||
///@}
|
||||
#endif // WPI_MEMORY_NAMESPACE_ALIAS_HPP_INCLUDED
|
||||
@@ -1,54 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_NEW_ALLOCATOR_HPP_INCLUDED
|
||||
#define WPI_MEMORY_NEW_ALLOCATOR_HPP_INCLUDED
|
||||
|
||||
/// \file
|
||||
/// Class \ref wpi::memory::new_allocator.
|
||||
|
||||
#include "detail/lowlevel_allocator.hpp"
|
||||
#include "config.hpp"
|
||||
|
||||
#if WPI_MEMORY_EXTERN_TEMPLATE
|
||||
#include "allocator_traits.hpp"
|
||||
#endif
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
struct allocator_info;
|
||||
|
||||
namespace detail
|
||||
{
|
||||
struct new_allocator_impl
|
||||
{
|
||||
static allocator_info info() noexcept;
|
||||
|
||||
static void* allocate(std::size_t size, std::size_t) noexcept;
|
||||
|
||||
static void deallocate(void* ptr, std::size_t size, std::size_t) noexcept;
|
||||
|
||||
static std::size_t max_node_size() noexcept;
|
||||
};
|
||||
|
||||
WPI_MEMORY_LL_ALLOCATOR_LEAK_CHECKER(new_allocator_impl,
|
||||
new_alloator_leak_checker)
|
||||
} // namespace detail
|
||||
|
||||
/// A stateless RawAllocator that allocates memory using (nothrow) <tt>operator new</tt>.
|
||||
/// If the operator returns \c nullptr, it behaves like \c new and loops calling \c std::new_handler,
|
||||
/// but instead of throwing a \c std::bad_alloc exception, it throws \ref out_of_memory.
|
||||
/// \ingroup memory_allocator
|
||||
using new_allocator =
|
||||
WPI_IMPL_DEFINED(detail::lowlevel_allocator<detail::new_allocator_impl>);
|
||||
|
||||
#if WPI_MEMORY_EXTERN_TEMPLATE
|
||||
extern template class detail::lowlevel_allocator<detail::new_allocator_impl>;
|
||||
extern template class allocator_traits<new_allocator>;
|
||||
#endif
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_NEW_ALLOCATOR_HPP_INCLUDED
|
||||
@@ -1,447 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_SEGREGATOR_HPP_INCLUDED
|
||||
#define WPI_MEMORY_SEGREGATOR_HPP_INCLUDED
|
||||
|
||||
/// \file
|
||||
/// Class template \ref wpi::memory::segregator and related classes.
|
||||
|
||||
#include "detail/ebo_storage.hpp"
|
||||
#include "detail/utility.hpp"
|
||||
#include "allocator_traits.hpp"
|
||||
#include "config.hpp"
|
||||
#include "error.hpp"
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
/// A Segregatable that allocates until a maximum size.
|
||||
/// \ingroup memory_adapter
|
||||
template <class RawAllocator>
|
||||
class threshold_segregatable : WPI_EBO(allocator_traits<RawAllocator>::allocator_type)
|
||||
{
|
||||
public:
|
||||
using allocator_type = typename allocator_traits<RawAllocator>::allocator_type;
|
||||
|
||||
/// \effects Creates it by passing the maximum size it will allocate
|
||||
/// and the allocator it uses.
|
||||
explicit threshold_segregatable(std::size_t max_size,
|
||||
allocator_type alloc = allocator_type())
|
||||
: allocator_type(detail::move(alloc)), max_size_(max_size)
|
||||
{
|
||||
}
|
||||
|
||||
/// \returns `true` if `size` is less then or equal to the maximum size,
|
||||
/// `false` otherwise.
|
||||
/// \note A return value of `true` means that the allocator will be used for the allocation.
|
||||
bool use_allocate_node(std::size_t size, std::size_t) noexcept
|
||||
{
|
||||
return size <= max_size_;
|
||||
}
|
||||
|
||||
/// \returns `true` if `count * size` is less then or equal to the maximum size,
|
||||
/// `false` otherwise.
|
||||
/// \note A return value of `true` means that the allocator will be used for the allocation.
|
||||
bool use_allocate_array(std::size_t count, std::size_t size, std::size_t) noexcept
|
||||
{
|
||||
return count * size <= max_size_;
|
||||
}
|
||||
|
||||
/// @{
|
||||
/// \returns A reference to the allocator it owns.
|
||||
allocator_type& get_allocator() noexcept
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
const allocator_type& get_allocator() const noexcept
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
/// @}
|
||||
|
||||
private:
|
||||
std::size_t max_size_;
|
||||
};
|
||||
|
||||
/// \returns A \ref threshold_segregatable with the same parameter.
|
||||
template <class RawAllocator>
|
||||
threshold_segregatable<typename std::decay<RawAllocator>::type> threshold(
|
||||
std::size_t max_size, RawAllocator&& alloc)
|
||||
{
|
||||
return threshold_segregatable<
|
||||
typename std::decay<RawAllocator>::type>(max_size,
|
||||
std::forward<RawAllocator>(alloc));
|
||||
}
|
||||
|
||||
/// A composable RawAllocator that will always fail.
|
||||
/// This is useful for compositioning or as last resort in \ref binary_segregator.
|
||||
/// \ingroup memory_allocator
|
||||
class null_allocator
|
||||
{
|
||||
public:
|
||||
/// \effects Will always throw.
|
||||
/// \throws A \ref out_of_fixed_memory exception.
|
||||
void* allocate_node(std::size_t size, std::size_t)
|
||||
{
|
||||
throw out_of_fixed_memory(info(), size);
|
||||
}
|
||||
|
||||
/// \requires Must not be called.
|
||||
void deallocate_node(void*, std::size_t, std::size_t) noexcept
|
||||
{
|
||||
WPI_MEMORY_UNREACHABLE("cannot be called with proper values");
|
||||
}
|
||||
|
||||
/// \effects Does nothing.
|
||||
/// \returns Always returns `nullptr`.
|
||||
void* try_allocate_node(std::size_t, std::size_t) noexcept
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/// \effects Does nothing.
|
||||
/// \returns Always returns `false`.
|
||||
bool try_deallocate_node(void*, std::size_t, std::size_t) noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
allocator_info info() const noexcept
|
||||
{
|
||||
return {WPI_MEMORY_LOG_PREFIX "::null_allocator", this};
|
||||
}
|
||||
};
|
||||
|
||||
/// A RawAllocator that either uses the Segregatable or the other `RawAllocator`.
|
||||
/// It is a faster alternative to \ref fallback_allocator that doesn't require a composable allocator
|
||||
/// and decides about the allocator to use purely with the `Segregatable` based on size and alignment.
|
||||
/// \ingroup memory_adapter
|
||||
template <class Segregatable, class RawAllocator>
|
||||
class binary_segregator
|
||||
: WPI_EBO(
|
||||
detail::ebo_storage<1, typename allocator_traits<RawAllocator>::allocator_type>)
|
||||
{
|
||||
using segregatable_traits = allocator_traits<typename Segregatable::allocator_type>;
|
||||
using fallback_traits = allocator_traits<RawAllocator>;
|
||||
|
||||
public:
|
||||
using segregatable = Segregatable;
|
||||
using segregatable_allocator_type = typename segregatable::allocator_type;
|
||||
using fallback_allocator_type = typename allocator_traits<RawAllocator>::allocator_type;
|
||||
|
||||
/// \effects Creates it by giving the Segregatable
|
||||
/// and the RawAllocator.
|
||||
explicit binary_segregator(segregatable s,
|
||||
fallback_allocator_type fallback = fallback_allocator_type())
|
||||
: detail::ebo_storage<1, fallback_allocator_type>(detail::move(fallback)),
|
||||
s_(detail::move(s))
|
||||
{
|
||||
}
|
||||
|
||||
/// @{
|
||||
/// \effects Uses the Segregatable to decide which allocator to use.
|
||||
/// Then forwards to the chosen allocator.
|
||||
void* allocate_node(std::size_t size, std::size_t alignment)
|
||||
{
|
||||
if (get_segregatable().use_allocate_node(size, alignment))
|
||||
return segregatable_traits::allocate_node(get_segregatable_allocator(), size,
|
||||
alignment);
|
||||
else
|
||||
return fallback_traits::allocate_node(get_fallback_allocator(), size,
|
||||
alignment);
|
||||
}
|
||||
|
||||
void deallocate_node(void* ptr, std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
if (get_segregatable().use_allocate_node(size, alignment))
|
||||
segregatable_traits::deallocate_node(get_segregatable_allocator(), ptr, size,
|
||||
alignment);
|
||||
else
|
||||
fallback_traits::deallocate_node(get_fallback_allocator(), ptr, size,
|
||||
alignment);
|
||||
}
|
||||
|
||||
void* allocate_array(std::size_t count, std::size_t size, std::size_t alignment)
|
||||
{
|
||||
if (get_segregatable().use_allocate_array(count, size, alignment))
|
||||
return segregatable_traits::allocate_array(get_segregatable_allocator(), count,
|
||||
size, alignment);
|
||||
else
|
||||
return fallback_traits::allocate_array(get_fallback_allocator(), count, size,
|
||||
alignment);
|
||||
}
|
||||
|
||||
void deallocate_array(void* array, std::size_t count, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
{
|
||||
if (get_segregatable().use_allocate_array(count, size, alignment))
|
||||
segregatable_traits::deallocate_array(get_segregatable_allocator(), array,
|
||||
count, size, alignment);
|
||||
else
|
||||
fallback_traits::deallocate_array(get_fallback_allocator(), array, count, size,
|
||||
alignment);
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \returns The maximum value of the fallback.
|
||||
/// \note It assumes that the fallback will be used for larger allocations,
|
||||
/// and the `Segregatable` for smaller ones.
|
||||
std::size_t max_node_size() const
|
||||
{
|
||||
return fallback_traits::max_node_size(get_fallback_allocator());
|
||||
}
|
||||
|
||||
std::size_t max_array_size() const
|
||||
{
|
||||
return fallback_traits::max_array_size(get_fallback_allocator());
|
||||
}
|
||||
|
||||
std::size_t max_alignemnt() const
|
||||
{
|
||||
return fallback_traits::max_alignment(get_fallback_allocator());
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \returns A reference to the segregatable allocator.
|
||||
/// This is the one primarily used.
|
||||
segregatable_allocator_type& get_segregatable_allocator() noexcept
|
||||
{
|
||||
return get_segregatable().get_allocator();
|
||||
}
|
||||
|
||||
const segregatable_allocator_type& get_segregatable_allocator() const noexcept
|
||||
{
|
||||
return get_segregatable().get_allocator();
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \returns A reference to the fallback allocator.
|
||||
/// It will be used if the Segregator doesn't want the alloction.
|
||||
fallback_allocator_type& get_fallback_allocator() noexcept
|
||||
{
|
||||
return detail::ebo_storage<1, fallback_allocator_type>::get();
|
||||
}
|
||||
|
||||
const fallback_allocator_type& get_fallback_allocator() const noexcept
|
||||
{
|
||||
return detail::ebo_storage<1, fallback_allocator_type>::get();
|
||||
}
|
||||
/// @}
|
||||
|
||||
private:
|
||||
segregatable& get_segregatable() noexcept
|
||||
{
|
||||
return s_;
|
||||
}
|
||||
|
||||
segregatable s_;
|
||||
};
|
||||
|
||||
namespace detail
|
||||
{
|
||||
template <class... Segregatables>
|
||||
struct make_segregator_t;
|
||||
|
||||
template <class Segregatable>
|
||||
struct make_segregator_t<Segregatable>
|
||||
{
|
||||
using type = binary_segregator<Segregatable, null_allocator>;
|
||||
};
|
||||
|
||||
template <class Segregatable, class RawAllocator>
|
||||
struct make_segregator_t<Segregatable, RawAllocator>
|
||||
{
|
||||
using type = binary_segregator<Segregatable, RawAllocator>;
|
||||
};
|
||||
|
||||
template <class Segregatable, class... Tail>
|
||||
struct make_segregator_t<Segregatable, Tail...>
|
||||
{
|
||||
using type =
|
||||
binary_segregator<Segregatable, typename make_segregator_t<Tail...>::type>;
|
||||
};
|
||||
|
||||
template <class Segregator, class Fallback = null_allocator>
|
||||
auto make_segregator(Segregator&& seg, Fallback&& f = null_allocator{})
|
||||
-> binary_segregator<typename std::decay<Segregator>::type,
|
||||
typename std::decay<Fallback>::type>
|
||||
{
|
||||
return binary_segregator<
|
||||
typename std::decay<Segregator>::type,
|
||||
typename std::decay<Fallback>::type>(std::forward<Segregator>(seg),
|
||||
std::forward<Fallback>(f));
|
||||
}
|
||||
|
||||
template <class Segregator, typename... Rest>
|
||||
auto make_segregator(Segregator&& seg, Rest&&... rest)
|
||||
-> binary_segregator<typename std::decay<Segregator>::type,
|
||||
decltype(make_segregator(std::forward<Rest>(rest)...))>
|
||||
{
|
||||
return binary_segregator<typename std::decay<Segregator>::type,
|
||||
decltype(make_segregator(std::forward<Rest>(
|
||||
rest)...))>(std::forward<Segregator>(seg),
|
||||
make_segregator(
|
||||
std::forward<Rest>(rest)...));
|
||||
}
|
||||
|
||||
template <std::size_t I, class Segregator>
|
||||
struct segregatable_type;
|
||||
|
||||
template <class Segregator, class Fallback>
|
||||
struct segregatable_type<0, binary_segregator<Segregator, Fallback>>
|
||||
{
|
||||
using type = typename Segregator::allocator_type;
|
||||
|
||||
static type& get(binary_segregator<Segregator, Fallback>& s)
|
||||
{
|
||||
return s.get_segregatable_allocator();
|
||||
}
|
||||
|
||||
static const type& get(const binary_segregator<Segregator, Fallback>& s)
|
||||
{
|
||||
return s.get_segregatable_allocator();
|
||||
}
|
||||
};
|
||||
|
||||
template <std::size_t I, class Segregator, class Fallback>
|
||||
struct segregatable_type<I, binary_segregator<Segregator, Fallback>>
|
||||
{
|
||||
using base = segregatable_type<I - 1, Fallback>;
|
||||
using type = typename base::type;
|
||||
|
||||
static type& get(binary_segregator<Segregator, Fallback>& s)
|
||||
{
|
||||
return base::get(s.get_fallback_allocator());
|
||||
}
|
||||
|
||||
static const type& get(const binary_segregator<Segregator, Fallback>& s)
|
||||
{
|
||||
return base::get(s.get_fallback_allocator());
|
||||
}
|
||||
};
|
||||
|
||||
template <class Fallback>
|
||||
struct fallback_type
|
||||
{
|
||||
using type = Fallback;
|
||||
|
||||
static const std::size_t size = 0u;
|
||||
|
||||
static type& get(Fallback& f)
|
||||
{
|
||||
return f;
|
||||
}
|
||||
|
||||
static const type& get(const Fallback& f)
|
||||
{
|
||||
return f;
|
||||
}
|
||||
};
|
||||
|
||||
template <class Segregator, class Fallback>
|
||||
struct fallback_type<binary_segregator<Segregator, Fallback>>
|
||||
{
|
||||
using base = fallback_type<Fallback>;
|
||||
using type = typename base::type;
|
||||
|
||||
static const std::size_t size = base::size + 1u;
|
||||
|
||||
static type& get(binary_segregator<Segregator, Fallback>& s)
|
||||
{
|
||||
return base::get(s.get_fallback_allocator());
|
||||
}
|
||||
|
||||
static const type& get(const binary_segregator<Segregator, Fallback>& s)
|
||||
{
|
||||
return base::get(s.get_fallback_allocator());
|
||||
}
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
/// Creates multiple nested \ref binary_segregator.
|
||||
/// If you pass one type, it must be a Segregatable.
|
||||
/// Then the result is a \ref binary_segregator with that `Segregatable` and \ref null_allocator as fallback.
|
||||
/// If you pass two types, the first one must be a `Segregatable`,
|
||||
/// the second one a RawAllocator.
|
||||
/// Then the result is a simple \ref binary_segregator with those arguments.
|
||||
/// If you pass more than one, the last one must be a `RawAllocator` all others `Segregatable`,
|
||||
/// the result is `binary_segregator<Head, segregator<Tail...>>`.
|
||||
/// \note It will result in an allocator that tries each `Segregatable` in the order specified
|
||||
/// using the last parameter as final fallback.
|
||||
/// \ingroup memory_adapter
|
||||
template <class... Allocators>
|
||||
WPI_ALIAS_TEMPLATE(segregator,
|
||||
typename detail::make_segregator_t<Allocators...>::type);
|
||||
|
||||
/// \returns A \ref segregator created from the allocators `args`.
|
||||
/// \relates segregator
|
||||
template <typename... Args>
|
||||
auto make_segregator(Args&&... args) -> segregator<typename std::decay<Args>::type...>
|
||||
{
|
||||
return detail::make_segregator(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/// The number of Segregatable a \ref segregator has.
|
||||
/// \relates segregator
|
||||
template <class Segregator>
|
||||
struct segregator_size
|
||||
{
|
||||
static const std::size_t value = detail::fallback_type<Segregator>::size;
|
||||
};
|
||||
|
||||
/// The type of the `I`th Segregatable.
|
||||
/// \relates segregator
|
||||
template <std::size_t I, class Segregator>
|
||||
using segregatable_allocator_type = typename detail::segregatable_type<I, Segregator>::type;
|
||||
|
||||
/// @{
|
||||
/// \returns The `I`th Segregatable.
|
||||
/// \relates segregrator
|
||||
template <std::size_t I, class Segregator, class Fallback>
|
||||
auto get_segregatable_allocator(binary_segregator<Segregator, Fallback>& s)
|
||||
-> segregatable_allocator_type<I, binary_segregator<Segregator, Fallback>>&
|
||||
{
|
||||
return detail::segregatable_type<I, binary_segregator<Segregator, Fallback>>::get(s);
|
||||
}
|
||||
|
||||
template <std::size_t I, class Segregator, class Fallback>
|
||||
auto get_segregatable_allocator(const binary_segregator<Segregator, Fallback>& s)
|
||||
-> const segregatable_allocator_type<I, binary_segregator<Segregator, Fallback>>
|
||||
{
|
||||
return detail::segregatable_type<I, binary_segregator<Segregator, Fallback>>::get(s);
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// The type of the final fallback RawAllocator.
|
||||
/// \relates segregator
|
||||
template <class Segregator>
|
||||
using fallback_allocator_type = typename detail::fallback_type<Segregator>::type;
|
||||
|
||||
/// @{
|
||||
/// \returns The final fallback RawAllocator.
|
||||
/// \relates segregator
|
||||
template <class Segregator, class Fallback>
|
||||
auto get_fallback_allocator(binary_segregator<Segregator, Fallback>& s)
|
||||
-> fallback_allocator_type<binary_segregator<Segregator, Fallback>>&
|
||||
{
|
||||
return detail::fallback_type<binary_segregator<Segregator, Fallback>>::get(s);
|
||||
}
|
||||
|
||||
template <class Segregator, class Fallback>
|
||||
auto get_fallback_allocator(const binary_segregator<Segregator, Fallback>& s)
|
||||
-> const fallback_allocator_type<binary_segregator<Segregator, Fallback>>&
|
||||
{
|
||||
return detail::fallback_type<binary_segregator<Segregator, Fallback>>::get(s);
|
||||
}
|
||||
/// @}
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_SEGREGATOR_HPP_INCLUDED
|
||||
@@ -1,197 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_SMART_PTR_HPP_INCLUDED
|
||||
#define WPI_MEMORY_SMART_PTR_HPP_INCLUDED
|
||||
|
||||
/// \file
|
||||
/// \c std::make_unique() / \c std::make_shared() replacement allocating memory through a RawAllocator.
|
||||
/// \note Only available on a hosted implementation.
|
||||
|
||||
#include "config.hpp"
|
||||
#if !WPI_HOSTED_IMPLEMENTATION
|
||||
#error "This header is only available for a hosted implementation."
|
||||
#endif
|
||||
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
#include "detail/utility.hpp"
|
||||
#include "deleter.hpp"
|
||||
#include "std_allocator.hpp"
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
template <typename T, class RawAllocator, typename... Args>
|
||||
auto allocate_unique(allocator_reference<RawAllocator> alloc, Args&&... args)
|
||||
-> std::unique_ptr<T, allocator_deleter<T, RawAllocator>>
|
||||
{
|
||||
using raw_ptr = std::unique_ptr<T, allocator_deallocator<T, RawAllocator>>;
|
||||
|
||||
auto memory = alloc.allocate_node(sizeof(T), alignof(T));
|
||||
// raw_ptr deallocates memory in case of constructor exception
|
||||
raw_ptr result(static_cast<T*>(memory), {alloc});
|
||||
// call constructor
|
||||
::new (memory) T(detail::forward<Args>(args)...);
|
||||
// pass ownership to return value using a deleter that calls destructor
|
||||
return {result.release(), {alloc}};
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
void construct(std::true_type, T* cur, T* end, Args&&... args)
|
||||
{
|
||||
for (; cur != end; ++cur)
|
||||
::new (static_cast<void*>(cur)) T(detail::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
void construct(std::false_type, T* begin, T* end, Args&&... args)
|
||||
{
|
||||
#if WPI_HAS_EXCEPTION_SUPPORT
|
||||
auto cur = begin;
|
||||
try
|
||||
{
|
||||
for (; cur != end; ++cur)
|
||||
::new (static_cast<void*>(cur)) T(detail::forward<Args>(args)...);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
for (auto el = begin; el != cur; ++el)
|
||||
el->~T();
|
||||
throw;
|
||||
}
|
||||
#else
|
||||
construct(std::true_type{}, begin, end, detail::forward<Args>(args)...);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename T, class RawAllocator>
|
||||
auto allocate_array_unique(std::size_t size, allocator_reference<RawAllocator> alloc)
|
||||
-> std::unique_ptr<T[], allocator_deleter<T[], RawAllocator>>
|
||||
{
|
||||
using raw_ptr = std::unique_ptr<T[], allocator_deallocator<T[], RawAllocator>>;
|
||||
|
||||
auto memory = alloc.allocate_array(size, sizeof(T), alignof(T));
|
||||
// raw_ptr deallocates memory in case of constructor exception
|
||||
raw_ptr result(static_cast<T*>(memory), {alloc, size});
|
||||
construct(std::integral_constant<bool, noexcept(T())>{}, result.get(),
|
||||
result.get() + size);
|
||||
// pass ownership to return value using a deleter that calls destructor
|
||||
return {result.release(), {alloc, size}};
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
/// A \c std::unique_ptr that deletes using a RawAllocator.
|
||||
///
|
||||
/// It is an alias template using \ref allocator_deleter as \c Deleter class.
|
||||
/// \ingroup memory_adapter
|
||||
template <typename T, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(unique_ptr,
|
||||
std::unique_ptr<T, allocator_deleter<T, RawAllocator>>);
|
||||
|
||||
/// A \c std::unique_ptr that deletes using a RawAllocator and allows polymorphic types.
|
||||
///
|
||||
/// It can only be created by converting a regular unique pointer to a pointer to a derived class,
|
||||
/// and is meant to be used inside containers.
|
||||
/// It is an alias template using \ref allocator_polymorphic_deleter as \c Deleter class.
|
||||
/// \note It has a relatively high overhead, so only use it if you have to.
|
||||
/// \ingroup memory_adapter
|
||||
template <class BaseType, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(
|
||||
unique_base_ptr,
|
||||
std::unique_ptr<BaseType, allocator_polymorphic_deleter<BaseType, RawAllocator>>);
|
||||
|
||||
/// Creates a \c std::unique_ptr using a RawAllocator for the allocation.
|
||||
/// \effects Allocates memory for the given type using the allocator
|
||||
/// and creates a new object inside it passing the given arguments to its constructor.
|
||||
/// \returns A \c std::unique_ptr owning that memory.
|
||||
/// \note If the allocator is stateful a reference to the \c RawAllocator will be stored inside the deleter,
|
||||
/// the caller has to ensure that the object lives as long as the smart pointer.
|
||||
/// \ingroup memory_adapter
|
||||
template <typename T, class RawAllocator, typename... Args>
|
||||
auto allocate_unique(RawAllocator&& alloc, Args&&... args) -> WPI_REQUIRES_RET(
|
||||
!std::is_array<T>::value,
|
||||
std::unique_ptr<T, allocator_deleter<T, typename std::decay<RawAllocator>::type>>)
|
||||
{
|
||||
return detail::allocate_unique<T>(make_allocator_reference(
|
||||
detail::forward<RawAllocator>(alloc)),
|
||||
detail::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/// Creates a \c std::unique_ptr using a type-erased RawAllocator for the allocation.
|
||||
/// It is the same as the other overload but stores the reference to the allocator type-erased inside the \c std::unique_ptr.
|
||||
/// \effects Allocates memory for the given type using the allocator
|
||||
/// and creates a new object inside it passing the given arguments to its constructor.
|
||||
/// \returns A \c std::unique_ptr with a type-erased allocator reference owning that memory.
|
||||
/// \note If the allocator is stateful a reference to the \c RawAllocator will be stored inside the deleter,
|
||||
/// the caller has to ensure that the object lives as long as the smart pointer.
|
||||
/// \ingroup memory_adapter
|
||||
template <typename T, class RawAllocator, typename... Args>
|
||||
auto allocate_unique(any_allocator, RawAllocator&& alloc, Args&&... args)
|
||||
-> WPI_REQUIRES_RET(!std::is_array<T>::value,
|
||||
std::unique_ptr<T, allocator_deleter<T, any_allocator>>)
|
||||
{
|
||||
return detail::allocate_unique<T, any_allocator>(make_allocator_reference(
|
||||
detail::forward<RawAllocator>(
|
||||
alloc)),
|
||||
detail::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/// Creates a \c std::unique_ptr owning an array using a RawAllocator for the allocation.
|
||||
/// \effects Allocates memory for an array of given size and value initializes each element inside of it.
|
||||
/// \returns A \c std::unique_ptr owning that array.
|
||||
/// \note If the allocator is stateful a reference to the \c RawAllocator will be stored inside the deleter,
|
||||
/// the caller has to ensure that the object lives as long as the smart pointer.
|
||||
/// \ingroup memory_adapter
|
||||
template <typename T, class RawAllocator>
|
||||
auto allocate_unique(RawAllocator&& alloc, std::size_t size) -> WPI_REQUIRES_RET(
|
||||
std::is_array<T>::value,
|
||||
std::unique_ptr<T, allocator_deleter<T, typename std::decay<RawAllocator>::type>>)
|
||||
{
|
||||
return detail::allocate_array_unique<
|
||||
typename std::remove_extent<T>::type>(size,
|
||||
make_allocator_reference(
|
||||
detail::forward<RawAllocator>(alloc)));
|
||||
}
|
||||
|
||||
/// Creates a \c std::unique_ptr owning an array using a type-erased RawAllocator for the allocation.
|
||||
/// It is the same as the other overload but stores the reference to the allocator type-erased inside the \c std::unique_ptr.
|
||||
/// \effects Allocates memory for an array of given size and value initializes each element inside of it.
|
||||
/// \returns A \c std::unique_ptr with a type-erased allocator reference owning that array.
|
||||
/// \note If the allocator is stateful a reference to the \c RawAllocator will be stored inside the deleter,
|
||||
/// the caller has to ensure that the object lives as long as the smart pointer.
|
||||
/// \ingroup memory_adapter
|
||||
template <typename T, class RawAllocator>
|
||||
auto allocate_unique(any_allocator, RawAllocator&& alloc, std::size_t size)
|
||||
-> WPI_REQUIRES_RET(std::is_array<T>::value,
|
||||
std::unique_ptr<T, allocator_deleter<T, any_allocator>>)
|
||||
{
|
||||
return detail::allocate_array_unique<typename std::remove_extent<T>::type,
|
||||
any_allocator>(size,
|
||||
make_allocator_reference(
|
||||
detail::forward<RawAllocator>(
|
||||
alloc)));
|
||||
}
|
||||
|
||||
/// Creates a \c std::shared_ptr using a RawAllocator for the allocation.
|
||||
/// It is similar to \c std::allocate_shared but uses a \c RawAllocator (and thus also supports any \c Allocator).
|
||||
/// \effects Calls \ref std_allocator::make_std_allocator to wrap the allocator and forwards to \c std::allocate_shared.
|
||||
/// \returns A \c std::shared_ptr created using \c std::allocate_shared.
|
||||
/// \note If the allocator is stateful a reference to the \c RawAllocator will be stored inside the shared pointer,
|
||||
/// the caller has to ensure that the object lives as long as the smart pointer.
|
||||
/// \ingroup memory_adapter
|
||||
template <typename T, class RawAllocator, typename... Args>
|
||||
std::shared_ptr<T> allocate_shared(RawAllocator&& alloc, Args&&... args)
|
||||
{
|
||||
return std::allocate_shared<T>(make_std_allocator<T>(
|
||||
detail::forward<RawAllocator>(alloc)),
|
||||
detail::forward<Args>(args)...);
|
||||
}
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_SMART_PTR_HPP_INCLUDED
|
||||
@@ -1,178 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_STATIC_ALLOCATOR_HPP_INCLUDED
|
||||
#define WPI_MEMORY_STATIC_ALLOCATOR_HPP_INCLUDED
|
||||
|
||||
/// \file
|
||||
/// Allocators using a static, fixed-sized storage.
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "detail/align.hpp"
|
||||
#include "detail/assert.hpp"
|
||||
#include "detail/memory_stack.hpp"
|
||||
#include "detail/utility.hpp"
|
||||
#include "config.hpp"
|
||||
|
||||
#if WPI_MEMORY_EXTERN_TEMPLATE
|
||||
#include "allocator_traits.hpp"
|
||||
#endif
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
/// Storage for a \ref static_allocator.
|
||||
/// Its constructor will take a reference to it and use it for its allocation.
|
||||
/// The storage type is simply a \c char array aligned for maximum alignment.
|
||||
/// \note It is not allowed to access the memory of the storage.
|
||||
/// \ingroup memory_allocator
|
||||
template <std::size_t Size>
|
||||
struct static_allocator_storage
|
||||
{
|
||||
alignas(detail::max_alignment) char storage[Size];
|
||||
};
|
||||
|
||||
static_assert(sizeof(static_allocator_storage<1024>) == 1024, "");
|
||||
static_assert(alignof(static_allocator_storage<1024>) == detail::max_alignment, "");
|
||||
|
||||
struct allocator_info;
|
||||
|
||||
/// A stateful RawAllocator that uses a fixed sized storage for the allocations.
|
||||
/// It works on a \ref static_allocator_storage and uses its memory for all allocations.
|
||||
/// Deallocations are not supported, memory cannot be marked as freed.<br>
|
||||
/// \note It is not allowed to share an \ref static_allocator_storage between multiple \ref static_allocator objects.
|
||||
/// \ingroup memory_allocator
|
||||
class static_allocator
|
||||
{
|
||||
public:
|
||||
using is_stateful = std::true_type;
|
||||
|
||||
/// \effects Creates it by passing it a \ref static_allocator_storage by reference.
|
||||
/// It will take the address of the storage and use its memory for the allocation.
|
||||
/// \requires The storage object must live as long as the allocator object.
|
||||
/// It must not be shared between multiple allocators,
|
||||
/// i.e. the object must not have been passed to a constructor before.
|
||||
template <std::size_t Size>
|
||||
static_allocator(static_allocator_storage<Size>& storage) noexcept
|
||||
: stack_(&storage), end_(stack_.top() + Size)
|
||||
{
|
||||
}
|
||||
|
||||
/// \effects A RawAllocator allocation function.
|
||||
/// It uses the specified \ref static_allocator_storage.
|
||||
/// \returns A pointer to a node, it will never be \c nullptr.
|
||||
/// \throws An exception of type \ref out_of_memory or whatever is thrown by its handler if the storage is exhausted.
|
||||
void* allocate_node(std::size_t size, std::size_t alignment);
|
||||
|
||||
/// \effects A RawAllocator deallocation function.
|
||||
/// It does nothing, deallocation is not supported by this allocator.
|
||||
void deallocate_node(void*, std::size_t, std::size_t) noexcept {}
|
||||
|
||||
/// \returns The maximum node size which is the capacity remaining inside the \ref static_allocator_storage.
|
||||
std::size_t max_node_size() const noexcept
|
||||
{
|
||||
return static_cast<std::size_t>(end_ - stack_.top());
|
||||
}
|
||||
|
||||
/// \returns The maximum possible value since there is no alignment restriction
|
||||
/// (except indirectly through the size of the \ref static_allocator_storage).
|
||||
std::size_t max_alignment() const noexcept
|
||||
{
|
||||
return std::size_t(-1);
|
||||
}
|
||||
|
||||
private:
|
||||
allocator_info info() const noexcept;
|
||||
|
||||
detail::fixed_memory_stack stack_;
|
||||
const char* end_;
|
||||
};
|
||||
|
||||
#if WPI_MEMORY_EXTERN_TEMPLATE
|
||||
extern template class allocator_traits<static_allocator>;
|
||||
#endif
|
||||
|
||||
struct memory_block;
|
||||
|
||||
/// A BlockAllocator that allocates the blocks from a fixed size storage.
|
||||
/// It works on a \ref static_allocator_storage and uses it for all allocations,
|
||||
/// deallocations are only allowed in reversed order which is guaranteed by \ref memory_arena.
|
||||
/// \note It is not allowed to share an \ref static_allocator_storage between multiple \ref static_allocator objects.
|
||||
/// \ingroup memory_allocator
|
||||
class static_block_allocator
|
||||
{
|
||||
public:
|
||||
/// \effects Creates it by passing it the block size and a \ref static_allocator_storage by reference.
|
||||
/// It will take the address of the storage and use it to allocate \c block_size'd blocks.
|
||||
/// \requires The storage object must live as long as the allocator object.
|
||||
/// It must not be shared between multiple allocators,
|
||||
/// i.e. the object must not have been passed to a constructor before.
|
||||
/// The size of the \ref static_allocator_storage must be a multiple of the (non-null) block size.
|
||||
template <std::size_t Size>
|
||||
static_block_allocator(std::size_t block_size,
|
||||
static_allocator_storage<Size>& storage) noexcept
|
||||
: cur_(static_cast<char*>(static_cast<void*>(&storage))),
|
||||
end_(cur_ + Size),
|
||||
block_size_(block_size)
|
||||
{
|
||||
WPI_MEMORY_ASSERT(block_size <= Size);
|
||||
WPI_MEMORY_ASSERT(Size % block_size == 0u);
|
||||
}
|
||||
|
||||
~static_block_allocator() noexcept = default;
|
||||
|
||||
/// @{
|
||||
/// \effects Moves the block allocator, it transfers ownership over the \ref static_allocator_storage.
|
||||
/// This does not invalidate any memory blocks.
|
||||
static_block_allocator(static_block_allocator&& other) noexcept
|
||||
: cur_(other.cur_), end_(other.end_), block_size_(other.block_size_)
|
||||
{
|
||||
other.cur_ = other.end_ = nullptr;
|
||||
other.block_size_ = 0;
|
||||
}
|
||||
|
||||
static_block_allocator& operator=(static_block_allocator&& other) noexcept
|
||||
{
|
||||
static_block_allocator tmp(detail::move(other));
|
||||
swap(*this, tmp);
|
||||
return *this;
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// \effects Swaps the ownership over the \ref static_allocator_storage.
|
||||
/// This does not invalidate any memory blocks.
|
||||
friend void swap(static_block_allocator& a, static_block_allocator& b) noexcept
|
||||
{
|
||||
detail::adl_swap(a.cur_, b.cur_);
|
||||
detail::adl_swap(a.end_, b.end_);
|
||||
detail::adl_swap(a.block_size_, b.block_size_);
|
||||
}
|
||||
|
||||
/// \effects Allocates a new block by returning the \ref next_block_size() bytes.
|
||||
/// \returns The new memory block.
|
||||
memory_block allocate_block();
|
||||
|
||||
/// \effects Deallocates the last memory block by marking the block as free again.
|
||||
/// This block will be returned again by the next call to \ref allocate_block().
|
||||
/// \requires \c block must be the current top block of the memory,
|
||||
/// this is guaranteed by \ref memory_arena.
|
||||
void deallocate_block(memory_block block) noexcept;
|
||||
|
||||
/// \returns The next block size, this is the size passed to the constructor.
|
||||
std::size_t next_block_size() const noexcept
|
||||
{
|
||||
return block_size_;
|
||||
}
|
||||
|
||||
private:
|
||||
allocator_info info() const noexcept;
|
||||
|
||||
char * cur_, *end_;
|
||||
std::size_t block_size_;
|
||||
};
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif //WPI_MEMORY_STATIC_ALLOCATOR_HPP_INCLUDED
|
||||
@@ -1,361 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_STD_ALLOCATOR_HPP_INCLUDED
|
||||
#define WPI_MEMORY_STD_ALLOCATOR_HPP_INCLUDED
|
||||
|
||||
/// \file
|
||||
/// Class \ref wpi::memory::std_allocator and related classes and functions.
|
||||
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
|
||||
#include "detail/utility.hpp"
|
||||
#include "config.hpp"
|
||||
#include "allocator_storage.hpp"
|
||||
#include "threading.hpp"
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
namespace traits_detail
|
||||
{
|
||||
template <class RawAllocator>
|
||||
auto propagate_on_container_swap(std_concept) ->
|
||||
typename RawAllocator::propagate_on_container_swap;
|
||||
|
||||
template <class RawAllocator>
|
||||
auto propagate_on_container_swap(min_concept) -> std::true_type;
|
||||
|
||||
template <class RawAllocator>
|
||||
auto propagate_on_container_move_assignment(std_concept) ->
|
||||
typename RawAllocator::propagate_on_container_move_assignment;
|
||||
|
||||
template <class RawAllocator>
|
||||
auto propagate_on_container_move_assignment(min_concept) -> std::true_type;
|
||||
|
||||
template <class RawAllocator>
|
||||
auto propagate_on_container_copy_assignment(std_concept) ->
|
||||
typename RawAllocator::propagate_on_container_copy_assignment;
|
||||
|
||||
template <class RawAllocator>
|
||||
auto propagate_on_container_copy_assignment(min_concept) -> std::true_type;
|
||||
} // namespace traits_detail
|
||||
|
||||
/// Controls the propagation of a \ref std_allocator for a certain RawAllocator.
|
||||
/// \ingroup memory_adapter
|
||||
template <class RawAllocator>
|
||||
struct propagation_traits
|
||||
{
|
||||
using propagate_on_container_swap =
|
||||
decltype(traits_detail::propagate_on_container_swap<RawAllocator>(
|
||||
traits_detail::full_concept{}));
|
||||
|
||||
using propagate_on_container_move_assignment =
|
||||
decltype(traits_detail::propagate_on_container_move_assignment<RawAllocator>(
|
||||
traits_detail::full_concept{}));
|
||||
|
||||
using propagate_on_container_copy_assignment =
|
||||
decltype(traits_detail::propagate_on_container_copy_assignment<RawAllocator>(
|
||||
traits_detail::full_concept{}));
|
||||
|
||||
template <class AllocReference>
|
||||
static AllocReference select_on_container_copy_construction(const AllocReference& alloc)
|
||||
{
|
||||
return alloc;
|
||||
}
|
||||
};
|
||||
|
||||
/// Wraps a RawAllocator and makes it a "normal" \c Allocator.
|
||||
/// It allows using a \c RawAllocator anywhere a \c Allocator is required.
|
||||
/// \ingroup memory_adapter
|
||||
template <typename T, class RawAllocator>
|
||||
class std_allocator :
|
||||
#if defined _MSC_VER && defined __clang__
|
||||
WPI_EBO(protected allocator_reference<RawAllocator>)
|
||||
#else
|
||||
WPI_EBO(allocator_reference<RawAllocator>)
|
||||
#endif
|
||||
{
|
||||
using alloc_reference = allocator_reference<RawAllocator>;
|
||||
// if it is any_allocator_reference an optimized implementation can be used
|
||||
using is_any = std::is_same<alloc_reference, any_allocator_reference>;
|
||||
|
||||
using prop_traits = propagation_traits<RawAllocator>;
|
||||
|
||||
public:
|
||||
//=== typedefs ===//
|
||||
using value_type = T;
|
||||
using pointer = T*;
|
||||
using const_pointer = const T*;
|
||||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
|
||||
using propagate_on_container_swap = typename prop_traits::propagate_on_container_swap;
|
||||
using propagate_on_container_move_assignment =
|
||||
typename prop_traits::propagate_on_container_move_assignment;
|
||||
using propagate_on_container_copy_assignment =
|
||||
typename prop_traits::propagate_on_container_copy_assignment;
|
||||
|
||||
template <typename U>
|
||||
struct rebind
|
||||
{
|
||||
using other = std_allocator<U, RawAllocator>;
|
||||
};
|
||||
|
||||
using allocator_type = typename alloc_reference::allocator_type;
|
||||
|
||||
//=== constructor ===//
|
||||
/// \effects Default constructs it by storing a default constructed, stateless \c RawAllocator inside the reference.
|
||||
/// \requires The \c RawAllocator type is stateless, otherwise the body of this function will not compile.
|
||||
std_allocator() noexcept : alloc_reference(allocator_type{})
|
||||
{
|
||||
#if !defined(__GNUC__) || (defined(_GLIBCXX_USE_CXX11_ABI) && _GLIBCXX_USE_CXX11_ABI != 0)
|
||||
// std::string requires default constructor for the small string optimization when using gcc's old ABI
|
||||
// so don't assert then to allow joint allocator
|
||||
static_assert(!alloc_reference::is_stateful::value,
|
||||
"default constructor must not be used for stateful allocators");
|
||||
#endif
|
||||
}
|
||||
|
||||
/// \effects Creates it from a reference to a \c RawAllocator.
|
||||
/// It will store an \ref allocator_reference to it.
|
||||
/// \requires The expression <tt>allocator_reference<RawAllocator>(alloc)</tt> is well-formed,
|
||||
/// that is either \c RawAlloc is the same as \c RawAllocator or \c RawAllocator is the tag type \ref any_allocator.
|
||||
/// If the requirement is not fulfilled this function does not participate in overload resolution.
|
||||
/// \note The caller has to ensure that the lifetime of the \c RawAllocator is at least as long as the lifetime
|
||||
/// of this \ref std_allocator object.
|
||||
template <
|
||||
class RawAlloc,
|
||||
// MSVC seems to ignore access rights in decltype SFINAE below
|
||||
// use this to prevent this constructor being chosen instead of move/copy for types inheriting from it
|
||||
WPI_REQUIRES((!std::is_base_of<std_allocator, RawAlloc>::value))>
|
||||
std_allocator(RawAlloc& alloc,
|
||||
WPI_SFINAE(alloc_reference(std::declval<RawAlloc&>()))) noexcept
|
||||
: alloc_reference(alloc)
|
||||
{
|
||||
}
|
||||
|
||||
/// \effects Creates it from a stateless, temporary \c RawAllocator object.
|
||||
/// It will not store a reference but create it on the fly.
|
||||
/// \requires The \c RawAllocator is stateless
|
||||
/// and the expression <tt>allocator_reference<RawAllocator>(alloc)</tt> is well-formed as above,
|
||||
/// otherwise this function does not participate in overload resolution.
|
||||
template <
|
||||
class RawAlloc,
|
||||
// MSVC seems to ignore access rights in decltype SFINAE below
|
||||
// use this to prevent this constructor being chosen instead of move/copy for types inheriting from it
|
||||
WPI_REQUIRES((!std::is_base_of<std_allocator, RawAlloc>::value))>
|
||||
std_allocator(const RawAlloc& alloc, WPI_SFINAE(alloc_reference(
|
||||
std::declval<const RawAlloc&>()))) noexcept
|
||||
: alloc_reference(alloc)
|
||||
{
|
||||
}
|
||||
|
||||
/// \effects Creates it from another \ref allocator_reference using the same allocator type.
|
||||
std_allocator(const alloc_reference& alloc) noexcept : alloc_reference(alloc) {}
|
||||
|
||||
/// \details Implicit conversion from any other \ref allocator_storage is forbidden
|
||||
/// to prevent accidentally wrapping another \ref allocator_storage inside a \ref allocator_reference.
|
||||
template <class StoragePolicy, class OtherMut>
|
||||
std_allocator(const allocator_storage<StoragePolicy, OtherMut>&) = delete;
|
||||
|
||||
/// @{
|
||||
/// \effects Creates it from another \ref std_allocator allocating a different type.
|
||||
/// This is required by the \c Allcoator concept and simply takes the same \ref allocator_reference.
|
||||
template <typename U>
|
||||
std_allocator(const std_allocator<U, RawAllocator>& alloc) noexcept
|
||||
: alloc_reference(alloc)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
std_allocator(std_allocator<U, RawAllocator>& alloc) noexcept : alloc_reference(alloc)
|
||||
{
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// \returns A copy of the allocator.
|
||||
/// This is required by the \c Allocator concept and forwards to the \ref propagation_traits.
|
||||
std_allocator<T, RawAllocator> select_on_container_copy_construction() const
|
||||
{
|
||||
return prop_traits::select_on_container_copy_construction(*this);
|
||||
}
|
||||
|
||||
//=== allocation/deallocation ===//
|
||||
/// \effects Allocates memory using the underlying RawAllocator.
|
||||
/// If \c n is \c 1, it will call <tt>allocate_node(sizeof(T), alignof(T))</tt>,
|
||||
/// otherwise <tt>allocate_array(n, sizeof(T), alignof(T))</tt>.
|
||||
/// \returns A pointer to a memory block suitable for \c n objects of type \c T.
|
||||
/// \throws Anything thrown by the \c RawAllocator.
|
||||
pointer allocate(size_type n, void* = nullptr)
|
||||
{
|
||||
return static_cast<pointer>(allocate_impl(is_any{}, n));
|
||||
}
|
||||
|
||||
/// \effects Deallcoates memory using the underlying RawAllocator.
|
||||
/// It will forward to the deallocation function in the same way as in \ref allocate().
|
||||
/// \requires The pointer must come from a previous call to \ref allocate() with the same \c n on this object or any copy of it.
|
||||
void deallocate(pointer p, size_type n) noexcept
|
||||
{
|
||||
deallocate_impl(is_any{}, p, n);
|
||||
}
|
||||
|
||||
//=== construction/destruction ===//
|
||||
/// \effects Creates an object of type \c U at given address using the passed arguments.
|
||||
template <typename U, typename... Args>
|
||||
void construct(U* p, Args&&... args)
|
||||
{
|
||||
void* mem = p;
|
||||
::new (mem) U(detail::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/// \effects Calls the destructor for an object of type \c U at given address.
|
||||
template <typename U>
|
||||
void destroy(U* p) noexcept
|
||||
{
|
||||
// This is to avoid a MSVS 2015 'unreferenced formal parameter' warning
|
||||
(void)p;
|
||||
p->~U();
|
||||
}
|
||||
|
||||
//=== getter ===//
|
||||
/// \returns The maximum size for an allocation which is <tt>max_array_size() / sizeof(value_type)</tt>.
|
||||
/// This is only an upper bound, not the exact maximum.
|
||||
size_type max_size() const noexcept
|
||||
{
|
||||
return this->max_array_size() / sizeof(value_type);
|
||||
}
|
||||
|
||||
/// @{
|
||||
/// \effects Returns a reference to the referenced allocator.
|
||||
/// \returns For stateful allocators: A (\c const) reference to the stored allocator.
|
||||
/// For stateless allocators: A temporary constructed allocator.
|
||||
auto get_allocator() noexcept
|
||||
-> decltype(std::declval<alloc_reference>().get_allocator())
|
||||
{
|
||||
return alloc_reference::get_allocator();
|
||||
}
|
||||
|
||||
auto get_allocator() const noexcept
|
||||
-> decltype(std::declval<const alloc_reference>().get_allocator())
|
||||
{
|
||||
return alloc_reference::get_allocator();
|
||||
}
|
||||
/// @}
|
||||
|
||||
private:
|
||||
// any_allocator_reference: use virtual function which already does a dispatch on node/array
|
||||
void* allocate_impl(std::true_type, size_type n)
|
||||
{
|
||||
return get_allocator().allocate_impl(n, sizeof(T), alignof(T));
|
||||
}
|
||||
|
||||
void deallocate_impl(std::true_type, void* ptr, size_type n)
|
||||
{
|
||||
get_allocator().deallocate_impl(ptr, n, sizeof(T), alignof(T));
|
||||
}
|
||||
|
||||
// alloc_reference: decide between node/array
|
||||
void* allocate_impl(std::false_type, size_type n)
|
||||
{
|
||||
if (n == 1)
|
||||
return this->allocate_node(sizeof(T), alignof(T));
|
||||
else
|
||||
return this->allocate_array(n, sizeof(T), alignof(T));
|
||||
}
|
||||
|
||||
void deallocate_impl(std::false_type, void* ptr, size_type n)
|
||||
{
|
||||
if (n == 1)
|
||||
this->deallocate_node(ptr, sizeof(T), alignof(T));
|
||||
else
|
||||
this->deallocate_array(ptr, n, sizeof(T), alignof(T));
|
||||
}
|
||||
|
||||
template <typename U> // stateful
|
||||
bool equal_to_impl(std::true_type,
|
||||
const std_allocator<U, RawAllocator>& other) const noexcept
|
||||
{
|
||||
return &get_allocator() == &other.get_allocator();
|
||||
}
|
||||
|
||||
template <typename U> // non-stateful
|
||||
bool equal_to_impl(std::false_type,
|
||||
const std_allocator<U, RawAllocator>&) const noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename U> // shared
|
||||
bool equal_to(std::true_type,
|
||||
const std_allocator<U, RawAllocator>& other) const noexcept
|
||||
{
|
||||
return get_allocator() == other.get_allocator();
|
||||
}
|
||||
|
||||
template <typename U> // not shared
|
||||
bool equal_to(std::false_type,
|
||||
const std_allocator<U, RawAllocator>& other) const noexcept
|
||||
{
|
||||
return equal_to_impl(typename allocator_traits<RawAllocator>::is_stateful{}, other);
|
||||
}
|
||||
|
||||
template <typename T1, typename T2, class Impl>
|
||||
friend bool operator==(const std_allocator<T1, Impl>& lhs,
|
||||
const std_allocator<T2, Impl>& rhs) noexcept;
|
||||
|
||||
template <typename U, class OtherRawAllocator>
|
||||
friend class std_allocator;
|
||||
};
|
||||
|
||||
/// \effects Compares two \ref std_allocator object, they are equal if either stateless or reference the same allocator.
|
||||
/// \returns The result of the comparision for equality.
|
||||
/// \relates std_allocator
|
||||
template <typename T, typename U, class Impl>
|
||||
bool operator==(const std_allocator<T, Impl>& lhs,
|
||||
const std_allocator<U, Impl>& rhs) noexcept
|
||||
{
|
||||
return lhs.equal_to(is_shared_allocator<Impl>{}, rhs);
|
||||
}
|
||||
|
||||
/// \effects Compares two \ref std_allocator object, they are equal if either stateless or reference the same allocator.
|
||||
/// \returns The result of the comparision for inequality.
|
||||
/// \relates std_allocator
|
||||
template <typename T, typename U, class Impl>
|
||||
bool operator!=(const std_allocator<T, Impl>& lhs,
|
||||
const std_allocator<U, Impl>& rhs) noexcept
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
/// \returns A new \ref std_allocator for a given type using a certain allocator object.
|
||||
/// \relates std_allocator
|
||||
template <typename T, class RawAllocator>
|
||||
auto make_std_allocator(RawAllocator&& allocator) noexcept
|
||||
-> std_allocator<T, typename std::decay<RawAllocator>::type>
|
||||
{
|
||||
return {detail::forward<RawAllocator>(allocator)};
|
||||
}
|
||||
|
||||
/// An alias template for \ref std_allocator using a type-erased RawAllocator.
|
||||
/// This is the same as using a \ref std_allocator with the tag type \ref any_allocator.
|
||||
/// The implementation is optimized to call fewer virtual functions.
|
||||
/// \ingroup memory_adapter
|
||||
template <typename T>
|
||||
WPI_ALIAS_TEMPLATE(any_std_allocator, std_allocator<T, any_allocator>);
|
||||
|
||||
/// \returns A new \ref any_std_allocator for a given type using a certain allocator object.
|
||||
/// \relates any_std_allocator
|
||||
template <typename T, class RawAllocator>
|
||||
any_std_allocator<T> make_any_std_allocator(RawAllocator&& allocator) noexcept
|
||||
{
|
||||
return {detail::forward<RawAllocator>(allocator)};
|
||||
}
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_STD_ALLOCATOR_HPP_INCLUDED
|
||||
@@ -1,334 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_TEMPORARY_ALLOCATOR_HPP_INCLUDED
|
||||
#define WPI_MEMORY_TEMPORARY_ALLOCATOR_HPP_INCLUDED
|
||||
|
||||
/// \file
|
||||
/// Class \ref wpi::memory::temporary_allocator and related functions.
|
||||
|
||||
#include "config.hpp"
|
||||
#include "memory_stack.hpp"
|
||||
|
||||
#if WPI_MEMORY_TEMPORARY_STACK_MODE >= 2
|
||||
#include <atomic>
|
||||
#endif
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
class temporary_allocator;
|
||||
class temporary_stack;
|
||||
|
||||
namespace detail
|
||||
{
|
||||
class temporary_block_allocator
|
||||
{
|
||||
public:
|
||||
explicit temporary_block_allocator(std::size_t block_size) noexcept;
|
||||
|
||||
memory_block allocate_block();
|
||||
|
||||
void deallocate_block(memory_block block);
|
||||
|
||||
std::size_t next_block_size() const noexcept
|
||||
{
|
||||
return block_size_;
|
||||
}
|
||||
|
||||
using growth_tracker = void (*)(std::size_t size);
|
||||
|
||||
growth_tracker set_growth_tracker(growth_tracker t) noexcept;
|
||||
|
||||
growth_tracker get_growth_tracker() noexcept;
|
||||
|
||||
private:
|
||||
growth_tracker tracker_;
|
||||
std::size_t block_size_;
|
||||
};
|
||||
|
||||
using temporary_stack_impl = memory_stack<temporary_block_allocator>;
|
||||
|
||||
class temporary_stack_list;
|
||||
|
||||
#if WPI_MEMORY_TEMPORARY_STACK_MODE >= 2
|
||||
class temporary_stack_list_node
|
||||
{
|
||||
public:
|
||||
// doesn't add into list
|
||||
temporary_stack_list_node() noexcept : in_use_(true) {}
|
||||
|
||||
temporary_stack_list_node(int) noexcept;
|
||||
|
||||
~temporary_stack_list_node() noexcept {}
|
||||
|
||||
private:
|
||||
temporary_stack_list_node* next_ = nullptr;
|
||||
std::atomic<bool> in_use_;
|
||||
|
||||
friend temporary_stack_list;
|
||||
};
|
||||
|
||||
static class temporary_allocator_dtor_t
|
||||
{
|
||||
public:
|
||||
temporary_allocator_dtor_t() noexcept;
|
||||
~temporary_allocator_dtor_t() noexcept;
|
||||
} temporary_allocator_dtor;
|
||||
#else
|
||||
class temporary_stack_list_node
|
||||
{
|
||||
protected:
|
||||
temporary_stack_list_node() noexcept {}
|
||||
|
||||
temporary_stack_list_node(int) noexcept {}
|
||||
|
||||
~temporary_stack_list_node() noexcept {}
|
||||
};
|
||||
#endif
|
||||
} // namespace detail
|
||||
|
||||
/// A wrapper around the \ref memory_stack that is used by the \ref temporary_allocator.
|
||||
/// There should be at least one per-thread.
|
||||
/// \ingroup memory_allocator
|
||||
class temporary_stack : WPI_EBO(detail::temporary_stack_list_node)
|
||||
{
|
||||
public:
|
||||
/// The type of the handler called when the internal \ref memory_stack grows.
|
||||
/// It gets the size of the new block that will be allocated.
|
||||
/// \requiredbe The handler shall log the growth, throw an exception or aborts the program.
|
||||
/// If this function does not return, the growth is prevented but the allocator unusable until memory is freed.
|
||||
/// \defaultbe The default handler does nothing.
|
||||
using growth_tracker = detail::temporary_block_allocator::growth_tracker;
|
||||
|
||||
/// \effects Sets \c h as the new \ref growth_tracker.
|
||||
/// A \c nullptr sets the default \ref growth_tracker.
|
||||
/// Each thread has its own, separate tracker.
|
||||
/// \returns The previous \ref growth_tracker. This is never \c nullptr.
|
||||
growth_tracker set_growth_tracker(growth_tracker t) noexcept
|
||||
{
|
||||
return stack_.get_allocator().set_growth_tracker(t);
|
||||
}
|
||||
|
||||
/// \returns The current \ref growth_tracker. This is never \c nullptr.
|
||||
growth_tracker get_growth_tracker() noexcept
|
||||
{
|
||||
return stack_.get_allocator().get_growth_tracker();
|
||||
}
|
||||
|
||||
/// \effects Creates it with a given initial size of the stack.
|
||||
/// It can grow if needed, although that is expensive.
|
||||
/// \requires `initial_size` must be greater than `0`.
|
||||
explicit temporary_stack(std::size_t initial_size) : stack_(initial_size), top_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
/// \returns `next_capacity()` of the internal `memory_stack`.
|
||||
std::size_t next_capacity() const noexcept
|
||||
{
|
||||
return stack_.next_capacity();
|
||||
}
|
||||
|
||||
private:
|
||||
temporary_stack(int i, std::size_t initial_size)
|
||||
: detail::temporary_stack_list_node(i), stack_(initial_size), top_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
using marker = detail::temporary_stack_impl::marker;
|
||||
|
||||
marker top() const noexcept
|
||||
{
|
||||
return stack_.top();
|
||||
}
|
||||
|
||||
void unwind(marker m) noexcept
|
||||
{
|
||||
stack_.unwind(m);
|
||||
}
|
||||
|
||||
detail::temporary_stack_impl stack_;
|
||||
temporary_allocator* top_;
|
||||
|
||||
#if !defined(DOXYGEN)
|
||||
friend temporary_allocator;
|
||||
friend memory_stack_raii_unwind<temporary_stack>;
|
||||
friend detail::temporary_stack_list;
|
||||
#endif
|
||||
};
|
||||
|
||||
/// Manually takes care of the lifetime of the per-thread \ref temporary_stack.
|
||||
/// The constructor will create it, if not already done, and the destructor will destroy it, if not already done.
|
||||
/// \note If there are multiple objects in a thread,
|
||||
/// this will lead to unnecessary construction and destruction of the stack.
|
||||
/// It is thus adviced to create one object on the top-level function of the thread, e.g. in `main()`.
|
||||
/// \note If `WPI_MEMORY_TEMPORARY_STACK_MODE == 2`, it is not necessary to use this class,
|
||||
/// the nifty counter will clean everything upon program termination.
|
||||
/// But it can still be used as an optimization if you have a thread that is terminated long before program exit.
|
||||
/// The automatic clean up will only occur much later.
|
||||
/// \note If `WPI_MEMORY_TEMPORARY_STACK_MODE == 0`, the use of this class has no effect,
|
||||
/// because the per-thread stack is disabled.
|
||||
/// \relatesalso temporary_stack
|
||||
class temporary_stack_initializer
|
||||
{
|
||||
public:
|
||||
static constexpr std::size_t default_stack_size = 4096u;
|
||||
|
||||
static const struct defer_create_t
|
||||
{
|
||||
defer_create_t() noexcept {}
|
||||
} defer_create;
|
||||
|
||||
/// \effects Does not create the per-thread stack.
|
||||
/// It will be created by the first call to \ref get_temporary_stack() in the current thread.
|
||||
/// \note If `WPI_MEMORY_TEMPORARY_STACK_MODE == 0`, this function has no effect.
|
||||
temporary_stack_initializer(defer_create_t) noexcept {}
|
||||
|
||||
/// \effects Creates the per-thread stack with the given default size if it wasn't already created.
|
||||
/// \requires `initial_size` must not be `0` if `WPI_MEMORY_TEMPORARY_STACK_MODE != 0`.
|
||||
/// \note If `WPI_MEMORY_TEMPORARY_STACK_MODE == 0`, this function will issue a warning in debug mode.
|
||||
/// This can be disabled by passing `0` as the initial size.
|
||||
temporary_stack_initializer(std::size_t initial_size = default_stack_size);
|
||||
|
||||
/// \effects Destroys the per-thread stack if it isn't already destroyed.
|
||||
~temporary_stack_initializer() noexcept;
|
||||
|
||||
temporary_stack_initializer(temporary_stack_initializer&&) = delete;
|
||||
temporary_stack_initializer& operator=(temporary_stack_initializer&&) = delete;
|
||||
};
|
||||
|
||||
/// \effects Creates the per-thread \ref temporary_stack with the given initial size,
|
||||
/// if it wasn't already created.
|
||||
/// \returns The per-thread \ref temporary_stack.
|
||||
/// \requires There must be a per-thread temporary stack (\ref WPI_MEMORY_TEMPORARY_STACK_MODE must not be equal to `0`).
|
||||
/// \note If \ref WPI_MEMORY_TEMPORARY_STACK_MODE is equal to `1`,
|
||||
/// this function can create the temporary stack.
|
||||
/// But if there is no \ref temporary_stack_initializer, it won't be destroyed.
|
||||
/// \relatesalso temporary_stack
|
||||
temporary_stack& get_temporary_stack(
|
||||
std::size_t initial_size = temporary_stack_initializer::default_stack_size);
|
||||
|
||||
/// A stateful RawAllocator that handles temporary allocations.
|
||||
/// It works similar to \c alloca() but uses a seperate \ref memory_stack for the allocations,
|
||||
/// instead of the actual program stack.
|
||||
/// This avoids the stack overflow error and is portable,
|
||||
/// with a similar speed.
|
||||
/// All allocations done in the scope of the allocator object are automatically freed when the object is destroyed.
|
||||
/// \ingroup memory_allocator
|
||||
class temporary_allocator
|
||||
{
|
||||
public:
|
||||
/// \effects Creates it by using the \ref get_temporary_stack() to get the temporary stack.
|
||||
/// \requires There must be a per-thread temporary stack (\ref WPI_MEMORY_TEMPORARY_STACK_MODE must not be equal to `0`).
|
||||
temporary_allocator();
|
||||
|
||||
/// \effects Creates it by giving it the \ref temporary_stack it uses for allocation.
|
||||
explicit temporary_allocator(temporary_stack& stack);
|
||||
|
||||
~temporary_allocator() noexcept;
|
||||
|
||||
temporary_allocator(temporary_allocator&&) = delete;
|
||||
temporary_allocator& operator=(temporary_allocator&&) = delete;
|
||||
|
||||
/// \effects Allocates memory from the internal \ref memory_stack by forwarding to it.
|
||||
/// \returns The result of \ref memory_stack::allocate().
|
||||
/// \requires `is_active()` must return `true`.
|
||||
void* allocate(std::size_t size, std::size_t alignment);
|
||||
|
||||
/// \returns Whether or not the allocator object is active.
|
||||
/// \note The active allocator object is the last object created for one stack.
|
||||
/// Moving changes the active allocator.
|
||||
bool is_active() const noexcept;
|
||||
|
||||
/// \effects Instructs it to release unnecessary memory after automatic unwinding occurs.
|
||||
/// This will effectively forward to \ref memory_stack::shrink_to_fit() of the internal stack.
|
||||
/// \note Like the use of the \ref temporary_stack_initializer this can be used as an optimization,
|
||||
/// to tell when the thread's \ref temporary_stack isn't needed anymore and can be destroyed.
|
||||
/// \note It doesn't call shrink to fit immediately, only in the destructor!
|
||||
void shrink_to_fit() noexcept;
|
||||
|
||||
/// \returns The internal stack the temporary allocator is using.
|
||||
/// \requires `is_active()` must return `true`.
|
||||
temporary_stack& get_stack() const noexcept
|
||||
{
|
||||
return unwind_.get_stack();
|
||||
}
|
||||
|
||||
private:
|
||||
memory_stack_raii_unwind<temporary_stack> unwind_;
|
||||
temporary_allocator* prev_;
|
||||
bool shrink_to_fit_;
|
||||
};
|
||||
|
||||
template <class Allocator>
|
||||
class allocator_traits;
|
||||
|
||||
/// Specialization of the \ref allocator_traits for \ref temporary_allocator classes.
|
||||
/// \note It is not allowed to mix calls through the specialization and through the member functions,
|
||||
/// i.e. \ref temporary_allocator::allocate() and this \c allocate_node().
|
||||
/// \ingroup memory_allocator
|
||||
template <>
|
||||
class allocator_traits<temporary_allocator>
|
||||
{
|
||||
public:
|
||||
using allocator_type = temporary_allocator;
|
||||
using is_stateful = std::true_type;
|
||||
|
||||
/// \returns The result of \ref temporary_allocator::allocate().
|
||||
static void* allocate_node(allocator_type& state, std::size_t size,
|
||||
std::size_t alignment)
|
||||
{
|
||||
detail::check_allocation_size<bad_node_size>(size,
|
||||
[&] { return max_node_size(state); },
|
||||
{WPI_MEMORY_LOG_PREFIX
|
||||
"::temporary_allocator",
|
||||
&state});
|
||||
return state.allocate(size, alignment);
|
||||
}
|
||||
|
||||
/// \returns The result of \ref temporary_allocator::allocate().
|
||||
static void* allocate_array(allocator_type& state, std::size_t count, std::size_t size,
|
||||
std::size_t alignment)
|
||||
{
|
||||
return allocate_node(state, count * size, alignment);
|
||||
}
|
||||
|
||||
/// @{
|
||||
/// \effects Does nothing besides bookmarking for leak checking, if that is enabled.
|
||||
/// Actual deallocation will be done automatically if the allocator object goes out of scope.
|
||||
static void deallocate_node(const allocator_type&, void*, std::size_t,
|
||||
std::size_t) noexcept
|
||||
{
|
||||
}
|
||||
|
||||
static void deallocate_array(const allocator_type&, void*, std::size_t, std::size_t,
|
||||
std::size_t) noexcept
|
||||
{
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \returns The maximum size which is \ref memory_stack::next_capacity() of the internal stack.
|
||||
static std::size_t max_node_size(const allocator_type& state) noexcept
|
||||
{
|
||||
return state.get_stack().next_capacity();
|
||||
}
|
||||
|
||||
static std::size_t max_array_size(const allocator_type& state) noexcept
|
||||
{
|
||||
return max_node_size(state);
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// \returns The maximum possible value since there is no alignment restriction
|
||||
/// (except indirectly through \ref memory_stack::next_capacity()).
|
||||
static std::size_t max_alignment(const allocator_type&) noexcept
|
||||
{
|
||||
return std::size_t(-1);
|
||||
}
|
||||
};
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_TEMPORARY_ALLOCATOR_HPP_INCLUDED
|
||||
@@ -1,154 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_THREADING_HPP_INCLUDED
|
||||
#define WPI_MEMORY_THREADING_HPP_INCLUDED
|
||||
|
||||
/// \file
|
||||
/// The mutex types.
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "allocator_traits.hpp"
|
||||
#include "config.hpp"
|
||||
|
||||
#if WPI_HOSTED_IMPLEMENTATION
|
||||
#include <mutex>
|
||||
#endif
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
/// A dummy \c Mutex class that does not lock anything.
|
||||
/// It is a valid \c Mutex and can be used to disable locking anywhere a \c Mutex is requested.
|
||||
/// \ingroup memory_core
|
||||
struct no_mutex
|
||||
{
|
||||
void lock() noexcept {}
|
||||
|
||||
bool try_lock() noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void unlock() noexcept {}
|
||||
};
|
||||
|
||||
/// Specifies whether or not a RawAllocator is thread safe as-is.
|
||||
/// This allows to use \ref no_mutex as an optimization.
|
||||
/// Note that stateless allocators are implictly thread-safe.
|
||||
/// Specialize it only for your own stateful allocators.
|
||||
/// \ingroup memory_core
|
||||
template <class RawAllocator>
|
||||
struct is_thread_safe_allocator
|
||||
: std::integral_constant<bool, !allocator_traits<RawAllocator>::is_stateful::value>
|
||||
{
|
||||
};
|
||||
|
||||
namespace detail
|
||||
{
|
||||
// selects a mutex for an Allocator
|
||||
// stateless allocators don't need locking
|
||||
template <class RawAllocator, class Mutex>
|
||||
using mutex_for =
|
||||
typename std::conditional<is_thread_safe_allocator<RawAllocator>::value, no_mutex,
|
||||
Mutex>::type;
|
||||
|
||||
// storage for mutexes to use EBO
|
||||
// it provides const lock/unlock function, inherit from it
|
||||
template <class Mutex>
|
||||
class mutex_storage
|
||||
{
|
||||
public:
|
||||
mutex_storage() noexcept = default;
|
||||
mutex_storage(const mutex_storage&) noexcept {}
|
||||
|
||||
mutex_storage& operator=(const mutex_storage&) noexcept
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
void lock() const
|
||||
{
|
||||
mutex_.lock();
|
||||
}
|
||||
|
||||
void unlock() const noexcept
|
||||
{
|
||||
mutex_.unlock();
|
||||
}
|
||||
|
||||
protected:
|
||||
~mutex_storage() noexcept = default;
|
||||
|
||||
private:
|
||||
mutable Mutex mutex_;
|
||||
};
|
||||
|
||||
template <>
|
||||
class mutex_storage<no_mutex>
|
||||
{
|
||||
public:
|
||||
mutex_storage() noexcept = default;
|
||||
|
||||
void lock() const noexcept {}
|
||||
void unlock() const noexcept {}
|
||||
|
||||
protected:
|
||||
~mutex_storage() noexcept = default;
|
||||
};
|
||||
|
||||
// non changeable pointer to an Allocator that keeps a lock
|
||||
// I don't think EBO is necessary here...
|
||||
template <class Alloc, class Mutex>
|
||||
class locked_allocator
|
||||
{
|
||||
public:
|
||||
locked_allocator(Alloc& alloc, Mutex& m) noexcept : mutex_(&m), alloc_(&alloc)
|
||||
{
|
||||
mutex_->lock();
|
||||
}
|
||||
|
||||
locked_allocator(locked_allocator&& other) noexcept
|
||||
: mutex_(other.mutex_), alloc_(other.alloc_)
|
||||
{
|
||||
other.mutex_ = nullptr;
|
||||
other.alloc_ = nullptr;
|
||||
}
|
||||
|
||||
~locked_allocator() noexcept
|
||||
{
|
||||
if (mutex_)
|
||||
mutex_->unlock();
|
||||
}
|
||||
|
||||
locked_allocator& operator=(locked_allocator&& other) noexcept = delete;
|
||||
|
||||
Alloc& operator*() const noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(alloc_);
|
||||
return *alloc_;
|
||||
}
|
||||
|
||||
Alloc* operator->() const noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(alloc_);
|
||||
return alloc_;
|
||||
}
|
||||
|
||||
private:
|
||||
Mutex* mutex_; // don't use unqiue_lock to avoid dependency
|
||||
Alloc* alloc_;
|
||||
};
|
||||
|
||||
template <class Alloc, class Mutex>
|
||||
locked_allocator<Alloc, Mutex> lock_allocator(Alloc& a, Mutex& m)
|
||||
{
|
||||
return {a, m};
|
||||
}
|
||||
} // namespace detail
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_THREADING_HPP_INCLUDED
|
||||
@@ -1,429 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_TRACKING_HPP_INCLUDED
|
||||
#define WPI_MEMORY_TRACKING_HPP_INCLUDED
|
||||
|
||||
/// \file
|
||||
/// Class \ref wpi::memory::tracked_allocator and related classes and functions.
|
||||
|
||||
#include "detail/utility.hpp"
|
||||
#include "allocator_traits.hpp"
|
||||
#include "memory_arena.hpp"
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
template <class Allocator, class Tracker>
|
||||
auto set_tracker(int, Allocator& allocator, Tracker* tracker) noexcept
|
||||
-> decltype(allocator.get_allocator().set_tracker(tracker))
|
||||
{
|
||||
return allocator.get_allocator().set_tracker(tracker);
|
||||
}
|
||||
template <class Allocator, class Tracker>
|
||||
void set_tracker(short, Allocator&, Tracker*) noexcept
|
||||
{
|
||||
}
|
||||
|
||||
// used with deeply_tracked_allocator
|
||||
template <class Tracker, class BlockAllocator>
|
||||
class deeply_tracked_block_allocator : WPI_EBO(BlockAllocator)
|
||||
{
|
||||
public:
|
||||
template <typename... Args>
|
||||
deeply_tracked_block_allocator(std::size_t block_size, Args&&... args)
|
||||
: BlockAllocator(block_size, detail::forward<Args>(args)...), tracker_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
memory_block allocate_block()
|
||||
{
|
||||
auto block = BlockAllocator::allocate_block();
|
||||
if (tracker_) // on first call tracker_ is nullptr
|
||||
tracker_->on_allocator_growth(block.memory, block.size);
|
||||
return block;
|
||||
}
|
||||
|
||||
void deallocate_block(memory_block block) noexcept
|
||||
{
|
||||
if (tracker_) // on last call tracker_ is nullptr again
|
||||
tracker_->on_allocator_shrinking(block.memory, block.size);
|
||||
BlockAllocator::deallocate_block(block);
|
||||
}
|
||||
|
||||
std::size_t next_block_size() const noexcept
|
||||
{
|
||||
return BlockAllocator::next_block_size();
|
||||
}
|
||||
|
||||
void set_tracker(Tracker* tracker) noexcept
|
||||
{
|
||||
tracker_ = tracker;
|
||||
}
|
||||
|
||||
private:
|
||||
Tracker* tracker_;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
/// A BlockAllocator adapter that tracks another allocator using a tracker.
|
||||
/// It wraps another BlockAllocator and calls the tracker function before forwarding to it.
|
||||
/// The class can then be used anywhere a BlockAllocator is required and the memory usage will be tracked.<br>
|
||||
/// It will only call the <tt>on_allocator_growth()</tt> and <tt>on_allocator_shrinking()</tt> tracking functions,
|
||||
/// since a BlockAllocator is normally used inside higher allocators only.
|
||||
/// \ingroup memory_adapter
|
||||
template <class Tracker, class BlockOrRawAllocator>
|
||||
class tracked_block_allocator
|
||||
: WPI_EBO(Tracker, make_block_allocator_t<BlockOrRawAllocator>)
|
||||
{
|
||||
public:
|
||||
using allocator_type = make_block_allocator_t<BlockOrRawAllocator>;
|
||||
using tracker = Tracker;
|
||||
|
||||
/// @{
|
||||
/// \effects Creates it by giving it a tracker and the tracked RawAllocator.
|
||||
/// It will embed both objects.
|
||||
explicit tracked_block_allocator(tracker t = {}) noexcept : tracker(detail::move(t)) {}
|
||||
|
||||
tracked_block_allocator(tracker t, allocator_type&& alloc) noexcept
|
||||
: tracker(detail::move(t)), allocator_type(detail::move(alloc))
|
||||
{
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// \effects Creates it in the form required by the concept.
|
||||
/// The allocator will be constructed using \c block_size and \c args.
|
||||
template <typename... Args>
|
||||
tracked_block_allocator(std::size_t block_size, tracker t, Args&&... args)
|
||||
: tracker(detail::move(t)), allocator_type(block_size, detail::forward<Args>(args)...)
|
||||
{
|
||||
}
|
||||
|
||||
/// \effects Calls <tt>Tracker::on_allocator_growth()</tt> after forwarding to the allocator.
|
||||
/// \returns The block as the returned by the allocator.
|
||||
memory_block allocate_block()
|
||||
{
|
||||
auto block = allocator_type::allocate_block();
|
||||
this->on_allocator_growth(block.memory, block.size);
|
||||
return block;
|
||||
}
|
||||
|
||||
/// \effects Calls <tt>Tracker::on_allocator_shrinking()</tt> and forwards to the allocator.
|
||||
void deallocate_block(memory_block block) noexcept
|
||||
{
|
||||
this->on_allocator_shrinking(block.memory, block.size);
|
||||
allocator_type::deallocate_block(block);
|
||||
}
|
||||
|
||||
/// \returns The next block size as returned by the allocator.
|
||||
std::size_t next_block_size() const noexcept
|
||||
{
|
||||
return allocator_type::next_block_size();
|
||||
}
|
||||
|
||||
/// @{
|
||||
/// \returns A (const) reference to the used allocator.
|
||||
allocator_type& get_allocator() noexcept
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
const allocator_type& get_allocator() const noexcept
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \returns A (const) reference to the tracker.
|
||||
tracker& get_tracker() noexcept
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
const tracker& get_tracker() const noexcept
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
/// @}
|
||||
};
|
||||
|
||||
/// Similar to \ref tracked_block_allocator, but shares the tracker with the higher level allocator.
|
||||
/// This allows tracking both (de-)allocations and growth with one tracker.
|
||||
/// \note Due to implementation reasons, it cannot track growth and shrinking in the constructor/destructor of the higher level allocator.
|
||||
/// \ingroup memory_adapter
|
||||
template <class Tracker, class BlockOrRawAllocator>
|
||||
using deeply_tracked_block_allocator = WPI_IMPL_DEFINED(
|
||||
detail::deeply_tracked_block_allocator<Tracker,
|
||||
make_block_allocator_t<BlockOrRawAllocator>>);
|
||||
|
||||
/// A RawAllocator adapter that tracks another allocator using a tracker.
|
||||
/// It wraps another RawAllocator and calls the tracker function before forwarding to it.
|
||||
/// The class can then be used anywhere a RawAllocator is required and the memory usage will be tracked.<br>
|
||||
/// If the RawAllocator uses \ref deeply_tracked_block_allocator as BlockAllocator,
|
||||
/// it will also track growth and shrinking of the allocator.
|
||||
/// \ingroup memory_adapter
|
||||
template <class Tracker, class RawAllocator>
|
||||
class tracked_allocator
|
||||
: WPI_EBO(Tracker, allocator_traits<RawAllocator>::allocator_type)
|
||||
{
|
||||
using traits = allocator_traits<RawAllocator>;
|
||||
using composable_traits = composable_allocator_traits<RawAllocator>;
|
||||
|
||||
public:
|
||||
using allocator_type = typename allocator_traits<RawAllocator>::allocator_type;
|
||||
using tracker = Tracker;
|
||||
|
||||
using is_stateful = std::integral_constant<bool, traits::is_stateful::value
|
||||
|| !std::is_empty<Tracker>::value>;
|
||||
|
||||
/// @{
|
||||
/// \effects Creates it by giving it a tracker and the tracked RawAllocator.
|
||||
/// It will embed both objects.
|
||||
/// \note This will never call the <tt>Tracker::on_allocator_growth()</tt> function.
|
||||
explicit tracked_allocator(tracker t = {}) noexcept
|
||||
: tracked_allocator(detail::move(t), allocator_type{})
|
||||
{
|
||||
}
|
||||
|
||||
tracked_allocator(tracker t, allocator_type&& allocator) noexcept
|
||||
: tracker(detail::move(t)), allocator_type(detail::move(allocator))
|
||||
{
|
||||
detail::set_tracker(0, get_allocator(), &get_tracker());
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// \effects Destroys both tracker and allocator.
|
||||
/// \note This will never call the <tt>Tracker::on_allocator_shrinking()</tt> function.
|
||||
~tracked_allocator() noexcept
|
||||
{
|
||||
detail::set_tracker(0, get_allocator(), static_cast<tracker*>(nullptr));
|
||||
}
|
||||
|
||||
/// @{
|
||||
/// \effects Moving moves both the tracker and the allocator.
|
||||
tracked_allocator(tracked_allocator&& other) noexcept
|
||||
: tracker(detail::move(other)), allocator_type(detail::move(other))
|
||||
{
|
||||
detail::set_tracker(0, get_allocator(), &get_tracker());
|
||||
}
|
||||
|
||||
tracked_allocator& operator=(tracked_allocator&& other) noexcept
|
||||
{
|
||||
tracker:: operator=(detail::move(other));
|
||||
allocator_type::operator=(detail::move(other));
|
||||
detail::set_tracker(0, get_allocator(), &get_tracker());
|
||||
return *this;
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// \effects Calls <tt>Tracker::on_node_allocation()</tt> and forwards to the allocator.
|
||||
/// If a growth occurs and the allocator is deeply tracked, also calls <tt>Tracker::on_allocator_growth()</tt>.
|
||||
/// \returns The result of <tt>allocate_node()</tt>
|
||||
void* allocate_node(std::size_t size, std::size_t alignment)
|
||||
{
|
||||
auto mem = traits::allocate_node(get_allocator(), size, alignment);
|
||||
this->on_node_allocation(mem, size, alignment);
|
||||
return mem;
|
||||
}
|
||||
|
||||
/// \effects Calls the composable node allocation function.
|
||||
/// If allocation was successful, also calls `Tracker::on_node_allocation()`.
|
||||
/// \returns The result of `try_allocate_node()`.
|
||||
void* try_allocate_node(std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
auto mem = composable_traits::try_allocate_node(get_allocator(), size, alignment);
|
||||
if (mem)
|
||||
this->on_node_allocation(mem, size, alignment);
|
||||
return mem;
|
||||
}
|
||||
|
||||
/// \effects Calls <tt>Tracker::on_array_allocation()</tt> and forwards to the allocator.
|
||||
/// If a growth occurs and the allocator is deeply tracked, also calls <tt>Tracker::on_allocator_growth()</tt>.
|
||||
/// \returns The result of <tt>allocate_array()</tt>
|
||||
void* allocate_array(std::size_t count, std::size_t size, std::size_t alignment)
|
||||
{
|
||||
auto mem = traits::allocate_array(get_allocator(), count, size, alignment);
|
||||
this->on_array_allocation(mem, count, size, alignment);
|
||||
return mem;
|
||||
}
|
||||
|
||||
/// \effects Calls the composable array allocation function.
|
||||
/// If allocation was succesful, also calls `Tracker::on_array_allocation()`.
|
||||
/// \returns The result of `try_allocate_array()`.
|
||||
void* try_allocate_array(std::size_t count, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
{
|
||||
auto mem =
|
||||
composable_traits::try_allocate_array(get_allocator(), count, size, alignment);
|
||||
if (mem)
|
||||
this->on_array_allocation(mem, count, size, alignment);
|
||||
return mem;
|
||||
}
|
||||
|
||||
/// \effects Calls <tt>Tracker::on_node_deallocation()</tt> and forwards to the allocator's <tt>deallocate_node()</tt>.
|
||||
/// If shrinking occurs and the allocator is deeply tracked, also calls <tt>Tracker::on_allocator_shrinking()</tt>.
|
||||
void deallocate_node(void* ptr, std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
this->on_node_deallocation(ptr, size, alignment);
|
||||
traits::deallocate_node(get_allocator(), ptr, size, alignment);
|
||||
}
|
||||
|
||||
/// \effects Calls the composable node deallocation function.
|
||||
/// If it was succesful, also calls `Tracker::on_node_deallocation()`.
|
||||
/// \returns The result of `try_deallocate_node()`.
|
||||
bool try_deallocate_node(void* ptr, std::size_t size, std::size_t alignment) noexcept
|
||||
{
|
||||
auto res =
|
||||
composable_traits::try_deallocate_node(get_allocator(), ptr, size, alignment);
|
||||
if (res)
|
||||
this->on_node_deallocation(ptr, size, alignment);
|
||||
return res;
|
||||
}
|
||||
|
||||
/// \effects Calls <tt>Tracker::on_array_deallocation()</tt> and forwards to the allocator's <tt>deallocate_array()</tt>.
|
||||
/// If shrinking occurs and the allocator is deeply tracked, also calls <tt>Tracker::on_allocator_shrinking()</tt>.
|
||||
void deallocate_array(void* ptr, std::size_t count, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
{
|
||||
this->on_array_deallocation(ptr, count, size, alignment);
|
||||
traits::deallocate_array(get_allocator(), ptr, count, size, alignment);
|
||||
}
|
||||
|
||||
/// \effects Calls the composable array deallocation function.
|
||||
/// If it was succesful, also calls `Tracker::on_array_deallocation()`.
|
||||
/// \returns The result of `try_deallocate_array()`.
|
||||
bool try_deallocate_array(void* ptr, std::size_t count, std::size_t size,
|
||||
std::size_t alignment) noexcept
|
||||
{
|
||||
auto res = composable_traits::try_deallocate_array(ptr, count, size, alignment);
|
||||
if (res)
|
||||
this->on_array_deallocation(ptr, count, size, alignment);
|
||||
return res;
|
||||
}
|
||||
|
||||
/// @{
|
||||
/// \returns The result of the corresponding function on the wrapped allocator.
|
||||
std::size_t max_node_size() const
|
||||
{
|
||||
return traits::max_node_size(get_allocator());
|
||||
}
|
||||
|
||||
std::size_t max_array_size() const
|
||||
{
|
||||
return traits::max_array_size(get_allocator());
|
||||
}
|
||||
|
||||
std::size_t max_alignment() const
|
||||
{
|
||||
return traits::max_alignment(get_allocator());
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \returns A (\c const) reference to the wrapped allocator.
|
||||
allocator_type& get_allocator() noexcept
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
const allocator_type& get_allocator() const noexcept
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// \returns A (\c const) reference to the tracker.
|
||||
tracker& get_tracker() noexcept
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
const tracker& get_tracker() const noexcept
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
/// @}
|
||||
};
|
||||
|
||||
/// \effects Takes a RawAllocator and wraps it with a tracker.
|
||||
/// \returns A \ref tracked_allocator with the corresponding parameters forwarded to the constructor.
|
||||
/// \relates tracked_allocator
|
||||
template <class Tracker, class RawAllocator>
|
||||
auto make_tracked_allocator(Tracker t, RawAllocator&& alloc)
|
||||
-> tracked_allocator<Tracker, typename std::decay<RawAllocator>::type>
|
||||
{
|
||||
return tracked_allocator<Tracker, typename std::decay<RawAllocator>::type>{detail::move(
|
||||
t),
|
||||
detail::move(
|
||||
alloc)};
|
||||
}
|
||||
|
||||
namespace detail
|
||||
{
|
||||
template <typename T, bool Block>
|
||||
struct is_block_or_raw_allocator_impl : std::true_type
|
||||
{
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct is_block_or_raw_allocator_impl<T, false> : memory::is_raw_allocator<T>
|
||||
{
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct is_block_or_raw_allocator
|
||||
: is_block_or_raw_allocator_impl<T, memory::is_block_allocator<T>::value>
|
||||
{
|
||||
};
|
||||
|
||||
template <class RawAllocator, class BlockAllocator>
|
||||
struct rebind_block_allocator;
|
||||
|
||||
template <template <typename...> class RawAllocator, typename... Args,
|
||||
class OtherBlockAllocator>
|
||||
struct rebind_block_allocator<RawAllocator<Args...>, OtherBlockAllocator>
|
||||
{
|
||||
using type =
|
||||
RawAllocator<typename std::conditional<is_block_or_raw_allocator<Args>::value,
|
||||
OtherBlockAllocator, Args>::type...>;
|
||||
};
|
||||
|
||||
template <class Tracker, class RawAllocator>
|
||||
using deeply_tracked_block_allocator_for =
|
||||
memory::deeply_tracked_block_allocator<Tracker,
|
||||
typename RawAllocator::allocator_type>;
|
||||
|
||||
template <class Tracker, class RawAllocator>
|
||||
using rebound_allocator = typename rebind_block_allocator<
|
||||
RawAllocator, deeply_tracked_block_allocator_for<Tracker, RawAllocator>>::type;
|
||||
} // namespace detail
|
||||
|
||||
/// A \ref tracked_allocator that has rebound any BlockAllocator to the corresponding \ref deeply_tracked_block_allocator.
|
||||
/// This makes it a deeply tracked allocator.<br>
|
||||
/// It replaces each template argument of the given RawAllocator for which \ref is_block_allocator or \ref is_raw_allocator is \c true with a \ref deeply_tracked_block_allocator.
|
||||
/// \ingroup memory_adapter
|
||||
template <class Tracker, class RawAllocator>
|
||||
WPI_ALIAS_TEMPLATE(
|
||||
deeply_tracked_allocator,
|
||||
tracked_allocator<Tracker, detail::rebound_allocator<Tracker, RawAllocator>>);
|
||||
|
||||
/// \effects Takes a RawAllocator and deeply wraps it with a tracker.
|
||||
/// \returns A \ref deeply_tracked_allocator with the corresponding parameters forwarded to the constructor.
|
||||
/// \relates deeply_tracked_allocator
|
||||
template <class RawAllocator, class Tracker, typename... Args>
|
||||
auto make_deeply_tracked_allocator(Tracker t, Args&&... args)
|
||||
-> deeply_tracked_allocator<Tracker, RawAllocator>
|
||||
{
|
||||
return deeply_tracked_allocator<Tracker, RawAllocator>(detail::move(t),
|
||||
{detail::forward<Args>(
|
||||
args)...});
|
||||
}
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_TRACKING_HPP_INCLUDED
|
||||
@@ -1,201 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_VIRTUAL_MEMORY_HPP_INCLUDED
|
||||
#define WPI_MEMORY_VIRTUAL_MEMORY_HPP_INCLUDED
|
||||
|
||||
/// \file
|
||||
/// Virtual memory api and (low-level) allocator classes.
|
||||
|
||||
#include <cstddef>
|
||||
#include <type_traits>
|
||||
|
||||
#include "detail/debug_helpers.hpp"
|
||||
#include "detail/utility.hpp"
|
||||
#include "config.hpp"
|
||||
|
||||
#if WPI_MEMORY_EXTERN_TEMPLATE
|
||||
#include "allocator_traits.hpp"
|
||||
#endif
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
struct virtual_memory_allocator_leak_handler
|
||||
{
|
||||
void operator()(std::ptrdiff_t amount);
|
||||
};
|
||||
|
||||
WPI_MEMORY_GLOBAL_LEAK_CHECKER(virtual_memory_allocator_leak_handler,
|
||||
virtual_memory_allocator_leak_checker)
|
||||
} // namespace detail
|
||||
|
||||
/// The page size of the virtual memory.
|
||||
/// All virtual memory allocations must be multiple of this size.
|
||||
/// It is usually 4KiB.
|
||||
/// \ingroup memory_allocator
|
||||
/// \deprecated use \ref get_virtual_memory_page_size instead.
|
||||
extern const std::size_t virtual_memory_page_size;
|
||||
|
||||
/// \returns the page size of the virtual memory.
|
||||
/// All virtual memory allocations must be multiple of this size.
|
||||
/// It is usually 4KiB.
|
||||
/// \ingroup memory_allocator
|
||||
std::size_t get_virtual_memory_page_size() noexcept;
|
||||
|
||||
/// Reserves virtual memory.
|
||||
/// \effects Reserves the given number of pages.
|
||||
/// Each page is \ref virtual_memory_page_size big.
|
||||
/// \returns The address of the first reserved page,
|
||||
/// or \c nullptr in case of error.
|
||||
/// \note The memory may not be used, it must first be commited.
|
||||
/// \ingroup memory_allocator
|
||||
void* virtual_memory_reserve(std::size_t no_pages) noexcept;
|
||||
|
||||
/// Releases reserved virtual memory.
|
||||
/// \effects Returns previously reserved pages to the system.
|
||||
/// \requires \c pages must come from a previous call to \ref virtual_memory_reserve with the same \c calc_no_pages,
|
||||
/// it must not be \c nullptr.
|
||||
/// \ingroup memory_allocator
|
||||
void virtual_memory_release(void* pages, std::size_t no_pages) noexcept;
|
||||
|
||||
/// Commits reserved virtual memory.
|
||||
/// \effects Marks \c calc_no_pages pages starting at the given address available for use.
|
||||
/// \returns The beginning of the committed area, i.e. \c memory, or \c nullptr in case of error.
|
||||
/// \requires The memory must be previously reserved.
|
||||
/// \ingroup memory_allocator
|
||||
void* virtual_memory_commit(void* memory, std::size_t no_pages) noexcept;
|
||||
|
||||
/// Decommits commited virtual memory.
|
||||
/// \effects Puts commited memory back in the reserved state.
|
||||
/// \requires \c memory must come from a previous call to \ref virtual_memory_commit with the same \c calc_no_pages
|
||||
/// it must not be \c nullptr.
|
||||
/// \ingroup memory_allocator
|
||||
void virtual_memory_decommit(void* memory, std::size_t no_pages) noexcept;
|
||||
|
||||
/// A stateless RawAllocator that allocates memory using the virtual memory allocation functions.
|
||||
/// It does not prereserve any memory and will always reserve and commit combined.
|
||||
/// \ingroup memory_allocator
|
||||
class virtual_memory_allocator
|
||||
: WPI_EBO(detail::global_leak_checker<detail::virtual_memory_allocator_leak_handler>)
|
||||
{
|
||||
public:
|
||||
using is_stateful = std::false_type;
|
||||
|
||||
virtual_memory_allocator() noexcept = default;
|
||||
virtual_memory_allocator(virtual_memory_allocator&&) noexcept {}
|
||||
~virtual_memory_allocator() noexcept = default;
|
||||
|
||||
virtual_memory_allocator& operator=(virtual_memory_allocator&&) noexcept
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// \effects A RawAllocator allocation function.
|
||||
/// It uses \ref virtual_memory_reserve followed by \ref virtual_memory_commit for the allocation.
|
||||
/// The number of pages allocated will be the minimum to hold \c size continuous bytes,
|
||||
/// i.e. \c size will be rounded up to the next multiple.
|
||||
/// If debug fences are activated, one additional page before and after the memory will be allocated.
|
||||
/// \returns A pointer to a node, it will never be \c nullptr.
|
||||
/// It will always be aligned on a fence boundary, regardless of the alignment parameter.
|
||||
/// \throws An exception of type \ref out_of_memory or whatever is thrown by its handler if the allocation fails.
|
||||
void* allocate_node(std::size_t size, std::size_t alignment);
|
||||
|
||||
/// \effects A RawAllocator deallocation function.
|
||||
/// It calls \ref virtual_memory_decommit followed by \ref virtual_memory_release for the deallocation.
|
||||
void deallocate_node(void* node, std::size_t size, std::size_t alignment) noexcept;
|
||||
|
||||
/// \returns The maximum node size by returning the maximum value.
|
||||
std::size_t max_node_size() const noexcept;
|
||||
|
||||
/// \returns The maximum alignment which is the same as the \ref virtual_memory_page_size.
|
||||
std::size_t max_alignment() const noexcept;
|
||||
};
|
||||
|
||||
#if WPI_MEMORY_EXTERN_TEMPLATE
|
||||
extern template class allocator_traits<virtual_memory_allocator>;
|
||||
#endif
|
||||
|
||||
struct memory_block;
|
||||
struct allocator_info;
|
||||
|
||||
/// A BlockAllocator that reserves virtual memory and commits it part by part.
|
||||
/// It is similar to \ref memory_stack but does not support growing and uses virtual memory,
|
||||
/// also meant for big blocks not small allocations.
|
||||
/// \ingroup memory_allocator
|
||||
class virtual_block_allocator
|
||||
{
|
||||
public:
|
||||
/// \effects Creates it giving it the block size and the total number of blocks it can allocate.
|
||||
/// It reserves enough virtual memory for <tt>block_size * no_blocks</tt>.
|
||||
/// \requires \c block_size must be non-zero and a multiple of the \ref virtual_memory_page_size.
|
||||
/// \c no_blocks must be bigger than \c 1.
|
||||
/// \throws \ref out_of_memory if it cannot reserve the virtual memory.
|
||||
explicit virtual_block_allocator(std::size_t block_size, std::size_t no_blocks);
|
||||
|
||||
/// \effects Releases the reserved virtual memory.
|
||||
~virtual_block_allocator() noexcept;
|
||||
|
||||
/// @{
|
||||
/// \effects Moves the block allocator, it transfers ownership over the reserved area.
|
||||
/// This does not invalidate any memory blocks.
|
||||
virtual_block_allocator(virtual_block_allocator&& other) noexcept
|
||||
: cur_(other.cur_), end_(other.end_), block_size_(other.block_size_)
|
||||
{
|
||||
other.cur_ = other.end_ = nullptr;
|
||||
other.block_size_ = 0;
|
||||
}
|
||||
|
||||
virtual_block_allocator& operator=(virtual_block_allocator&& other) noexcept
|
||||
{
|
||||
virtual_block_allocator tmp(detail::move(other));
|
||||
swap(*this, tmp);
|
||||
return *this;
|
||||
}
|
||||
/// @}
|
||||
|
||||
/// \effects Swaps the ownership over the reserved memory.
|
||||
/// This does not invalidate any memory blocks.
|
||||
friend void swap(virtual_block_allocator& a, virtual_block_allocator& b) noexcept
|
||||
{
|
||||
detail::adl_swap(a.cur_, b.cur_);
|
||||
detail::adl_swap(a.end_, b.end_);
|
||||
detail::adl_swap(a.block_size_, b.block_size_);
|
||||
}
|
||||
|
||||
/// \effects Allocates a new memory block by committing the next \ref next_block_size() number of bytes.
|
||||
/// \returns The \ref memory_block committed.
|
||||
/// \throws \ref out_of_memory if it cannot commit the memory or the \ref capacity_left() is exhausted.
|
||||
memory_block allocate_block();
|
||||
|
||||
/// \effects Deallocates the last allocated memory block by decommitting it.
|
||||
/// This block will be returned again on the next call to \ref allocate_block().
|
||||
/// \requires \c block must be the current top block of the memory,
|
||||
/// this is guaranteed by \ref memory_arena.
|
||||
void deallocate_block(memory_block block) noexcept;
|
||||
|
||||
/// \returns The next block size, this is the block size of the constructor.
|
||||
std::size_t next_block_size() const noexcept
|
||||
{
|
||||
return block_size_;
|
||||
}
|
||||
|
||||
/// \returns The number of blocks that can be committed until it runs out of memory.
|
||||
std::size_t capacity_left() const noexcept
|
||||
{
|
||||
return static_cast<std::size_t>(end_ - cur_) / block_size_;
|
||||
}
|
||||
|
||||
private:
|
||||
allocator_info info() noexcept;
|
||||
|
||||
char * cur_, *end_;
|
||||
std::size_t block_size_;
|
||||
};
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif //WPI_MEMORY_VIRTUAL_MEMORY_HPP_INCLUDED
|
||||
@@ -1,108 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include "wpi/memory/debugging.hpp"
|
||||
|
||||
#if WPI_HOSTED_IMPLEMENTATION
|
||||
#include <cstdio>
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "wpi/memory/error.hpp"
|
||||
|
||||
using namespace wpi::memory;
|
||||
|
||||
namespace
|
||||
{
|
||||
void default_leak_handler(const allocator_info& info, std::ptrdiff_t amount) noexcept
|
||||
{
|
||||
#if WPI_HOSTED_IMPLEMENTATION
|
||||
if (amount > 0)
|
||||
std::fprintf(stderr, "[%s] Allocator %s (at %p) leaked %zu bytes.\n",
|
||||
WPI_MEMORY_LOG_PREFIX, info.name, info.allocator,
|
||||
std::size_t(amount));
|
||||
else
|
||||
std::fprintf(stderr,
|
||||
"[%s] Allocator %s (at %p) has deallocated %zu bytes more than "
|
||||
"ever allocated "
|
||||
"(it's amazing you're able to see this message!).\n",
|
||||
WPI_MEMORY_LOG_PREFIX, info.name, info.allocator,
|
||||
std::size_t(-amount));
|
||||
#else
|
||||
(void)info;
|
||||
(void)amount;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::atomic<leak_handler> leak_h(default_leak_handler);
|
||||
} // namespace
|
||||
|
||||
leak_handler wpi::memory::set_leak_handler(leak_handler h)
|
||||
{
|
||||
return leak_h.exchange(h ? h : default_leak_handler);
|
||||
}
|
||||
|
||||
leak_handler wpi::memory::get_leak_handler()
|
||||
{
|
||||
return leak_h;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
void default_invalid_ptr_handler(const allocator_info& info, const void* ptr) noexcept
|
||||
{
|
||||
#if WPI_HOSTED_IMPLEMENTATION
|
||||
std::fprintf(stderr,
|
||||
"[%s] Deallocation function of allocator %s (at %p) received invalid "
|
||||
"pointer %p\n",
|
||||
WPI_MEMORY_LOG_PREFIX, info.name, info.allocator, ptr);
|
||||
#endif
|
||||
(void)info;
|
||||
(void)ptr;
|
||||
std::abort();
|
||||
}
|
||||
|
||||
std::atomic<invalid_pointer_handler> invalid_ptr_h(default_invalid_ptr_handler);
|
||||
} // namespace
|
||||
|
||||
invalid_pointer_handler wpi::memory::set_invalid_pointer_handler(invalid_pointer_handler h)
|
||||
{
|
||||
return invalid_ptr_h.exchange(h ? h : default_invalid_ptr_handler);
|
||||
}
|
||||
|
||||
invalid_pointer_handler wpi::memory::get_invalid_pointer_handler()
|
||||
{
|
||||
return invalid_ptr_h;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
void default_buffer_overflow_handler(const void* memory, std::size_t node_size,
|
||||
const void* ptr) noexcept
|
||||
{
|
||||
#if WPI_HOSTED_IMPLEMENTATION
|
||||
std::fprintf(stderr,
|
||||
"[%s] Buffer overflow at address %p detected, corresponding memory "
|
||||
"block %p has only size %zu.\n",
|
||||
WPI_MEMORY_LOG_PREFIX, ptr, memory, node_size);
|
||||
#endif
|
||||
(void)memory;
|
||||
(void)node_size;
|
||||
(void)ptr;
|
||||
std::abort();
|
||||
}
|
||||
|
||||
std::atomic<buffer_overflow_handler> buffer_overflow_h(default_buffer_overflow_handler);
|
||||
} // namespace
|
||||
|
||||
buffer_overflow_handler wpi::memory::set_buffer_overflow_handler(buffer_overflow_handler h)
|
||||
{
|
||||
return buffer_overflow_h.exchange(h ? h : default_buffer_overflow_handler);
|
||||
}
|
||||
|
||||
buffer_overflow_handler wpi::memory::get_buffer_overflow_handler()
|
||||
{
|
||||
return buffer_overflow_h;
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include "wpi/memory/detail/align.hpp"
|
||||
|
||||
#include "wpi/memory/detail/ilog2.hpp"
|
||||
|
||||
using namespace wpi::memory;
|
||||
using namespace detail;
|
||||
|
||||
bool wpi::memory::detail::is_aligned(void* ptr, std::size_t alignment) noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(is_valid_alignment(alignment));
|
||||
auto address = reinterpret_cast<std::uintptr_t>(ptr);
|
||||
return address % alignment == 0u;
|
||||
}
|
||||
|
||||
std::size_t wpi::memory::detail::alignment_for(std::size_t size) noexcept
|
||||
{
|
||||
return size >= max_alignment ? max_alignment : (std::size_t(1) << ilog2(size));
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include "wpi/memory/detail/assert.hpp"
|
||||
|
||||
#if WPI_HOSTED_IMPLEMENTATION
|
||||
#include <cstdio>
|
||||
#endif
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "wpi/memory/error.hpp"
|
||||
|
||||
using namespace wpi::memory;
|
||||
using namespace detail;
|
||||
|
||||
void detail::handle_failed_assert(const char* msg, const char* file, int line,
|
||||
const char* fnc) noexcept
|
||||
{
|
||||
#if WPI_HOSTED_IMPLEMENTATION
|
||||
std::fprintf(stderr, "[%s] Assertion failure in function %s (%s:%d): %s.\n",
|
||||
WPI_MEMORY_LOG_PREFIX, fnc, file, line, msg);
|
||||
#endif
|
||||
std::abort();
|
||||
}
|
||||
|
||||
void detail::handle_warning(const char* msg, const char* file, int line, const char* fnc) noexcept
|
||||
{
|
||||
#if WPI_HOSTED_IMPLEMENTATION
|
||||
std::fprintf(stderr, "[%s] Warning triggered in function %s (%s:%d): %s.\n",
|
||||
WPI_MEMORY_LOG_PREFIX, fnc, file, line, msg);
|
||||
#endif
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include "wpi/memory/detail/debug_helpers.hpp"
|
||||
|
||||
#if WPI_HOSTED_IMPLEMENTATION
|
||||
#include <cstring>
|
||||
#endif
|
||||
|
||||
#include "wpi/memory/debugging.hpp"
|
||||
|
||||
using namespace wpi::memory;
|
||||
using namespace detail;
|
||||
|
||||
#if WPI_MEMORY_DEBUG_FILL
|
||||
void detail::debug_fill(void* memory, std::size_t size, debug_magic m) noexcept
|
||||
{
|
||||
#if WPI_HOSTED_IMPLEMENTATION
|
||||
std::memset(memory, static_cast<int>(m), size);
|
||||
#else
|
||||
// do the naive loop :(
|
||||
auto ptr = static_cast<unsigned char*>(memory);
|
||||
for (std::size_t i = 0u; i != size; ++i)
|
||||
*ptr++ = static_cast<unsigned char>(m);
|
||||
#endif
|
||||
}
|
||||
|
||||
void* detail::debug_is_filled(void* memory, std::size_t size, debug_magic m) noexcept
|
||||
{
|
||||
auto byte = static_cast<unsigned char*>(memory);
|
||||
for (auto end = byte + size; byte != end; ++byte)
|
||||
if (*byte != static_cast<unsigned char>(m))
|
||||
return byte;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void* detail::debug_fill_new(void* memory, std::size_t node_size, std::size_t fence_size) noexcept
|
||||
{
|
||||
if (!debug_fence_size)
|
||||
fence_size = 0u; // force override of fence_size
|
||||
|
||||
auto mem = static_cast<char*>(memory);
|
||||
debug_fill(mem, fence_size, debug_magic::fence_memory);
|
||||
|
||||
mem += fence_size;
|
||||
debug_fill(mem, node_size, debug_magic::new_memory);
|
||||
|
||||
debug_fill(mem + node_size, fence_size, debug_magic::fence_memory);
|
||||
|
||||
return mem;
|
||||
}
|
||||
|
||||
void* detail::debug_fill_free(void* memory, std::size_t node_size, std::size_t fence_size) noexcept
|
||||
{
|
||||
if (!debug_fence_size)
|
||||
fence_size = 0u; // force override of fence_size
|
||||
|
||||
debug_fill(memory, node_size, debug_magic::freed_memory);
|
||||
|
||||
auto pre_fence = static_cast<unsigned char*>(memory) - fence_size;
|
||||
if (auto pre_dirty = debug_is_filled(pre_fence, fence_size, debug_magic::fence_memory))
|
||||
get_buffer_overflow_handler()(memory, node_size, pre_dirty);
|
||||
|
||||
auto post_mem = static_cast<unsigned char*>(memory) + node_size;
|
||||
if (auto post_dirty = debug_is_filled(post_mem, fence_size, debug_magic::fence_memory))
|
||||
get_buffer_overflow_handler()(memory, node_size, post_dirty);
|
||||
|
||||
return pre_fence;
|
||||
}
|
||||
|
||||
void detail::debug_fill_internal(void* memory, std::size_t size, bool free) noexcept
|
||||
{
|
||||
debug_fill(memory, size,
|
||||
free ? debug_magic::internal_freed_memory : debug_magic::internal_memory);
|
||||
}
|
||||
#endif
|
||||
|
||||
void detail::debug_handle_invalid_ptr(const allocator_info& info, void* ptr)
|
||||
{
|
||||
get_invalid_pointer_handler()(info, ptr);
|
||||
}
|
||||
|
||||
void detail::debug_handle_memory_leak(const allocator_info& info, std::ptrdiff_t amount)
|
||||
{
|
||||
get_leak_handler()(info, amount);
|
||||
}
|
||||
@@ -1,556 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include "wpi/memory/detail/free_list.hpp"
|
||||
|
||||
#include "wpi/memory/detail/align.hpp"
|
||||
#include "wpi/memory/detail/debug_helpers.hpp"
|
||||
#include "wpi/memory/detail/assert.hpp"
|
||||
#include "wpi/memory/debugging.hpp"
|
||||
#include "wpi/memory/error.hpp"
|
||||
|
||||
#include "free_list_utils.hpp"
|
||||
|
||||
using namespace wpi::memory;
|
||||
using namespace detail;
|
||||
|
||||
namespace
|
||||
{
|
||||
// i.e. array
|
||||
struct interval
|
||||
{
|
||||
char* prev; // last before
|
||||
char* first; // first in
|
||||
char* last; // last in
|
||||
char* next; // first after
|
||||
|
||||
// number of nodes in the interval
|
||||
std::size_t size(std::size_t node_size) const noexcept
|
||||
{
|
||||
// last is inclusive, so add actual_size to it
|
||||
// note: cannot use next, might not be directly after
|
||||
auto end = last + node_size;
|
||||
WPI_MEMORY_ASSERT(static_cast<std::size_t>(end - first) % node_size == 0u);
|
||||
return static_cast<std::size_t>(end - first) / node_size;
|
||||
}
|
||||
};
|
||||
|
||||
// searches for n consecutive bytes
|
||||
// begin and end are the proxy nodes
|
||||
// assumes list is not empty
|
||||
// similar to list_search_array()
|
||||
interval list_search_array(char* first, std::size_t bytes_needed,
|
||||
std::size_t node_size) noexcept
|
||||
{
|
||||
interval i;
|
||||
i.prev = nullptr;
|
||||
i.first = first;
|
||||
// i.last/next are used as iterator for the end of the interval
|
||||
i.last = first;
|
||||
i.next = list_get_next(first);
|
||||
|
||||
auto bytes_so_far = node_size;
|
||||
while (i.next)
|
||||
{
|
||||
if (i.last + node_size != i.next) // not continous
|
||||
{
|
||||
// restart at next
|
||||
i.prev = i.last;
|
||||
i.first = i.next;
|
||||
i.last = i.next;
|
||||
i.next = list_get_next(i.last);
|
||||
|
||||
bytes_so_far = node_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
// extend interval
|
||||
auto new_next = list_get_next(i.next);
|
||||
i.last = i.next;
|
||||
i.next = new_next;
|
||||
|
||||
bytes_so_far += node_size;
|
||||
if (bytes_so_far >= bytes_needed)
|
||||
return i;
|
||||
}
|
||||
}
|
||||
// not enough continuous space
|
||||
return {nullptr, nullptr, nullptr, nullptr};
|
||||
}
|
||||
|
||||
// similar to list_search_array()
|
||||
// begin/end are proxy nodes
|
||||
interval xor_list_search_array(char* begin, char* end, std::size_t bytes_needed,
|
||||
std::size_t node_size) noexcept
|
||||
{
|
||||
interval i;
|
||||
i.prev = begin;
|
||||
i.first = xor_list_get_other(begin, nullptr);
|
||||
// i.last/next are used as iterator for the end of the interval
|
||||
i.last = i.first;
|
||||
i.next = xor_list_get_other(i.last, i.prev);
|
||||
|
||||
auto bytes_so_far = node_size;
|
||||
while (i.next != end)
|
||||
{
|
||||
if (i.last + node_size != i.next) // not continous
|
||||
{
|
||||
// restart at i.next
|
||||
i.prev = i.last;
|
||||
i.first = i.next;
|
||||
i.last = i.next;
|
||||
i.next = xor_list_get_other(i.first, i.prev);
|
||||
|
||||
bytes_so_far = node_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
// extend interval
|
||||
auto new_next = xor_list_get_other(i.next, i.last);
|
||||
i.last = i.next;
|
||||
i.next = new_next;
|
||||
|
||||
bytes_so_far += node_size;
|
||||
if (bytes_so_far >= bytes_needed)
|
||||
return i;
|
||||
}
|
||||
}
|
||||
// not enough continuous space
|
||||
return {nullptr, nullptr, nullptr, nullptr};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
constexpr std::size_t free_memory_list::min_element_size;
|
||||
constexpr std::size_t free_memory_list::min_element_alignment;
|
||||
|
||||
free_memory_list::free_memory_list(std::size_t node_size) noexcept
|
||||
: first_(nullptr),
|
||||
node_size_(node_size > min_element_size ? node_size : min_element_size),
|
||||
capacity_(0u)
|
||||
{
|
||||
}
|
||||
|
||||
free_memory_list::free_memory_list(std::size_t node_size, void* mem, std::size_t size) noexcept
|
||||
: free_memory_list(node_size)
|
||||
{
|
||||
insert(mem, size);
|
||||
}
|
||||
|
||||
free_memory_list::free_memory_list(free_memory_list&& other) noexcept
|
||||
: first_(other.first_), node_size_(other.node_size_), capacity_(other.capacity_)
|
||||
{
|
||||
other.first_ = nullptr;
|
||||
other.capacity_ = 0u;
|
||||
}
|
||||
|
||||
free_memory_list& free_memory_list::operator=(free_memory_list&& other) noexcept
|
||||
{
|
||||
free_memory_list tmp(detail::move(other));
|
||||
swap(*this, tmp);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void wpi::memory::detail::swap(free_memory_list& a, free_memory_list& b) noexcept
|
||||
{
|
||||
detail::adl_swap(a.first_, b.first_);
|
||||
detail::adl_swap(a.node_size_, b.node_size_);
|
||||
detail::adl_swap(a.capacity_, b.capacity_);
|
||||
}
|
||||
|
||||
void free_memory_list::insert(void* mem, std::size_t size) noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(mem);
|
||||
WPI_MEMORY_ASSERT(is_aligned(mem, alignment()));
|
||||
detail::debug_fill_internal(mem, size, false);
|
||||
|
||||
insert_impl(mem, size);
|
||||
}
|
||||
|
||||
void* free_memory_list::allocate() noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(!empty());
|
||||
--capacity_;
|
||||
|
||||
auto mem = first_;
|
||||
first_ = list_get_next(first_);
|
||||
return detail::debug_fill_new(mem, node_size_, 0);
|
||||
}
|
||||
|
||||
void* free_memory_list::allocate(std::size_t n) noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(!empty());
|
||||
if (n <= node_size_)
|
||||
return allocate();
|
||||
|
||||
auto i = list_search_array(first_, n, node_size_);
|
||||
if (i.first == nullptr)
|
||||
return nullptr;
|
||||
|
||||
if (i.prev)
|
||||
list_set_next(i.prev, i.next); // change next from previous to first after
|
||||
else
|
||||
first_ = i.next;
|
||||
capacity_ -= i.size(node_size_);
|
||||
|
||||
return detail::debug_fill_new(i.first, n, 0);
|
||||
}
|
||||
|
||||
void free_memory_list::deallocate(void* ptr) noexcept
|
||||
{
|
||||
++capacity_;
|
||||
|
||||
auto node = static_cast<char*>(detail::debug_fill_free(ptr, node_size_, 0));
|
||||
list_set_next(node, first_);
|
||||
first_ = node;
|
||||
}
|
||||
|
||||
void free_memory_list::deallocate(void* ptr, std::size_t n) noexcept
|
||||
{
|
||||
if (n <= node_size_)
|
||||
deallocate(ptr);
|
||||
else
|
||||
{
|
||||
auto mem = detail::debug_fill_free(ptr, n, 0);
|
||||
insert_impl(mem, n);
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t free_memory_list::alignment() const noexcept
|
||||
{
|
||||
return alignment_for(node_size_);
|
||||
}
|
||||
|
||||
void free_memory_list::insert_impl(void* mem, std::size_t size) noexcept
|
||||
{
|
||||
auto no_nodes = size / node_size_;
|
||||
WPI_MEMORY_ASSERT(no_nodes > 0);
|
||||
|
||||
auto cur = static_cast<char*>(mem);
|
||||
for (std::size_t i = 0u; i != no_nodes - 1; ++i)
|
||||
{
|
||||
list_set_next(cur, cur + node_size_);
|
||||
cur += node_size_;
|
||||
}
|
||||
list_set_next(cur, first_);
|
||||
first_ = static_cast<char*>(mem);
|
||||
|
||||
capacity_ += no_nodes;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// converts a block into a linked list
|
||||
void xor_link_block(void* memory, std::size_t node_size, std::size_t no_nodes, char* prev,
|
||||
char* next) noexcept
|
||||
{
|
||||
auto cur = static_cast<char*>(memory);
|
||||
xor_list_change(prev, next, cur); // change next pointer of prev
|
||||
|
||||
auto last_cur = prev;
|
||||
for (std::size_t i = 0u; i != no_nodes - 1; ++i)
|
||||
{
|
||||
xor_list_set(cur, last_cur,
|
||||
cur + node_size); // cur gets last_cur and next node in continous memory
|
||||
last_cur = cur;
|
||||
cur += node_size;
|
||||
}
|
||||
xor_list_set(cur, last_cur, next); // last memory node gets next as next
|
||||
xor_list_change(next, prev, cur); // change prev pointer of next
|
||||
}
|
||||
|
||||
struct pos
|
||||
{
|
||||
char *prev, *next;
|
||||
};
|
||||
|
||||
// finds position to insert memory to keep list ordered
|
||||
// first_prev -> first -> ... (memory somewhere here) ... -> last -> last_next
|
||||
pos find_pos_interval(const allocator_info& info, char* memory, char* first_prev, char* first,
|
||||
char* last, char* last_next) noexcept
|
||||
{
|
||||
// note: first_prev/last_next can be the proxy nodes, then first_prev isn't necessarily less than first!
|
||||
WPI_MEMORY_ASSERT(less(first, memory) && less(memory, last));
|
||||
|
||||
// need to insert somewhere in the middle
|
||||
// search through the entire list
|
||||
// search from both ends at once
|
||||
auto cur_forward = first;
|
||||
auto prev_forward = first_prev;
|
||||
|
||||
auto cur_backward = last;
|
||||
auto prev_backward = last_next;
|
||||
|
||||
do
|
||||
{
|
||||
if (greater(cur_forward, memory))
|
||||
return {prev_forward, cur_forward};
|
||||
else if (less(cur_backward, memory))
|
||||
// the next position is the previous backwards pointer
|
||||
return {cur_backward, prev_backward};
|
||||
debug_check_double_dealloc([&]
|
||||
{ return cur_forward != memory && cur_backward != memory; },
|
||||
info, memory);
|
||||
xor_list_iter_next(cur_forward, prev_forward);
|
||||
xor_list_iter_next(cur_backward, prev_backward);
|
||||
} while (less(prev_forward, prev_backward));
|
||||
|
||||
// ran outside of list
|
||||
debug_check_double_dealloc([] { return false; }, info, memory);
|
||||
return {nullptr, nullptr};
|
||||
}
|
||||
|
||||
// finds the position in the entire list
|
||||
pos find_pos(const allocator_info& info, char* memory, char* begin_node, char* end_node,
|
||||
char* last_dealloc, char* last_dealloc_prev) noexcept
|
||||
{
|
||||
auto first = xor_list_get_other(begin_node, nullptr);
|
||||
auto last = xor_list_get_other(end_node, nullptr);
|
||||
|
||||
if (greater(first, memory))
|
||||
// insert at front
|
||||
return {begin_node, first};
|
||||
else if (less(last, memory))
|
||||
// insert at the end
|
||||
return {last, end_node};
|
||||
else if (less(last_dealloc_prev, memory) && less(memory, last_dealloc))
|
||||
// insert before last_dealloc
|
||||
return {last_dealloc_prev, last_dealloc};
|
||||
else if (less(memory, last_dealloc))
|
||||
// insert into [first, last_dealloc_prev]
|
||||
return find_pos_interval(info, memory, begin_node, first, last_dealloc_prev,
|
||||
last_dealloc);
|
||||
else if (greater(memory, last_dealloc))
|
||||
// insert into (last_dealloc, last]
|
||||
return find_pos_interval(info, memory, last_dealloc_prev, last_dealloc, last, end_node);
|
||||
|
||||
WPI_MEMORY_UNREACHABLE("memory must be in some half or outside");
|
||||
return {nullptr, nullptr};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
constexpr std::size_t ordered_free_memory_list::min_element_size;
|
||||
constexpr std::size_t ordered_free_memory_list::min_element_alignment;
|
||||
|
||||
ordered_free_memory_list::ordered_free_memory_list(std::size_t node_size) noexcept
|
||||
: node_size_(node_size > min_element_size ? node_size : min_element_size),
|
||||
capacity_(0u),
|
||||
last_dealloc_(end_node()),
|
||||
last_dealloc_prev_(begin_node())
|
||||
{
|
||||
xor_list_set(begin_node(), nullptr, end_node());
|
||||
xor_list_set(end_node(), begin_node(), nullptr);
|
||||
}
|
||||
|
||||
ordered_free_memory_list::ordered_free_memory_list(ordered_free_memory_list&& other) noexcept
|
||||
: node_size_(other.node_size_), capacity_(other.capacity_)
|
||||
{
|
||||
if (!other.empty())
|
||||
{
|
||||
auto first = xor_list_get_other(other.begin_node(), nullptr);
|
||||
auto last = xor_list_get_other(other.end_node(), nullptr);
|
||||
|
||||
xor_list_set(begin_node(), nullptr, first);
|
||||
xor_list_change(first, other.begin_node(), begin_node());
|
||||
xor_list_change(last, other.end_node(), end_node());
|
||||
xor_list_set(end_node(), last, nullptr);
|
||||
|
||||
other.capacity_ = 0u;
|
||||
xor_list_set(other.begin_node(), nullptr, other.end_node());
|
||||
xor_list_set(other.end_node(), other.begin_node(), nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
xor_list_set(begin_node(), nullptr, end_node());
|
||||
xor_list_set(end_node(), begin_node(), nullptr);
|
||||
}
|
||||
|
||||
// for programming convenience, last_dealloc is reset
|
||||
last_dealloc_prev_ = begin_node();
|
||||
last_dealloc_ = xor_list_get_other(last_dealloc_prev_, nullptr);
|
||||
}
|
||||
|
||||
void wpi::memory::detail::swap(ordered_free_memory_list& a,
|
||||
ordered_free_memory_list& b) noexcept
|
||||
{
|
||||
auto a_first = xor_list_get_other(a.begin_node(), nullptr);
|
||||
auto a_last = xor_list_get_other(a.end_node(), nullptr);
|
||||
|
||||
auto b_first = xor_list_get_other(b.begin_node(), nullptr);
|
||||
auto b_last = xor_list_get_other(b.end_node(), nullptr);
|
||||
|
||||
if (!a.empty())
|
||||
{
|
||||
xor_list_set(b.begin_node(), nullptr, a_first);
|
||||
xor_list_change(a_first, a.begin_node(), b.begin_node());
|
||||
xor_list_change(a_last, a.end_node(), b.end_node());
|
||||
xor_list_set(b.end_node(), a_last, nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
xor_list_set(b.begin_node(), nullptr, b.end_node());
|
||||
xor_list_set(b.end_node(), b.begin_node(), nullptr);
|
||||
}
|
||||
|
||||
if (!b.empty())
|
||||
{
|
||||
xor_list_set(a.begin_node(), nullptr, b_first);
|
||||
xor_list_change(b_first, b.begin_node(), a.begin_node());
|
||||
xor_list_change(b_last, b.end_node(), a.end_node());
|
||||
xor_list_set(a.end_node(), b_last, nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
xor_list_set(a.begin_node(), nullptr, a.end_node());
|
||||
xor_list_set(a.end_node(), a.begin_node(), nullptr);
|
||||
}
|
||||
|
||||
detail::adl_swap(a.node_size_, b.node_size_);
|
||||
detail::adl_swap(a.capacity_, b.capacity_);
|
||||
|
||||
// for programming convenience, last_dealloc is reset
|
||||
a.last_dealloc_prev_ = a.begin_node();
|
||||
a.last_dealloc_ = xor_list_get_other(a.last_dealloc_prev_, nullptr);
|
||||
|
||||
b.last_dealloc_prev_ = b.begin_node();
|
||||
b.last_dealloc_ = xor_list_get_other(b.last_dealloc_prev_, nullptr);
|
||||
}
|
||||
|
||||
void ordered_free_memory_list::insert(void* mem, std::size_t size) noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(mem);
|
||||
WPI_MEMORY_ASSERT(is_aligned(mem, alignment()));
|
||||
detail::debug_fill_internal(mem, size, false);
|
||||
|
||||
insert_impl(mem, size);
|
||||
}
|
||||
|
||||
void* ordered_free_memory_list::allocate() noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(!empty());
|
||||
|
||||
// remove first node
|
||||
auto prev = begin_node();
|
||||
auto node = xor_list_get_other(prev, nullptr);
|
||||
auto next = xor_list_get_other(node, prev);
|
||||
|
||||
xor_list_set(prev, nullptr, next); // link prev to next
|
||||
xor_list_change(next, node, prev); // change prev of next
|
||||
--capacity_;
|
||||
|
||||
if (node == last_dealloc_)
|
||||
{
|
||||
// move last_dealloc_ one further in
|
||||
last_dealloc_ = next;
|
||||
WPI_MEMORY_ASSERT(last_dealloc_prev_ == prev);
|
||||
}
|
||||
else if (node == last_dealloc_prev_)
|
||||
{
|
||||
// now the previous node is the node before ours
|
||||
last_dealloc_prev_ = prev;
|
||||
WPI_MEMORY_ASSERT(last_dealloc_ == next);
|
||||
}
|
||||
|
||||
return detail::debug_fill_new(node, node_size_, 0);
|
||||
}
|
||||
|
||||
void* ordered_free_memory_list::allocate(std::size_t n) noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(!empty());
|
||||
|
||||
if (n <= node_size_)
|
||||
return allocate();
|
||||
|
||||
auto i = xor_list_search_array(begin_node(), end_node(), n, node_size_);
|
||||
if (i.first == nullptr)
|
||||
return nullptr;
|
||||
|
||||
xor_list_change(i.prev, i.first, i.next); // change next pointer from i.prev to i.next
|
||||
xor_list_change(i.next, i.last, i.prev); // change prev pointer from i.next to i.prev
|
||||
capacity_ -= i.size(node_size_);
|
||||
|
||||
// if last_dealloc_ points into the array being removed
|
||||
if ((less_equal(i.first, last_dealloc_) && less_equal(last_dealloc_, i.last)))
|
||||
{
|
||||
// move last_dealloc just outside range
|
||||
last_dealloc_ = i.next;
|
||||
last_dealloc_prev_ = i.prev;
|
||||
}
|
||||
// if the previous deallocation is the last element of the array
|
||||
else if (last_dealloc_prev_ == i.last)
|
||||
{
|
||||
// it is now the last element before the array
|
||||
WPI_MEMORY_ASSERT(last_dealloc_ == i.next);
|
||||
last_dealloc_prev_ = i.prev;
|
||||
}
|
||||
|
||||
return detail::debug_fill_new(i.first, n, 0);
|
||||
}
|
||||
|
||||
void ordered_free_memory_list::deallocate(void* ptr) noexcept
|
||||
{
|
||||
auto node = static_cast<char*>(debug_fill_free(ptr, node_size_, 0));
|
||||
|
||||
auto p =
|
||||
find_pos(allocator_info(WPI_MEMORY_LOG_PREFIX "::detail::ordered_free_memory_list",
|
||||
this),
|
||||
node, begin_node(), end_node(), last_dealloc_, last_dealloc_prev_);
|
||||
|
||||
xor_list_insert(node, p.prev, p.next);
|
||||
++capacity_;
|
||||
|
||||
last_dealloc_ = node;
|
||||
last_dealloc_prev_ = p.prev;
|
||||
}
|
||||
|
||||
void ordered_free_memory_list::deallocate(void* ptr, std::size_t n) noexcept
|
||||
{
|
||||
if (n <= node_size_)
|
||||
deallocate(ptr);
|
||||
else
|
||||
{
|
||||
auto mem = detail::debug_fill_free(ptr, n, 0);
|
||||
auto prev = insert_impl(mem, n);
|
||||
|
||||
last_dealloc_ = static_cast<char*>(mem);
|
||||
last_dealloc_prev_ = prev;
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t ordered_free_memory_list::alignment() const noexcept
|
||||
{
|
||||
return alignment_for(node_size_);
|
||||
}
|
||||
|
||||
char* ordered_free_memory_list::insert_impl(void* mem, std::size_t size) noexcept
|
||||
{
|
||||
auto no_nodes = size / node_size_;
|
||||
WPI_MEMORY_ASSERT(no_nodes > 0);
|
||||
|
||||
auto p =
|
||||
find_pos(allocator_info(WPI_MEMORY_LOG_PREFIX "::detail::ordered_free_memory_list",
|
||||
this),
|
||||
static_cast<char*>(mem), begin_node(), end_node(), last_dealloc_,
|
||||
last_dealloc_prev_);
|
||||
|
||||
xor_link_block(mem, node_size_, no_nodes, p.prev, p.next);
|
||||
capacity_ += no_nodes;
|
||||
|
||||
if (p.prev == last_dealloc_prev_)
|
||||
{
|
||||
last_dealloc_ = static_cast<char*>(mem);
|
||||
}
|
||||
|
||||
return p.prev;
|
||||
}
|
||||
|
||||
char* ordered_free_memory_list::begin_node() noexcept
|
||||
{
|
||||
void* mem = &begin_proxy_;
|
||||
return static_cast<char*>(mem);
|
||||
}
|
||||
|
||||
char* ordered_free_memory_list::end_node() noexcept
|
||||
{
|
||||
void* mem = &end_proxy_;
|
||||
return static_cast<char*>(mem);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include "wpi/memory/detail/free_list_array.hpp"
|
||||
|
||||
#include "wpi/memory/detail/assert.hpp"
|
||||
#include "wpi/memory/detail/ilog2.hpp"
|
||||
|
||||
using namespace wpi::memory;
|
||||
using namespace detail;
|
||||
|
||||
std::size_t log2_access_policy::index_from_size(std::size_t size) noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT_MSG(size, "size must not be zero");
|
||||
return ilog2_ceil(size);
|
||||
}
|
||||
|
||||
std::size_t log2_access_policy::size_from_index(std::size_t index) noexcept
|
||||
{
|
||||
return std::size_t(1) << index;
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef WPI_MEMORY_SRC_DETAIL_FREE_LIST_UTILS_HPP_INCLUDED
|
||||
#define WPI_MEMORY_SRC_DETAIL_FREE_LIST_UTILS_HPP_INCLUDED
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "wpi/memory/config.hpp"
|
||||
#include "wpi/memory/detail/align.hpp"
|
||||
#include "wpi/memory/detail/assert.hpp"
|
||||
|
||||
#if WPI_HOSTED_IMPLEMENTATION
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#endif
|
||||
|
||||
namespace wpi
|
||||
{
|
||||
namespace memory
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
//=== storage ===///
|
||||
// reads stored integer value
|
||||
inline std::uintptr_t get_int(void* address) noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(address);
|
||||
std::uintptr_t res;
|
||||
#if WPI_HOSTED_IMPLEMENTATION
|
||||
std::memcpy(&res, address, sizeof(std::uintptr_t));
|
||||
#else
|
||||
auto mem = static_cast<char*>(static_cast<void*>(&res));
|
||||
for (auto i = 0u; i != sizeof(std::uintptr_t); ++i)
|
||||
mem[i] = static_cast<char*>(address)[i];
|
||||
#endif
|
||||
return res;
|
||||
}
|
||||
|
||||
// sets stored integer value
|
||||
inline void set_int(void* address, std::uintptr_t i) noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(address);
|
||||
#if WPI_HOSTED_IMPLEMENTATION
|
||||
std::memcpy(address, &i, sizeof(std::uintptr_t));
|
||||
#else
|
||||
auto mem = static_cast<char*>(static_cast<void*>(&i));
|
||||
for (auto i = 0u; i != sizeof(std::uintptr_t); ++i)
|
||||
static_cast<char*>(address)[i] = mem[i];
|
||||
#endif
|
||||
}
|
||||
|
||||
// pointer to integer
|
||||
inline std::uintptr_t to_int(char* ptr) noexcept
|
||||
{
|
||||
return reinterpret_cast<std::uintptr_t>(ptr);
|
||||
}
|
||||
|
||||
// integer to pointer
|
||||
inline char* from_int(std::uintptr_t i) noexcept
|
||||
{
|
||||
return reinterpret_cast<char*>(i);
|
||||
}
|
||||
|
||||
//=== intrusive linked list ===//
|
||||
// reads a stored pointer value
|
||||
inline char* list_get_next(void* address) noexcept
|
||||
{
|
||||
return from_int(get_int(address));
|
||||
}
|
||||
|
||||
// stores a pointer value
|
||||
inline void list_set_next(void* address, char* ptr) noexcept
|
||||
{
|
||||
set_int(address, to_int(ptr));
|
||||
}
|
||||
|
||||
//=== intrusive xor linked list ===//
|
||||
// returns the other pointer given one pointer
|
||||
inline char* xor_list_get_other(void* address, char* prev_or_next) noexcept
|
||||
{
|
||||
return from_int(get_int(address) ^ to_int(prev_or_next));
|
||||
}
|
||||
|
||||
// sets the next and previous pointer (order actually does not matter)
|
||||
inline void xor_list_set(void* address, char* prev, char* next) noexcept
|
||||
{
|
||||
set_int(address, to_int(prev) ^ to_int(next));
|
||||
}
|
||||
|
||||
// changes other pointer given one pointer
|
||||
inline void xor_list_change(void* address, char* old_ptr, char* new_ptr) noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(address);
|
||||
auto other = xor_list_get_other(address, old_ptr);
|
||||
xor_list_set(address, other, new_ptr);
|
||||
}
|
||||
|
||||
// advances a pointer pair forward/backward
|
||||
inline void xor_list_iter_next(char*& cur, char*& prev) noexcept
|
||||
{
|
||||
auto next = xor_list_get_other(cur, prev);
|
||||
prev = cur;
|
||||
cur = next;
|
||||
}
|
||||
|
||||
// links new node between prev and next
|
||||
inline void xor_list_insert(char* new_node, char* prev, char* next) noexcept
|
||||
{
|
||||
xor_list_set(new_node, prev, next);
|
||||
xor_list_change(prev, next, new_node); // change prev's next to new_node
|
||||
xor_list_change(next, prev, new_node); // change next's prev to new_node
|
||||
}
|
||||
|
||||
//=== sorted list utils ===//
|
||||
// if std::less/std::greater not available compare integer representation and hope it works
|
||||
inline bool less(void* a, void* b) noexcept
|
||||
{
|
||||
#if WPI_HOSTED_IMPLEMENTATION
|
||||
return std::less<void*>()(a, b);
|
||||
#else
|
||||
return to_int(a) < to_int(b);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool less_equal(void* a, void* b) noexcept
|
||||
{
|
||||
return a == b || less(a, b);
|
||||
}
|
||||
|
||||
inline bool greater(void* a, void* b) noexcept
|
||||
{
|
||||
#if WPI_HOSTED_IMPLEMENTATION
|
||||
return std::greater<void*>()(a, b);
|
||||
#else
|
||||
return to_int(a) < to_int(b);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool greater_equal(void* a, void* b) noexcept
|
||||
{
|
||||
return a == b || greater(a, b);
|
||||
}
|
||||
} // namespace detail
|
||||
} // namespace memory
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPI_MEMORY_SRC_DETAIL_FREE_LIST_UTILS_HPP_INCLUDED
|
||||
@@ -1,394 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include "wpi/memory/detail/small_free_list.hpp"
|
||||
|
||||
#include <new>
|
||||
|
||||
#include "wpi/memory/detail/debug_helpers.hpp"
|
||||
#include "wpi/memory/detail/assert.hpp"
|
||||
#include "wpi/memory/error.hpp"
|
||||
|
||||
#include "free_list_utils.hpp"
|
||||
|
||||
using namespace wpi::memory;
|
||||
using namespace detail;
|
||||
|
||||
struct wpi::memory::detail::chunk : chunk_base
|
||||
{
|
||||
// gives it the size of the memory block it is created in and the size of a node
|
||||
chunk(std::size_t total_memory, std::size_t node_size) noexcept
|
||||
: chunk_base(static_cast<unsigned char>((total_memory - chunk_memory_offset) / node_size))
|
||||
{
|
||||
static_assert(sizeof(chunk) == sizeof(chunk_base), "chunk must not have members");
|
||||
WPI_MEMORY_ASSERT((total_memory - chunk_memory_offset) / node_size
|
||||
<= chunk_max_nodes);
|
||||
WPI_MEMORY_ASSERT(capacity > 0);
|
||||
auto p = list_memory();
|
||||
for (unsigned char i = 0u; i != no_nodes; p += node_size)
|
||||
*p = ++i;
|
||||
}
|
||||
|
||||
// returns memory of the free list
|
||||
unsigned char* list_memory() noexcept
|
||||
{
|
||||
auto mem = static_cast<void*>(this);
|
||||
return static_cast<unsigned char*>(mem) + chunk_memory_offset;
|
||||
}
|
||||
|
||||
// returns the nth node
|
||||
unsigned char* node_memory(unsigned char i, std::size_t node_size) noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(i < no_nodes);
|
||||
return list_memory() + i * node_size;
|
||||
}
|
||||
|
||||
// checks whether a node came from this chunk
|
||||
bool from(unsigned char* node, std::size_t node_size) noexcept
|
||||
{
|
||||
auto begin = list_memory();
|
||||
auto end = list_memory() + no_nodes * node_size;
|
||||
return (begin <= node) & (node < end);
|
||||
}
|
||||
|
||||
// checks whether a node is already in this chunk
|
||||
bool contains(unsigned char* node, std::size_t node_size) noexcept
|
||||
{
|
||||
auto cur_index = first_free;
|
||||
while (cur_index != no_nodes)
|
||||
{
|
||||
auto cur_mem = node_memory(cur_index, node_size);
|
||||
if (cur_mem == node)
|
||||
return true;
|
||||
cur_index = *cur_mem;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// allocates a single node
|
||||
// chunk most not be empty
|
||||
unsigned char* allocate(std::size_t node_size) noexcept
|
||||
{
|
||||
--capacity;
|
||||
|
||||
auto node = node_memory(first_free, node_size);
|
||||
first_free = *node;
|
||||
return node;
|
||||
}
|
||||
|
||||
// deallocates a single node given its address and index
|
||||
// it must be from this chunk
|
||||
void deallocate(unsigned char* node, unsigned char node_index) noexcept
|
||||
{
|
||||
++capacity;
|
||||
|
||||
*node = first_free;
|
||||
first_free = node_index;
|
||||
}
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
// converts a chunk_base to a chunk (if it is one)
|
||||
chunk* make_chunk(chunk_base* c) noexcept
|
||||
{
|
||||
return static_cast<chunk*>(c);
|
||||
}
|
||||
|
||||
// same as above but also requires a certain size
|
||||
chunk* make_chunk(chunk_base* c, std::size_t size_needed) noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(size_needed <= chunk_max_nodes);
|
||||
return c->capacity >= size_needed ? make_chunk(c) : nullptr;
|
||||
}
|
||||
|
||||
// checks if memory was from a chunk, assumes chunk isn't proxy
|
||||
chunk* from_chunk(chunk_base* c, unsigned char* node, std::size_t node_size) noexcept
|
||||
{
|
||||
auto res = make_chunk(c);
|
||||
return res->from(node, node_size) ? res : nullptr;
|
||||
}
|
||||
|
||||
// inserts already interconnected chunks into the list
|
||||
// list will be kept ordered
|
||||
void insert_chunks(chunk_base* list, chunk_base* begin, chunk_base* end) noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(begin && end);
|
||||
|
||||
if (list->next == list) // empty
|
||||
{
|
||||
begin->prev = list;
|
||||
end->next = list->next;
|
||||
list->next = begin;
|
||||
list->prev = end;
|
||||
}
|
||||
else if (less(list->prev, begin)) // insert at end
|
||||
{
|
||||
list->prev->next = begin;
|
||||
begin->prev = list->prev;
|
||||
end->next = list;
|
||||
list->prev = end;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto prev = list;
|
||||
auto cur = list->next;
|
||||
while (less(cur, begin))
|
||||
{
|
||||
prev = cur;
|
||||
cur = cur->next;
|
||||
}
|
||||
WPI_MEMORY_ASSERT(greater(cur, end));
|
||||
WPI_MEMORY_ASSERT(prev == list || less(prev, begin));
|
||||
prev->next = begin;
|
||||
begin->prev = prev;
|
||||
end->next = cur;
|
||||
cur->prev = end;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
constexpr std::size_t small_free_memory_list::min_element_size;
|
||||
constexpr std::size_t small_free_memory_list::min_element_alignment;
|
||||
|
||||
small_free_memory_list::small_free_memory_list(std::size_t node_size) noexcept
|
||||
: node_size_(node_size), capacity_(0u), alloc_chunk_(&base_), dealloc_chunk_(&base_)
|
||||
{
|
||||
}
|
||||
|
||||
small_free_memory_list::small_free_memory_list(std::size_t node_size, void* mem,
|
||||
std::size_t size) noexcept
|
||||
: small_free_memory_list(node_size)
|
||||
{
|
||||
insert(mem, size);
|
||||
}
|
||||
|
||||
small_free_memory_list::small_free_memory_list(small_free_memory_list&& other) noexcept
|
||||
: node_size_(other.node_size_),
|
||||
capacity_(other.capacity_),
|
||||
// reset markers for simplicity
|
||||
alloc_chunk_(&base_),
|
||||
dealloc_chunk_(&base_)
|
||||
{
|
||||
if (!other.empty())
|
||||
{
|
||||
base_.next = other.base_.next;
|
||||
base_.prev = other.base_.prev;
|
||||
other.base_.next->prev = &base_;
|
||||
other.base_.prev->next = &base_;
|
||||
|
||||
other.base_.next = &other.base_;
|
||||
other.base_.prev = &other.base_;
|
||||
other.capacity_ = 0u;
|
||||
}
|
||||
else
|
||||
{
|
||||
base_.next = &base_;
|
||||
base_.prev = &base_;
|
||||
}
|
||||
}
|
||||
|
||||
void wpi::memory::detail::swap(small_free_memory_list& a, small_free_memory_list& b) noexcept
|
||||
{
|
||||
auto b_next = b.base_.next;
|
||||
auto b_prev = b.base_.prev;
|
||||
|
||||
if (!a.empty())
|
||||
{
|
||||
b.base_.next = a.base_.next;
|
||||
b.base_.prev = a.base_.prev;
|
||||
b.base_.next->prev = &b.base_;
|
||||
b.base_.prev->next = &b.base_;
|
||||
}
|
||||
else
|
||||
{
|
||||
b.base_.next = &b.base_;
|
||||
b.base_.prev = &b.base_;
|
||||
}
|
||||
|
||||
if (!b.empty())
|
||||
{
|
||||
a.base_.next = b_next;
|
||||
a.base_.prev = b_prev;
|
||||
a.base_.next->prev = &a.base_;
|
||||
a.base_.prev->next = &a.base_;
|
||||
}
|
||||
else
|
||||
{
|
||||
a.base_.next = &a.base_;
|
||||
a.base_.prev = &a.base_;
|
||||
}
|
||||
|
||||
detail::adl_swap(a.node_size_, b.node_size_);
|
||||
detail::adl_swap(a.capacity_, b.capacity_);
|
||||
|
||||
// reset markers for simplicity
|
||||
a.alloc_chunk_ = a.dealloc_chunk_ = &a.base_;
|
||||
b.alloc_chunk_ = b.dealloc_chunk_ = &b.base_;
|
||||
}
|
||||
|
||||
void small_free_memory_list::insert(void* mem, std::size_t size) noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(mem);
|
||||
WPI_MEMORY_ASSERT(is_aligned(mem, max_alignment));
|
||||
debug_fill_internal(mem, size, false);
|
||||
|
||||
auto total_chunk_size = chunk_memory_offset + node_size_ * chunk_max_nodes;
|
||||
auto align_buffer = align_offset(total_chunk_size, alignof(chunk));
|
||||
|
||||
auto no_chunks = size / (total_chunk_size + align_buffer);
|
||||
auto remainder = size % (total_chunk_size + align_buffer);
|
||||
|
||||
auto memory = static_cast<char*>(mem);
|
||||
auto construct_chunk = [&](std::size_t total_memory, std::size_t node_size)
|
||||
{
|
||||
WPI_MEMORY_ASSERT(align_offset(memory, alignof(chunk)) == 0);
|
||||
return ::new (static_cast<void*>(memory)) chunk(total_memory, node_size);
|
||||
};
|
||||
|
||||
auto prev = static_cast<chunk_base*>(nullptr);
|
||||
for (auto i = std::size_t(0); i != no_chunks; ++i)
|
||||
{
|
||||
auto c = construct_chunk(total_chunk_size, node_size_);
|
||||
|
||||
c->prev = prev;
|
||||
if (prev)
|
||||
prev->next = c;
|
||||
prev = c;
|
||||
|
||||
memory += total_chunk_size;
|
||||
memory += align_buffer;
|
||||
}
|
||||
|
||||
auto new_nodes = no_chunks * chunk_max_nodes;
|
||||
if (remainder >= chunk_memory_offset + node_size_) // at least one node
|
||||
{
|
||||
auto c = construct_chunk(remainder, node_size_);
|
||||
|
||||
c->prev = prev;
|
||||
if (prev)
|
||||
prev->next = c;
|
||||
prev = c;
|
||||
|
||||
new_nodes += c->no_nodes;
|
||||
}
|
||||
|
||||
WPI_MEMORY_ASSERT_MSG(new_nodes > 0, "memory block too small");
|
||||
insert_chunks(&base_, static_cast<chunk_base*>(mem), prev);
|
||||
capacity_ += new_nodes;
|
||||
}
|
||||
|
||||
std::size_t small_free_memory_list::usable_size(std::size_t size) const noexcept
|
||||
{
|
||||
auto total_chunk_size = chunk_memory_offset + node_size_ * chunk_max_nodes;
|
||||
auto no_chunks = size / total_chunk_size;
|
||||
auto remainder = size % total_chunk_size;
|
||||
|
||||
return no_chunks * chunk_max_nodes * node_size_
|
||||
+ (remainder > chunk_memory_offset ? remainder - chunk_memory_offset : 0u);
|
||||
}
|
||||
|
||||
void* small_free_memory_list::allocate() noexcept
|
||||
{
|
||||
auto chunk = find_chunk_impl(1);
|
||||
alloc_chunk_ = chunk;
|
||||
WPI_MEMORY_ASSERT(chunk && chunk->capacity >= 1);
|
||||
|
||||
--capacity_;
|
||||
|
||||
auto mem = chunk->allocate(node_size_);
|
||||
WPI_MEMORY_ASSERT(mem);
|
||||
return detail::debug_fill_new(mem, node_size_, 0);
|
||||
}
|
||||
|
||||
void small_free_memory_list::deallocate(void* mem) noexcept
|
||||
{
|
||||
auto info =
|
||||
allocator_info(WPI_MEMORY_LOG_PREFIX "::detail::small_free_memory_list", this);
|
||||
|
||||
auto node = static_cast<unsigned char*>(detail::debug_fill_free(mem, node_size_, 0));
|
||||
|
||||
auto chunk = find_chunk_impl(node);
|
||||
dealloc_chunk_ = chunk;
|
||||
// memory was never allocated from list
|
||||
detail::debug_check_pointer([&] { return chunk != nullptr; }, info, mem);
|
||||
|
||||
auto offset = static_cast<std::size_t>(node - chunk->list_memory());
|
||||
// memory is not at the right position
|
||||
debug_check_pointer([&] { return offset % node_size_ == 0u; }, info, mem);
|
||||
// double-free
|
||||
debug_check_double_dealloc([&] { return !chunk->contains(node, node_size_); }, info, mem);
|
||||
|
||||
auto index = offset / node_size_;
|
||||
WPI_MEMORY_ASSERT(index < chunk->no_nodes);
|
||||
chunk->deallocate(node, static_cast<unsigned char>(index));
|
||||
|
||||
++capacity_;
|
||||
}
|
||||
|
||||
std::size_t small_free_memory_list::alignment() const noexcept
|
||||
{
|
||||
return alignment_for(node_size_);
|
||||
}
|
||||
|
||||
chunk* small_free_memory_list::find_chunk_impl(std::size_t n) noexcept
|
||||
{
|
||||
if (auto c = make_chunk(alloc_chunk_, n))
|
||||
return c;
|
||||
else if ((c = make_chunk(dealloc_chunk_, n)) != nullptr)
|
||||
return c;
|
||||
|
||||
auto cur_forward = alloc_chunk_->next;
|
||||
auto cur_backward = alloc_chunk_->prev;
|
||||
|
||||
do
|
||||
{
|
||||
if (auto c = make_chunk(cur_forward, n))
|
||||
return c;
|
||||
else if ((c = make_chunk(cur_backward, n)) != nullptr)
|
||||
return c;
|
||||
|
||||
cur_forward = cur_forward->next;
|
||||
cur_backward = cur_backward->prev;
|
||||
WPI_MEMORY_ASSERT(cur_forward != alloc_chunk_);
|
||||
WPI_MEMORY_ASSERT(cur_backward != alloc_chunk_);
|
||||
} while (true);
|
||||
WPI_MEMORY_UNREACHABLE("there is memory available somewhere...");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
chunk* small_free_memory_list::find_chunk_impl(unsigned char* node, chunk_base* first,
|
||||
chunk_base* last) noexcept
|
||||
{
|
||||
do
|
||||
{
|
||||
if (auto c = from_chunk(first, node, node_size_))
|
||||
return c;
|
||||
else if ((c = from_chunk(last, node, node_size_)) != nullptr)
|
||||
return c;
|
||||
|
||||
first = first->next;
|
||||
last = last->prev;
|
||||
} while (!greater(first, last));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
chunk* small_free_memory_list::find_chunk_impl(unsigned char* node) noexcept
|
||||
{
|
||||
if (auto c = from_chunk(dealloc_chunk_, node, node_size_))
|
||||
return c;
|
||||
else if ((c = from_chunk(alloc_chunk_, node, node_size_)) != nullptr)
|
||||
return c;
|
||||
else if (less(dealloc_chunk_, node))
|
||||
{
|
||||
// node is in (dealloc_chunk_, base_.prev]
|
||||
return find_chunk_impl(node, dealloc_chunk_->next, base_.prev);
|
||||
}
|
||||
else if (greater(dealloc_chunk_, node))
|
||||
{
|
||||
// node is in [base.next, dealloc_chunk_)
|
||||
return find_chunk_impl(node, base_.next, dealloc_chunk_->prev);
|
||||
}
|
||||
WPI_MEMORY_UNREACHABLE("must be in one half");
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include "wpi/memory/error.hpp"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#if WPI_HOSTED_IMPLEMENTATION
|
||||
#include <cstdio>
|
||||
#endif
|
||||
|
||||
using namespace wpi::memory;
|
||||
|
||||
namespace
|
||||
{
|
||||
void default_out_of_memory_handler(const allocator_info& info, std::size_t amount) noexcept
|
||||
{
|
||||
#if WPI_HOSTED_IMPLEMENTATION
|
||||
std::fprintf(stderr,
|
||||
"[%s] Allocator %s (at %p) ran out of memory trying to allocate %zu bytes.\n",
|
||||
WPI_MEMORY_LOG_PREFIX, info.name, info.allocator, amount);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::atomic<out_of_memory::handler> out_of_memory_h(default_out_of_memory_handler);
|
||||
} // namespace
|
||||
|
||||
out_of_memory::handler out_of_memory::set_handler(out_of_memory::handler h)
|
||||
{
|
||||
return out_of_memory_h.exchange(h ? h : default_out_of_memory_handler);
|
||||
}
|
||||
|
||||
out_of_memory::handler out_of_memory::get_handler()
|
||||
{
|
||||
return out_of_memory_h;
|
||||
}
|
||||
|
||||
out_of_memory::out_of_memory(const allocator_info& info, std::size_t amount)
|
||||
: info_(info), amount_(amount)
|
||||
{
|
||||
out_of_memory_h.load()(info, amount);
|
||||
}
|
||||
|
||||
const char* out_of_memory::what() const noexcept
|
||||
{
|
||||
return "low-level allocator is out of memory";
|
||||
}
|
||||
|
||||
const char* out_of_fixed_memory::what() const noexcept
|
||||
{
|
||||
return "fixed size allocator is out of memory";
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
void default_bad_alloc_size_handler(const allocator_info& info, std::size_t passed,
|
||||
std::size_t supported) noexcept
|
||||
{
|
||||
#if WPI_HOSTED_IMPLEMENTATION
|
||||
std::fprintf(stderr,
|
||||
"[%s] Allocator %s (at %p) received invalid size/alignment %zu, "
|
||||
"max supported is %zu\n",
|
||||
WPI_MEMORY_LOG_PREFIX, info.name, info.allocator, passed, supported);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::atomic<bad_allocation_size::handler> bad_alloc_size_h(default_bad_alloc_size_handler);
|
||||
} // namespace
|
||||
|
||||
bad_allocation_size::handler bad_allocation_size::set_handler(bad_allocation_size::handler h)
|
||||
{
|
||||
return bad_alloc_size_h.exchange(h ? h : default_bad_alloc_size_handler);
|
||||
}
|
||||
|
||||
bad_allocation_size::handler bad_allocation_size::get_handler()
|
||||
{
|
||||
return bad_alloc_size_h;
|
||||
}
|
||||
|
||||
bad_allocation_size::bad_allocation_size(const allocator_info& info, std::size_t passed,
|
||||
std::size_t supported)
|
||||
: info_(info), passed_(passed), supported_(supported)
|
||||
{
|
||||
bad_alloc_size_h.load()(info_, passed_, supported_);
|
||||
}
|
||||
|
||||
const char* bad_allocation_size::what() const noexcept
|
||||
{
|
||||
return "allocation node size exceeds supported maximum of allocator";
|
||||
}
|
||||
|
||||
const char* bad_node_size::what() const noexcept
|
||||
{
|
||||
return "allocation node size exceeds supported maximum of allocator";
|
||||
}
|
||||
|
||||
const char* bad_array_size::what() const noexcept
|
||||
{
|
||||
return "allocation array size exceeds supported maximum of allocator";
|
||||
}
|
||||
|
||||
const char* bad_alignment::what() const noexcept
|
||||
{
|
||||
return "allocation alignment exceeds supported maximum of allocator";
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include "wpi/memory/heap_allocator.hpp"
|
||||
|
||||
#include "wpi/memory/error.hpp"
|
||||
|
||||
using namespace wpi::memory;
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <malloc.h>
|
||||
#include <windows.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
HANDLE get_process_heap() noexcept
|
||||
{
|
||||
static auto heap = GetProcessHeap();
|
||||
return heap;
|
||||
}
|
||||
|
||||
std::size_t max_size() noexcept
|
||||
{
|
||||
return _HEAP_MAXREQ;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void* wpi::memory::heap_alloc(std::size_t size) noexcept
|
||||
{
|
||||
return HeapAlloc(get_process_heap(), 0, size);
|
||||
}
|
||||
|
||||
void wpi::memory::heap_dealloc(void* ptr, std::size_t) noexcept
|
||||
{
|
||||
HeapFree(get_process_heap(), 0, ptr);
|
||||
}
|
||||
|
||||
#elif WPI_HOSTED_IMPLEMENTATION
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
|
||||
void* wpi::memory::heap_alloc(std::size_t size) noexcept
|
||||
{
|
||||
return std::malloc(size);
|
||||
}
|
||||
|
||||
void wpi::memory::heap_dealloc(void* ptr, std::size_t) noexcept
|
||||
{
|
||||
std::free(ptr);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
std::size_t max_size() noexcept
|
||||
{
|
||||
return std::allocator_traits<std::allocator<char>>::max_size({});
|
||||
}
|
||||
} // namespace
|
||||
#else
|
||||
// no implementation for heap_alloc/heap_dealloc
|
||||
|
||||
namespace
|
||||
{
|
||||
std::size_t max_size() noexcept
|
||||
{
|
||||
return std::size_t(-1);
|
||||
}
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
allocator_info detail::heap_allocator_impl::info() noexcept
|
||||
{
|
||||
return {WPI_MEMORY_LOG_PREFIX "::heap_allocator", nullptr};
|
||||
}
|
||||
|
||||
std::size_t detail::heap_allocator_impl::max_node_size() noexcept
|
||||
{
|
||||
return max_size();
|
||||
}
|
||||
|
||||
#if WPI_MEMORY_EXTERN_TEMPLATE
|
||||
template class detail::lowlevel_allocator<detail::heap_allocator_impl>;
|
||||
template class wpi::memory::allocator_traits<heap_allocator>;
|
||||
#endif
|
||||
@@ -1,12 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include "wpi/memory/iteration_allocator.hpp"
|
||||
|
||||
using namespace wpi::memory;
|
||||
|
||||
#if WPI_MEMORY_EXTERN_TEMPLATE
|
||||
template class wpi::memory::iteration_allocator<2>;
|
||||
template class wpi::memory::allocator_traits<iteration_allocator<2>>;
|
||||
template class wpi::memory::composable_allocator_traits<iteration_allocator<2>>;
|
||||
#endif
|
||||
@@ -1,23 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include "wpi/memory/config.hpp"
|
||||
#if WPI_HOSTED_IMPLEMENTATION
|
||||
|
||||
#include "wpi/memory/malloc_allocator.hpp"
|
||||
|
||||
#include "wpi/memory/error.hpp"
|
||||
|
||||
using namespace wpi::memory;
|
||||
|
||||
allocator_info detail::malloc_allocator_impl::info() noexcept
|
||||
{
|
||||
return {WPI_MEMORY_LOG_PREFIX "::malloc_allocator", nullptr};
|
||||
}
|
||||
|
||||
#if WPI_MEMORY_EXTERN_TEMPLATE
|
||||
template class detail::lowlevel_allocator<detail::malloc_allocator_impl>;
|
||||
template class wpi::memory::allocator_traits<malloc_allocator>;
|
||||
#endif
|
||||
|
||||
#endif // WPI_HOSTED_IMPLEMENTATION
|
||||
@@ -1,72 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include "wpi/memory/memory_arena.hpp"
|
||||
|
||||
#include <new>
|
||||
|
||||
#include "wpi/memory/detail/align.hpp"
|
||||
|
||||
using namespace wpi::memory;
|
||||
using namespace detail;
|
||||
|
||||
void memory_block_stack::push(allocated_mb block) noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(block.size >= sizeof(node));
|
||||
WPI_MEMORY_ASSERT(is_aligned(block.memory, max_alignment));
|
||||
auto next = ::new (block.memory) node(head_, block.size - implementation_offset());
|
||||
head_ = next;
|
||||
}
|
||||
|
||||
memory_block_stack::allocated_mb memory_block_stack::pop() noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(head_);
|
||||
auto to_pop = head_;
|
||||
head_ = head_->prev;
|
||||
return {to_pop, to_pop->usable_size + implementation_offset()};
|
||||
}
|
||||
|
||||
void memory_block_stack::steal_top(memory_block_stack& other) noexcept
|
||||
{
|
||||
WPI_MEMORY_ASSERT(other.head_);
|
||||
auto to_steal = other.head_;
|
||||
other.head_ = other.head_->prev;
|
||||
|
||||
to_steal->prev = head_;
|
||||
head_ = to_steal;
|
||||
}
|
||||
|
||||
bool memory_block_stack::owns(const void* ptr) const noexcept
|
||||
{
|
||||
auto address = static_cast<const char*>(ptr);
|
||||
for (auto cur = head_; cur; cur = cur->prev)
|
||||
{
|
||||
auto mem = static_cast<char*>(static_cast<void*>(cur)) + implementation_offset();
|
||||
if (address >= mem && address < mem + cur->usable_size)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t memory_block_stack::size() const noexcept
|
||||
{
|
||||
std::size_t res = 0u;
|
||||
for (auto cur = head_; cur; cur = cur->prev)
|
||||
++res;
|
||||
return res;
|
||||
}
|
||||
|
||||
#if WPI_MEMORY_EXTERN_TEMPLATE
|
||||
template class wpi::memory::memory_arena<static_block_allocator, true>;
|
||||
template class wpi::memory::memory_arena<static_block_allocator, false>;
|
||||
template class wpi::memory::memory_arena<virtual_block_allocator, true>;
|
||||
template class wpi::memory::memory_arena<virtual_block_allocator, false>;
|
||||
|
||||
template class wpi::memory::growing_block_allocator<>;
|
||||
template class wpi::memory::memory_arena<growing_block_allocator<>, true>;
|
||||
template class wpi::memory::memory_arena<growing_block_allocator<>, false>;
|
||||
|
||||
template class wpi::memory::fixed_block_allocator<>;
|
||||
template class wpi::memory::memory_arena<fixed_block_allocator<>, true>;
|
||||
template class wpi::memory::memory_arena<fixed_block_allocator<>, false>;
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user