fs: implement path size reduction a simpler way

This commit is contained in:
Michael Scire 2022-03-24 20:16:59 -07:00
parent 48ee88c7ce
commit 8adf410b1c
2 changed files with 127 additions and 232 deletions

View file

@ -18,9 +18,6 @@
namespace ams::fs {
/* Controls whether MakeUniqueBuffer uses a custom buffer wrapper which wraps the size inline. */
#define AMS_FS_IMPL_MAKE_UNIQUE_BUFFER_WITH_INLINE_SIZE
/* ACCURATE_TO_VERSION: Unknown */
using AllocateFunction = void *(*)(size_t);
using DeallocateFunction = void (*)(void *, size_t);
@ -134,144 +131,6 @@ namespace ams::fs {
}
};
#if defined(AMS_FS_IMPL_MAKE_UNIQUE_BUFFER_WITH_INLINE_SIZE)
template<std::convertible_to<u8> BufferEntryType>
class BufferWithInlineSize final {
static_assert(sizeof(BufferEntryType) == 1);
NON_COPYABLE(BufferWithInlineSize);
NON_MOVEABLE(BufferWithInlineSize);
private:
template<std::unsigned_integral T> static constexpr inline T EncodedSizeMask = util::IsLittleEndian() ? (static_cast<T>(3u) << (BITSIZEOF(T) - 2)) : static_cast<T>(3u);
template<std::unsigned_integral T> static constexpr inline T EncodedSize1 = util::IsLittleEndian() ? (static_cast<T>(0u) << (BITSIZEOF(T) - 2)) : static_cast<T>(0u);
template<std::unsigned_integral T> static constexpr inline T EncodedSize2 = util::IsLittleEndian() ? (static_cast<T>(1u) << (BITSIZEOF(T) - 2)) : static_cast<T>(1u);
template<std::unsigned_integral T> static constexpr inline T EncodedSize4 = util::IsLittleEndian() ? (static_cast<T>(2u) << (BITSIZEOF(T) - 2)) : static_cast<T>(2u);
template<std::unsigned_integral T> static constexpr inline T EncodedSize8 = util::IsLittleEndian() ? (static_cast<T>(3u) << (BITSIZEOF(T) - 2)) : static_cast<T>(3u);
static constexpr inline u64 TestSize1Mask = ~((static_cast<u64>(1u) << (BITSIZEOF(u8) - 2)) - static_cast<u64>(1));
static constexpr inline u64 TestSize2Mask = ~((static_cast<u64>(1u) << (BITSIZEOF(u16) - 2)) - static_cast<u64>(1));
static constexpr inline u64 TestSize4Mask = ~((static_cast<u64>(1u) << (BITSIZEOF(u32) - 2)) - static_cast<u64>(1));
static constexpr inline u64 TestSize8Mask = ~((static_cast<u64>(1u) << (BITSIZEOF(u64) - 2)) - static_cast<u64>(1));
template<std::unsigned_integral SizeType>
static constexpr ALWAYS_INLINE SizeType EncodeSize(SizeType type, size_t size) noexcept {
if constexpr (util::IsLittleEndian()) {
return type | static_cast<SizeType>(size);
} else {
return type | (static_cast<SizeType>(size) << 2);
}
}
template<std::unsigned_integral SizeType>
static constexpr ALWAYS_INLINE size_t DecodeSize(const SizeType encoded) noexcept {
if constexpr (util::IsLittleEndian()) {
/* Small optimization: 1-byte size has size type field == 0 and no shifting, can return the value directly. */
if constexpr (sizeof(SizeType) == 1) {
static_assert(EncodedSize1<SizeType> == 0);
return encoded;
} else {
/* On little endian, we want to mask out the high bits storing the size field. */
constexpr SizeType DecodedSizeMask = static_cast<SizeType>(~EncodedSizeMask<SizeType>);
return encoded & DecodedSizeMask;
}
} else {
/* On big endian, we want to shift out the low bits storing the size type field. */
return encoded >> 2;
}
}
template<std::unsigned_integral SizeType>
static ALWAYS_INLINE void DeleteBufferImpl(BufferEntryType *buffer) noexcept {
/* Get pointer to start of allocation. */
SizeType *alloc = reinterpret_cast<SizeType *>(buffer) - 1;
/* Decode the size of the allocation. */
const size_t alloc_size = sizeof(SizeType) + DecodeSize<SizeType>(*alloc);
/* Delete the buffer. */
return ::ams::fs::impl::Deallocate(alloc, alloc_size);
}
template<std::unsigned_integral SizeType, SizeType EncodedSizeType>
static std::unique_ptr<BufferWithInlineSize> MakeBuffer(size_t size) noexcept {
/* Allocate a buffer. */
SizeType *alloc = static_cast<SizeType *>(::ams::fs::impl::Allocate(sizeof(SizeType) + size));
if (AMS_UNLIKELY(alloc == nullptr)) {
return nullptr;
}
/* Write the encoded size. */
if constexpr (util::IsLittleEndian()) {
*alloc = EncodedSizeType | static_cast<SizeType>(size);
} else {
*alloc = EncodedSizeType | (static_cast<SizeType>(size) << 2);
}
/* Return our buffer. */
return std::unique_ptr<BufferWithInlineSize>(reinterpret_cast<BufferWithInlineSize *>(alloc + 1));
}
static void DeleteBuffer(BufferEntryType *buffer) noexcept {
/* Convert to u8 pointer */
const u8 *buffer_u8 = reinterpret_cast<const u8 *>(buffer);
/* Determine the storage size for the size. */
const auto size_type = buffer_u8[-1] & EncodedSizeMask<u8>;
if (size_type == EncodedSize1<u8>) {
return DeleteBufferImpl<u8>(buffer);
} else if (size_type == EncodedSize2<u8>) {
return DeleteBufferImpl<u16>(buffer);
} else if (size_type == EncodedSize4<u8>) {
return DeleteBufferImpl<u32>(buffer);
} else /* if (size_type == EncodedSize8<u8>) */ {
return DeleteBufferImpl<u64>(buffer);
}
}
private:
BufferEntryType m_buffer[1];
private:
ALWAYS_INLINE BufferWithInlineSize() noexcept { /* ... */ }
public:
ALWAYS_INLINE ~BufferWithInlineSize() noexcept { /* ... */ }
public:
ALWAYS_INLINE operator BufferEntryType *() noexcept {
return m_buffer;
}
ALWAYS_INLINE operator const BufferEntryType *() const noexcept {
return m_buffer;
}
public:
static ALWAYS_INLINE std::unique_ptr<BufferWithInlineSize> Make(size_t size) noexcept {
/* Create based on overhead size. */
if (!(size & TestSize1Mask)) {
return MakeBuffer<u8, EncodedSize1<u8>>(size);
} else if (!(size & TestSize2Mask)) {
return MakeBuffer<u16, EncodedSize2<u16>>(size);
} else if (!(size & TestSize4Mask)) {
return MakeBuffer<u32, EncodedSize4<u32>>(size);
} else /* if (!(size & TestSize8Mask)) */ {
/* Check pre-condition. */
AMS_ASSERT(!(size & TestSize8Mask));
return MakeBuffer<u64, EncodedSize8<u64>>(size);
}
}
public:
static ALWAYS_INLINE void *operator new(size_t) noexcept { AMS_ABORT(AMS_CURRENT_FUNCTION_NAME); }
static ALWAYS_INLINE void *operator new(size_t size, Newable *placement) noexcept { AMS_ABORT(AMS_CURRENT_FUNCTION_NAME); }
static ALWAYS_INLINE void operator delete(void *ptr, size_t) noexcept {
/* Delete the buffer. */
DeleteBuffer(reinterpret_cast<BufferWithInlineSize *>(ptr)->m_buffer);
}
static void *operator new[](size_t size) noexcept = delete;
static void operator delete[](void *ptr, size_t size) noexcept = delete;
};
#endif
template<typename T>
auto MakeUnique() {
/* Check that we're not using MakeUnique unnecessarily. */
@ -296,15 +155,6 @@ namespace ams::fs {
return ReturnType(static_cast<T *>(::ams::fs::impl::Allocate(alloc_size)), Deleter(alloc_size));
}
template<typename T>
auto MakeUniqueBuffer(size_t size) {
#if defined(AMS_FS_IMPL_MAKE_UNIQUE_BUFFER_WITH_INLINE_SIZE)
return BufferWithInlineSize<T>::Make(size);
#else
return ::ams::fs::impl::MakeUnique<T[]>(size);
#endif
}
}
template<typename T, typename... Args>

