From 57d4660b5fb820beb7ce16f1eb13b06de5a1f88f Mon Sep 17 00:00:00 2001 From: Kurt McMillan Date: Thu, 26 Mar 2026 20:27:12 +0000 Subject: [PATCH 01/28] file: Add File::dioMemAlign() --- src/amd_detail/backend/fastpath.cpp | 10 ++--- src/amd_detail/file.cpp | 20 ++++++++-- src/amd_detail/file.h | 26 ++++++++++--- test/amd_detail/fastpath.cpp | 14 +++++++ test/amd_detail/handle.cpp | 57 ++++++++++++++++++++++++++++- test/amd_detail/mfile.h | 1 + 6 files changed, 111 insertions(+), 17 deletions(-) diff --git a/src/amd_detail/backend/fastpath.cpp b/src/amd_detail/backend/fastpath.cpp index 60671b21..307d9bd0 100644 --- a/src/amd_detail/backend/fastpath.cpp +++ b/src/amd_detail/backend/fastpath.cpp @@ -143,21 +143,19 @@ Fastpath::score(shared_ptr file, shared_ptr buffer, size_t size, accept_io &= 0 <= file_offset; accept_io &= 0 <= buffer_offset; - uint64_t mem_align_mask{4096 - 1}; uint64_t offset_align_mask{4096 - 1}; - #if defined(STATX_DIOALIGN) const struct statx &stx{file->getStatx()}; accept_io &= !!(stx.stx_mask & STATX_DIOALIGN); accept_io &= stx.stx_dio_offset_align && stx.stx_dio_mem_align; - mem_align_mask = stx.stx_dio_mem_align - 1; offset_align_mask = stx.stx_dio_offset_align - 1; #endif - accept_io &= !(size & offset_align_mask); accept_io &= !(file_offset & static_cast(offset_align_mask)); - auto buffer_address{reinterpret_cast(buffer->getBuffer())}; - accept_io &= !((buffer_address + buffer_offset) & static_cast(mem_align_mask)); + + const uint32_t dio_mem_align{file->dioMemAlign()}; + const auto mem_addr{reinterpret_cast(buffer->getBuffer()) + buffer_offset}; + accept_io &= dio_mem_align && !(mem_addr & (dio_mem_align - 1)); return accept_io ? 100 : -1; } diff --git a/src/amd_detail/file.cpp b/src/amd_detail/file.cpp index f2c3f452..845b09a1 100644 --- a/src/amd_detail/file.cpp +++ b/src/amd_detail/file.cpp @@ -16,6 +16,7 @@ #include #include #include +#include using namespace std; @@ -31,7 +32,12 @@ UnregisteredFile::UnregisteredFile(int fd) #endif )}, flags{Context::get()->fcntl(fd, F_GETFL, 0)}, - mountinfo{Context::get()->getMountInfo(makedev(stx.stx_dev_major, stx.stx_dev_minor))} + mountinfo{Context::get()->getMountInfo(makedev(stx.stx_dev_major, stx.stx_dev_minor))}, +#if defined(STATX_DIOALIGN) + m_dio_mem_align{stx.stx_mask & STATX_DIOALIGN ? stx.stx_dio_mem_align : 4096} +#else + m_dio_mem_align{4096} +#endif { std::string path = "/proc/self/fd/" + std::to_string(fd); @@ -50,7 +56,8 @@ UnregisteredFile::UnregisteredFile(int fd) if (e.code().value() != EINVAL) { throw; } - unbuffered_fd = nullopt; + unbuffered_fd = nullopt; + m_dio_mem_align = 0; } } } @@ -63,7 +70,8 @@ IFile::getHandle() const File::File(UnregisteredFile &&uf, const PassKey &) : client_fd{std::move(uf.client_fd)}, buffered_fd{std::move(uf.buffered_fd)}, - unbuffered_fd{std::move(uf.unbuffered_fd)}, stx{uf.stx}, status_flags{uf.flags}, mountinfo{uf.mountinfo} + unbuffered_fd{std::move(uf.unbuffered_fd)}, stx{uf.stx}, status_flags{uf.flags}, + mountinfo{uf.mountinfo}, m_dio_mem_align{uf.m_dio_mem_align} { } @@ -106,6 +114,12 @@ File::getMountInfo() const return mountinfo; } +uint32_t +File::dioMemAlign() const noexcept +{ + return m_dio_mem_align; +} + shared_ptr FileMap::getFile(hipFileHandle_t fh) { diff --git a/src/amd_detail/file.h b/src/amd_detail/file.h index eaafdb17..86b0cfd8 100644 --- a/src/amd_detail/file.h +++ b/src/amd_detail/file.h @@ -67,6 +67,10 @@ class UnregisteredFile { /// @brief Information obtained from /proc/self/mountinfo std::optional mountinfo; + + /// @brief Memory alignment (in bytes) requirement for direct IO. If the file does not support direct IO, + /// this will be 0. If alignment information is not available from statx, this will be set to 4096. + uint32_t m_dio_mem_align; }; class IFile { @@ -77,12 +81,13 @@ class IFile { /// @return The handle for this file virtual hipFileHandle_t getHandle() const; - virtual int getClientFd() const = 0; - virtual int getBufferedFd() const = 0; - virtual std::optional getUnbufferedFd() const = 0; - virtual const struct statx &getStatx() const noexcept = 0; - virtual int getStatusFlags() const = 0; - virtual std::optional getMountInfo() const = 0; + virtual int getClientFd() const = 0; + virtual int getBufferedFd() const = 0; + virtual std::optional getUnbufferedFd() const = 0; + virtual const struct statx &getStatx() const noexcept = 0; + virtual int getStatusFlags() const = 0; + virtual std::optional getMountInfo() const = 0; + virtual uint32_t dioMemAlign() const noexcept = 0; }; class FileMap; @@ -107,6 +112,11 @@ class File : public IFile { virtual int getStatusFlags() const override; virtual std::optional getMountInfo() const override; + /// @brief Get the memory (in bytes) alignment requirement for direct IO on this file. If the file does + /// not support direct IO, this will return 0. + /// @return The memory alignment (in bytes) requirement for direct IO, 0 if direct IO is not supported + virtual uint32_t dioMemAlign() const noexcept override; + /// @brief Construct a registered file /// @param uf An unregistered file /// @param k Key class instance (see passkey.h) @@ -132,6 +142,10 @@ class File : public IFile { /// @brief Mount information for the filesystem backing fd std::optional mountinfo; + + /// @brief Memory alignment (in bytes) requirement for direct IO. If the file does not support direct IO, + /// this will be 0. + uint32_t m_dio_mem_align; }; class FileMap { diff --git a/test/amd_detail/fastpath.cpp b/test/amd_detail/fastpath.cpp index d02aa31c..246f61ac 100644 --- a/test/amd_detail/fastpath.cpp +++ b/test/amd_detail/fastpath.cpp @@ -110,6 +110,7 @@ TEST_F(FastpathTest, ScoreAcceptsIoWithDefaults) EXPECT_CALL(*mfile, getStatx).WillOnce(ReturnRef(DEFAULT_STATX)); #endif EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); + EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET), SCORE_ACCEPT); @@ -124,6 +125,7 @@ TEST_F(FastpathTest, ScoreRejectsIoIfFastpathIsDisabled) EXPECT_CALL(*mfile, getStatx).WillOnce(ReturnRef(DEFAULT_STATX)); #endif EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); + EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET), SCORE_REJECT); @@ -138,6 +140,7 @@ TEST_F(FastpathTest, ScoreRejectsIoIfUnbufferedFdNotAvailable) EXPECT_CALL(*mfile, getStatx).WillOnce(ReturnRef(DEFAULT_STATX)); #endif EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); + EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET), SCORE_REJECT); @@ -152,6 +155,7 @@ TEST_F(FastpathTest, ScoreRejectsIoWithNegativeAlignedFileOffset) EXPECT_CALL(*mfile, getStatx).WillOnce(ReturnRef(DEFAULT_STATX)); #endif EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); + EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, -static_cast(DEFAULT_OFFSET_ALIGN), DEFAULT_BUFFER_OFFSET), @@ -167,6 +171,7 @@ TEST_F(FastpathTest, ScoreRejectsIoWithNegativeAlignedBufferOffset) EXPECT_CALL(*mfile, getStatx).WillOnce(ReturnRef(DEFAULT_STATX)); #endif EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); + EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, -static_cast(DEFAULT_MEM_ALIGN)), @@ -186,6 +191,7 @@ TEST_F(FastpathTest, ScoreRejectsIoIfBufferAddressPlusBufferOffsetIsUnaligned) EXPECT_CALL(*mbuffer, getBuffer) .WillOnce(Return(reinterpret_cast(reinterpret_cast(DEFAULT_BUFFER_ADDR) + (DEFAULT_MEM_ALIGN >> 1)))); + EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, static_cast(DEFAULT_MEM_ALIGN)), @@ -203,6 +209,7 @@ TEST_P(FastpathSupportedHipMemoryParam, Score) EXPECT_CALL(*mfile, getStatx).WillOnce(ReturnRef(DEFAULT_STATX)); #endif EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); + EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET), SCORE_ACCEPT); @@ -221,6 +228,7 @@ TEST_P(FastpathUnsupportedHipMemoryParam, Score) EXPECT_CALL(*mfile, getStatx).WillOnce(ReturnRef(DEFAULT_STATX)); #endif EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); + EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET), SCORE_REJECT); @@ -240,6 +248,7 @@ TEST_P(FastpathAlignedIoSizesParam, Score) EXPECT_CALL(*mfile, getStatx).WillOnce(ReturnRef(DEFAULT_STATX)); #endif EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); + EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); ASSERT_EQ(Fastpath().score(mfile, mbuffer, GetParam(), DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET), SCORE_ACCEPT); @@ -260,6 +269,7 @@ TEST_P(FastpathUnalignedIoSizesParam, Score) EXPECT_CALL(*mfile, getStatx).WillOnce(ReturnRef(DEFAULT_STATX)); #endif EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); + EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); ASSERT_EQ(Fastpath().score(mfile, mbuffer, GetParam(), DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET), SCORE_REJECT); @@ -280,6 +290,7 @@ TEST_P(FastpathAlignedFileOffsetsParam, Score) EXPECT_CALL(*mfile, getStatx).WillOnce(ReturnRef(DEFAULT_STATX)); #endif EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); + EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, GetParam(), DEFAULT_BUFFER_OFFSET), GetParam() >= 0 ? SCORE_ACCEPT : SCORE_REJECT); @@ -302,6 +313,7 @@ TEST_P(FastpathUnalignedFileOffsetsParam, Score) EXPECT_CALL(*mfile, getStatx).WillOnce(ReturnRef(DEFAULT_STATX)); #endif EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); + EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, GetParam(), DEFAULT_BUFFER_OFFSET), SCORE_REJECT); @@ -326,6 +338,7 @@ TEST_P(FastpathAlignedBufferOffsetsParam, Score) EXPECT_CALL(*mfile, getStatx).WillOnce(ReturnRef(DEFAULT_STATX)); #endif EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); + EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, GetParam()), GetParam() >= 0 ? SCORE_ACCEPT : SCORE_REJECT); @@ -349,6 +362,7 @@ TEST_P(FastpathUnalignedBufferOffsetsParam, Score) EXPECT_CALL(*mfile, getStatx).WillOnce(ReturnRef(DEFAULT_STATX)); #endif EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); + EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, GetParam()), SCORE_REJECT); diff --git a/test/amd_detail/handle.cpp b/test/amd_detail/handle.cpp index a067a5c1..d1431ccc 100644 --- a/test/amd_detail/handle.cpp +++ b/test/amd_detail/handle.cpp @@ -168,8 +168,14 @@ TEST_F(HipFileHandle, file_initialization) { int fd{0x12345678}; int fd_flags{~O_DIRECT}; // All flags, except O_DIRECT, set - struct statx stxbuf; - memset(&stxbuf, 0xA5, sizeof(stxbuf)); + struct statx stxbuf {}; +#if defined(STATX_DIOALIGN) + stxbuf.stx_mask = STATX_TYPE | STATX_MODE | STATX_DIOALIGN; + stxbuf.stx_dio_mem_align = 4; + stxbuf.stx_dio_offset_align = 512; +#else + stxbuf.stx_mask = STATX_TYPE | STATX_MODE; +#endif // In this test, the registered file is destroyed _after_ the mocks are // destroyed. Use an eventfd so that when FileDescriptor calls close, it @@ -200,6 +206,11 @@ TEST_F(HipFileHandle, file_initialization) EXPECT_EQ(mountinfo.type, file->getMountInfo().value().type); EXPECT_EQ(mountinfo.options.ext4.journaling_mode, file->getMountInfo().value().options.ext4.journaling_mode); +#if defined(STATX_DIOALIGN) + EXPECT_EQ(file->dioMemAlign(), stxbuf.stx_dio_mem_align); +#else + EXPECT_EQ(file->dioMemAlign(), 4096); +#endif } TEST_F(HipFileHandle, register_handle_internal_linux_fd_already_registered) @@ -445,4 +456,46 @@ TEST_F(HipFileHandle, UnregisteredFileConstructorThrowsOnErrOtherThanEinval) ASSERT_THROW(UnregisteredFile{777777}, std::system_error); } +#if defined(STATX_DIOALIGN) +TEST_F(HipFileHandle, UnregisteredFileDioMemAlignMatchesStatxDioMemAlign) +{ + int open_fd{888888}; + struct statx stxbuf {}; + stxbuf.stx_mask = STATX_TYPE | STATX_MODE | STATX_DIOALIGN; + stxbuf.stx_dio_mem_align = 1234; + ExpectUnregisteredFileBuilder(msys, mlibmounthelper) + .fd_flags(O_DIRECT) + .open_fd(open_fd) + .statx(stxbuf) + .build(); + UnregisteredFile uf{777777}; + ASSERT_EQ(uf.m_dio_mem_align, 1234); + + EXPECT_CALL(msys, close(open_fd)); +} +#endif + +TEST_F(HipFileHandle, UnregisteredFileDioMemAlignIsPageSizeIfStatxDoesntHaveDioAlign) +{ + int open_fd{888888}; + struct statx stxbuf {}; + stxbuf.stx_mask = STATX_TYPE | STATX_MODE; + ExpectUnregisteredFileBuilder(msys, mlibmounthelper) + .fd_flags(O_DIRECT) + .open_fd(open_fd) + .statx(stxbuf) + .build(); + UnregisteredFile uf{777777}; + ASSERT_EQ(uf.m_dio_mem_align, 4096); + + EXPECT_CALL(msys, close(open_fd)); +} + +TEST_F(HipFileHandle, UnregisteredFileDioMemAlignIsZeroIfUnableToOpenUnbufferedFd) +{ + ExpectUnregisteredFileBuilder(msys, mlibmounthelper).fd_flags(~O_DIRECT).open_throws(EINVAL).build(); + UnregisteredFile uf{777777}; + ASSERT_EQ(uf.m_dio_mem_align, 0); +} + HIPFILE_WARN_NO_GLOBAL_CTOR_ON diff --git a/test/amd_detail/mfile.h b/test/amd_detail/mfile.h index 4a8a50dc..f9d34e76 100644 --- a/test/amd_detail/mfile.h +++ b/test/amd_detail/mfile.h @@ -25,6 +25,7 @@ class MFile : public IFile { MOCK_METHOD(const struct statx &, getStatx, (), (const, noexcept, override)); MOCK_METHOD(int, getStatusFlags, (), (const, override)); MOCK_METHOD(std::optional, getMountInfo, (), (const, override)); + MOCK_METHOD(uint32_t, dioMemAlign, (), (const, noexcept, override)); }; class MFileMap : public FileMap { From c3e8917f655b894292b69c00534094cdb22b5eff Mon Sep 17 00:00:00 2001 From: Kurt McMillan Date: Thu, 26 Mar 2026 21:56:40 +0000 Subject: [PATCH 02/28] file: Add File::dioOffsetAlign() --- src/amd_detail/backend/fastpath.cpp | 12 ++----- src/amd_detail/file.cpp | 18 +++++++--- src/amd_detail/file.h | 30 ++++++++++++---- test/amd_detail/fastpath.cpp | 56 ++++++++--------------------- test/amd_detail/handle.cpp | 44 +++++++++++++++++++++++ test/amd_detail/mfile.h | 1 + 6 files changed, 98 insertions(+), 63 deletions(-) diff --git a/src/amd_detail/backend/fastpath.cpp b/src/amd_detail/backend/fastpath.cpp index 307d9bd0..89b1ce72 100644 --- a/src/amd_detail/backend/fastpath.cpp +++ b/src/amd_detail/backend/fastpath.cpp @@ -143,15 +143,9 @@ Fastpath::score(shared_ptr file, shared_ptr buffer, size_t size, accept_io &= 0 <= file_offset; accept_io &= 0 <= buffer_offset; - uint64_t offset_align_mask{4096 - 1}; -#if defined(STATX_DIOALIGN) - const struct statx &stx{file->getStatx()}; - accept_io &= !!(stx.stx_mask & STATX_DIOALIGN); - accept_io &= stx.stx_dio_offset_align && stx.stx_dio_mem_align; - offset_align_mask = stx.stx_dio_offset_align - 1; -#endif - accept_io &= !(size & offset_align_mask); - accept_io &= !(file_offset & static_cast(offset_align_mask)); + const uint32_t dio_offset_align{file->dioOffsetAlign()}; + accept_io &= dio_offset_align && !(file_offset & (dio_offset_align - 1)); + accept_io &= dio_offset_align && !(size & (dio_offset_align - 1)); const uint32_t dio_mem_align{file->dioMemAlign()}; const auto mem_addr{reinterpret_cast(buffer->getBuffer()) + buffer_offset}; diff --git a/src/amd_detail/file.cpp b/src/amd_detail/file.cpp index 845b09a1..af0ca6ab 100644 --- a/src/amd_detail/file.cpp +++ b/src/amd_detail/file.cpp @@ -34,9 +34,10 @@ UnregisteredFile::UnregisteredFile(int fd) flags{Context::get()->fcntl(fd, F_GETFL, 0)}, mountinfo{Context::get()->getMountInfo(makedev(stx.stx_dev_major, stx.stx_dev_minor))}, #if defined(STATX_DIOALIGN) - m_dio_mem_align{stx.stx_mask & STATX_DIOALIGN ? stx.stx_dio_mem_align : 4096} + m_dio_mem_align{stx.stx_mask & STATX_DIOALIGN ? stx.stx_dio_mem_align : 4096}, + m_dio_offset_align{stx.stx_mask & STATX_DIOALIGN ? stx.stx_dio_offset_align : 4096} #else - m_dio_mem_align{4096} + m_dio_mem_align{4096}, m_dio_offset_align{4096} #endif { std::string path = "/proc/self/fd/" + std::to_string(fd); @@ -56,8 +57,9 @@ UnregisteredFile::UnregisteredFile(int fd) if (e.code().value() != EINVAL) { throw; } - unbuffered_fd = nullopt; - m_dio_mem_align = 0; + unbuffered_fd = nullopt; + m_dio_mem_align = 0; + m_dio_offset_align = 0; } } } @@ -71,7 +73,7 @@ IFile::getHandle() const File::File(UnregisteredFile &&uf, const PassKey &) : client_fd{std::move(uf.client_fd)}, buffered_fd{std::move(uf.buffered_fd)}, unbuffered_fd{std::move(uf.unbuffered_fd)}, stx{uf.stx}, status_flags{uf.flags}, - mountinfo{uf.mountinfo}, m_dio_mem_align{uf.m_dio_mem_align} + mountinfo{uf.mountinfo}, m_dio_mem_align{uf.m_dio_mem_align}, m_dio_offset_align{uf.m_dio_offset_align} { } @@ -120,6 +122,12 @@ File::dioMemAlign() const noexcept return m_dio_mem_align; } +uint32_t +File::dioOffsetAlign() const noexcept +{ + return m_dio_offset_align; +} + shared_ptr FileMap::getFile(hipFileHandle_t fh) { diff --git a/src/amd_detail/file.h b/src/amd_detail/file.h index 86b0cfd8..86c2d0a3 100644 --- a/src/amd_detail/file.h +++ b/src/amd_detail/file.h @@ -71,6 +71,11 @@ class UnregisteredFile { /// @brief Memory alignment (in bytes) requirement for direct IO. If the file does not support direct IO, /// this will be 0. If alignment information is not available from statx, this will be set to 4096. uint32_t m_dio_mem_align; + + /// @brief Alignment (in bytes) required for file offsets and IO segment lengths for direct IO. If the + /// file does not support direct IO, this will be 0. If alignment information is not available from statx, + /// this will be set to 4096. + uint32_t m_dio_offset_align; }; class IFile { @@ -81,13 +86,14 @@ class IFile { /// @return The handle for this file virtual hipFileHandle_t getHandle() const; - virtual int getClientFd() const = 0; - virtual int getBufferedFd() const = 0; - virtual std::optional getUnbufferedFd() const = 0; - virtual const struct statx &getStatx() const noexcept = 0; - virtual int getStatusFlags() const = 0; - virtual std::optional getMountInfo() const = 0; - virtual uint32_t dioMemAlign() const noexcept = 0; + virtual int getClientFd() const = 0; + virtual int getBufferedFd() const = 0; + virtual std::optional getUnbufferedFd() const = 0; + virtual const struct statx &getStatx() const noexcept = 0; + virtual int getStatusFlags() const = 0; + virtual std::optional getMountInfo() const = 0; + virtual uint32_t dioMemAlign() const noexcept = 0; + virtual uint32_t dioOffsetAlign() const noexcept = 0; }; class FileMap; @@ -117,6 +123,12 @@ class File : public IFile { /// @return The memory alignment (in bytes) requirement for direct IO, 0 if direct IO is not supported virtual uint32_t dioMemAlign() const noexcept override; + /// @brief The alignment (in bytes) required for file offsets and I/O segment lengths for direct I/O + /// (O_DIRECT) on this file, or 0 if direct I/O is not supported on this file + /// @return The alignment (in bytes) required for file offsets and I/O segment lengths for direct I/O, 0 + /// if direct I/O is not supported + virtual uint32_t dioOffsetAlign() const noexcept override; + /// @brief Construct a registered file /// @param uf An unregistered file /// @param k Key class instance (see passkey.h) @@ -146,6 +158,10 @@ class File : public IFile { /// @brief Memory alignment (in bytes) requirement for direct IO. If the file does not support direct IO, /// this will be 0. uint32_t m_dio_mem_align; + + /// @brief Alignment (in bytes) required for file offsets and I/O segment lengths for direct I/O + /// (O_DIRECT). + uint32_t m_dio_offset_align; }; class FileMap { diff --git a/test/amd_detail/fastpath.cpp b/test/amd_detail/fastpath.cpp index 246f61ac..0bde6651 100644 --- a/test/amd_detail/fastpath.cpp +++ b/test/amd_detail/fastpath.cpp @@ -106,9 +106,7 @@ TEST_F(FastpathTest, ScoreAcceptsIoWithDefaults) EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); -#if defined(STATX_DIOALIGN) - EXPECT_CALL(*mfile, getStatx).WillOnce(ReturnRef(DEFAULT_STATX)); -#endif + EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); @@ -121,9 +119,7 @@ TEST_F(FastpathTest, ScoreRejectsIoIfFastpathIsDisabled) EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(false)); EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); -#if defined(STATX_DIOALIGN) - EXPECT_CALL(*mfile, getStatx).WillOnce(ReturnRef(DEFAULT_STATX)); -#endif + EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); @@ -136,9 +132,7 @@ TEST_F(FastpathTest, ScoreRejectsIoIfUnbufferedFdNotAvailable) EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(nullopt)); EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); -#if defined(STATX_DIOALIGN) - EXPECT_CALL(*mfile, getStatx).WillOnce(ReturnRef(DEFAULT_STATX)); -#endif + EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); @@ -151,9 +145,7 @@ TEST_F(FastpathTest, ScoreRejectsIoWithNegativeAlignedFileOffset) EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); -#if defined(STATX_DIOALIGN) - EXPECT_CALL(*mfile, getStatx).WillOnce(ReturnRef(DEFAULT_STATX)); -#endif + EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); @@ -167,9 +159,7 @@ TEST_F(FastpathTest, ScoreRejectsIoWithNegativeAlignedBufferOffset) EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); -#if defined(STATX_DIOALIGN) - EXPECT_CALL(*mfile, getStatx).WillOnce(ReturnRef(DEFAULT_STATX)); -#endif + EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); @@ -183,9 +173,7 @@ TEST_F(FastpathTest, ScoreRejectsIoIfBufferAddressPlusBufferOffsetIsUnaligned) EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); -#if defined(STATX_DIOALIGN) - EXPECT_CALL(*mfile, getStatx).WillOnce(ReturnRef(DEFAULT_STATX)); -#endif + EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); // The DEFAULT_BUFFER_ADDR is DEFAULT_MEM_ALIGN aligned. Ensure that this // test's buffer is not DEFAULT_MEM_ALIGN aligned. EXPECT_CALL(*mbuffer, getBuffer) @@ -205,9 +193,7 @@ TEST_P(FastpathSupportedHipMemoryParam, Score) EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); EXPECT_CALL(*mbuffer, getType).WillOnce(Return(GetParam())); -#if defined(STATX_DIOALIGN) - EXPECT_CALL(*mfile, getStatx).WillOnce(ReturnRef(DEFAULT_STATX)); -#endif + EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); @@ -224,9 +210,7 @@ TEST_P(FastpathUnsupportedHipMemoryParam, Score) EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); EXPECT_CALL(*mbuffer, getType).WillOnce(Return(GetParam())); -#if defined(STATX_DIOALIGN) - EXPECT_CALL(*mfile, getStatx).WillOnce(ReturnRef(DEFAULT_STATX)); -#endif + EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); @@ -244,9 +228,7 @@ TEST_P(FastpathAlignedIoSizesParam, Score) EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); -#if defined(STATX_DIOALIGN) - EXPECT_CALL(*mfile, getStatx).WillOnce(ReturnRef(DEFAULT_STATX)); -#endif + EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); @@ -265,9 +247,7 @@ TEST_P(FastpathUnalignedIoSizesParam, Score) EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); -#if defined(STATX_DIOALIGN) - EXPECT_CALL(*mfile, getStatx).WillOnce(ReturnRef(DEFAULT_STATX)); -#endif + EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); @@ -286,9 +266,7 @@ TEST_P(FastpathAlignedFileOffsetsParam, Score) EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); -#if defined(STATX_DIOALIGN) - EXPECT_CALL(*mfile, getStatx).WillOnce(ReturnRef(DEFAULT_STATX)); -#endif + EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); @@ -309,9 +287,7 @@ TEST_P(FastpathUnalignedFileOffsetsParam, Score) EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); -#if defined(STATX_DIOALIGN) - EXPECT_CALL(*mfile, getStatx).WillOnce(ReturnRef(DEFAULT_STATX)); -#endif + EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); @@ -334,9 +310,7 @@ TEST_P(FastpathAlignedBufferOffsetsParam, Score) EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); -#if defined(STATX_DIOALIGN) - EXPECT_CALL(*mfile, getStatx).WillOnce(ReturnRef(DEFAULT_STATX)); -#endif + EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); @@ -358,9 +332,7 @@ TEST_P(FastpathUnalignedBufferOffsetsParam, Score) EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); -#if defined(STATX_DIOALIGN) - EXPECT_CALL(*mfile, getStatx).WillOnce(ReturnRef(DEFAULT_STATX)); -#endif + EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); diff --git a/test/amd_detail/handle.cpp b/test/amd_detail/handle.cpp index d1431ccc..c4d2ff84 100644 --- a/test/amd_detail/handle.cpp +++ b/test/amd_detail/handle.cpp @@ -208,8 +208,10 @@ TEST_F(HipFileHandle, file_initialization) file->getMountInfo().value().options.ext4.journaling_mode); #if defined(STATX_DIOALIGN) EXPECT_EQ(file->dioMemAlign(), stxbuf.stx_dio_mem_align); + EXPECT_EQ(file->dioOffsetAlign(), stxbuf.stx_dio_offset_align); #else EXPECT_EQ(file->dioMemAlign(), 4096); + EXPECT_EQ(file->dioOffsetAlign(), 4096); #endif } @@ -475,6 +477,25 @@ TEST_F(HipFileHandle, UnregisteredFileDioMemAlignMatchesStatxDioMemAlign) } #endif +#if defined(STATX_DIOALIGN) +TEST_F(HipFileHandle, UnregisteredFileDioOffsetAlignMatchesStatxDioOffsetAlign) +{ + int open_fd{888888}; + struct statx stxbuf {}; + stxbuf.stx_mask = STATX_TYPE | STATX_MODE | STATX_DIOALIGN; + stxbuf.stx_dio_offset_align = 1234; + ExpectUnregisteredFileBuilder(msys, mlibmounthelper) + .fd_flags(O_DIRECT) + .open_fd(open_fd) + .statx(stxbuf) + .build(); + UnregisteredFile uf{777777}; + ASSERT_EQ(uf.m_dio_offset_align, 1234); + + EXPECT_CALL(msys, close(open_fd)); +} +#endif + TEST_F(HipFileHandle, UnregisteredFileDioMemAlignIsPageSizeIfStatxDoesntHaveDioAlign) { int open_fd{888888}; @@ -491,6 +512,22 @@ TEST_F(HipFileHandle, UnregisteredFileDioMemAlignIsPageSizeIfStatxDoesntHaveDioA EXPECT_CALL(msys, close(open_fd)); } +TEST_F(HipFileHandle, UnregisteredFileDioOffsetAlignIsPageSizeIfStatxDoesntHaveDioAlign) +{ + int open_fd{888888}; + struct statx stxbuf {}; + stxbuf.stx_mask = STATX_TYPE | STATX_MODE; + ExpectUnregisteredFileBuilder(msys, mlibmounthelper) + .fd_flags(O_DIRECT) + .open_fd(open_fd) + .statx(stxbuf) + .build(); + UnregisteredFile uf{777777}; + ASSERT_EQ(uf.m_dio_offset_align, 4096); + + EXPECT_CALL(msys, close(open_fd)); +} + TEST_F(HipFileHandle, UnregisteredFileDioMemAlignIsZeroIfUnableToOpenUnbufferedFd) { ExpectUnregisteredFileBuilder(msys, mlibmounthelper).fd_flags(~O_DIRECT).open_throws(EINVAL).build(); @@ -498,4 +535,11 @@ TEST_F(HipFileHandle, UnregisteredFileDioMemAlignIsZeroIfUnableToOpenUnbufferedF ASSERT_EQ(uf.m_dio_mem_align, 0); } +TEST_F(HipFileHandle, UnregisteredFileDioOffsetAlignIsZeroIfUnableToOpenUnbufferedFd) +{ + ExpectUnregisteredFileBuilder(msys, mlibmounthelper).fd_flags(~O_DIRECT).open_throws(EINVAL).build(); + UnregisteredFile uf{777777}; + ASSERT_EQ(uf.m_dio_offset_align, 0); +} + HIPFILE_WARN_NO_GLOBAL_CTOR_ON diff --git a/test/amd_detail/mfile.h b/test/amd_detail/mfile.h index f9d34e76..ded04f53 100644 --- a/test/amd_detail/mfile.h +++ b/test/amd_detail/mfile.h @@ -26,6 +26,7 @@ class MFile : public IFile { MOCK_METHOD(int, getStatusFlags, (), (const, override)); MOCK_METHOD(std::optional, getMountInfo, (), (const, override)); MOCK_METHOD(uint32_t, dioMemAlign, (), (const, noexcept, override)); + MOCK_METHOD(uint32_t, dioOffsetAlign, (), (const, noexcept, override)); }; class MFileMap : public FileMap { From 92f1457cc7a177fcca7cc3ab2bb72cc2838d322b Mon Sep 17 00:00:00 2001 From: Kurt McMillan Date: Fri, 27 Mar 2026 17:42:32 +0000 Subject: [PATCH 03/28] file: Add File::isBlockDevice() --- src/amd_detail/file.cpp | 9 ++++- src/amd_detail/file.h | 9 +++++ test/amd_detail/handle.cpp | 71 ++++++++++++++++++++++++++++++++++++++ test/amd_detail/mfile.h | 1 + 4 files changed, 89 insertions(+), 1 deletion(-) diff --git a/src/amd_detail/file.cpp b/src/amd_detail/file.cpp index af0ca6ab..e92d30a7 100644 --- a/src/amd_detail/file.cpp +++ b/src/amd_detail/file.cpp @@ -73,7 +73,8 @@ IFile::getHandle() const File::File(UnregisteredFile &&uf, const PassKey &) : client_fd{std::move(uf.client_fd)}, buffered_fd{std::move(uf.buffered_fd)}, unbuffered_fd{std::move(uf.unbuffered_fd)}, stx{uf.stx}, status_flags{uf.flags}, - mountinfo{uf.mountinfo}, m_dio_mem_align{uf.m_dio_mem_align}, m_dio_offset_align{uf.m_dio_offset_align} + mountinfo{uf.mountinfo}, m_dio_mem_align{uf.m_dio_mem_align}, m_dio_offset_align{uf.m_dio_offset_align}, + m_is_block_device{(uf.stx.stx_mask & STATX_TYPE) && S_ISBLK(uf.stx.stx_mode)} { } @@ -128,6 +129,12 @@ File::dioOffsetAlign() const noexcept return m_dio_offset_align; } +bool +File::isBlockDevice() const noexcept +{ + return m_is_block_device; +} + shared_ptr FileMap::getFile(hipFileHandle_t fh) { diff --git a/src/amd_detail/file.h b/src/amd_detail/file.h index 86c2d0a3..10904b26 100644 --- a/src/amd_detail/file.h +++ b/src/amd_detail/file.h @@ -94,6 +94,7 @@ class IFile { virtual std::optional getMountInfo() const = 0; virtual uint32_t dioMemAlign() const noexcept = 0; virtual uint32_t dioOffsetAlign() const noexcept = 0; + virtual bool isBlockDevice() const noexcept = 0; }; class FileMap; @@ -129,6 +130,11 @@ class File : public IFile { /// if direct I/O is not supported virtual uint32_t dioOffsetAlign() const noexcept override; + /// @brief Whether this file is a block device. If statx did not return type information, this will return + /// false. + /// @return True if the file is a block device, false otherwise + virtual bool isBlockDevice() const noexcept override; + /// @brief Construct a registered file /// @param uf An unregistered file /// @param k Key class instance (see passkey.h) @@ -162,6 +168,9 @@ class File : public IFile { /// @brief Alignment (in bytes) required for file offsets and I/O segment lengths for direct I/O /// (O_DIRECT). uint32_t m_dio_offset_align; + + /// @brief Whether statx reported that the file is a block device + bool m_is_block_device; }; class FileMap { diff --git a/test/amd_detail/handle.cpp b/test/amd_detail/handle.cpp index c4d2ff84..ebb7de30 100644 --- a/test/amd_detail/handle.cpp +++ b/test/amd_detail/handle.cpp @@ -176,6 +176,7 @@ TEST_F(HipFileHandle, file_initialization) #else stxbuf.stx_mask = STATX_TYPE | STATX_MODE; #endif + stxbuf.stx_mode = S_IFREG; // In this test, the registered file is destroyed _after_ the mocks are // destroyed. Use an eventfd so that when FileDescriptor calls close, it @@ -213,6 +214,7 @@ TEST_F(HipFileHandle, file_initialization) EXPECT_EQ(file->dioMemAlign(), 4096); EXPECT_EQ(file->dioOffsetAlign(), 4096); #endif + EXPECT_FALSE(file->isBlockDevice()); } TEST_F(HipFileHandle, register_handle_internal_linux_fd_already_registered) @@ -542,4 +544,73 @@ TEST_F(HipFileHandle, UnregisteredFileDioOffsetAlignIsZeroIfUnableToOpenUnbuffer ASSERT_EQ(uf.m_dio_offset_align, 0); } +TEST_F(HipFileHandle, IsBlockDeviceReturnsTrueForBlockDevice) +{ + int fd{0xBADF00D}; + struct statx stxbuf {}; + stxbuf.stx_mask = STATX_TYPE; + stxbuf.stx_mode = S_IFBLK; + + // Return an eventfd as the file descriptor for the bufferd fd open so that when + // FileDescriptor calls close, it has a valid file descriptor to close. + int open_fd{eventfd(0, 0)}; + ASSERT_NE(open_fd, -1); + + ExpectUnregisteredFileBuilder(msys, mlibmounthelper) + .fd_flags(O_DIRECT) + .statx(stxbuf) + .open_fd(open_fd) + .build(); + auto fh{Context::get()->registerFile(fd)}; + auto file{Context::get()->getFile(fh)}; + + EXPECT_TRUE(file->isBlockDevice()); +} + +TEST_F(HipFileHandle, IsBlockDeviceReturnsFalseForRegularFile) +{ + int fd{0xBADF00D}; + struct statx stxbuf {}; + stxbuf.stx_mask = STATX_TYPE; + stxbuf.stx_mode = S_IFREG; + + // Return an eventfd as the file descriptor for the bufferd fd open so that when + // FileDescriptor calls close, it has a valid file descriptor to close. + int open_fd{eventfd(0, 0)}; + ASSERT_NE(open_fd, -1); + + ExpectUnregisteredFileBuilder(msys, mlibmounthelper) + .fd_flags(O_DIRECT) + .statx(stxbuf) + .open_fd(open_fd) + .build(); + auto fh{Context::get()->registerFile(fd)}; + auto file{Context::get()->getFile(fh)}; + + EXPECT_FALSE(file->isBlockDevice()); +} + +TEST_F(HipFileHandle, IsBlockDeviceReturnsFalseWhenStatxTypeNotAvailable) +{ + int fd{0xBADF00D}; + struct statx stxbuf {}; + stxbuf.stx_mask = 0; + stxbuf.stx_mode = S_IFBLK; + + // Return an eventfd as the file descriptor for the bufferd fd open so that when + // FileDescriptor calls close, it has a valid file descriptor to close. + int open_fd{eventfd(0, 0)}; + ASSERT_NE(open_fd, -1); + + ExpectUnregisteredFileBuilder(msys, mlibmounthelper) + .fd_flags(O_DIRECT) + .statx(stxbuf) + .open_fd(open_fd) + .build(); + auto fh{Context::get()->registerFile(fd)}; + auto file{Context::get()->getFile(fh)}; + + EXPECT_FALSE(file->isBlockDevice()); +} + HIPFILE_WARN_NO_GLOBAL_CTOR_ON diff --git a/test/amd_detail/mfile.h b/test/amd_detail/mfile.h index ded04f53..543d937c 100644 --- a/test/amd_detail/mfile.h +++ b/test/amd_detail/mfile.h @@ -27,6 +27,7 @@ class MFile : public IFile { MOCK_METHOD(std::optional, getMountInfo, (), (const, override)); MOCK_METHOD(uint32_t, dioMemAlign, (), (const, noexcept, override)); MOCK_METHOD(uint32_t, dioOffsetAlign, (), (const, noexcept, override)); + MOCK_METHOD(bool, isBlockDevice, (), (const, noexcept, override)); }; class MFileMap : public FileMap { From cf4688f76c06a991af5cf5a27ff46736ac5f1214 Mon Sep 17 00:00:00 2001 From: Kurt McMillan Date: Fri, 27 Mar 2026 17:42:32 +0000 Subject: [PATCH 04/28] file: Add File::isRegularFile() --- src/amd_detail/file.cpp | 9 ++++- src/amd_detail/file.h | 9 +++++ test/amd_detail/handle.cpp | 70 ++++++++++++++++++++++++++++++++++++++ test/amd_detail/mfile.h | 1 + 4 files changed, 88 insertions(+), 1 deletion(-) diff --git a/src/amd_detail/file.cpp b/src/amd_detail/file.cpp index e92d30a7..266b1105 100644 --- a/src/amd_detail/file.cpp +++ b/src/amd_detail/file.cpp @@ -74,7 +74,8 @@ File::File(UnregisteredFile &&uf, const PassKey &) : client_fd{std::move(uf.client_fd)}, buffered_fd{std::move(uf.buffered_fd)}, unbuffered_fd{std::move(uf.unbuffered_fd)}, stx{uf.stx}, status_flags{uf.flags}, mountinfo{uf.mountinfo}, m_dio_mem_align{uf.m_dio_mem_align}, m_dio_offset_align{uf.m_dio_offset_align}, - m_is_block_device{(uf.stx.stx_mask & STATX_TYPE) && S_ISBLK(uf.stx.stx_mode)} + m_is_block_device{(uf.stx.stx_mask & STATX_TYPE) && S_ISBLK(uf.stx.stx_mode)}, + m_is_regular_file{(uf.stx.stx_mask & STATX_TYPE) && S_ISREG(uf.stx.stx_mode)} { } @@ -135,6 +136,12 @@ File::isBlockDevice() const noexcept return m_is_block_device; } +bool +File::isRegularFile() const noexcept +{ + return m_is_regular_file; +} + shared_ptr FileMap::getFile(hipFileHandle_t fh) { diff --git a/src/amd_detail/file.h b/src/amd_detail/file.h index 10904b26..fffd8a76 100644 --- a/src/amd_detail/file.h +++ b/src/amd_detail/file.h @@ -95,6 +95,7 @@ class IFile { virtual uint32_t dioMemAlign() const noexcept = 0; virtual uint32_t dioOffsetAlign() const noexcept = 0; virtual bool isBlockDevice() const noexcept = 0; + virtual bool isRegularFile() const noexcept = 0; }; class FileMap; @@ -135,6 +136,11 @@ class File : public IFile { /// @return True if the file is a block device, false otherwise virtual bool isBlockDevice() const noexcept override; + /// @brief Whether this file is a regular file. If statx did not return type information, this will return + /// false. + /// @return True if the file is a regular file, false otherwise + virtual bool isRegularFile() const noexcept override; + /// @brief Construct a registered file /// @param uf An unregistered file /// @param k Key class instance (see passkey.h) @@ -171,6 +177,9 @@ class File : public IFile { /// @brief Whether statx reported that the file is a block device bool m_is_block_device; + + /// @brief Whether statx reported that the file is a regular file + bool m_is_regular_file; }; class FileMap { diff --git a/test/amd_detail/handle.cpp b/test/amd_detail/handle.cpp index ebb7de30..7f1c115f 100644 --- a/test/amd_detail/handle.cpp +++ b/test/amd_detail/handle.cpp @@ -215,6 +215,7 @@ TEST_F(HipFileHandle, file_initialization) EXPECT_EQ(file->dioOffsetAlign(), 4096); #endif EXPECT_FALSE(file->isBlockDevice()); + EXPECT_TRUE(file->isRegularFile()); } TEST_F(HipFileHandle, register_handle_internal_linux_fd_already_registered) @@ -613,4 +614,73 @@ TEST_F(HipFileHandle, IsBlockDeviceReturnsFalseWhenStatxTypeNotAvailable) EXPECT_FALSE(file->isBlockDevice()); } +TEST_F(HipFileHandle, IsRegularFileReturnsTrueForRegularFile) +{ + int fd{0xBADF00D}; + struct statx stxbuf {}; + stxbuf.stx_mask = STATX_TYPE; + stxbuf.stx_mode = S_IFREG; + + // Return an eventfd as the file descriptor for the bufferd fd open so that when + // FileDescriptor calls close, it has a valid file descriptor to close. + int open_fd{eventfd(0, 0)}; + ASSERT_NE(open_fd, -1); + + ExpectUnregisteredFileBuilder(msys, mlibmounthelper) + .fd_flags(O_DIRECT) + .statx(stxbuf) + .open_fd(open_fd) + .build(); + auto fh{Context::get()->registerFile(fd)}; + auto file{Context::get()->getFile(fh)}; + + EXPECT_TRUE(file->isRegularFile()); +} + +TEST_F(HipFileHandle, IsRegularFileReturnsFalseForBlockDevice) +{ + int fd{0xBADF00D}; + struct statx stxbuf {}; + stxbuf.stx_mask = STATX_TYPE; + stxbuf.stx_mode = S_IFBLK; + + // Return an eventfd as the file descriptor for the bufferd fd open so that when + // FileDescriptor calls close, it has a valid file descriptor to close. + int open_fd{eventfd(0, 0)}; + ASSERT_NE(open_fd, -1); + + ExpectUnregisteredFileBuilder(msys, mlibmounthelper) + .fd_flags(O_DIRECT) + .statx(stxbuf) + .open_fd(open_fd) + .build(); + auto fh{Context::get()->registerFile(fd)}; + auto file{Context::get()->getFile(fh)}; + + EXPECT_FALSE(file->isRegularFile()); +} + +TEST_F(HipFileHandle, IsRegularFileReturnsFalseWhenStatxTypeNotAvailable) +{ + int fd{0xBADF00D}; + struct statx stxbuf {}; + stxbuf.stx_mask = 0; + stxbuf.stx_mode = S_IFBLK; + + // Return an eventfd as the file descriptor for the bufferd fd open so that when + // FileDescriptor calls close, it has a valid file descriptor to close. + int open_fd{eventfd(0, 0)}; + ASSERT_NE(open_fd, -1); + + ExpectUnregisteredFileBuilder(msys, mlibmounthelper) + .fd_flags(O_DIRECT) + .statx(stxbuf) + .open_fd(open_fd) + .build(); + auto fh{Context::get()->registerFile(fd)}; + auto file{Context::get()->getFile(fh)}; + + EXPECT_FALSE(file->isRegularFile()); +} + HIPFILE_WARN_NO_GLOBAL_CTOR_ON diff --git a/test/amd_detail/mfile.h b/test/amd_detail/mfile.h index 543d937c..def83b8f 100644 --- a/test/amd_detail/mfile.h +++ b/test/amd_detail/mfile.h @@ -28,6 +28,7 @@ class MFile : public IFile { MOCK_METHOD(uint32_t, dioMemAlign, (), (const, noexcept, override)); MOCK_METHOD(uint32_t, dioOffsetAlign, (), (const, noexcept, override)); MOCK_METHOD(bool, isBlockDevice, (), (const, noexcept, override)); + MOCK_METHOD(bool, isRegularFile, (), (const, noexcept, override)); }; class MFileMap : public FileMap { From 4acef44af592f066d3d23c9cd7be6eefbb4595a1 Mon Sep 17 00:00:00 2001 From: Kurt McMillan Date: Thu, 26 Mar 2026 22:11:57 +0000 Subject: [PATCH 05/28] file: Cleanup - Remove File::getStatx() The necessary data from the statx struct is now cached in File. --- src/amd_detail/file.cpp | 10 ++-------- src/amd_detail/file.h | 5 ----- test/amd_detail/handle.cpp | 2 -- test/amd_detail/mfile.h | 1 - 4 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/amd_detail/file.cpp b/src/amd_detail/file.cpp index 266b1105..901dcad2 100644 --- a/src/amd_detail/file.cpp +++ b/src/amd_detail/file.cpp @@ -72,8 +72,8 @@ IFile::getHandle() const File::File(UnregisteredFile &&uf, const PassKey &) : client_fd{std::move(uf.client_fd)}, buffered_fd{std::move(uf.buffered_fd)}, - unbuffered_fd{std::move(uf.unbuffered_fd)}, stx{uf.stx}, status_flags{uf.flags}, - mountinfo{uf.mountinfo}, m_dio_mem_align{uf.m_dio_mem_align}, m_dio_offset_align{uf.m_dio_offset_align}, + unbuffered_fd{std::move(uf.unbuffered_fd)}, status_flags{uf.flags}, mountinfo{uf.mountinfo}, + m_dio_mem_align{uf.m_dio_mem_align}, m_dio_offset_align{uf.m_dio_offset_align}, m_is_block_device{(uf.stx.stx_mask & STATX_TYPE) && S_ISBLK(uf.stx.stx_mode)}, m_is_regular_file{(uf.stx.stx_mask & STATX_TYPE) && S_ISREG(uf.stx.stx_mode)} { @@ -100,12 +100,6 @@ File::getUnbufferedFd() const return nullopt; } -const struct statx & -File::getStatx() const noexcept -{ - return stx; -} - int File::getStatusFlags() const { diff --git a/src/amd_detail/file.h b/src/amd_detail/file.h index fffd8a76..addfb44b 100644 --- a/src/amd_detail/file.h +++ b/src/amd_detail/file.h @@ -89,7 +89,6 @@ class IFile { virtual int getClientFd() const = 0; virtual int getBufferedFd() const = 0; virtual std::optional getUnbufferedFd() const = 0; - virtual const struct statx &getStatx() const noexcept = 0; virtual int getStatusFlags() const = 0; virtual std::optional getMountInfo() const = 0; virtual uint32_t dioMemAlign() const noexcept = 0; @@ -116,7 +115,6 @@ class File : public IFile { virtual int getClientFd() const override; virtual int getBufferedFd() const override; virtual std::optional getUnbufferedFd() const override; - virtual const struct statx &getStatx() const noexcept override; virtual int getStatusFlags() const override; virtual std::optional getMountInfo() const override; @@ -156,9 +154,6 @@ class File : public IFile { /// @brief Unbuffered file descriptor (O_DIRECT) std::optional unbuffered_fd; - /// @brief File status information obtained from statx (2) - struct statx stx; - /// @brief The file's status flags. See fcntl(2) /// /// Used to determine if the O_DIRECT flag is set diff --git a/test/amd_detail/handle.cpp b/test/amd_detail/handle.cpp index 7f1c115f..e1f73e51 100644 --- a/test/amd_detail/handle.cpp +++ b/test/amd_detail/handle.cpp @@ -201,8 +201,6 @@ TEST_F(HipFileHandle, file_initialization) EXPECT_EQ(fd, file->getClientFd()); EXPECT_EQ(fd, file->getBufferedFd()); EXPECT_EQ(open_fd, file->getUnbufferedFd()); - auto file_stx{file->getStatx()}; - EXPECT_EQ(0, memcmp(&file_stx, &stxbuf, sizeof(stxbuf))); EXPECT_EQ(fd_flags, file->getStatusFlags()); EXPECT_EQ(mountinfo.type, file->getMountInfo().value().type); EXPECT_EQ(mountinfo.options.ext4.journaling_mode, diff --git a/test/amd_detail/mfile.h b/test/amd_detail/mfile.h index def83b8f..c40a2e2f 100644 --- a/test/amd_detail/mfile.h +++ b/test/amd_detail/mfile.h @@ -22,7 +22,6 @@ class MFile : public IFile { MOCK_METHOD(int, getClientFd, (), (const, override)); MOCK_METHOD(int, getBufferedFd, (), (const, override)); MOCK_METHOD(std::optional, getUnbufferedFd, (), (const, override)); - MOCK_METHOD(const struct statx &, getStatx, (), (const, noexcept, override)); MOCK_METHOD(int, getStatusFlags, (), (const, override)); MOCK_METHOD(std::optional, getMountInfo, (), (const, override)); MOCK_METHOD(uint32_t, dioMemAlign, (), (const, noexcept, override)); From 4452b2c58a9c95e8847e00cf6bc86c40e730bc57 Mon Sep 17 00:00:00 2001 From: Kurt McMillan Date: Fri, 27 Mar 2026 19:20:11 +0000 Subject: [PATCH 06/28] file: Add File::onExt4Ordered() --- src/amd_detail/file.cpp | 10 +++++- src/amd_detail/file.h | 9 +++++ test/amd_detail/handle.cpp | 71 ++++++++++++++++++++++++++++++++++++++ test/amd_detail/mfile.h | 1 + 4 files changed, 90 insertions(+), 1 deletion(-) diff --git a/src/amd_detail/file.cpp b/src/amd_detail/file.cpp index 901dcad2..a2d998ee 100644 --- a/src/amd_detail/file.cpp +++ b/src/amd_detail/file.cpp @@ -75,7 +75,9 @@ File::File(UnregisteredFile &&uf, const PassKey &) unbuffered_fd{std::move(uf.unbuffered_fd)}, status_flags{uf.flags}, mountinfo{uf.mountinfo}, m_dio_mem_align{uf.m_dio_mem_align}, m_dio_offset_align{uf.m_dio_offset_align}, m_is_block_device{(uf.stx.stx_mask & STATX_TYPE) && S_ISBLK(uf.stx.stx_mode)}, - m_is_regular_file{(uf.stx.stx_mask & STATX_TYPE) && S_ISREG(uf.stx.stx_mode)} + m_is_regular_file{(uf.stx.stx_mask & STATX_TYPE) && S_ISREG(uf.stx.stx_mode)}, + m_on_ext4_ordered{uf.mountinfo && uf.mountinfo->type == FilesystemType::ext4 && + uf.mountinfo->options.ext4.journaling_mode == ExtJournalingMode::ordered} { } @@ -136,6 +138,12 @@ File::isRegularFile() const noexcept return m_is_regular_file; } +bool +File::onExt4Ordered() const noexcept +{ + return m_on_ext4_ordered; +} + shared_ptr FileMap::getFile(hipFileHandle_t fh) { diff --git a/src/amd_detail/file.h b/src/amd_detail/file.h index addfb44b..9091f07c 100644 --- a/src/amd_detail/file.h +++ b/src/amd_detail/file.h @@ -95,6 +95,7 @@ class IFile { virtual uint32_t dioOffsetAlign() const noexcept = 0; virtual bool isBlockDevice() const noexcept = 0; virtual bool isRegularFile() const noexcept = 0; + virtual bool onExt4Ordered() const noexcept = 0; }; class FileMap; @@ -139,6 +140,11 @@ class File : public IFile { /// @return True if the file is a regular file, false otherwise virtual bool isRegularFile() const noexcept override; + /// @brief Whether this file is on an ext4 filesystem with ordered journaling mode. If mountinfo is not + /// available, this will return false. + /// @return True if the file is on an ext4 filesystem with ordered journaling mode, false otherwise + virtual bool onExt4Ordered() const noexcept override; + /// @brief Construct a registered file /// @param uf An unregistered file /// @param k Key class instance (see passkey.h) @@ -175,6 +181,9 @@ class File : public IFile { /// @brief Whether statx reported that the file is a regular file bool m_is_regular_file; + + /// @brief Whether the file is on an ext4 filesystem with ordered journaling mode + bool m_on_ext4_ordered; }; class FileMap { diff --git a/test/amd_detail/handle.cpp b/test/amd_detail/handle.cpp index e1f73e51..c3f8541a 100644 --- a/test/amd_detail/handle.cpp +++ b/test/amd_detail/handle.cpp @@ -214,6 +214,7 @@ TEST_F(HipFileHandle, file_initialization) #endif EXPECT_FALSE(file->isBlockDevice()); EXPECT_TRUE(file->isRegularFile()); + EXPECT_TRUE(file->onExt4Ordered()); } TEST_F(HipFileHandle, register_handle_internal_linux_fd_already_registered) @@ -681,4 +682,74 @@ TEST_F(HipFileHandle, IsRegularFileReturnsFalseWhenStatxTypeNotAvailable) EXPECT_FALSE(file->isRegularFile()); } +TEST_F(HipFileHandle, OnExt4OrderedReturnsTrueForExt4Ordered) +{ + int fd{0xBADF00D}; + + // Return an eventfd as the file descriptor for the bufferd fd open so that when + // FileDescriptor calls close, it has a valid file descriptor to close. + int open_fd{eventfd(0, 0)}; + ASSERT_NE(open_fd, -1); + + MountInfo mountinfo; + mountinfo.type = FilesystemType::ext4; + mountinfo.options.ext4.journaling_mode = ExtJournalingMode::ordered; + + ExpectUnregisteredFileBuilder(msys, mlibmounthelper) + .fd_flags(O_DIRECT) + .mountinfo(mountinfo) + .open_fd(open_fd) + .build(); + auto fh{Context::get()->registerFile(fd)}; + auto file{Context::get()->getFile(fh)}; + + EXPECT_TRUE(file->onExt4Ordered()); +} + +TEST_F(HipFileHandle, OnExt4OrderedReturnsFalseForExt4Journal) +{ + int fd{0xBADF00D}; + + // Return an eventfd as the file descriptor for the bufferd fd open so that when + // FileDescriptor calls close, it has a valid file descriptor to close. + int open_fd{eventfd(0, 0)}; + ASSERT_NE(open_fd, -1); + + MountInfo mountinfo; + mountinfo.type = FilesystemType::ext4; + mountinfo.options.ext4.journaling_mode = ExtJournalingMode::journal; + + ExpectUnregisteredFileBuilder(msys, mlibmounthelper) + .fd_flags(O_DIRECT) + .mountinfo(mountinfo) + .open_fd(open_fd) + .build(); + auto fh{Context::get()->registerFile(fd)}; + auto file{Context::get()->getFile(fh)}; + + EXPECT_FALSE(file->onExt4Ordered()); +} + +TEST_F(HipFileHandle, OnExt4OrderedReturnsFalseForOtherFileSystem) +{ + int fd{0xBADF00D}; + + // Return an eventfd as the file descriptor for the bufferd fd open so that when + // FileDescriptor calls close, it has a valid file descriptor to close. + int open_fd{eventfd(0, 0)}; + ASSERT_NE(open_fd, -1); + + MountInfo mountinfo; + mountinfo.type = FilesystemType::other; + + ExpectUnregisteredFileBuilder(msys, mlibmounthelper) + .fd_flags(O_DIRECT) + .mountinfo(mountinfo) + .open_fd(open_fd) + .build(); + auto fh{Context::get()->registerFile(fd)}; + auto file{Context::get()->getFile(fh)}; + + EXPECT_FALSE(file->onExt4Ordered()); +} HIPFILE_WARN_NO_GLOBAL_CTOR_ON diff --git a/test/amd_detail/mfile.h b/test/amd_detail/mfile.h index c40a2e2f..bd30ec39 100644 --- a/test/amd_detail/mfile.h +++ b/test/amd_detail/mfile.h @@ -28,6 +28,7 @@ class MFile : public IFile { MOCK_METHOD(uint32_t, dioOffsetAlign, (), (const, noexcept, override)); MOCK_METHOD(bool, isBlockDevice, (), (const, noexcept, override)); MOCK_METHOD(bool, isRegularFile, (), (const, noexcept, override)); + MOCK_METHOD(bool, onExt4Ordered, (), (const, noexcept, override)); }; class MFileMap : public FileMap { From 2b4e67ae43e61acfb2ec318e8d2f0f24df77c94c Mon Sep 17 00:00:00 2001 From: Kurt McMillan Date: Fri, 27 Mar 2026 19:20:11 +0000 Subject: [PATCH 07/28] file: Add File::onXfs() --- src/amd_detail/file.cpp | 9 ++++++- src/amd_detail/file.h | 9 +++++++ test/amd_detail/handle.cpp | 48 ++++++++++++++++++++++++++++++++++++++ test/amd_detail/mfile.h | 1 + 4 files changed, 66 insertions(+), 1 deletion(-) diff --git a/src/amd_detail/file.cpp b/src/amd_detail/file.cpp index a2d998ee..637edad1 100644 --- a/src/amd_detail/file.cpp +++ b/src/amd_detail/file.cpp @@ -77,7 +77,8 @@ File::File(UnregisteredFile &&uf, const PassKey &) m_is_block_device{(uf.stx.stx_mask & STATX_TYPE) && S_ISBLK(uf.stx.stx_mode)}, m_is_regular_file{(uf.stx.stx_mask & STATX_TYPE) && S_ISREG(uf.stx.stx_mode)}, m_on_ext4_ordered{uf.mountinfo && uf.mountinfo->type == FilesystemType::ext4 && - uf.mountinfo->options.ext4.journaling_mode == ExtJournalingMode::ordered} + uf.mountinfo->options.ext4.journaling_mode == ExtJournalingMode::ordered}, + m_on_xfs{uf.mountinfo && uf.mountinfo->type == FilesystemType::xfs} { } @@ -144,6 +145,12 @@ File::onExt4Ordered() const noexcept return m_on_ext4_ordered; } +bool +File::onXfs() const noexcept +{ + return m_on_xfs; +} + shared_ptr FileMap::getFile(hipFileHandle_t fh) { diff --git a/src/amd_detail/file.h b/src/amd_detail/file.h index 9091f07c..575020f8 100644 --- a/src/amd_detail/file.h +++ b/src/amd_detail/file.h @@ -96,6 +96,7 @@ class IFile { virtual bool isBlockDevice() const noexcept = 0; virtual bool isRegularFile() const noexcept = 0; virtual bool onExt4Ordered() const noexcept = 0; + virtual bool onXfs() const noexcept = 0; }; class FileMap; @@ -145,6 +146,11 @@ class File : public IFile { /// @return True if the file is on an ext4 filesystem with ordered journaling mode, false otherwise virtual bool onExt4Ordered() const noexcept override; + /// @brief Whether this file is on an xfs filesystem. If mountinfo is not available, this will return + /// false. + /// @return True if the file is on an xfs filesystem, false otherwise + virtual bool onXfs() const noexcept override; + /// @brief Construct a registered file /// @param uf An unregistered file /// @param k Key class instance (see passkey.h) @@ -184,6 +190,9 @@ class File : public IFile { /// @brief Whether the file is on an ext4 filesystem with ordered journaling mode bool m_on_ext4_ordered; + + /// @brief Whether the file is on an xfs filesystem + bool m_on_xfs; }; class FileMap { diff --git a/test/amd_detail/handle.cpp b/test/amd_detail/handle.cpp index c3f8541a..8139e6df 100644 --- a/test/amd_detail/handle.cpp +++ b/test/amd_detail/handle.cpp @@ -215,6 +215,7 @@ TEST_F(HipFileHandle, file_initialization) EXPECT_FALSE(file->isBlockDevice()); EXPECT_TRUE(file->isRegularFile()); EXPECT_TRUE(file->onExt4Ordered()); + EXPECT_FALSE(file->onXfs()); } TEST_F(HipFileHandle, register_handle_internal_linux_fd_already_registered) @@ -752,4 +753,51 @@ TEST_F(HipFileHandle, OnExt4OrderedReturnsFalseForOtherFileSystem) EXPECT_FALSE(file->onExt4Ordered()); } + +TEST_F(HipFileHandle, OnXfsReturnsTrueForXfs) +{ + int fd{0xBADF00D}; + + // Return an eventfd as the file descriptor for the bufferd fd open so that when + // FileDescriptor calls close, it has a valid file descriptor to close. + int open_fd{eventfd(0, 0)}; + ASSERT_NE(open_fd, -1); + + MountInfo mountinfo; + mountinfo.type = FilesystemType::xfs; + + ExpectUnregisteredFileBuilder(msys, mlibmounthelper) + .fd_flags(O_DIRECT) + .mountinfo(mountinfo) + .open_fd(open_fd) + .build(); + auto fh{Context::get()->registerFile(fd)}; + auto file{Context::get()->getFile(fh)}; + + EXPECT_TRUE(file->onXfs()); +} + +TEST_F(HipFileHandle, OnXfsReturnsFalseForOtherFileSystem) +{ + int fd{0xBADF00D}; + + // Return an eventfd as the file descriptor for the bufferd fd open so that when + // FileDescriptor calls close, it has a valid file descriptor to close. + int open_fd{eventfd(0, 0)}; + ASSERT_NE(open_fd, -1); + + MountInfo mountinfo; + mountinfo.type = FilesystemType::other; + + ExpectUnregisteredFileBuilder(msys, mlibmounthelper) + .fd_flags(O_DIRECT) + .mountinfo(mountinfo) + .open_fd(open_fd) + .build(); + auto fh{Context::get()->registerFile(fd)}; + auto file{Context::get()->getFile(fh)}; + + EXPECT_FALSE(file->onXfs()); +} + HIPFILE_WARN_NO_GLOBAL_CTOR_ON diff --git a/test/amd_detail/mfile.h b/test/amd_detail/mfile.h index bd30ec39..8fe5cb1b 100644 --- a/test/amd_detail/mfile.h +++ b/test/amd_detail/mfile.h @@ -29,6 +29,7 @@ class MFile : public IFile { MOCK_METHOD(bool, isBlockDevice, (), (const, noexcept, override)); MOCK_METHOD(bool, isRegularFile, (), (const, noexcept, override)); MOCK_METHOD(bool, onExt4Ordered, (), (const, noexcept, override)); + MOCK_METHOD(bool, onXfs, (), (const, noexcept, override)); }; class MFileMap : public FileMap { From db2f699c0eabac90a06337ae759fe0b22f1c1cd9 Mon Sep 17 00:00:00 2001 From: Kurt McMillan Date: Fri, 27 Mar 2026 19:32:41 +0000 Subject: [PATCH 08/28] file: Cleanup - Remove File::getMountInfo() The mount information the fastpath needs can now be obtained from File::onXfs() and File::onExt4Ordered(). --- src/amd_detail/file.cpp | 10 ++-------- src/amd_detail/file.h | 33 ++++++++++++++------------------- test/amd_detail/handle.cpp | 3 --- test/amd_detail/mfile.h | 1 - 4 files changed, 16 insertions(+), 31 deletions(-) diff --git a/src/amd_detail/file.cpp b/src/amd_detail/file.cpp index 637edad1..88024190 100644 --- a/src/amd_detail/file.cpp +++ b/src/amd_detail/file.cpp @@ -72,8 +72,8 @@ IFile::getHandle() const File::File(UnregisteredFile &&uf, const PassKey &) : client_fd{std::move(uf.client_fd)}, buffered_fd{std::move(uf.buffered_fd)}, - unbuffered_fd{std::move(uf.unbuffered_fd)}, status_flags{uf.flags}, mountinfo{uf.mountinfo}, - m_dio_mem_align{uf.m_dio_mem_align}, m_dio_offset_align{uf.m_dio_offset_align}, + unbuffered_fd{std::move(uf.unbuffered_fd)}, status_flags{uf.flags}, m_dio_mem_align{uf.m_dio_mem_align}, + m_dio_offset_align{uf.m_dio_offset_align}, m_is_block_device{(uf.stx.stx_mask & STATX_TYPE) && S_ISBLK(uf.stx.stx_mode)}, m_is_regular_file{(uf.stx.stx_mask & STATX_TYPE) && S_ISREG(uf.stx.stx_mode)}, m_on_ext4_ordered{uf.mountinfo && uf.mountinfo->type == FilesystemType::ext4 && @@ -109,12 +109,6 @@ File::getStatusFlags() const return status_flags; } -optional -File::getMountInfo() const -{ - return mountinfo; -} - uint32_t File::dioMemAlign() const noexcept { diff --git a/src/amd_detail/file.h b/src/amd_detail/file.h index 575020f8..5887baef 100644 --- a/src/amd_detail/file.h +++ b/src/amd_detail/file.h @@ -86,17 +86,16 @@ class IFile { /// @return The handle for this file virtual hipFileHandle_t getHandle() const; - virtual int getClientFd() const = 0; - virtual int getBufferedFd() const = 0; - virtual std::optional getUnbufferedFd() const = 0; - virtual int getStatusFlags() const = 0; - virtual std::optional getMountInfo() const = 0; - virtual uint32_t dioMemAlign() const noexcept = 0; - virtual uint32_t dioOffsetAlign() const noexcept = 0; - virtual bool isBlockDevice() const noexcept = 0; - virtual bool isRegularFile() const noexcept = 0; - virtual bool onExt4Ordered() const noexcept = 0; - virtual bool onXfs() const noexcept = 0; + virtual int getClientFd() const = 0; + virtual int getBufferedFd() const = 0; + virtual std::optional getUnbufferedFd() const = 0; + virtual int getStatusFlags() const = 0; + virtual uint32_t dioMemAlign() const noexcept = 0; + virtual uint32_t dioOffsetAlign() const noexcept = 0; + virtual bool isBlockDevice() const noexcept = 0; + virtual bool isRegularFile() const noexcept = 0; + virtual bool onExt4Ordered() const noexcept = 0; + virtual bool onXfs() const noexcept = 0; }; class FileMap; @@ -114,11 +113,10 @@ class File : public IFile { File(File &&) = delete; File &operator=(File &&) = delete; - virtual int getClientFd() const override; - virtual int getBufferedFd() const override; - virtual std::optional getUnbufferedFd() const override; - virtual int getStatusFlags() const override; - virtual std::optional getMountInfo() const override; + virtual int getClientFd() const override; + virtual int getBufferedFd() const override; + virtual std::optional getUnbufferedFd() const override; + virtual int getStatusFlags() const override; /// @brief Get the memory (in bytes) alignment requirement for direct IO on this file. If the file does /// not support direct IO, this will return 0. @@ -171,9 +169,6 @@ class File : public IFile { /// Used to determine if the O_DIRECT flag is set int status_flags; - /// @brief Mount information for the filesystem backing fd - std::optional mountinfo; - /// @brief Memory alignment (in bytes) requirement for direct IO. If the file does not support direct IO, /// this will be 0. uint32_t m_dio_mem_align; diff --git a/test/amd_detail/handle.cpp b/test/amd_detail/handle.cpp index 8139e6df..0a950cbd 100644 --- a/test/amd_detail/handle.cpp +++ b/test/amd_detail/handle.cpp @@ -202,9 +202,6 @@ TEST_F(HipFileHandle, file_initialization) EXPECT_EQ(fd, file->getBufferedFd()); EXPECT_EQ(open_fd, file->getUnbufferedFd()); EXPECT_EQ(fd_flags, file->getStatusFlags()); - EXPECT_EQ(mountinfo.type, file->getMountInfo().value().type); - EXPECT_EQ(mountinfo.options.ext4.journaling_mode, - file->getMountInfo().value().options.ext4.journaling_mode); #if defined(STATX_DIOALIGN) EXPECT_EQ(file->dioMemAlign(), stxbuf.stx_dio_mem_align); EXPECT_EQ(file->dioOffsetAlign(), stxbuf.stx_dio_offset_align); diff --git a/test/amd_detail/mfile.h b/test/amd_detail/mfile.h index 8fe5cb1b..b9eac4f6 100644 --- a/test/amd_detail/mfile.h +++ b/test/amd_detail/mfile.h @@ -23,7 +23,6 @@ class MFile : public IFile { MOCK_METHOD(int, getBufferedFd, (), (const, override)); MOCK_METHOD(std::optional, getUnbufferedFd, (), (const, override)); MOCK_METHOD(int, getStatusFlags, (), (const, override)); - MOCK_METHOD(std::optional, getMountInfo, (), (const, override)); MOCK_METHOD(uint32_t, dioMemAlign, (), (const, noexcept, override)); MOCK_METHOD(uint32_t, dioOffsetAlign, (), (const, noexcept, override)); MOCK_METHOD(bool, isBlockDevice, (), (const, noexcept, override)); From d2e4e282533b0b9be09fd88e665a05adf5828910 Mon Sep 17 00:00:00 2001 From: Kurt McMillan Date: Fri, 27 Mar 2026 19:32:41 +0000 Subject: [PATCH 09/28] file: Cleanup - Remove File::getStatusFlags() At one time this was used to determine if O_DIRECT was set on File's file descriptor. File now holds buffered (~O_DIRECT) and unbuffered (O_DIRECT) file descriptors. Clients can use the file descriptor that best suits their purpose. --- src/amd_detail/file.cpp | 8 +------- src/amd_detail/file.h | 7 ------- test/amd_detail/handle.cpp | 1 - test/amd_detail/mfile.h | 1 - 4 files changed, 1 insertion(+), 16 deletions(-) diff --git a/src/amd_detail/file.cpp b/src/amd_detail/file.cpp index 88024190..113df2e4 100644 --- a/src/amd_detail/file.cpp +++ b/src/amd_detail/file.cpp @@ -72,7 +72,7 @@ IFile::getHandle() const File::File(UnregisteredFile &&uf, const PassKey &) : client_fd{std::move(uf.client_fd)}, buffered_fd{std::move(uf.buffered_fd)}, - unbuffered_fd{std::move(uf.unbuffered_fd)}, status_flags{uf.flags}, m_dio_mem_align{uf.m_dio_mem_align}, + unbuffered_fd{std::move(uf.unbuffered_fd)}, m_dio_mem_align{uf.m_dio_mem_align}, m_dio_offset_align{uf.m_dio_offset_align}, m_is_block_device{(uf.stx.stx_mask & STATX_TYPE) && S_ISBLK(uf.stx.stx_mode)}, m_is_regular_file{(uf.stx.stx_mask & STATX_TYPE) && S_ISREG(uf.stx.stx_mode)}, @@ -103,12 +103,6 @@ File::getUnbufferedFd() const return nullopt; } -int -File::getStatusFlags() const -{ - return status_flags; -} - uint32_t File::dioMemAlign() const noexcept { diff --git a/src/amd_detail/file.h b/src/amd_detail/file.h index 5887baef..be567289 100644 --- a/src/amd_detail/file.h +++ b/src/amd_detail/file.h @@ -89,7 +89,6 @@ class IFile { virtual int getClientFd() const = 0; virtual int getBufferedFd() const = 0; virtual std::optional getUnbufferedFd() const = 0; - virtual int getStatusFlags() const = 0; virtual uint32_t dioMemAlign() const noexcept = 0; virtual uint32_t dioOffsetAlign() const noexcept = 0; virtual bool isBlockDevice() const noexcept = 0; @@ -116,7 +115,6 @@ class File : public IFile { virtual int getClientFd() const override; virtual int getBufferedFd() const override; virtual std::optional getUnbufferedFd() const override; - virtual int getStatusFlags() const override; /// @brief Get the memory (in bytes) alignment requirement for direct IO on this file. If the file does /// not support direct IO, this will return 0. @@ -164,11 +162,6 @@ class File : public IFile { /// @brief Unbuffered file descriptor (O_DIRECT) std::optional unbuffered_fd; - /// @brief The file's status flags. See fcntl(2) - /// - /// Used to determine if the O_DIRECT flag is set - int status_flags; - /// @brief Memory alignment (in bytes) requirement for direct IO. If the file does not support direct IO, /// this will be 0. uint32_t m_dio_mem_align; diff --git a/test/amd_detail/handle.cpp b/test/amd_detail/handle.cpp index 0a950cbd..b8e2ebb1 100644 --- a/test/amd_detail/handle.cpp +++ b/test/amd_detail/handle.cpp @@ -201,7 +201,6 @@ TEST_F(HipFileHandle, file_initialization) EXPECT_EQ(fd, file->getClientFd()); EXPECT_EQ(fd, file->getBufferedFd()); EXPECT_EQ(open_fd, file->getUnbufferedFd()); - EXPECT_EQ(fd_flags, file->getStatusFlags()); #if defined(STATX_DIOALIGN) EXPECT_EQ(file->dioMemAlign(), stxbuf.stx_dio_mem_align); EXPECT_EQ(file->dioOffsetAlign(), stxbuf.stx_dio_offset_align); diff --git a/test/amd_detail/mfile.h b/test/amd_detail/mfile.h index b9eac4f6..b37cada4 100644 --- a/test/amd_detail/mfile.h +++ b/test/amd_detail/mfile.h @@ -22,7 +22,6 @@ class MFile : public IFile { MOCK_METHOD(int, getClientFd, (), (const, override)); MOCK_METHOD(int, getBufferedFd, (), (const, override)); MOCK_METHOD(std::optional, getUnbufferedFd, (), (const, override)); - MOCK_METHOD(int, getStatusFlags, (), (const, override)); MOCK_METHOD(uint32_t, dioMemAlign, (), (const, noexcept, override)); MOCK_METHOD(uint32_t, dioOffsetAlign, (), (const, noexcept, override)); MOCK_METHOD(bool, isBlockDevice, (), (const, noexcept, override)); From eafaeee4ddce7d30cc828df55cf499ead8e5b76d Mon Sep 17 00:00:00 2001 From: Kurt McMillan Date: Fri, 27 Mar 2026 20:17:45 +0000 Subject: [PATCH 10/28] file: Cleanup - Rename File::unbuffered_fd -> File::m_unbuffered_fd --- src/amd_detail/file.cpp | 6 +++--- src/amd_detail/file.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/amd_detail/file.cpp b/src/amd_detail/file.cpp index 113df2e4..1b8e5b61 100644 --- a/src/amd_detail/file.cpp +++ b/src/amd_detail/file.cpp @@ -72,7 +72,7 @@ IFile::getHandle() const File::File(UnregisteredFile &&uf, const PassKey &) : client_fd{std::move(uf.client_fd)}, buffered_fd{std::move(uf.buffered_fd)}, - unbuffered_fd{std::move(uf.unbuffered_fd)}, m_dio_mem_align{uf.m_dio_mem_align}, + m_unbuffered_fd{std::move(uf.unbuffered_fd)}, m_dio_mem_align{uf.m_dio_mem_align}, m_dio_offset_align{uf.m_dio_offset_align}, m_is_block_device{(uf.stx.stx_mask & STATX_TYPE) && S_ISBLK(uf.stx.stx_mode)}, m_is_regular_file{(uf.stx.stx_mask & STATX_TYPE) && S_ISREG(uf.stx.stx_mode)}, @@ -97,8 +97,8 @@ File::getBufferedFd() const optional File::getUnbufferedFd() const { - if (unbuffered_fd) { - return unbuffered_fd.value().get(); + if (m_unbuffered_fd) { + return m_unbuffered_fd.value().get(); } return nullopt; } diff --git a/src/amd_detail/file.h b/src/amd_detail/file.h index be567289..724353b6 100644 --- a/src/amd_detail/file.h +++ b/src/amd_detail/file.h @@ -160,7 +160,7 @@ class File : public IFile { FileDescriptor buffered_fd; /// @brief Unbuffered file descriptor (O_DIRECT) - std::optional unbuffered_fd; + std::optional m_unbuffered_fd; /// @brief Memory alignment (in bytes) requirement for direct IO. If the file does not support direct IO, /// this will be 0. From a7423fbb6dc955cefeebcb82e6e14278f9922e08 Mon Sep 17 00:00:00 2001 From: Kurt McMillan Date: Fri, 27 Mar 2026 20:19:44 +0000 Subject: [PATCH 11/28] file: Cleanup - Add noexcept to File::getUnbufferedFd() --- src/amd_detail/file.cpp | 2 +- src/amd_detail/file.h | 20 ++++++++++---------- test/amd_detail/mfile.h | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/amd_detail/file.cpp b/src/amd_detail/file.cpp index 1b8e5b61..d9489288 100644 --- a/src/amd_detail/file.cpp +++ b/src/amd_detail/file.cpp @@ -95,7 +95,7 @@ File::getBufferedFd() const } optional -File::getUnbufferedFd() const +File::getUnbufferedFd() const noexcept { if (m_unbuffered_fd) { return m_unbuffered_fd.value().get(); diff --git a/src/amd_detail/file.h b/src/amd_detail/file.h index 724353b6..92c71b34 100644 --- a/src/amd_detail/file.h +++ b/src/amd_detail/file.h @@ -86,15 +86,15 @@ class IFile { /// @return The handle for this file virtual hipFileHandle_t getHandle() const; - virtual int getClientFd() const = 0; - virtual int getBufferedFd() const = 0; - virtual std::optional getUnbufferedFd() const = 0; - virtual uint32_t dioMemAlign() const noexcept = 0; - virtual uint32_t dioOffsetAlign() const noexcept = 0; - virtual bool isBlockDevice() const noexcept = 0; - virtual bool isRegularFile() const noexcept = 0; - virtual bool onExt4Ordered() const noexcept = 0; - virtual bool onXfs() const noexcept = 0; + virtual int getClientFd() const = 0; + virtual int getBufferedFd() const = 0; + virtual std::optional getUnbufferedFd() const noexcept = 0; + virtual uint32_t dioMemAlign() const noexcept = 0; + virtual uint32_t dioOffsetAlign() const noexcept = 0; + virtual bool isBlockDevice() const noexcept = 0; + virtual bool isRegularFile() const noexcept = 0; + virtual bool onExt4Ordered() const noexcept = 0; + virtual bool onXfs() const noexcept = 0; }; class FileMap; @@ -114,7 +114,7 @@ class File : public IFile { virtual int getClientFd() const override; virtual int getBufferedFd() const override; - virtual std::optional getUnbufferedFd() const override; + virtual std::optional getUnbufferedFd() const noexcept override; /// @brief Get the memory (in bytes) alignment requirement for direct IO on this file. If the file does /// not support direct IO, this will return 0. diff --git a/test/amd_detail/mfile.h b/test/amd_detail/mfile.h index b37cada4..66bbfbec 100644 --- a/test/amd_detail/mfile.h +++ b/test/amd_detail/mfile.h @@ -21,7 +21,7 @@ class MFile : public IFile { MOCK_METHOD(hipFileHandle_t, getHandle, (), (const, override)); MOCK_METHOD(int, getClientFd, (), (const, override)); MOCK_METHOD(int, getBufferedFd, (), (const, override)); - MOCK_METHOD(std::optional, getUnbufferedFd, (), (const, override)); + MOCK_METHOD(std::optional, getUnbufferedFd, (), (const, noexcept, override)); MOCK_METHOD(uint32_t, dioMemAlign, (), (const, noexcept, override)); MOCK_METHOD(uint32_t, dioOffsetAlign, (), (const, noexcept, override)); MOCK_METHOD(bool, isBlockDevice, (), (const, noexcept, override)); From 15af43a26a1baed5f186fc6712cfbee4e5bdc962 Mon Sep 17 00:00:00 2001 From: Kurt McMillan Date: Fri, 27 Mar 2026 20:23:07 +0000 Subject: [PATCH 12/28] file: Cleanup - Rename File::getUnbufferedFd() -> File::unbufferedFd() --- src/amd_detail/backend/fastpath.cpp | 4 +-- src/amd_detail/file.cpp | 2 +- src/amd_detail/file.h | 28 ++++++++++--------- test/amd_detail/fastpath.cpp | 42 ++++++++++++++--------------- test/amd_detail/handle.cpp | 2 +- test/amd_detail/mfile.h | 2 +- 6 files changed, 42 insertions(+), 38 deletions(-) diff --git a/src/amd_detail/backend/fastpath.cpp b/src/amd_detail/backend/fastpath.cpp index 89b1ce72..79aebdad 100644 --- a/src/amd_detail/backend/fastpath.cpp +++ b/src/amd_detail/backend/fastpath.cpp @@ -136,7 +136,7 @@ Fastpath::score(shared_ptr file, shared_ptr buffer, size_t size, accept_io &= Context::get()->fastpath(); - accept_io &= file->getUnbufferedFd().has_value(); + accept_io &= file->unbufferedFd().has_value(); accept_io &= buffer->getType() == hipMemoryTypeDevice; @@ -166,7 +166,7 @@ Fastpath::_io_impl(IoType type, shared_ptr file, shared_ptr buff hipAmdFileHandle_t handle{}; size_t nbytes{}; - handle.fd = file->getUnbufferedFd().value(); + handle.fd = file->unbufferedFd().value(); if (!paramsValid(buffer, size, file_offset, buffer_offset)) { throw std::invalid_argument("The selected file or buffer region is invalid"); diff --git a/src/amd_detail/file.cpp b/src/amd_detail/file.cpp index d9489288..77806d7a 100644 --- a/src/amd_detail/file.cpp +++ b/src/amd_detail/file.cpp @@ -95,7 +95,7 @@ File::getBufferedFd() const } optional -File::getUnbufferedFd() const noexcept +File::unbufferedFd() const noexcept { if (m_unbuffered_fd) { return m_unbuffered_fd.value().get(); diff --git a/src/amd_detail/file.h b/src/amd_detail/file.h index 92c71b34..a43403ca 100644 --- a/src/amd_detail/file.h +++ b/src/amd_detail/file.h @@ -86,15 +86,15 @@ class IFile { /// @return The handle for this file virtual hipFileHandle_t getHandle() const; - virtual int getClientFd() const = 0; - virtual int getBufferedFd() const = 0; - virtual std::optional getUnbufferedFd() const noexcept = 0; - virtual uint32_t dioMemAlign() const noexcept = 0; - virtual uint32_t dioOffsetAlign() const noexcept = 0; - virtual bool isBlockDevice() const noexcept = 0; - virtual bool isRegularFile() const noexcept = 0; - virtual bool onExt4Ordered() const noexcept = 0; - virtual bool onXfs() const noexcept = 0; + virtual int getClientFd() const = 0; + virtual int getBufferedFd() const = 0; + virtual std::optional unbufferedFd() const noexcept = 0; + virtual uint32_t dioMemAlign() const noexcept = 0; + virtual uint32_t dioOffsetAlign() const noexcept = 0; + virtual bool isBlockDevice() const noexcept = 0; + virtual bool isRegularFile() const noexcept = 0; + virtual bool onExt4Ordered() const noexcept = 0; + virtual bool onXfs() const noexcept = 0; }; class FileMap; @@ -112,9 +112,13 @@ class File : public IFile { File(File &&) = delete; File &operator=(File &&) = delete; - virtual int getClientFd() const override; - virtual int getBufferedFd() const override; - virtual std::optional getUnbufferedFd() const noexcept override; + virtual int getClientFd() const override; + virtual int getBufferedFd() const override; + + /// @brief Get the unbuffered file descriptor for this file. Returns nullopt if the file does not support + /// direct IO or if there was an error obtaining the unbuffered fd. + /// @return The unbuffered file descriptor, or nullopt if not available + virtual std::optional unbufferedFd() const noexcept override; /// @brief Get the memory (in bytes) alignment requirement for direct IO on this file. If the file does /// not support direct IO, this will return 0. diff --git a/test/amd_detail/fastpath.cpp b/test/amd_detail/fastpath.cpp index 0bde6651..9478acda 100644 --- a/test/amd_detail/fastpath.cpp +++ b/test/amd_detail/fastpath.cpp @@ -104,7 +104,7 @@ TEST_F(FastpathTest, TestDefaults) TEST_F(FastpathTest, ScoreAcceptsIoWithDefaults) { EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); - EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); + EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); @@ -117,7 +117,7 @@ TEST_F(FastpathTest, ScoreAcceptsIoWithDefaults) TEST_F(FastpathTest, ScoreRejectsIoIfFastpathIsDisabled) { EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(false)); - EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); + EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); @@ -130,7 +130,7 @@ TEST_F(FastpathTest, ScoreRejectsIoIfFastpathIsDisabled) TEST_F(FastpathTest, ScoreRejectsIoIfUnbufferedFdNotAvailable) { EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); - EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(nullopt)); + EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(nullopt)); EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); @@ -143,7 +143,7 @@ TEST_F(FastpathTest, ScoreRejectsIoIfUnbufferedFdNotAvailable) TEST_F(FastpathTest, ScoreRejectsIoWithNegativeAlignedFileOffset) { EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); - EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); + EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); @@ -157,7 +157,7 @@ TEST_F(FastpathTest, ScoreRejectsIoWithNegativeAlignedFileOffset) TEST_F(FastpathTest, ScoreRejectsIoWithNegativeAlignedBufferOffset) { EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); - EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); + EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); @@ -171,7 +171,7 @@ TEST_F(FastpathTest, ScoreRejectsIoWithNegativeAlignedBufferOffset) TEST_F(FastpathTest, ScoreRejectsIoIfBufferAddressPlusBufferOffsetIsUnaligned) { EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); - EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); + EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); // The DEFAULT_BUFFER_ADDR is DEFAULT_MEM_ALIGN aligned. Ensure that this @@ -191,7 +191,7 @@ struct FastpathSupportedHipMemoryParam : public FastpathTestBase, public TestWit TEST_P(FastpathSupportedHipMemoryParam, Score) { EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); - EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); + EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); EXPECT_CALL(*mbuffer, getType).WillOnce(Return(GetParam())); EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); @@ -208,7 +208,7 @@ struct FastpathUnsupportedHipMemoryParam : public FastpathTestBase, public TestW TEST_P(FastpathUnsupportedHipMemoryParam, Score) { EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); - EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); + EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); EXPECT_CALL(*mbuffer, getType).WillOnce(Return(GetParam())); EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); @@ -226,7 +226,7 @@ struct FastpathAlignedIoSizesParam : public FastpathTestBase, public TestWithPar TEST_P(FastpathAlignedIoSizesParam, Score) { EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); - EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); + EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); @@ -245,7 +245,7 @@ struct FastpathUnalignedIoSizesParam : public FastpathTestBase, public TestWithP TEST_P(FastpathUnalignedIoSizesParam, Score) { EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); - EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); + EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); @@ -264,7 +264,7 @@ struct FastpathAlignedFileOffsetsParam : public FastpathTestBase, public TestWit TEST_P(FastpathAlignedFileOffsetsParam, Score) { EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); - EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); + EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); @@ -285,7 +285,7 @@ struct FastpathUnalignedFileOffsetsParam : public FastpathTestBase, public TestW TEST_P(FastpathUnalignedFileOffsetsParam, Score) { EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); - EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); + EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); @@ -308,7 +308,7 @@ struct FastpathAlignedBufferOffsetsParam : public FastpathTestBase, public TestW TEST_P(FastpathAlignedBufferOffsetsParam, Score) { EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); - EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); + EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); @@ -330,7 +330,7 @@ struct FastpathUnalignedBufferOffsetsParam : public FastpathTestBase, public Tes TEST_P(FastpathUnalignedBufferOffsetsParam, Score) { EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); - EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); + EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); @@ -358,7 +358,7 @@ struct FastpathIoParam : public FastpathTestBase, public TestWithParam { EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); EXPECT_CALL(*mbuffer, getLength).WillOnce(Return(DEFAULT_BUFFER_LENGTH)); - EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); + EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); } // Setup expectations on the mocks called to validate IO arguments and @@ -375,7 +375,7 @@ struct FastpathIoParam : public FastpathTestBase, public TestWithParam { EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(bufptr)); EXPECT_CALL(*mbuffer, getLength).WillOnce(Return(buflen)); - EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(fd)); + EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(fd)); } // Setup expectations on the mocks called to validate IO arguments and @@ -600,7 +600,7 @@ TEST_P(FastpathIoParam, IoWithFallbackThrowsAFallbackIneligibleException) EXPECT_CALL(mhip, hipInit).WillRepeatedly(Return()); EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); EXPECT_CALL(*mbuffer, getLength).WillOnce(Return(DEFAULT_BUFFER_LENGTH)); - EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); + EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); switch (GetParam()) { case IoType::Read: @@ -629,7 +629,7 @@ TEST_P(FastpathIoParam, IoWithFallbackThrowsHipRuntimeException) EXPECT_CALL(mhip, hipInit).WillOnce(Return()); EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); EXPECT_CALL(*mbuffer, getLength).WillOnce(Return(DEFAULT_BUFFER_LENGTH)); - EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); + EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); switch (GetParam()) { case IoType::Read: @@ -657,7 +657,7 @@ TEST_P(FastpathIoParam, IoThrowsAFallbackEligibleENODEV) EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); EXPECT_CALL(*mbuffer, getLength).WillOnce(Return(DEFAULT_BUFFER_LENGTH)); EXPECT_CALL(mhip, hipInit).WillOnce(Return()); - EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); + EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); switch (GetParam()) { case IoType::Read: @@ -688,7 +688,7 @@ TEST_P(FastpathIoParam, IoThrowsAFallbackEligibleEREMOTEIO) EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); EXPECT_CALL(*mbuffer, getLength).WillOnce(Return(DEFAULT_BUFFER_LENGTH)); EXPECT_CALL(mhip, hipInit).WillOnce(Return()); - EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); + EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); switch (GetParam()) { case IoType::Read: @@ -725,7 +725,7 @@ TEST_P(FastpathIoParam, FallbackRejectsIoRequest) EXPECT_CALL(mhip, hipInit).WillRepeatedly(Return()); EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); EXPECT_CALL(*mbuffer, getLength).WillOnce(Return(DEFAULT_BUFFER_LENGTH)); - EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); + EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); EXPECT_CALL(*m_fallback, score).WillOnce(Return(SCORE_REJECT)); switch (GetParam()) { diff --git a/test/amd_detail/handle.cpp b/test/amd_detail/handle.cpp index b8e2ebb1..2d70a526 100644 --- a/test/amd_detail/handle.cpp +++ b/test/amd_detail/handle.cpp @@ -200,7 +200,7 @@ TEST_F(HipFileHandle, file_initialization) EXPECT_EQ(fh, file->getHandle()); EXPECT_EQ(fd, file->getClientFd()); EXPECT_EQ(fd, file->getBufferedFd()); - EXPECT_EQ(open_fd, file->getUnbufferedFd()); + EXPECT_EQ(open_fd, file->unbufferedFd()); #if defined(STATX_DIOALIGN) EXPECT_EQ(file->dioMemAlign(), stxbuf.stx_dio_mem_align); EXPECT_EQ(file->dioOffsetAlign(), stxbuf.stx_dio_offset_align); diff --git a/test/amd_detail/mfile.h b/test/amd_detail/mfile.h index 66bbfbec..582d425d 100644 --- a/test/amd_detail/mfile.h +++ b/test/amd_detail/mfile.h @@ -21,7 +21,7 @@ class MFile : public IFile { MOCK_METHOD(hipFileHandle_t, getHandle, (), (const, override)); MOCK_METHOD(int, getClientFd, (), (const, override)); MOCK_METHOD(int, getBufferedFd, (), (const, override)); - MOCK_METHOD(std::optional, getUnbufferedFd, (), (const, noexcept, override)); + MOCK_METHOD(std::optional, unbufferedFd, (), (const, noexcept, override)); MOCK_METHOD(uint32_t, dioMemAlign, (), (const, noexcept, override)); MOCK_METHOD(uint32_t, dioOffsetAlign, (), (const, noexcept, override)); MOCK_METHOD(bool, isBlockDevice, (), (const, noexcept, override)); From 8591ae388a63630306dd39cbf58a94718a00840a Mon Sep 17 00:00:00 2001 From: Kurt McMillan Date: Fri, 27 Mar 2026 20:05:09 +0000 Subject: [PATCH 13/28] file: Cleanup - Rename File::buffered_fd -> File::m_buffered_fd --- src/amd_detail/file.cpp | 4 ++-- src/amd_detail/file.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/amd_detail/file.cpp b/src/amd_detail/file.cpp index 77806d7a..151da39e 100644 --- a/src/amd_detail/file.cpp +++ b/src/amd_detail/file.cpp @@ -71,7 +71,7 @@ IFile::getHandle() const } File::File(UnregisteredFile &&uf, const PassKey &) - : client_fd{std::move(uf.client_fd)}, buffered_fd{std::move(uf.buffered_fd)}, + : client_fd{std::move(uf.client_fd)}, m_buffered_fd{std::move(uf.buffered_fd)}, m_unbuffered_fd{std::move(uf.unbuffered_fd)}, m_dio_mem_align{uf.m_dio_mem_align}, m_dio_offset_align{uf.m_dio_offset_align}, m_is_block_device{(uf.stx.stx_mask & STATX_TYPE) && S_ISBLK(uf.stx.stx_mode)}, @@ -91,7 +91,7 @@ File::getClientFd() const int File::getBufferedFd() const { - return buffered_fd.get(); + return m_buffered_fd.get(); } optional diff --git a/src/amd_detail/file.h b/src/amd_detail/file.h index a43403ca..d8aff865 100644 --- a/src/amd_detail/file.h +++ b/src/amd_detail/file.h @@ -161,7 +161,7 @@ class File : public IFile { FileDescriptor client_fd; /// @brief Buffered file descriptor (!O_DIRECT) - FileDescriptor buffered_fd; + FileDescriptor m_buffered_fd; /// @brief Unbuffered file descriptor (O_DIRECT) std::optional m_unbuffered_fd; From e3ab457e4c1bda7e1a79c9c79234047d01d6e33a Mon Sep 17 00:00:00 2001 From: Kurt McMillan Date: Fri, 27 Mar 2026 20:09:38 +0000 Subject: [PATCH 14/28] file: Cleanup - Add noexcept to File::getBufferedFd() --- src/amd_detail/file.cpp | 2 +- src/amd_detail/file.h | 4 ++-- test/amd_detail/mfile.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/amd_detail/file.cpp b/src/amd_detail/file.cpp index 151da39e..165002f3 100644 --- a/src/amd_detail/file.cpp +++ b/src/amd_detail/file.cpp @@ -89,7 +89,7 @@ File::getClientFd() const } int -File::getBufferedFd() const +File::getBufferedFd() const noexcept { return m_buffered_fd.get(); } diff --git a/src/amd_detail/file.h b/src/amd_detail/file.h index d8aff865..d8e61daf 100644 --- a/src/amd_detail/file.h +++ b/src/amd_detail/file.h @@ -87,7 +87,7 @@ class IFile { virtual hipFileHandle_t getHandle() const; virtual int getClientFd() const = 0; - virtual int getBufferedFd() const = 0; + virtual int getBufferedFd() const noexcept = 0; virtual std::optional unbufferedFd() const noexcept = 0; virtual uint32_t dioMemAlign() const noexcept = 0; virtual uint32_t dioOffsetAlign() const noexcept = 0; @@ -113,7 +113,7 @@ class File : public IFile { File &operator=(File &&) = delete; virtual int getClientFd() const override; - virtual int getBufferedFd() const override; + virtual int getBufferedFd() const noexcept override; /// @brief Get the unbuffered file descriptor for this file. Returns nullopt if the file does not support /// direct IO or if there was an error obtaining the unbuffered fd. diff --git a/test/amd_detail/mfile.h b/test/amd_detail/mfile.h index 582d425d..4ee283a0 100644 --- a/test/amd_detail/mfile.h +++ b/test/amd_detail/mfile.h @@ -20,7 +20,7 @@ class MFile : public IFile { public: MOCK_METHOD(hipFileHandle_t, getHandle, (), (const, override)); MOCK_METHOD(int, getClientFd, (), (const, override)); - MOCK_METHOD(int, getBufferedFd, (), (const, override)); + MOCK_METHOD(int, getBufferedFd, (), (const, noexcept, override)); MOCK_METHOD(std::optional, unbufferedFd, (), (const, noexcept, override)); MOCK_METHOD(uint32_t, dioMemAlign, (), (const, noexcept, override)); MOCK_METHOD(uint32_t, dioOffsetAlign, (), (const, noexcept, override)); From 56154598fa7be7f3b420bd31edba1cfc62d77f88 Mon Sep 17 00:00:00 2001 From: Kurt McMillan Date: Fri, 27 Mar 2026 20:11:36 +0000 Subject: [PATCH 15/28] file: Cleanup - Rename File::getBufferedFd() -> File::bufferedFd() --- src/amd_detail/backend/fallback.cpp | 12 ++++++------ src/amd_detail/file.cpp | 2 +- src/amd_detail/file.h | 7 +++++-- test/amd_detail/async.cpp | 6 +++--- test/amd_detail/handle.cpp | 2 +- test/amd_detail/mfile.h | 2 +- 6 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/amd_detail/backend/fallback.cpp b/src/amd_detail/backend/fallback.cpp index 13c4b036..b82da993 100644 --- a/src/amd_detail/backend/fallback.cpp +++ b/src/amd_detail/backend/fallback.cpp @@ -97,7 +97,7 @@ Fallback::_io_impl(IoType type, std::shared_ptr file, std::shared_ptr::get()->pread(file->getBufferedFd(), bounce_buffer.get(), count, offset); + Context::get()->pread(file->bufferedFd(), bounce_buffer.get(), count, offset); if (io_bytes > 0) { Context::get()->hipMemcpy(device_buffer_position, bounce_buffer.get(), static_cast(io_bytes), hipMemcpyHostToDevice); @@ -107,9 +107,9 @@ Fallback::_io_impl(IoType type, std::shared_ptr file, std::shared_ptr::get()->hipMemcpy(bounce_buffer.get(), device_buffer_position, count, hipMemcpyDeviceToHost); Context::get()->hipStreamSynchronize(nullptr); - io_bytes = Context::get()->pwrite(file->getBufferedFd(), bounce_buffer.get(), count, - offset); - Context::get()->fdatasync(file->getBufferedFd()); + io_bytes = + Context::get()->pwrite(file->bufferedFd(), bounce_buffer.get(), count, offset); + Context::get()->fdatasync(file->bufferedFd()); break; default: throw std::runtime_error("Invalid IO type"); @@ -274,11 +274,11 @@ async_io_cpu_copy(void *userargs) try { switch (op->io_type) { case IoType::Read: - ret = Context::get()->pread(op->file->getBufferedFd(), cur_buf_position, + ret = Context::get()->pread(op->file->bufferedFd(), cur_buf_position, remaining_bytes, cur_file_offset); break; case IoType::Write: - ret = Context::get()->pwrite(op->file->getBufferedFd(), cur_buf_position, + ret = Context::get()->pwrite(op->file->bufferedFd(), cur_buf_position, remaining_bytes, cur_file_offset); break; default: diff --git a/src/amd_detail/file.cpp b/src/amd_detail/file.cpp index 165002f3..3a113953 100644 --- a/src/amd_detail/file.cpp +++ b/src/amd_detail/file.cpp @@ -89,7 +89,7 @@ File::getClientFd() const } int -File::getBufferedFd() const noexcept +File::bufferedFd() const noexcept { return m_buffered_fd.get(); } diff --git a/src/amd_detail/file.h b/src/amd_detail/file.h index d8e61daf..b4a656e8 100644 --- a/src/amd_detail/file.h +++ b/src/amd_detail/file.h @@ -87,7 +87,7 @@ class IFile { virtual hipFileHandle_t getHandle() const; virtual int getClientFd() const = 0; - virtual int getBufferedFd() const noexcept = 0; + virtual int bufferedFd() const noexcept = 0; virtual std::optional unbufferedFd() const noexcept = 0; virtual uint32_t dioMemAlign() const noexcept = 0; virtual uint32_t dioOffsetAlign() const noexcept = 0; @@ -113,7 +113,10 @@ class File : public IFile { File &operator=(File &&) = delete; virtual int getClientFd() const override; - virtual int getBufferedFd() const noexcept override; + + /// @brief Get the buffered file descriptor (!O_DIRECT) + /// @return The buffered file descriptor + virtual int bufferedFd() const noexcept override; /// @brief Get the unbuffered file descriptor for this file. Returns nullopt if the file does not support /// direct IO or if there was an error obtaining the unbuffered fd. diff --git a/test/amd_detail/async.cpp b/test/amd_detail/async.cpp index f1801c6c..5749b5c9 100644 --- a/test/amd_detail/async.cpp +++ b/test/amd_detail/async.cpp @@ -665,7 +665,7 @@ TEST_P(AsyncIoOpWithParams, cpuIOReturnsFullSize) else { EXPECT_CALL(msys, pwrite).WillOnce(Return(size)); } - EXPECT_CALL(*mfile, getBufferedFd); + EXPECT_CALL(*mfile, bufferedFd); async_io_cpu_copy(op.get()); ASSERT_EQ(op->bytes_transferred_internal, size); } @@ -678,7 +678,7 @@ TEST_P(AsyncIoOpWithParams, cpuCopyReadPreadPwriteErrorReturnsError) else { EXPECT_CALL(msys, pwrite).WillOnce(Throw(std::system_error(3, std::generic_category()))); } - EXPECT_CALL(*mfile, getBufferedFd); + EXPECT_CALL(*mfile, bufferedFd); async_io_cpu_copy(op.get()); ASSERT_EQ(op->bytes_transferred_internal, -1); } @@ -695,7 +695,7 @@ TEST_P(AsyncIoOpWithParams, cpuCopyReadPreadPwriteRetriesOnEINTR) .WillOnce(Throw(std::system_error(EINTR, std::generic_category()))) .WillOnce(Return(size)); } - EXPECT_CALL(*mfile, getBufferedFd).Times(2); + EXPECT_CALL(*mfile, bufferedFd).Times(2); async_io_cpu_copy(op.get()); ASSERT_EQ(op->bytes_transferred_internal, size); diff --git a/test/amd_detail/handle.cpp b/test/amd_detail/handle.cpp index 2d70a526..e2f10beb 100644 --- a/test/amd_detail/handle.cpp +++ b/test/amd_detail/handle.cpp @@ -199,7 +199,7 @@ TEST_F(HipFileHandle, file_initialization) EXPECT_EQ(fh, file->getHandle()); EXPECT_EQ(fd, file->getClientFd()); - EXPECT_EQ(fd, file->getBufferedFd()); + EXPECT_EQ(fd, file->bufferedFd()); EXPECT_EQ(open_fd, file->unbufferedFd()); #if defined(STATX_DIOALIGN) EXPECT_EQ(file->dioMemAlign(), stxbuf.stx_dio_mem_align); diff --git a/test/amd_detail/mfile.h b/test/amd_detail/mfile.h index 4ee283a0..140ea41f 100644 --- a/test/amd_detail/mfile.h +++ b/test/amd_detail/mfile.h @@ -20,7 +20,7 @@ class MFile : public IFile { public: MOCK_METHOD(hipFileHandle_t, getHandle, (), (const, override)); MOCK_METHOD(int, getClientFd, (), (const, override)); - MOCK_METHOD(int, getBufferedFd, (), (const, noexcept, override)); + MOCK_METHOD(int, bufferedFd, (), (const, noexcept, override)); MOCK_METHOD(std::optional, unbufferedFd, (), (const, noexcept, override)); MOCK_METHOD(uint32_t, dioMemAlign, (), (const, noexcept, override)); MOCK_METHOD(uint32_t, dioOffsetAlign, (), (const, noexcept, override)); From 7ce34f9b22d8c753346a5363becb8167a21049e7 Mon Sep 17 00:00:00 2001 From: Kurt McMillan Date: Fri, 27 Mar 2026 20:30:28 +0000 Subject: [PATCH 16/28] File: Cleanup - Rename File::client_fd -> File::m_client_fd --- src/amd_detail/file.cpp | 4 ++-- src/amd_detail/file.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/amd_detail/file.cpp b/src/amd_detail/file.cpp index 3a113953..7b35a8a7 100644 --- a/src/amd_detail/file.cpp +++ b/src/amd_detail/file.cpp @@ -71,7 +71,7 @@ IFile::getHandle() const } File::File(UnregisteredFile &&uf, const PassKey &) - : client_fd{std::move(uf.client_fd)}, m_buffered_fd{std::move(uf.buffered_fd)}, + : m_client_fd{std::move(uf.client_fd)}, m_buffered_fd{std::move(uf.buffered_fd)}, m_unbuffered_fd{std::move(uf.unbuffered_fd)}, m_dio_mem_align{uf.m_dio_mem_align}, m_dio_offset_align{uf.m_dio_offset_align}, m_is_block_device{(uf.stx.stx_mask & STATX_TYPE) && S_ISBLK(uf.stx.stx_mode)}, @@ -85,7 +85,7 @@ File::File(UnregisteredFile &&uf, const PassKey &) int File::getClientFd() const { - return client_fd.get(); + return m_client_fd.get(); } int diff --git a/src/amd_detail/file.h b/src/amd_detail/file.h index b4a656e8..05d9d298 100644 --- a/src/amd_detail/file.h +++ b/src/amd_detail/file.h @@ -161,7 +161,7 @@ class File : public IFile { private: /// @brief The file descriptor provided by the client - FileDescriptor client_fd; + FileDescriptor m_client_fd; /// @brief Buffered file descriptor (!O_DIRECT) FileDescriptor m_buffered_fd; From ff9b2b734b5b9ae80af9e0d81fcc9e2b0e65cc45 Mon Sep 17 00:00:00 2001 From: Kurt McMillan Date: Fri, 27 Mar 2026 20:32:02 +0000 Subject: [PATCH 17/28] file: Cleanup - Add noexcept to File::getClientFd() --- src/amd_detail/file.cpp | 2 +- src/amd_detail/file.h | 4 ++-- test/amd_detail/mfile.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/amd_detail/file.cpp b/src/amd_detail/file.cpp index 7b35a8a7..a27988a1 100644 --- a/src/amd_detail/file.cpp +++ b/src/amd_detail/file.cpp @@ -83,7 +83,7 @@ File::File(UnregisteredFile &&uf, const PassKey &) } int -File::getClientFd() const +File::getClientFd() const noexcept { return m_client_fd.get(); } diff --git a/src/amd_detail/file.h b/src/amd_detail/file.h index 05d9d298..7ce6a566 100644 --- a/src/amd_detail/file.h +++ b/src/amd_detail/file.h @@ -86,7 +86,7 @@ class IFile { /// @return The handle for this file virtual hipFileHandle_t getHandle() const; - virtual int getClientFd() const = 0; + virtual int getClientFd() const noexcept = 0; virtual int bufferedFd() const noexcept = 0; virtual std::optional unbufferedFd() const noexcept = 0; virtual uint32_t dioMemAlign() const noexcept = 0; @@ -112,7 +112,7 @@ class File : public IFile { File(File &&) = delete; File &operator=(File &&) = delete; - virtual int getClientFd() const override; + virtual int getClientFd() const noexcept override; /// @brief Get the buffered file descriptor (!O_DIRECT) /// @return The buffered file descriptor diff --git a/test/amd_detail/mfile.h b/test/amd_detail/mfile.h index 140ea41f..3b1f97e8 100644 --- a/test/amd_detail/mfile.h +++ b/test/amd_detail/mfile.h @@ -19,7 +19,7 @@ namespace hipFile { class MFile : public IFile { public: MOCK_METHOD(hipFileHandle_t, getHandle, (), (const, override)); - MOCK_METHOD(int, getClientFd, (), (const, override)); + MOCK_METHOD(int, getClientFd, (), (const, noexcept, override)); MOCK_METHOD(int, bufferedFd, (), (const, noexcept, override)); MOCK_METHOD(std::optional, unbufferedFd, (), (const, noexcept, override)); MOCK_METHOD(uint32_t, dioMemAlign, (), (const, noexcept, override)); From db1da075bdccc5814b3c16acf1a998085af1d785 Mon Sep 17 00:00:00 2001 From: Kurt McMillan Date: Fri, 27 Mar 2026 20:34:24 +0000 Subject: [PATCH 18/28] file: Cleanup - Rename File::getClientFd() -> File::clientFd() --- src/amd_detail/file.cpp | 10 +++++----- src/amd_detail/file.h | 4 ++-- test/amd_detail/handle.cpp | 2 +- test/amd_detail/mfile.h | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/amd_detail/file.cpp b/src/amd_detail/file.cpp index a27988a1..2633a62e 100644 --- a/src/amd_detail/file.cpp +++ b/src/amd_detail/file.cpp @@ -83,7 +83,7 @@ File::File(UnregisteredFile &&uf, const PassKey &) } int -File::getClientFd() const noexcept +File::clientFd() const noexcept { return m_client_fd.get(); } @@ -157,9 +157,9 @@ FileMap::registerFile(UnregisteredFile &&uf) throw FileAlreadyRegistered(); } - auto file = std::shared_ptr(new File(std::move(uf), PassKey{})); - from_fd[file->getClientFd()] = file; - from_fh[file->getHandle()] = file; + auto file = std::shared_ptr(new File(std::move(uf), PassKey{})); + from_fd[file->clientFd()] = file; + from_fh[file->getHandle()] = file; return file->getHandle(); } @@ -177,7 +177,7 @@ FileMap::deregisterFile(hipFileHandle_t fh) throw FileOperationsOutstanding(); } - from_fd.erase(itr->second->getClientFd()); + from_fd.erase(itr->second->clientFd()); from_fh.erase(fh); } diff --git a/src/amd_detail/file.h b/src/amd_detail/file.h index 7ce6a566..9f5eb650 100644 --- a/src/amd_detail/file.h +++ b/src/amd_detail/file.h @@ -86,7 +86,7 @@ class IFile { /// @return The handle for this file virtual hipFileHandle_t getHandle() const; - virtual int getClientFd() const noexcept = 0; + virtual int clientFd() const noexcept = 0; virtual int bufferedFd() const noexcept = 0; virtual std::optional unbufferedFd() const noexcept = 0; virtual uint32_t dioMemAlign() const noexcept = 0; @@ -112,7 +112,7 @@ class File : public IFile { File(File &&) = delete; File &operator=(File &&) = delete; - virtual int getClientFd() const noexcept override; + virtual int clientFd() const noexcept override; /// @brief Get the buffered file descriptor (!O_DIRECT) /// @return The buffered file descriptor diff --git a/test/amd_detail/handle.cpp b/test/amd_detail/handle.cpp index e2f10beb..43c1eb69 100644 --- a/test/amd_detail/handle.cpp +++ b/test/amd_detail/handle.cpp @@ -198,7 +198,7 @@ TEST_F(HipFileHandle, file_initialization) auto file{Context::get()->getFile(fh)}; EXPECT_EQ(fh, file->getHandle()); - EXPECT_EQ(fd, file->getClientFd()); + EXPECT_EQ(fd, file->clientFd()); EXPECT_EQ(fd, file->bufferedFd()); EXPECT_EQ(open_fd, file->unbufferedFd()); #if defined(STATX_DIOALIGN) diff --git a/test/amd_detail/mfile.h b/test/amd_detail/mfile.h index 3b1f97e8..e4110b5d 100644 --- a/test/amd_detail/mfile.h +++ b/test/amd_detail/mfile.h @@ -19,7 +19,7 @@ namespace hipFile { class MFile : public IFile { public: MOCK_METHOD(hipFileHandle_t, getHandle, (), (const, override)); - MOCK_METHOD(int, getClientFd, (), (const, noexcept, override)); + MOCK_METHOD(int, clientFd, (), (const, noexcept, override)); MOCK_METHOD(int, bufferedFd, (), (const, noexcept, override)); MOCK_METHOD(std::optional, unbufferedFd, (), (const, noexcept, override)); MOCK_METHOD(uint32_t, dioMemAlign, (), (const, noexcept, override)); From f75f9c796e2108811022d050c98bc896ca0e7a54 Mon Sep 17 00:00:00 2001 From: Kurt McMillan Date: Fri, 27 Mar 2026 20:36:54 +0000 Subject: [PATCH 19/28] file: Cleanup - Add noexcept to File::getHandle() --- src/amd_detail/file.cpp | 2 +- src/amd_detail/file.h | 2 +- test/amd_detail/mfile.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/amd_detail/file.cpp b/src/amd_detail/file.cpp index 2633a62e..5dfeaf0f 100644 --- a/src/amd_detail/file.cpp +++ b/src/amd_detail/file.cpp @@ -65,7 +65,7 @@ UnregisteredFile::UnregisteredFile(int fd) } hipFileHandle_t -IFile::getHandle() const +IFile::getHandle() const noexcept { return reinterpret_cast(const_cast(this)); } diff --git a/src/amd_detail/file.h b/src/amd_detail/file.h index 9f5eb650..c7dcb5dd 100644 --- a/src/amd_detail/file.h +++ b/src/amd_detail/file.h @@ -84,7 +84,7 @@ class IFile { /// @brief Get the handle for this file /// @return The handle for this file - virtual hipFileHandle_t getHandle() const; + virtual hipFileHandle_t getHandle() const noexcept; virtual int clientFd() const noexcept = 0; virtual int bufferedFd() const noexcept = 0; diff --git a/test/amd_detail/mfile.h b/test/amd_detail/mfile.h index e4110b5d..e5ad7d51 100644 --- a/test/amd_detail/mfile.h +++ b/test/amd_detail/mfile.h @@ -18,7 +18,7 @@ namespace hipFile { class MFile : public IFile { public: - MOCK_METHOD(hipFileHandle_t, getHandle, (), (const, override)); + MOCK_METHOD(hipFileHandle_t, getHandle, (), (const, noexcept, override)); MOCK_METHOD(int, clientFd, (), (const, noexcept, override)); MOCK_METHOD(int, bufferedFd, (), (const, noexcept, override)); MOCK_METHOD(std::optional, unbufferedFd, (), (const, noexcept, override)); From 6abd9c3ef60b6747ee6fa57da13b904a9241a676 Mon Sep 17 00:00:00 2001 From: Kurt McMillan Date: Fri, 27 Mar 2026 20:38:49 +0000 Subject: [PATCH 20/28] file: Cleanup - Rename File::getHandle() -> File::handle() --- src/amd_detail/batch/batch.cpp | 2 +- src/amd_detail/file.cpp | 10 +++++----- src/amd_detail/file.h | 2 +- test/amd_detail/batch/batch.cpp | 9 ++++----- test/amd_detail/handle.cpp | 2 +- test/amd_detail/mfile.h | 2 +- 6 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/amd_detail/batch/batch.cpp b/src/amd_detail/batch/batch.cpp index 1c228735..9b4eaa12 100644 --- a/src/amd_detail/batch/batch.cpp +++ b/src/amd_detail/batch/batch.cpp @@ -56,7 +56,7 @@ BatchOperation::BatchOperation(std::unique_ptr params, } // Check File parameters - if (io_params->fh != file->getHandle()) { + if (io_params->fh != file->handle()) { throw std::invalid_argument("File does not match handle specified in io_params."); } if (io_params->u.batch.file_offset < 0) { diff --git a/src/amd_detail/file.cpp b/src/amd_detail/file.cpp index 5dfeaf0f..b66985f8 100644 --- a/src/amd_detail/file.cpp +++ b/src/amd_detail/file.cpp @@ -65,7 +65,7 @@ UnregisteredFile::UnregisteredFile(int fd) } hipFileHandle_t -IFile::getHandle() const noexcept +IFile::handle() const noexcept { return reinterpret_cast(const_cast(this)); } @@ -157,11 +157,11 @@ FileMap::registerFile(UnregisteredFile &&uf) throw FileAlreadyRegistered(); } - auto file = std::shared_ptr(new File(std::move(uf), PassKey{})); - from_fd[file->clientFd()] = file; - from_fh[file->getHandle()] = file; + auto file = std::shared_ptr(new File(std::move(uf), PassKey{})); + from_fd[file->clientFd()] = file; + from_fh[file->handle()] = file; - return file->getHandle(); + return file->handle(); } void diff --git a/src/amd_detail/file.h b/src/amd_detail/file.h index c7dcb5dd..07b48514 100644 --- a/src/amd_detail/file.h +++ b/src/amd_detail/file.h @@ -84,7 +84,7 @@ class IFile { /// @brief Get the handle for this file /// @return The handle for this file - virtual hipFileHandle_t getHandle() const noexcept; + virtual hipFileHandle_t handle() const noexcept; virtual int clientFd() const noexcept = 0; virtual int bufferedFd() const noexcept = 0; diff --git a/test/amd_detail/batch/batch.cpp b/test/amd_detail/batch/batch.cpp index 1c1c0044..ef0c1494 100644 --- a/test/amd_detail/batch/batch.cpp +++ b/test/amd_detail/batch/batch.cpp @@ -47,7 +47,7 @@ struct HipFileBatch : public HipFileUnopened { EXPECT_CALL(*default_mock_buffer, getLength).WillRepeatedly(Return(1)); default_mock_file = std::make_shared>(); - EXPECT_CALL(*default_mock_file, getHandle).WillRepeatedly(Return(file_handle)); + EXPECT_CALL(*default_mock_file, handle).WillRepeatedly(Return(file_handle)); io_params = std::make_unique(); io_params->u.batch.devPtr_base = const_cast(buffer_pointer); @@ -86,8 +86,7 @@ TEST_F(HipFileBatch, CreateOperationBadBuffer) TEST_F(HipFileBatch, CreateOperationBadFileHandle) { - EXPECT_CALL(*default_mock_file, getHandle) - .WillOnce(Return(reinterpret_cast(0xFACEFEED))); + EXPECT_CALL(*default_mock_file, handle).WillOnce(Return(reinterpret_cast(0xFACEFEED))); EXPECT_THROW(BatchOperation(std::move(io_params), default_mock_buffer, default_mock_file), std::invalid_argument); } @@ -242,13 +241,13 @@ struct HipFileBatchContext : public HipFileUnopened { EXPECT_CALL(*default_mock_buffer, getLength).WillRepeatedly(Return(default_mock_buffer_length)); default_mock_file = std::make_shared>(); - EXPECT_CALL(*default_mock_file, getHandle).WillRepeatedly(Return(default_mock_file.get())); + EXPECT_CALL(*default_mock_file, handle).WillRepeatedly(Return(default_mock_file.get())); file_buffer_pair default_fb_pair = {default_mock_file, default_mock_buffer}; io_params.u.batch.devPtr_base = default_mock_buffer->getBuffer(); io_params.u.batch.size = 1; - io_params.fh = default_mock_file->getHandle(); + io_params.fh = default_mock_file->handle(); io_params.mode = hipFileBatch; io_params.opcode = hipFileBatchRead; diff --git a/test/amd_detail/handle.cpp b/test/amd_detail/handle.cpp index 43c1eb69..2dda8231 100644 --- a/test/amd_detail/handle.cpp +++ b/test/amd_detail/handle.cpp @@ -197,7 +197,7 @@ TEST_F(HipFileHandle, file_initialization) auto fh{Context::get()->registerFile(fd)}; auto file{Context::get()->getFile(fh)}; - EXPECT_EQ(fh, file->getHandle()); + EXPECT_EQ(fh, file->handle()); EXPECT_EQ(fd, file->clientFd()); EXPECT_EQ(fd, file->bufferedFd()); EXPECT_EQ(open_fd, file->unbufferedFd()); diff --git a/test/amd_detail/mfile.h b/test/amd_detail/mfile.h index e5ad7d51..f351bfa4 100644 --- a/test/amd_detail/mfile.h +++ b/test/amd_detail/mfile.h @@ -18,7 +18,7 @@ namespace hipFile { class MFile : public IFile { public: - MOCK_METHOD(hipFileHandle_t, getHandle, (), (const, noexcept, override)); + MOCK_METHOD(hipFileHandle_t, handle, (), (const, noexcept, override)); MOCK_METHOD(int, clientFd, (), (const, noexcept, override)); MOCK_METHOD(int, bufferedFd, (), (const, noexcept, override)); MOCK_METHOD(std::optional, unbufferedFd, (), (const, noexcept, override)); From c904776ca1d03cebaef5aa91dd7719826e491878 Mon Sep 17 00:00:00 2001 From: Kurt McMillan Date: Mon, 30 Mar 2026 17:23:40 +0000 Subject: [PATCH 21/28] fastpath/test: Cleanup - Use the builder pattern to simplify Fastpath::score expectations This will reduce the blast radius of the following changes. --- test/amd_detail/fastpath.cpp | 227 +++++++++++++++++------------------ 1 file changed, 109 insertions(+), 118 deletions(-) diff --git a/test/amd_detail/fastpath.cpp b/test/amd_detail/fastpath.cpp index 9478acda..5d943a76 100644 --- a/test/amd_detail/fastpath.cpp +++ b/test/amd_detail/fastpath.cpp @@ -42,11 +42,11 @@ static int SCORE_ACCEPT{100}; static const int SCORE_REJECT{-1}; #if defined(STATX_DIOALIGN) -static uint32_t DEFAULT_MEM_ALIGN{4}; -static uint32_t DEFAULT_OFFSET_ALIGN{512}; +static const uint32_t DEFAULT_MEM_ALIGN{4}; +static const uint32_t DEFAULT_OFFSET_ALIGN{512}; #else -static uint32_t DEFAULT_MEM_ALIGN{4096}; -static uint32_t DEFAULT_OFFSET_ALIGN{4096}; +static const uint32_t DEFAULT_MEM_ALIGN{4096}; +static const uint32_t DEFAULT_OFFSET_ALIGN{4096}; #endif namespace hipFile { @@ -58,26 +58,17 @@ operator==(const hipAmdFileHandle_t &lhs, const hipAmdFileHandle_t &rhs) } // Provide default values for variables used in fastpath tests -struct FastpathTestBase { - const bool DEFAULT_ENABLE{true}; - const size_t DEFAULT_IO_SIZE{1024 * 1024}; - void *const DEFAULT_BUFFER_ADDR{reinterpret_cast(0xABAD'CAFE'0000'0000)}; - const off_t DEFAULT_BUFFER_OFFSET{DEFAULT_MEM_ALIGN}; - const size_t DEFAULT_BUFFER_LENGTH{DEFAULT_IO_SIZE + static_cast(DEFAULT_BUFFER_OFFSET)}; - const hipMemoryType DEFAULT_BUFFER_TYPE{hipMemoryTypeDevice}; - const optional DEFAULT_UNBUFFERED_FD{7}; - const off_t DEFAULT_FILE_OFFSET{8192}; - const struct statx DEFAULT_STATX { - []() { - struct statx stx {}; -#if defined(STATX_DIOALIGN) - stx.stx_mask = STATX_DIOALIGN; - stx.stx_dio_mem_align = DEFAULT_MEM_ALIGN; - stx.stx_dio_offset_align = DEFAULT_OFFSET_ALIGN; -#endif - return stx; - }() - }; +class FastpathTestBase { +public: + static constexpr bool DEFAULT_ENABLE{true}; + static constexpr size_t DEFAULT_IO_SIZE{1024 * 1024}; + static constexpr uintptr_t DEFAULT_BUFFER_ADDR{0xABAD'CAFE'0000'0000}; + static constexpr off_t DEFAULT_BUFFER_OFFSET{DEFAULT_MEM_ALIGN}; + static constexpr size_t DEFAULT_BUFFER_LENGTH{DEFAULT_IO_SIZE + + static_cast(DEFAULT_BUFFER_OFFSET)}; + static constexpr hipMemoryType DEFAULT_BUFFER_TYPE{hipMemoryTypeDevice}; + static constexpr optional DEFAULT_UNBUFFERED_FD{7}; + static constexpr off_t DEFAULT_FILE_OFFSET{8192}; // Buffer and file mocks used to setup expectations shared_ptr> mfile{make_shared>()}; @@ -86,6 +77,76 @@ struct FastpathTestBase { StrictMock mcfg{}; }; +class FastpathScoreExpectations; + +class FastpathScoreExpectationsBuilder { +public: + StrictMock &m_mcfg; + shared_ptr> m_mfile; + shared_ptr> m_mbuffer; + + optional m_fastpath_enabled; + optional m_buffer_addr; + optional m_buffer_type; + optional> m_unbuffered_fd; + + FastpathScoreExpectationsBuilder(StrictMock &mcfg, shared_ptr> mfile, + shared_ptr> mbuffer) + : m_mcfg(mcfg), m_mfile(mfile), m_mbuffer(mbuffer) + { + } + + FastpathScoreExpectationsBuilder &fastpathEnabled(bool enabled) + { + m_fastpath_enabled = enabled; + return *this; + } + + FastpathScoreExpectationsBuilder &bufferAddr(void *addr) + { + m_buffer_addr = addr; + return *this; + } + + FastpathScoreExpectationsBuilder &bufferType(hipMemoryType type) + { + m_buffer_type = type; + return *this; + } + + FastpathScoreExpectationsBuilder &unbufferedFd(optional fd) + { + m_unbuffered_fd = fd; + return *this; + } + + FastpathScoreExpectations build(); +}; + +class FastpathScoreExpectations { +public: + FastpathScoreExpectations(const FastpathScoreExpectationsBuilder &builder) + { + EXPECT_CALL(builder.m_mcfg, fastpath()) + .WillOnce(Return(builder.m_fastpath_enabled.value_or(FastpathTestBase::DEFAULT_ENABLE))); + EXPECT_CALL(*builder.m_mfile, unbufferedFd) + .WillOnce(Return(builder.m_unbuffered_fd.value_or(FastpathTestBase::DEFAULT_UNBUFFERED_FD))); + EXPECT_CALL(*builder.m_mbuffer, getType) + .WillOnce(Return(builder.m_buffer_type.value_or(FastpathTestBase::DEFAULT_BUFFER_TYPE))); + EXPECT_CALL(*builder.m_mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); + EXPECT_CALL(*builder.m_mbuffer, getBuffer) + .WillOnce(Return(builder.m_buffer_addr.value_or( + reinterpret_cast(FastpathTestBase::DEFAULT_BUFFER_ADDR)))); + EXPECT_CALL(*builder.m_mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); + } +}; + +FastpathScoreExpectations +FastpathScoreExpectationsBuilder::build() +{ + return FastpathScoreExpectations(*this); +} + struct FastpathTest : public FastpathTestBase, public Test {}; TEST_F(FastpathTest, TestDefaults) @@ -95,7 +156,7 @@ TEST_F(FastpathTest, TestDefaults) ASSERT_TRUE(DEFAULT_MEM_ALIGN > 1); ASSERT_FALSE((DEFAULT_OFFSET_ALIGN & (DEFAULT_OFFSET_ALIGN - 1))); ASSERT_TRUE(DEFAULT_OFFSET_ALIGN > 1); - ASSERT_FALSE((reinterpret_cast(DEFAULT_BUFFER_ADDR) & (DEFAULT_MEM_ALIGN - 1))); + ASSERT_FALSE((DEFAULT_BUFFER_ADDR & (DEFAULT_MEM_ALIGN - 1))); ASSERT_FALSE((DEFAULT_BUFFER_OFFSET & (DEFAULT_MEM_ALIGN - 1))); ASSERT_FALSE((DEFAULT_IO_SIZE & (DEFAULT_OFFSET_ALIGN - 1))); ASSERT_FALSE((DEFAULT_FILE_OFFSET & (DEFAULT_OFFSET_ALIGN - 1))); @@ -103,12 +164,7 @@ TEST_F(FastpathTest, TestDefaults) TEST_F(FastpathTest, ScoreAcceptsIoWithDefaults) { - EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); - EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); - EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); - EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); - EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); - EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); + FastpathScoreExpectationsBuilder(mcfg, mfile, mbuffer).build(); ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET), SCORE_ACCEPT); @@ -116,12 +172,7 @@ TEST_F(FastpathTest, ScoreAcceptsIoWithDefaults) TEST_F(FastpathTest, ScoreRejectsIoIfFastpathIsDisabled) { - EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(false)); - EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); - EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); - EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); - EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); - EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); + FastpathScoreExpectationsBuilder(mcfg, mfile, mbuffer).fastpathEnabled(false).build(); ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET), SCORE_REJECT); @@ -129,12 +180,7 @@ TEST_F(FastpathTest, ScoreRejectsIoIfFastpathIsDisabled) TEST_F(FastpathTest, ScoreRejectsIoIfUnbufferedFdNotAvailable) { - EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); - EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(nullopt)); - EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); - EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); - EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); - EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); + FastpathScoreExpectationsBuilder(mcfg, mfile, mbuffer).unbufferedFd(nullopt).build(); ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET), SCORE_REJECT); @@ -142,12 +188,7 @@ TEST_F(FastpathTest, ScoreRejectsIoIfUnbufferedFdNotAvailable) TEST_F(FastpathTest, ScoreRejectsIoWithNegativeAlignedFileOffset) { - EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); - EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); - EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); - EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); - EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); - EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); + FastpathScoreExpectationsBuilder(mcfg, mfile, mbuffer).build(); ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, -static_cast(DEFAULT_OFFSET_ALIGN), DEFAULT_BUFFER_OFFSET), @@ -156,12 +197,7 @@ TEST_F(FastpathTest, ScoreRejectsIoWithNegativeAlignedFileOffset) TEST_F(FastpathTest, ScoreRejectsIoWithNegativeAlignedBufferOffset) { - EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); - EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); - EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); - EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); - EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); - EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); + FastpathScoreExpectationsBuilder(mcfg, mfile, mbuffer).build(); ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, -static_cast(DEFAULT_MEM_ALIGN)), @@ -170,16 +206,11 @@ TEST_F(FastpathTest, ScoreRejectsIoWithNegativeAlignedBufferOffset) TEST_F(FastpathTest, ScoreRejectsIoIfBufferAddressPlusBufferOffsetIsUnaligned) { - EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); - EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); - EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); - EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); // The DEFAULT_BUFFER_ADDR is DEFAULT_MEM_ALIGN aligned. Ensure that this // test's buffer is not DEFAULT_MEM_ALIGN aligned. - EXPECT_CALL(*mbuffer, getBuffer) - .WillOnce(Return(reinterpret_cast(reinterpret_cast(DEFAULT_BUFFER_ADDR) + - (DEFAULT_MEM_ALIGN >> 1)))); - EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); + FastpathScoreExpectationsBuilder(mcfg, mfile, mbuffer) + .bufferAddr(reinterpret_cast(DEFAULT_BUFFER_ADDR + (DEFAULT_MEM_ALIGN >> 1))) + .build(); ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, static_cast(DEFAULT_MEM_ALIGN)), @@ -190,12 +221,7 @@ struct FastpathSupportedHipMemoryParam : public FastpathTestBase, public TestWit TEST_P(FastpathSupportedHipMemoryParam, Score) { - EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); - EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); - EXPECT_CALL(*mbuffer, getType).WillOnce(Return(GetParam())); - EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); - EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); - EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); + FastpathScoreExpectationsBuilder(mcfg, mfile, mbuffer).bufferType(GetParam()).build(); ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET), SCORE_ACCEPT); @@ -207,12 +233,7 @@ struct FastpathUnsupportedHipMemoryParam : public FastpathTestBase, public TestW TEST_P(FastpathUnsupportedHipMemoryParam, Score) { - EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); - EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); - EXPECT_CALL(*mbuffer, getType).WillOnce(Return(GetParam())); - EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); - EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); - EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); + FastpathScoreExpectationsBuilder(mcfg, mfile, mbuffer).bufferType(GetParam()).build(); ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET), SCORE_REJECT); @@ -225,12 +246,7 @@ struct FastpathAlignedIoSizesParam : public FastpathTestBase, public TestWithPar TEST_P(FastpathAlignedIoSizesParam, Score) { - EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); - EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); - EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); - EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); - EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); - EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); + FastpathScoreExpectationsBuilder(mcfg, mfile, mbuffer).build(); ASSERT_EQ(Fastpath().score(mfile, mbuffer, GetParam(), DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET), SCORE_ACCEPT); @@ -244,12 +260,7 @@ struct FastpathUnalignedIoSizesParam : public FastpathTestBase, public TestWithP TEST_P(FastpathUnalignedIoSizesParam, Score) { - EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); - EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); - EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); - EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); - EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); - EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); + FastpathScoreExpectationsBuilder(mcfg, mfile, mbuffer).build(); ASSERT_EQ(Fastpath().score(mfile, mbuffer, GetParam(), DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET), SCORE_REJECT); @@ -263,12 +274,7 @@ struct FastpathAlignedFileOffsetsParam : public FastpathTestBase, public TestWit TEST_P(FastpathAlignedFileOffsetsParam, Score) { - EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); - EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); - EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); - EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); - EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); - EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); + FastpathScoreExpectationsBuilder(mcfg, mfile, mbuffer).build(); ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, GetParam(), DEFAULT_BUFFER_OFFSET), GetParam() >= 0 ? SCORE_ACCEPT : SCORE_REJECT); @@ -284,12 +290,7 @@ struct FastpathUnalignedFileOffsetsParam : public FastpathTestBase, public TestW TEST_P(FastpathUnalignedFileOffsetsParam, Score) { - EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); - EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); - EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); - EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); - EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); - EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); + FastpathScoreExpectationsBuilder(mcfg, mfile, mbuffer).build(); ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, GetParam(), DEFAULT_BUFFER_OFFSET), SCORE_REJECT); @@ -307,12 +308,7 @@ struct FastpathAlignedBufferOffsetsParam : public FastpathTestBase, public TestW TEST_P(FastpathAlignedBufferOffsetsParam, Score) { - EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); - EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); - EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); - EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); - EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); - EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); + FastpathScoreExpectationsBuilder(mcfg, mfile, mbuffer).build(); ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, GetParam()), GetParam() >= 0 ? SCORE_ACCEPT : SCORE_REJECT); @@ -329,12 +325,7 @@ struct FastpathUnalignedBufferOffsetsParam : public FastpathTestBase, public Tes TEST_P(FastpathUnalignedBufferOffsetsParam, Score) { - EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); - EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); - EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); - EXPECT_CALL(*mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); - EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); - EXPECT_CALL(*mfile, dioMemAlign).WillOnce(Return(DEFAULT_MEM_ALIGN)); + FastpathScoreExpectationsBuilder(mcfg, mfile, mbuffer).build(); ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, GetParam()), SCORE_REJECT); @@ -356,7 +347,7 @@ struct FastpathIoParam : public FastpathTestBase, public TestWithParam { void expect_validate() { EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(DEFAULT_ENABLE)); - EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); + EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(reinterpret_cast(DEFAULT_BUFFER_ADDR))); EXPECT_CALL(*mbuffer, getLength).WillOnce(Return(DEFAULT_BUFFER_LENGTH)); EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); } @@ -573,7 +564,7 @@ TEST_P(FastpathIoParam, IoSizeIsTruncatedToMaxRWCount) const size_t buffer_size{SIZE_MAX}; const size_t io_size{SIZE_MAX}; - expect_io(DEFAULT_UNBUFFERED_FD, DEFAULT_BUFFER_ADDR, buffer_size); + expect_io(DEFAULT_UNBUFFERED_FD, reinterpret_cast(DEFAULT_BUFFER_ADDR), buffer_size); switch (GetParam()) { case IoType::Read: EXPECT_CALL(mhip, hipAmdFileRead(_, _, MAX_RW_COUNT, _)).WillOnce(Return(MAX_RW_COUNT)); @@ -598,7 +589,7 @@ TEST_P(FastpathIoParam, IoWithFallbackThrowsAFallbackIneligibleException) EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(true)); EXPECT_CALL(mhip, hipInit).WillRepeatedly(Return()); - EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); + EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(reinterpret_cast(DEFAULT_BUFFER_ADDR))); EXPECT_CALL(*mbuffer, getLength).WillOnce(Return(DEFAULT_BUFFER_LENGTH)); EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); @@ -627,7 +618,7 @@ TEST_P(FastpathIoParam, IoWithFallbackThrowsHipRuntimeException) EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(true)); EXPECT_CALL(mhip, hipInit).WillOnce(Return()); - EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); + EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(reinterpret_cast(DEFAULT_BUFFER_ADDR))); EXPECT_CALL(*mbuffer, getLength).WillOnce(Return(DEFAULT_BUFFER_LENGTH)); EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); @@ -654,7 +645,7 @@ TEST_P(FastpathIoParam, IoThrowsAFallbackEligibleENODEV) backend->register_fallback_backend(m_fallback); EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(true)); - EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); + EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(reinterpret_cast(DEFAULT_BUFFER_ADDR))); EXPECT_CALL(*mbuffer, getLength).WillOnce(Return(DEFAULT_BUFFER_LENGTH)); EXPECT_CALL(mhip, hipInit).WillOnce(Return()); EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); @@ -685,7 +676,7 @@ TEST_P(FastpathIoParam, IoThrowsAFallbackEligibleEREMOTEIO) backend->register_fallback_backend(m_fallback); EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(true)); - EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); + EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(reinterpret_cast(DEFAULT_BUFFER_ADDR))); EXPECT_CALL(*mbuffer, getLength).WillOnce(Return(DEFAULT_BUFFER_LENGTH)); EXPECT_CALL(mhip, hipInit).WillOnce(Return()); EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); @@ -723,7 +714,7 @@ TEST_P(FastpathIoParam, FallbackRejectsIoRequest) EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(true)); EXPECT_CALL(mhip, hipInit).WillRepeatedly(Return()); - EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); + EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(reinterpret_cast(DEFAULT_BUFFER_ADDR))); EXPECT_CALL(*mbuffer, getLength).WillOnce(Return(DEFAULT_BUFFER_LENGTH)); EXPECT_CALL(*mfile, unbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); EXPECT_CALL(*m_fallback, score).WillOnce(Return(SCORE_REJECT)); From 2835d42fdf4897ef62c6bfa60daefa99eb5067f3 Mon Sep 17 00:00:00 2001 From: Kurt McMillan Date: Mon, 30 Mar 2026 18:05:12 +0000 Subject: [PATCH 22/28] fastpath: Fastpath::score accepts IO targeting a regular file --- src/amd_detail/backend/fastpath.cpp | 2 ++ test/amd_detail/fastpath.cpp | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/amd_detail/backend/fastpath.cpp b/src/amd_detail/backend/fastpath.cpp index 79aebdad..abad2cd1 100644 --- a/src/amd_detail/backend/fastpath.cpp +++ b/src/amd_detail/backend/fastpath.cpp @@ -143,6 +143,8 @@ Fastpath::score(shared_ptr file, shared_ptr buffer, size_t size, accept_io &= 0 <= file_offset; accept_io &= 0 <= buffer_offset; + accept_io &= file->isRegularFile(); + const uint32_t dio_offset_align{file->dioOffsetAlign()}; accept_io &= dio_offset_align && !(file_offset & (dio_offset_align - 1)); accept_io &= dio_offset_align && !(size & (dio_offset_align - 1)); diff --git a/test/amd_detail/fastpath.cpp b/test/amd_detail/fastpath.cpp index 5d943a76..fa72b74e 100644 --- a/test/amd_detail/fastpath.cpp +++ b/test/amd_detail/fastpath.cpp @@ -69,6 +69,7 @@ class FastpathTestBase { static constexpr hipMemoryType DEFAULT_BUFFER_TYPE{hipMemoryTypeDevice}; static constexpr optional DEFAULT_UNBUFFERED_FD{7}; static constexpr off_t DEFAULT_FILE_OFFSET{8192}; + static constexpr bool DEFAULT_IS_REGULAR_FILE{true}; // Buffer and file mocks used to setup expectations shared_ptr> mfile{make_shared>()}; @@ -89,6 +90,7 @@ class FastpathScoreExpectationsBuilder { optional m_buffer_addr; optional m_buffer_type; optional> m_unbuffered_fd; + optional m_is_regular_file; FastpathScoreExpectationsBuilder(StrictMock &mcfg, shared_ptr> mfile, shared_ptr> mbuffer) @@ -120,6 +122,12 @@ class FastpathScoreExpectationsBuilder { return *this; } + FastpathScoreExpectationsBuilder &isRegularFile(bool is_regular_file) + { + m_is_regular_file = is_regular_file; + return *this; + } + FastpathScoreExpectations build(); }; @@ -133,6 +141,8 @@ class FastpathScoreExpectations { .WillOnce(Return(builder.m_unbuffered_fd.value_or(FastpathTestBase::DEFAULT_UNBUFFERED_FD))); EXPECT_CALL(*builder.m_mbuffer, getType) .WillOnce(Return(builder.m_buffer_type.value_or(FastpathTestBase::DEFAULT_BUFFER_TYPE))); + EXPECT_CALL(*builder.m_mfile, isRegularFile) + .WillOnce(Return(builder.m_is_regular_file.value_or(FastpathTestBase::DEFAULT_IS_REGULAR_FILE))); EXPECT_CALL(*builder.m_mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); EXPECT_CALL(*builder.m_mbuffer, getBuffer) .WillOnce(Return(builder.m_buffer_addr.value_or( @@ -160,6 +170,7 @@ TEST_F(FastpathTest, TestDefaults) ASSERT_FALSE((DEFAULT_BUFFER_OFFSET & (DEFAULT_MEM_ALIGN - 1))); ASSERT_FALSE((DEFAULT_IO_SIZE & (DEFAULT_OFFSET_ALIGN - 1))); ASSERT_FALSE((DEFAULT_FILE_OFFSET & (DEFAULT_OFFSET_ALIGN - 1))); + ASSERT_TRUE(DEFAULT_IS_REGULAR_FILE); } TEST_F(FastpathTest, ScoreAcceptsIoWithDefaults) @@ -217,6 +228,22 @@ TEST_F(FastpathTest, ScoreRejectsIoIfBufferAddressPlusBufferOffsetIsUnaligned) SCORE_REJECT); } +TEST_F(FastpathTest, ScoreAcceptsIoIfFileIsRegularFile) +{ + FastpathScoreExpectationsBuilder(mcfg, mfile, mbuffer).isRegularFile(true).build(); + + ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET), + SCORE_ACCEPT); +} + +TEST_F(FastpathTest, ScoreRejectsIoIfFileIsNotRegularFile) +{ + FastpathScoreExpectationsBuilder(mcfg, mfile, mbuffer).isRegularFile(false).build(); + + ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET), + SCORE_REJECT); +} + struct FastpathSupportedHipMemoryParam : public FastpathTestBase, public TestWithParam {}; TEST_P(FastpathSupportedHipMemoryParam, Score) From 8ecb3d0ced7c854c4b694620c74852a3bc1c99ac Mon Sep 17 00:00:00 2001 From: Kurt McMillan Date: Mon, 30 Mar 2026 17:47:27 +0000 Subject: [PATCH 23/28] fastpath: Fastpath::score accepts IO targeting a block device --- src/amd_detail/backend/fastpath.cpp | 4 +++- test/amd_detail/fastpath.cpp | 25 ++++++++++++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/amd_detail/backend/fastpath.cpp b/src/amd_detail/backend/fastpath.cpp index abad2cd1..b6fb205a 100644 --- a/src/amd_detail/backend/fastpath.cpp +++ b/src/amd_detail/backend/fastpath.cpp @@ -143,7 +143,9 @@ Fastpath::score(shared_ptr file, shared_ptr buffer, size_t size, accept_io &= 0 <= file_offset; accept_io &= 0 <= buffer_offset; - accept_io &= file->isRegularFile(); + bool is_regular_file{file->isRegularFile()}; + bool is_block_device{file->isBlockDevice()}; + accept_io &= is_block_device || is_regular_file; const uint32_t dio_offset_align{file->dioOffsetAlign()}; accept_io &= dio_offset_align && !(file_offset & (dio_offset_align - 1)); diff --git a/test/amd_detail/fastpath.cpp b/test/amd_detail/fastpath.cpp index fa72b74e..1c75fd9a 100644 --- a/test/amd_detail/fastpath.cpp +++ b/test/amd_detail/fastpath.cpp @@ -70,6 +70,7 @@ class FastpathTestBase { static constexpr optional DEFAULT_UNBUFFERED_FD{7}; static constexpr off_t DEFAULT_FILE_OFFSET{8192}; static constexpr bool DEFAULT_IS_REGULAR_FILE{true}; + static constexpr bool DEFAULT_IS_BLOCK_DEVICE{false}; // Buffer and file mocks used to setup expectations shared_ptr> mfile{make_shared>()}; @@ -91,6 +92,7 @@ class FastpathScoreExpectationsBuilder { optional m_buffer_type; optional> m_unbuffered_fd; optional m_is_regular_file; + optional m_is_block_device; FastpathScoreExpectationsBuilder(StrictMock &mcfg, shared_ptr> mfile, shared_ptr> mbuffer) @@ -128,6 +130,12 @@ class FastpathScoreExpectationsBuilder { return *this; } + FastpathScoreExpectationsBuilder &isBlockDevice(bool is_block_device) + { + m_is_block_device = is_block_device; + return *this; + } + FastpathScoreExpectations build(); }; @@ -143,6 +151,8 @@ class FastpathScoreExpectations { .WillOnce(Return(builder.m_buffer_type.value_or(FastpathTestBase::DEFAULT_BUFFER_TYPE))); EXPECT_CALL(*builder.m_mfile, isRegularFile) .WillOnce(Return(builder.m_is_regular_file.value_or(FastpathTestBase::DEFAULT_IS_REGULAR_FILE))); + EXPECT_CALL(*builder.m_mfile, isBlockDevice) + .WillOnce(Return(builder.m_is_block_device.value_or(FastpathTestBase::DEFAULT_IS_BLOCK_DEVICE))); EXPECT_CALL(*builder.m_mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); EXPECT_CALL(*builder.m_mbuffer, getBuffer) .WillOnce(Return(builder.m_buffer_addr.value_or( @@ -171,6 +181,7 @@ TEST_F(FastpathTest, TestDefaults) ASSERT_FALSE((DEFAULT_IO_SIZE & (DEFAULT_OFFSET_ALIGN - 1))); ASSERT_FALSE((DEFAULT_FILE_OFFSET & (DEFAULT_OFFSET_ALIGN - 1))); ASSERT_TRUE(DEFAULT_IS_REGULAR_FILE); + ASSERT_FALSE(DEFAULT_IS_BLOCK_DEVICE); } TEST_F(FastpathTest, ScoreAcceptsIoWithDefaults) @@ -230,15 +241,23 @@ TEST_F(FastpathTest, ScoreRejectsIoIfBufferAddressPlusBufferOffsetIsUnaligned) TEST_F(FastpathTest, ScoreAcceptsIoIfFileIsRegularFile) { - FastpathScoreExpectationsBuilder(mcfg, mfile, mbuffer).isRegularFile(true).build(); + FastpathScoreExpectationsBuilder(mcfg, mfile, mbuffer).isRegularFile(true).isBlockDevice(false).build(); + + ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET), + SCORE_ACCEPT); +} + +TEST_F(FastpathTest, ScoreAcceptsIoIfFileIsBlockDevice) +{ + FastpathScoreExpectationsBuilder(mcfg, mfile, mbuffer).isRegularFile(false).isBlockDevice(true).build(); ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET), SCORE_ACCEPT); } -TEST_F(FastpathTest, ScoreRejectsIoIfFileIsNotRegularFile) +TEST_F(FastpathTest, ScoreRejectsIoIfFileIsNotRegularFileOrBlockDevice) { - FastpathScoreExpectationsBuilder(mcfg, mfile, mbuffer).isRegularFile(false).build(); + FastpathScoreExpectationsBuilder(mcfg, mfile, mbuffer).isRegularFile(false).isBlockDevice(false).build(); ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET), SCORE_REJECT); From e60a16afe79a10b2d94f93a5da9b69f8bd1ebc78 Mon Sep 17 00:00:00 2001 From: Kurt McMillan Date: Mon, 30 Mar 2026 17:47:27 +0000 Subject: [PATCH 24/28] fastpath: Fastpath::score accepts IO targeting a regular file on ext4 (ordered) --- src/amd_detail/backend/fastpath.cpp | 3 ++- test/amd_detail/fastpath.cpp | 23 +++++++++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/amd_detail/backend/fastpath.cpp b/src/amd_detail/backend/fastpath.cpp index b6fb205a..6beaf76d 100644 --- a/src/amd_detail/backend/fastpath.cpp +++ b/src/amd_detail/backend/fastpath.cpp @@ -145,7 +145,8 @@ Fastpath::score(shared_ptr file, shared_ptr buffer, size_t size, bool is_regular_file{file->isRegularFile()}; bool is_block_device{file->isBlockDevice()}; - accept_io &= is_block_device || is_regular_file; + bool on_ext4_ordered{file->onExt4Ordered()}; + accept_io &= is_block_device || (is_regular_file && on_ext4_ordered); const uint32_t dio_offset_align{file->dioOffsetAlign()}; accept_io &= dio_offset_align && !(file_offset & (dio_offset_align - 1)); diff --git a/test/amd_detail/fastpath.cpp b/test/amd_detail/fastpath.cpp index 1c75fd9a..11b2e89a 100644 --- a/test/amd_detail/fastpath.cpp +++ b/test/amd_detail/fastpath.cpp @@ -71,6 +71,7 @@ class FastpathTestBase { static constexpr off_t DEFAULT_FILE_OFFSET{8192}; static constexpr bool DEFAULT_IS_REGULAR_FILE{true}; static constexpr bool DEFAULT_IS_BLOCK_DEVICE{false}; + static constexpr bool DEFAULT_ON_EXT4_ORDERED{true}; // Buffer and file mocks used to setup expectations shared_ptr> mfile{make_shared>()}; @@ -93,6 +94,7 @@ class FastpathScoreExpectationsBuilder { optional> m_unbuffered_fd; optional m_is_regular_file; optional m_is_block_device; + optional m_on_ext4_ordered; FastpathScoreExpectationsBuilder(StrictMock &mcfg, shared_ptr> mfile, shared_ptr> mbuffer) @@ -136,6 +138,12 @@ class FastpathScoreExpectationsBuilder { return *this; } + FastpathScoreExpectationsBuilder &onExt4Ordered(bool on_ext4_ordered) + { + m_on_ext4_ordered = on_ext4_ordered; + return *this; + } + FastpathScoreExpectations build(); }; @@ -151,6 +159,8 @@ class FastpathScoreExpectations { .WillOnce(Return(builder.m_buffer_type.value_or(FastpathTestBase::DEFAULT_BUFFER_TYPE))); EXPECT_CALL(*builder.m_mfile, isRegularFile) .WillOnce(Return(builder.m_is_regular_file.value_or(FastpathTestBase::DEFAULT_IS_REGULAR_FILE))); + EXPECT_CALL(*builder.m_mfile, onExt4Ordered) + .WillOnce(Return(builder.m_on_ext4_ordered.value_or(FastpathTestBase::DEFAULT_ON_EXT4_ORDERED))); EXPECT_CALL(*builder.m_mfile, isBlockDevice) .WillOnce(Return(builder.m_is_block_device.value_or(FastpathTestBase::DEFAULT_IS_BLOCK_DEVICE))); EXPECT_CALL(*builder.m_mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); @@ -182,6 +192,7 @@ TEST_F(FastpathTest, TestDefaults) ASSERT_FALSE((DEFAULT_FILE_OFFSET & (DEFAULT_OFFSET_ALIGN - 1))); ASSERT_TRUE(DEFAULT_IS_REGULAR_FILE); ASSERT_FALSE(DEFAULT_IS_BLOCK_DEVICE); + ASSERT_TRUE(DEFAULT_ON_EXT4_ORDERED); } TEST_F(FastpathTest, ScoreAcceptsIoWithDefaults) @@ -239,14 +250,22 @@ TEST_F(FastpathTest, ScoreRejectsIoIfBufferAddressPlusBufferOffsetIsUnaligned) SCORE_REJECT); } -TEST_F(FastpathTest, ScoreAcceptsIoIfFileIsRegularFile) +TEST_F(FastpathTest, ScoreAcceptsIoIfFileIsRegularAndOnExt4Ordered) { - FastpathScoreExpectationsBuilder(mcfg, mfile, mbuffer).isRegularFile(true).isBlockDevice(false).build(); + FastpathScoreExpectationsBuilder(mcfg, mfile, mbuffer).isRegularFile(true).onExt4Ordered(true).build(); ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET), SCORE_ACCEPT); } +TEST_F(FastpathTest, ScoreRejectsIoIfFileIsRegularAndNotOnExt4Ordered) +{ + FastpathScoreExpectationsBuilder(mcfg, mfile, mbuffer).isRegularFile(true).onExt4Ordered(false).build(); + + ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET), + SCORE_REJECT); +} + TEST_F(FastpathTest, ScoreAcceptsIoIfFileIsBlockDevice) { FastpathScoreExpectationsBuilder(mcfg, mfile, mbuffer).isRegularFile(false).isBlockDevice(true).build(); From bc71ad8764d4ac23e8f6f55a14c13e9f13849e79 Mon Sep 17 00:00:00 2001 From: Kurt McMillan Date: Mon, 30 Mar 2026 17:47:27 +0000 Subject: [PATCH 25/28] fastpath: Fastpath::score accepts IO targeting a regular file on xfs --- src/amd_detail/backend/fastpath.cpp | 3 ++- test/amd_detail/fastpath.cpp | 27 +++++++++++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/amd_detail/backend/fastpath.cpp b/src/amd_detail/backend/fastpath.cpp index 6beaf76d..f630a839 100644 --- a/src/amd_detail/backend/fastpath.cpp +++ b/src/amd_detail/backend/fastpath.cpp @@ -146,7 +146,8 @@ Fastpath::score(shared_ptr file, shared_ptr buffer, size_t size, bool is_regular_file{file->isRegularFile()}; bool is_block_device{file->isBlockDevice()}; bool on_ext4_ordered{file->onExt4Ordered()}; - accept_io &= is_block_device || (is_regular_file && on_ext4_ordered); + bool on_xfs{file->onXfs()}; + accept_io &= is_block_device || (is_regular_file && (on_ext4_ordered || on_xfs)); const uint32_t dio_offset_align{file->dioOffsetAlign()}; accept_io &= dio_offset_align && !(file_offset & (dio_offset_align - 1)); diff --git a/test/amd_detail/fastpath.cpp b/test/amd_detail/fastpath.cpp index 11b2e89a..87127ea0 100644 --- a/test/amd_detail/fastpath.cpp +++ b/test/amd_detail/fastpath.cpp @@ -72,6 +72,7 @@ class FastpathTestBase { static constexpr bool DEFAULT_IS_REGULAR_FILE{true}; static constexpr bool DEFAULT_IS_BLOCK_DEVICE{false}; static constexpr bool DEFAULT_ON_EXT4_ORDERED{true}; + static constexpr bool DEFAULT_ON_XFS{false}; // Buffer and file mocks used to setup expectations shared_ptr> mfile{make_shared>()}; @@ -95,6 +96,7 @@ class FastpathScoreExpectationsBuilder { optional m_is_regular_file; optional m_is_block_device; optional m_on_ext4_ordered; + optional m_on_xfs; FastpathScoreExpectationsBuilder(StrictMock &mcfg, shared_ptr> mfile, shared_ptr> mbuffer) @@ -144,6 +146,12 @@ class FastpathScoreExpectationsBuilder { return *this; } + FastpathScoreExpectationsBuilder &onXfs(bool on_xfs) + { + m_on_xfs = on_xfs; + return *this; + } + FastpathScoreExpectations build(); }; @@ -161,6 +169,8 @@ class FastpathScoreExpectations { .WillOnce(Return(builder.m_is_regular_file.value_or(FastpathTestBase::DEFAULT_IS_REGULAR_FILE))); EXPECT_CALL(*builder.m_mfile, onExt4Ordered) .WillOnce(Return(builder.m_on_ext4_ordered.value_or(FastpathTestBase::DEFAULT_ON_EXT4_ORDERED))); + EXPECT_CALL(*builder.m_mfile, onXfs) + .WillOnce(Return(builder.m_on_xfs.value_or(FastpathTestBase::DEFAULT_ON_XFS))); EXPECT_CALL(*builder.m_mfile, isBlockDevice) .WillOnce(Return(builder.m_is_block_device.value_or(FastpathTestBase::DEFAULT_IS_BLOCK_DEVICE))); EXPECT_CALL(*builder.m_mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); @@ -193,6 +203,7 @@ TEST_F(FastpathTest, TestDefaults) ASSERT_TRUE(DEFAULT_IS_REGULAR_FILE); ASSERT_FALSE(DEFAULT_IS_BLOCK_DEVICE); ASSERT_TRUE(DEFAULT_ON_EXT4_ORDERED); + ASSERT_FALSE(DEFAULT_ON_XFS); } TEST_F(FastpathTest, ScoreAcceptsIoWithDefaults) @@ -258,9 +269,21 @@ TEST_F(FastpathTest, ScoreAcceptsIoIfFileIsRegularAndOnExt4Ordered) SCORE_ACCEPT); } -TEST_F(FastpathTest, ScoreRejectsIoIfFileIsRegularAndNotOnExt4Ordered) +TEST_F(FastpathTest, ScoreAcceptsIoIfFileIsRegularAndOnXfs) +{ + FastpathScoreExpectationsBuilder(mcfg, mfile, mbuffer).isRegularFile(true).onXfs(true).build(); + + ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET), + SCORE_ACCEPT); +} + +TEST_F(FastpathTest, ScoreRejectsIoIfFileIsRegularAndNotOnExt4OrderedNorXfs) { - FastpathScoreExpectationsBuilder(mcfg, mfile, mbuffer).isRegularFile(true).onExt4Ordered(false).build(); + FastpathScoreExpectationsBuilder(mcfg, mfile, mbuffer) + .isRegularFile(true) + .onExt4Ordered(false) + .onXfs(false) + .build(); ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET), SCORE_REJECT); From c5198e7d0982be25e7944bcb3d6d9a83f7e58519 Mon Sep 17 00:00:00 2001 From: Kurt McMillan Date: Wed, 25 Mar 2026 16:27:53 +0000 Subject: [PATCH 26/28] config: Introduce HIPFILE_UNSUPPORTED_FILE_SYSTEMS environment variable This will be used by the to determine if IO to unsupported file systems should be permitted to use the fastpath backend. --- src/amd_detail/configuration.cpp | 7 +++++++ src/amd_detail/configuration.h | 5 +++++ src/amd_detail/environment.cpp | 6 ++++++ src/amd_detail/environment.h | 13 +++++++++++++ test/amd_detail/configuration.cpp | 30 ++++++++++++++++++++++++++++++ test/amd_detail/mconfiguration.h | 1 + 6 files changed, 62 insertions(+) diff --git a/src/amd_detail/configuration.cpp b/src/amd_detail/configuration.cpp index 97b88e19..427e8622 100644 --- a/src/amd_detail/configuration.cpp +++ b/src/amd_detail/configuration.cpp @@ -46,3 +46,10 @@ Configuration::statsLevel() const noexcept HIPFILE_STATIC unsigned int stats_level_env{Environment::stats_level().value_or(0)}; return stats_level_env; } + +bool +Configuration::unsupportedFileSystems() const noexcept +{ + HIPFILE_STATIC bool unsupported_file_systems_env{Environment::unsupported_file_systems().value_or(false)}; + return unsupported_file_systems_env; +} diff --git a/src/amd_detail/configuration.h b/src/amd_detail/configuration.h index 8648a62d..c8a7eab1 100644 --- a/src/amd_detail/configuration.h +++ b/src/amd_detail/configuration.h @@ -37,6 +37,11 @@ class Configuration { /// @brief Shows the level of detail for stats collection /// @return 0 if stats collection disabled, higher levels of detail as value increases virtual unsigned int statsLevel() const noexcept; + + /// @brief Checks if unsupported file systems are allowed in the fastpath backend + /// @return true if unsupported file systems are allowed, false if only supported file systems are + /// permitted (default) + virtual bool unsupportedFileSystems() const noexcept; }; } diff --git a/src/amd_detail/environment.cpp b/src/amd_detail/environment.cpp index 1f6d15ed..efbf1608 100644 --- a/src/amd_detail/environment.cpp +++ b/src/amd_detail/environment.cpp @@ -64,6 +64,12 @@ Environment::force_compat_mode() return Environment::get(Environment::FORCE_COMPAT_MODE); } +optional +Environment::unsupported_file_systems() +{ + return Environment::get(Environment::UNSUPPORTED_FILE_SYSTEMS); +} + optional Environment::stats_level() { diff --git a/src/amd_detail/environment.h b/src/amd_detail/environment.h index dc6f63d9..374b85ea 100644 --- a/src/amd_detail/environment.h +++ b/src/amd_detail/environment.h @@ -47,6 +47,19 @@ class Environment { static constexpr const char *const STATS_LEVEL{"HIPFILE_STATS_LEVEL"}; static std::optional stats_level(); + + /// @brief Allow unsupported file systems in the fastpath backend + /// + /// If enabled, the fastpath backend will allow I/O on file systems other than + /// ext4 (with ordered journaling) and xfs. If disabled (default), only supported + /// file systems are permitted. + static constexpr const char *const UNSUPPORTED_FILE_SYSTEMS{"HIPFILE_UNSUPPORTED_FILE_SYSTEMS"}; + + /// @brief Get the value of HIPFILE_UNSUPPORTED_FILE_SYSTEMS from the environment + /// @return An optional boolean value if HIPFILE_UNSUPPORTED_FILE_SYSTEMS was set, + /// nullopt if HIPFILE_UNSUPPORTED_FILE_SYSTEMS was unset or had a value other than + /// true or false. + static std::optional unsupported_file_systems(); }; } diff --git a/test/amd_detail/configuration.cpp b/test/amd_detail/configuration.cpp index e465f99f..3a719940 100644 --- a/test/amd_detail/configuration.cpp +++ b/test/amd_detail/configuration.cpp @@ -48,6 +48,12 @@ struct HipFileConfiguration : public Test { EXPECT_CALL(msys, getenv(StrEq(hipFile::Environment::STATS_LEVEL))) .WillOnce(Return(const_cast(hipfile_stats_level))); } + + void expect_configuration_unsupported_file_systems(const char *hipfile_unsupported_file_systems) + { + EXPECT_CALL(msys, getenv(StrEq(hipFile::Environment::UNSUPPORTED_FILE_SYSTEMS))) + .WillOnce(Return(const_cast(hipfile_unsupported_file_systems))); + } }; TEST_F(HipFileConfiguration, FastpathEnabledIfForceCompatModeEnvironmentVariableIsNotSet) @@ -194,4 +200,28 @@ TEST_F(HipFileConfiguration, StatsLevelEnvironmentVariableIsSet) ASSERT_EQ(1, Configuration().statsLevel()); } +TEST_F(HipFileConfiguration, UnsupportedFilesystemsDisabledIfEnvironmentVariableIsNotSet) +{ + expect_configuration_unsupported_file_systems(nullptr); + ASSERT_FALSE(Configuration().unsupportedFileSystems()); +} + +TEST_F(HipFileConfiguration, UnsupportedFilesystemsDisabledIfEnvironmentVariableIsInvalid) +{ + expect_configuration_unsupported_file_systems("not-a-bool"); + ASSERT_FALSE(Configuration().unsupportedFileSystems()); +} + +TEST_F(HipFileConfiguration, UnsupportedFilesystemsEnabledIfEnvironmentVariableIsTrue) +{ + expect_configuration_unsupported_file_systems("true"); + ASSERT_TRUE(Configuration().unsupportedFileSystems()); +} + +TEST_F(HipFileConfiguration, UnsupportedFilesystemsDisabledIfEnvironmentVariableIsFalse) +{ + expect_configuration_unsupported_file_systems("false"); + ASSERT_FALSE(Configuration().unsupportedFileSystems()); +} + HIPFILE_WARN_NO_GLOBAL_CTOR_ON diff --git a/test/amd_detail/mconfiguration.h b/test/amd_detail/mconfiguration.h index fe910247..1194ad74 100644 --- a/test/amd_detail/mconfiguration.h +++ b/test/amd_detail/mconfiguration.h @@ -22,6 +22,7 @@ struct MConfiguration : Configuration { MOCK_METHOD(bool, fallback, (), (const, noexcept, override)); MOCK_METHOD(void, fallback, (bool), (noexcept, override)); MOCK_METHOD(unsigned int, statsLevel, (), (const, noexcept, override)); + MOCK_METHOD(bool, unsupportedFileSystems, (), (const, noexcept, override)); }; } From 9b97cb42a383634a16b9b15dd35505e069c2605f Mon Sep 17 00:00:00 2001 From: Kurt McMillan Date: Mon, 30 Mar 2026 19:17:10 +0000 Subject: [PATCH 27/28] fastpath: HIPFILE_UNSUPPORTED_FILE_SYSTEMS=true overrides file system checks in Fastpath::score If HIPFILE_UNSUPPORTED_FILE_SYSTEMS=true, Fastpath::score will accept IO to regular files on any filesystem. --- src/amd_detail/backend/fastpath.cpp | 4 +++- test/amd_detail/fastpath.cpp | 24 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/amd_detail/backend/fastpath.cpp b/src/amd_detail/backend/fastpath.cpp index f630a839..3ea3fe1f 100644 --- a/src/amd_detail/backend/fastpath.cpp +++ b/src/amd_detail/backend/fastpath.cpp @@ -147,7 +147,9 @@ Fastpath::score(shared_ptr file, shared_ptr buffer, size_t size, bool is_block_device{file->isBlockDevice()}; bool on_ext4_ordered{file->onExt4Ordered()}; bool on_xfs{file->onXfs()}; - accept_io &= is_block_device || (is_regular_file && (on_ext4_ordered || on_xfs)); + bool unsupported_file_systems{Context::get()->unsupportedFileSystems()}; + accept_io &= + is_block_device || (is_regular_file && (unsupported_file_systems || on_ext4_ordered || on_xfs)); const uint32_t dio_offset_align{file->dioOffsetAlign()}; accept_io &= dio_offset_align && !(file_offset & (dio_offset_align - 1)); diff --git a/test/amd_detail/fastpath.cpp b/test/amd_detail/fastpath.cpp index 87127ea0..31a1e659 100644 --- a/test/amd_detail/fastpath.cpp +++ b/test/amd_detail/fastpath.cpp @@ -73,6 +73,7 @@ class FastpathTestBase { static constexpr bool DEFAULT_IS_BLOCK_DEVICE{false}; static constexpr bool DEFAULT_ON_EXT4_ORDERED{true}; static constexpr bool DEFAULT_ON_XFS{false}; + static constexpr bool DEFAULT_UNSUPPORTED_FILE_SYSTEMS{false}; // Buffer and file mocks used to setup expectations shared_ptr> mfile{make_shared>()}; @@ -97,6 +98,7 @@ class FastpathScoreExpectationsBuilder { optional m_is_block_device; optional m_on_ext4_ordered; optional m_on_xfs; + optional m_unsupported_file_systems; FastpathScoreExpectationsBuilder(StrictMock &mcfg, shared_ptr> mfile, shared_ptr> mbuffer) @@ -152,6 +154,12 @@ class FastpathScoreExpectationsBuilder { return *this; } + FastpathScoreExpectationsBuilder &unsupportedFileSystems(bool unsupported_file_systems) + { + m_unsupported_file_systems = unsupported_file_systems; + return *this; + } + FastpathScoreExpectations build(); }; @@ -171,6 +179,9 @@ class FastpathScoreExpectations { .WillOnce(Return(builder.m_on_ext4_ordered.value_or(FastpathTestBase::DEFAULT_ON_EXT4_ORDERED))); EXPECT_CALL(*builder.m_mfile, onXfs) .WillOnce(Return(builder.m_on_xfs.value_or(FastpathTestBase::DEFAULT_ON_XFS))); + EXPECT_CALL(builder.m_mcfg, unsupportedFileSystems) + .WillOnce(Return(builder.m_unsupported_file_systems.value_or( + FastpathTestBase::DEFAULT_UNSUPPORTED_FILE_SYSTEMS))); EXPECT_CALL(*builder.m_mfile, isBlockDevice) .WillOnce(Return(builder.m_is_block_device.value_or(FastpathTestBase::DEFAULT_IS_BLOCK_DEVICE))); EXPECT_CALL(*builder.m_mfile, dioOffsetAlign).WillOnce(Return(DEFAULT_OFFSET_ALIGN)); @@ -289,6 +300,19 @@ TEST_F(FastpathTest, ScoreRejectsIoIfFileIsRegularAndNotOnExt4OrderedNorXfs) SCORE_REJECT); } +TEST_F(FastpathTest, ScoreAcceptsIoIfFileIsRegularNotOnExt4OrderedNorXfsIfUnsupportedFileSystemsIsTrue) +{ + FastpathScoreExpectationsBuilder(mcfg, mfile, mbuffer) + .isRegularFile(true) + .onExt4Ordered(false) + .onXfs(false) + .unsupportedFileSystems(true) + .build(); + + ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET), + SCORE_ACCEPT); +} + TEST_F(FastpathTest, ScoreAcceptsIoIfFileIsBlockDevice) { FastpathScoreExpectationsBuilder(mcfg, mfile, mbuffer).isRegularFile(false).isBlockDevice(true).build(); From 192215b49b2c752b7a7a72217b650a06b6446d7c Mon Sep 17 00:00:00 2001 From: Kurt McMillan Date: Wed, 25 Mar 2026 21:16:59 +0000 Subject: [PATCH 28/28] hipFile: Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16c7943c..a13a12b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ * The AIS optimized IO path will automatically fallback to the POSIX IO path if a failure occurs and the compatibility mode has not been disabled. * Added check in the Fastpath/AIS backend to ensure the HIP Runtime is initialized. This avoids causing a segfault in the HIP Runtime. * The default CMake build type was changed from `Debug` to `RelWithDebInfo` +* Added file type and file system validation in Fastpath. Fastpath will only accept IO targeting block devices or regular files backed by xfs or ext4 with ordered journaling mode. Other file systems can be explicitly allowed via the `HIPFILE_UNSUPPORTED_FILE_SYSTEMS` environment variable. ### Removed * The rocFile library has been completely removed and the code is now a part of hipFile.