View file

@ -24,6 +24,7 @@ namespace ams::fs {
class DirectoryPathParser;
/* ACCURATE_TO_VERSION: 13.4.0.0 */
/* NOTE: Intentional inaccuracy in custom WriteBuffer class, to save 0x10 bytes (0x28 -> 0x18) over Nintendo's implementation. */
class Path {
NON_COPYABLE(Path);
NON_MOVEABLE(Path);
@ -32,53 +33,105 @@ namespace ams::fs {
static constexpr size_t WriteBufferAlignmentLength = 8;
private:
friend class DirectoryPathParser;
private:
using WriteBuffer = decltype(::ams::fs::impl::MakeUniqueBuffer<char>(0));
public:
class WriteBuffer {
NON_COPYABLE(WriteBuffer);
private:
char *m_buffer;
size_t m_length_and_is_normalized;
public:
constexpr WriteBuffer() : m_buffer(nullptr), m_length_and_is_normalized(0) { /* ... */ }
template<std::same_as<WriteBuffer> T>
static ALWAYS_INLINE char *GetBuffer(T &write_buffer) {
if constexpr (std::same_as<T, decltype(::ams::fs::impl::MakeUnique<char[]>(0))>) {
return write_buffer.get();
} else {
return static_cast<char *>(*write_buffer);
}
}
constexpr ~WriteBuffer() {
if (m_buffer != nullptr) {
::ams::fs::impl::Deallocate(m_buffer, this->GetLength());
this->ResetBuffer();
}
}
static ALWAYS_INLINE WriteBuffer MakeWriteBuffer(size_t size) {
return ::ams::fs::impl::MakeUniqueBuffer<char>(size);
}
constexpr WriteBuffer(WriteBuffer &&rhs) : m_buffer(rhs.m_buffer), m_length_and_is_normalized(rhs.m_length_and_is_normalized) {
rhs.ResetBuffer();
}
constexpr WriteBuffer &operator=(WriteBuffer &&rhs) {
m_buffer = rhs.m_buffer;
m_length_and_is_normalized = rhs.m_length_and_is_normalized;
rhs.ResetBuffer();
return *this;
}
constexpr ALWAYS_INLINE void ResetBuffer() {
m_buffer = nullptr;
this->SetLength(0);
}
constexpr ALWAYS_INLINE char *Get() const {
return m_buffer;
}
constexpr ALWAYS_INLINE size_t GetLength() const {
return m_length_and_is_normalized >> 1;
}
constexpr ALWAYS_INLINE bool IsNormalized() const {
return static_cast<bool>(m_length_and_is_normalized & 1);
}
constexpr ALWAYS_INLINE void SetNormalized() {
m_length_and_is_normalized |= static_cast<size_t>(1);
}
constexpr ALWAYS_INLINE void SetNotNormalized() {
m_length_and_is_normalized &= ~static_cast<size_t>(1);
}
private:
constexpr ALWAYS_INLINE WriteBuffer(char *buffer, size_t length) : m_buffer(buffer), m_length_and_is_normalized(0) {
this->SetLength(length);
}
public:
static WriteBuffer Make(size_t length) {
if (void *alloc = ::ams::fs::impl::Allocate(length); alloc != nullptr) {
return WriteBuffer(static_cast<char *>(alloc), length);
} else {
return WriteBuffer();
}
}
private:
constexpr ALWAYS_INLINE void SetLength(size_t size) {
m_length_and_is_normalized = (m_length_and_is_normalized & 1) | (size << 1);
}
};
private:
const char *m_str;
util::TypedStorage<WriteBuffer> m_write_buffer;
size_t m_write_buffer_length_and_is_normalized;
WriteBuffer m_write_buffer;
public:
Path() : m_str(EmptyPath), m_write_buffer_length_and_is_normalized(0) {
util::ConstructAt(m_write_buffer, nullptr);
constexpr Path() : m_str(EmptyPath), m_write_buffer() {
/* ... */
}
constexpr Path(const char *s, util::ConstantInitializeTag) : m_str(s), m_write_buffer(), m_write_buffer_length_and_is_normalized(1) { }
constexpr ~Path() {
if (!std::is_constant_evaluated()) {
util::DestroyAt(m_write_buffer);
}
constexpr Path(const char *s, util::ConstantInitializeTag) : m_str(s), m_write_buffer() {
m_write_buffer.SetNormalized();
}
constexpr ~Path() { /* ... */ }
WriteBuffer ReleaseBuffer() {
/* Check pre-conditions. */
AMS_ASSERT(util::GetReference(m_write_buffer) != nullptr);
AMS_ASSERT(m_write_buffer.Get() != nullptr);
/* Reset. */
m_str = EmptyPath;
this->SetWriteBufferLength(0);
/* Return our write buffer. */
return std::move(util::GetReference(m_write_buffer));
return std::move(m_write_buffer);
}
constexpr Result SetShallowBuffer(const char *buffer) {
/* Check pre-conditions. */
AMS_ASSERT(this->GetWriteBufferLength() == 0);
AMS_ASSERT(m_write_buffer.GetLength() == 0);
/* Check the buffer is valid. */
R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument());
@ -121,7 +174,7 @@ namespace ams::fs {
R_TRY(this->Preallocate(len + 1));
/* Copy the path. */
const size_t copied = util::Strlcpy<char>(GetBuffer(util::GetReference(m_write_buffer)), rhs.GetString(), len + 1);
const size_t copied = util::Strlcpy<char>(m_write_buffer.Get(), rhs.GetString(), len + 1);
R_UNLESS(copied == len, fs::ResultUnexpectedInPathA());
/* Set normalized. */
@ -165,7 +218,7 @@ namespace ams::fs {
R_TRY(this->Preallocate(len + 1));
/* Format our path into our new buffer. */
const auto real_len = util::VSNPrintf(GetBuffer(util::GetReference(m_write_buffer)), this->GetWriteBufferLength(), fmt, vl);
const auto real_len = util::VSNPrintf(m_write_buffer.Get(), m_write_buffer.GetLength(), fmt, vl);
AMS_ASSERT(real_len == len);
AMS_UNUSED(real_len);
@ -186,8 +239,8 @@ namespace ams::fs {
R_TRY(this->InitializeImpl(path, std::strlen(path)));
/* Replace slashes as desired. */
if (const auto write_buffer_length = this->GetWriteBufferLength(); write_buffer_length > 1) {
fs::Replace(this->GetWriteBuffer(), write_buffer_length - 1, '\\', '/');
if (const auto write_buffer_length = m_write_buffer.GetLength(); write_buffer_length > 1) {
fs::Replace(m_write_buffer.Get(), write_buffer_length - 1, '\\', '/');
}
/* Set not normalized. */
@ -204,8 +257,8 @@ namespace ams::fs {
R_TRY(this->InitializeImpl(path, std::strlen(path)));
/* Replace slashes as desired. */
if (this->GetWriteBufferLength() > 1) {
if (auto *p = this->GetWriteBuffer(); p[0] == '/' && p[1] == '/') {
if (m_write_buffer.GetLength() > 1) {
if (auto *p = m_write_buffer.Get(); p[0] == '/' && p[1] == '/') {
p[0] = '\\';
p[1] = '\\';
}
@ -229,7 +282,7 @@ namespace ams::fs {
/* Replace unc as desired. */
if (m_str[0]) {
auto *p = this->GetWriteBuffer();
auto *p = m_write_buffer.Get();
/* Replace :/// -> \\ as needed. */
if (auto *sep = std::strstr(p, ":///"); sep != nullptr) {
@ -336,8 +389,8 @@ namespace ams::fs {
/* Reset our write buffer. */
WriteBuffer old_write_buffer;
if (util::GetReference(m_write_buffer) != nullptr) {
old_write_buffer = std::move(util::GetReference(m_write_buffer));
if (m_write_buffer.Get() != nullptr) {
old_write_buffer = std::move(m_write_buffer);
this->ClearBuffer();
}
@ -345,9 +398,9 @@ namespace ams::fs {
R_TRY(this->Preallocate(cur_len + 1 + child_len + 1));
/* Get our write buffer. */
auto *dst = this->GetWriteBuffer();
if (old_write_buffer != nullptr && cur_len > 0) {
util::Strlcpy<char>(dst, static_cast<char *>(*old_write_buffer), cur_len + 1);
auto *dst = m_write_buffer.Get();
if (old_write_buffer.Get() != nullptr && cur_len > 0) {
util::Strlcpy<char>(dst, old_write_buffer.Get(), cur_len + 1);
}
/* Add separator. */
@ -388,15 +441,15 @@ namespace ams::fs {
Result RemoveChild() {
/* If we don't have a write-buffer, ensure that we have one. */
if (util::GetReference(m_write_buffer) == nullptr) {
if (m_write_buffer.Get() == nullptr) {
if (const auto len = std::strlen(m_str); len > 0) {
R_TRY(this->Preallocate(len));
util::Strlcpy<char>(GetBuffer(util::GetReference(m_write_buffer)), m_str, len + 1);
util::Strlcpy<char>(m_write_buffer.Get(), m_str, len + 1);
}
}
/* Check that it's possible for us to remove a child. */
auto *p = this->GetWriteBuffer();
auto *p = m_write_buffer.Get();
s32 len = std::strlen(p);
R_UNLESS(len != 1 || (p[0] != '/' && p[0] != '.'), fs::ResultNotImplemented());
@ -436,7 +489,7 @@ namespace ams::fs {
/* If we're not normalized, normalize. */
if (!normalized) {
/* Determine necessary buffer length. */
auto len = this->GetWriteBufferLength();
auto len = m_write_buffer.GetLength();
if (flags.IsRelativePathAllowed() && fs::IsPathRelative(m_str)) {
len += 2;
}
@ -446,14 +499,14 @@ namespace ams::fs {
/* Allocate a new buffer. */
const size_t size = util::AlignUp(len, WriteBufferAlignmentLength);
auto buf = MakeWriteBuffer(size);
R_UNLESS(buf != nullptr, fs::ResultAllocationMemoryFailedMakeUnique());
auto buf = WriteBuffer::Make(size);
R_UNLESS(buf.Get() != nullptr, fs::ResultAllocationMemoryFailedMakeUnique());
/* Normalize into it. */
R_TRY(PathFormatter::Normalize(*buf, size, GetBuffer(util::GetReference(m_write_buffer)), this->GetWriteBufferLength(), flags));
R_TRY(PathFormatter::Normalize(buf.Get(), size, m_write_buffer.Get(), m_write_buffer.GetLength(), flags));
/* Set the normalized buffer as our buffer. */
this->SetModifiableBuffer(std::move(buf), size);
this->SetModifiableBuffer(std::move(buf));
}
/* Set normalized. */
@ -462,41 +515,43 @@ namespace ams::fs {
}
private:
void ClearBuffer() {
util::GetReference(m_write_buffer).reset();
this->SetWriteBufferLength(0);
m_write_buffer.ResetBuffer();
m_str = EmptyPath;
}
void SetModifiableBuffer(WriteBuffer &&buffer, size_t size) {
void SetModifiableBuffer(WriteBuffer &&buffer) {
/* Check pre-conditions. */
AMS_ASSERT(buffer.get() != nullptr);
AMS_ASSERT(size > 0);
AMS_ASSERT(util::IsAligned(size, WriteBufferAlignmentLength));
AMS_ASSERT(buffer.Get() != nullptr);
AMS_ASSERT(buffer.GetLength() > 0);
AMS_ASSERT(util::IsAligned(buffer.GetLength(), WriteBufferAlignmentLength));
/* Get whether we're normalized. */
if (m_write_buffer.IsNormalized()) {
buffer.SetNormalized();
} else {
buffer.SetNotNormalized();
}
/* Set write buffer. */
util::GetReference(m_write_buffer) = std::move(buffer);
this->SetWriteBufferLength(size);
m_str = GetBuffer(util::GetReference(m_write_buffer));
m_write_buffer = std::move(buffer);
m_str = m_write_buffer.Get();
}
constexpr void SetReadOnlyBuffer(const char *buffer) {
m_str = buffer;
if (!std::is_constant_evaluated()) {
util::GetReference(m_write_buffer) = nullptr;
this->SetWriteBufferLength(0);
}
m_write_buffer.ResetBuffer();
}
Result Preallocate(size_t length) {
/* Allocate additional space, if needed. */
if (length > this->GetWriteBufferLength()) {
if (length > m_write_buffer.GetLength()) {
/* Allocate buffer. */
const size_t size = util::AlignUp(length, WriteBufferAlignmentLength);
auto buf = MakeWriteBuffer(size);
R_UNLESS(buf != nullptr, fs::ResultAllocationMemoryFailedMakeUnique());
auto buf = WriteBuffer::Make(size);
R_UNLESS(buf.Get() != nullptr, fs::ResultAllocationMemoryFailedMakeUnique());
/* Set write buffer. */
this->SetModifiableBuffer(std::move(buf), size);
this->SetModifiableBuffer(std::move(buf));
}
R_SUCCEED();
@ -508,7 +563,7 @@ namespace ams::fs {
R_TRY(this->Preallocate(size + 1));
/* Copy the path. */
const size_t copied = util::Strlcpy<char>(this->GetWriteBuffer(), path, size + 1);
const size_t copied = util::Strlcpy<char>(m_write_buffer.Get(), path, size + 1);
R_UNLESS(copied >= size, fs::ResultUnexpectedInPathA());
} else {
/* We can just clear the buffer. */
@ -518,30 +573,20 @@ namespace ams::fs {
R_SUCCEED();
}
char *GetWriteBuffer() {
AMS_ASSERT(util::GetReference(m_write_buffer) != nullptr);
return GetBuffer(util::GetReference(m_write_buffer));
constexpr char *GetWriteBuffer() {
AMS_ASSERT(m_write_buffer.Get() != nullptr);
return m_write_buffer.Get();
}
constexpr ALWAYS_INLINE size_t GetWriteBufferLength() const {
return m_write_buffer_length_and_is_normalized >> 1;
return m_write_buffer.GetLength();
}
constexpr ALWAYS_INLINE void SetWriteBufferLength(size_t size) {
m_write_buffer_length_and_is_normalized = (m_write_buffer_length_and_is_normalized & 1) | (size << 1);
}
constexpr ALWAYS_INLINE bool IsNormalized() const { return m_write_buffer.IsNormalized(); }
constexpr ALWAYS_INLINE bool IsNormalized() const {
return static_cast<bool>(m_write_buffer_length_and_is_normalized & 1);
}
constexpr ALWAYS_INLINE void SetNormalized() { m_write_buffer.SetNormalized(); }
constexpr ALWAYS_INLINE void SetNormalized() {
m_write_buffer_length_and_is_normalized |= static_cast<size_t>(1);
}
constexpr ALWAYS_INLINE void SetNotNormalized() {
m_write_buffer_length_and_is_normalized &= ~static_cast<size_t>(1);
}
constexpr ALWAYS_INLINE void SetNotNormalized() { m_write_buffer.SetNotNormalized(); }
public:
ALWAYS_INLINE bool operator==(const fs::Path &rhs) const { return std::strcmp(this->GetString(), rhs.GetString()) == 0; }
ALWAYS_INLINE bool operator!=(const fs::Path &rhs) const { return !(*this == rhs); }