diff --git a/.codespellrc b/.codespellrc index 892f73de..9ab80c80 100644 --- a/.codespellrc +++ b/.codespellrc @@ -3,3 +3,4 @@ [codespell] check-hidden = true skip = ./.git +ignore-words-list = hsa, diff --git a/hipfile/examples/aiscp/aiscp.cpp b/hipfile/examples/aiscp/aiscp.cpp index 14e1715a..ac670965 100644 --- a/hipfile/examples/aiscp/aiscp.cpp +++ b/hipfile/examples/aiscp/aiscp.cpp @@ -122,14 +122,16 @@ main(int argc, char *argv[]) } nbytes = hipFileRead(src_handle, devbuf, file_size, 0, 0); - if (-1 == nbytes || file_size != static_cast(nbytes)) { - fprintf(stderr, "Could not read from %s (%zd) (%s)\n", src_path, nbytes, strerror(errno)); + if (nbytes < 0 || file_size != static_cast(nbytes)) { + fprintf(stderr, "Could not read from %s (%zd) (%s)\n", src_path, nbytes, + IS_HIPFILE_ERR(nbytes) ? HIPFILE_ERRSTR(nbytes) : strerror(errno)); goto free_devbuf; } nbytes = hipFileWrite(dst_handle, devbuf, file_size, 0, 0); - if (-1 == nbytes || file_size != static_cast(nbytes)) { - fprintf(stderr, "Could not write to %s (%zd) (%s)\n", dst_path, nbytes, strerror(errno)); + if (nbytes < 0 || file_size != static_cast(nbytes)) { + fprintf(stderr, "Could not write to %s (%zd) (%s)\n", src_path, nbytes, + IS_HIPFILE_ERR(nbytes) ? HIPFILE_ERRSTR(nbytes) : strerror(errno)); goto free_devbuf; } diff --git a/rocfile/src/CMakeLists.txt b/rocfile/src/CMakeLists.txt index 3bde410a..782f2dd7 100644 --- a/rocfile/src/CMakeLists.txt +++ b/rocfile/src/CMakeLists.txt @@ -6,6 +6,7 @@ set(ROCFILE_SOURCES async.cpp backend/asyncop-fallback.cpp backend/fallback.cpp + backend/fastpath.cpp batch/batch.cpp buffer.cpp context.cpp diff --git a/rocfile/src/backend/fastpath.cpp b/rocfile/src/backend/fastpath.cpp new file mode 100644 index 00000000..74c5dd80 --- /dev/null +++ b/rocfile/src/backend/fastpath.cpp @@ -0,0 +1,158 @@ +/* Copyright (c) Advanced Micro Devices, Inc. All rights reserved. + * + * SPDX-License-Identifier: MIT + */ + +#include "buffer.h" +#include "context.h" +#include "fastpath.h" +#include "file.h" +#include "hip.h" +#include "io.h" + +#include +#include +#include +#include + +using namespace rocFile; +using namespace std; + +/* The fastpath backend is used when: + * - The file has been opened with the O_DIRECT flag + * - file_offset is 4KiB aligned + * - buffer_offset is 4KiB aligned + * - size is a multiple of 4KiB + * - The buffer type is hipMemoryTypeDevice + * + * When using the fastpath the IO flows through the following + * - rocFile HIP wrapper (userspace) + * - repository: hipFile + * - file: rocfile/hip.cpp + * - methods: Hip::hipAmdFileRead(...), Hip::hipAmdFileWrite(...) + * - throws + * - std::system_error if status is non-zero (set by kfd) + * - Hip::runtime_error if hip runtime does not return hipSuccess or + * if rocFile was unable to find hipAmdFileRead/hipAmdFileWrite + * - HIP Runtime (userspace) + * - repository: rocm-systems + * - file: projects/clr/hipamd/src/hip_storage.cpp + * - functions: hipAmdFileRead(...), hipAmdFileWrite(...) + * - hipAmdFileRead & hipAmdFileWrite are not exposed through a public + * header so hipGetProcAddress() is used to lookup each function's function + * pointer + * - returns + * - hipSuccess if size is zero + * - This check should be removed + * - hipErrorInvalidDevice if unable to lookup current device + * - hipErrorUnknown if rocr runtime does not return success + * - ROCR Runtime (userspace) + * - repository: rocm-systems + * - file: projects/clr/rocclr/device/rocm/rocdevice.cpp + * - methods: Device::amdFileRead(...), Device::amdFileWrite(...) + * - Does not perform any input validation + * - Logs on every IO failure + * - size_copied and status are never modified + * - returns error if HSA does not return HSA_STATUS_SUCCESS + * - HSA (userspace) + * - repository: rocm-systems + * - file: projects/rocr-runtime/runtime/hsa-runtime/core/runtime/hsa_ext_amd.cpp + * - hsa_amd_ais_file_read(...), hsa_amd_ais_file_write(...) + * - status and size_coppied untouched + * - returns + * - HSA_STATUS_ERROR_INVALID_ARGUMENT if size is zero + * - This check should be be removed + * - HSA_STATUS_ERROR_INVALID_ARGUMENT if devicePtr is NULL + * - This check should be be removed + * - HSA_STATUS_ERROR if hsaKmtAisReadWriteFile(...) does not return success + * - Thunk (userspace) + * - repository: rocm-systems + * - file: projects/rocr-runtime/libhsakmt/src/ais.c + * - functions: hsaKmtAisReadWriteFile(...), + * - If the buffer pointer, and io_size combination are invalid, return HSAKMT_STATUS_INVALID_PARAMETER + * - converts buffer pointer + io_size to a buffer handle (no alignment check) + * - size_copied and status are untouched + * - returns + * - HSAKMT_STATUS_INVALID_PARAMETER if + * - the buffer pointer + size combination are invalid + * - if AisFlags (read/write) are invalid + * - HSAKMT_STATUS_ERROR if the kfd ioctl does not return success + * - KFD (chardev) (kernel) + * - repository: ROCm + * - file: amdgpu/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c + * - function: kfd_ioctl_ais(...) + * - returns + * - -EINVAL if op != READ or WRITE + * - -EINVAL if fd < 0 + * - -EINVAL if unable to lookup device + * - -NODEV if AIS is not initialized on the device + * - -ESRCH if unable to bind the process to the device + * - -EINVAL if the buffer's domain is not AMDGPU_GEM_DOMAIN_VRAM + * - an error if it is unable to pin the buffer object + * - error is from amdgpu_amdkfd_gpuvm_pin_bo(...) + * - the result of the call to kfd_ais_rw_file(...) + * - the value returned is also written to the status variable + * - KFD (kernel) + * - repository: ROCm + * - file: amdgpu/drivers/gpu/drm/amd/amdkfd/kfd_ais.c + * - function: kfd_ais_rw_file(...) + * - Calls dev_dbg on every successful IO + * - May want to remove this + * - returns + * - -EINVAL if file_offset or size are not page aligned (4K) + * - This check should be removed /if/ the IO stack checks for alignment. + * - -EBADF unable to lookup the file object + * - -ENODEV if unable to lookup the storage device + * - -ENODEV if pcie_p2pdma_distance < 0 + * - This should be changed to return -EREMOTE + * - -EINVAL if buffer object domain is not AMDGPU_GEM_DOMAIN_VRAM + * - an error if unable to create a sg_table for the buffer object + * - error is from amdgpu_amdkfd_gpuvm_get_sg_table(...) + * - -ENOMEM if unable to init a bvec + * - -EIO on short reads + * - Need to confirm & fix + */ + +int +Fastpath::score(shared_ptr file, shared_ptr buffer, size_t size, hoff_t file_offset, + hoff_t buffer_offset) const +{ + bool accept_io{true}; + + accept_io &= !!(file->getStatusFlags() & O_DIRECT); + + accept_io &= buffer->getType() == hipMemoryTypeDevice; + + accept_io &= 0 <= file_offset; + accept_io &= 0 <= buffer_offset; + + accept_io &= !(size & 0xFFF); + accept_io &= !(file_offset & 0xFFF); + accept_io &= !(buffer_offset & 0xFFF); + + return accept_io ? 100 : -1; +} + +ssize_t +Fastpath::io(IoType type, shared_ptr file, shared_ptr buffer, size_t size, hoff_t file_offset, + hoff_t buffer_offset) +{ + void *devptr{reinterpret_cast(reinterpret_cast(buffer->getBuffer()) + buffer_offset)}; + hipAmdFileHandle_t handle{}; + size_t nbytes{}; + + handle.fd = file->getFd(); + + switch (type) { + case IoType::Read: + nbytes = Context::get()->hipAmdFileRead(handle, devptr, size, file_offset); + break; + case IoType::Write: + nbytes = Context::get()->hipAmdFileWrite(handle, devptr, size, file_offset); + break; + default: + throw std::runtime_error("Invalid IoType"); + } + + return static_cast(nbytes); +} diff --git a/rocfile/src/backend/fastpath.h b/rocfile/src/backend/fastpath.h new file mode 100644 index 00000000..91b9863f --- /dev/null +++ b/rocfile/src/backend/fastpath.h @@ -0,0 +1,36 @@ +/* Copyright (c) Advanced Micro Devices, Inc. All rights reserved. + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include "backend.h" +#include "hipfile-types.h" + +#include +#include + +namespace rocFile { +class IBuffer; +} +namespace rocFile { +class IFile; +} +namespace rocFile { +enum class IoType; +} + +namespace rocFile { + +struct Fastpath : public Backend { + virtual ~Fastpath() override = default; + + int score(std::shared_ptr file, std::shared_ptr buffer, size_t size, hoff_t file_offset, + hoff_t buffer_offset) const override; + + ssize_t io(IoType type, std::shared_ptr file, std::shared_ptr buffer, size_t size, + hoff_t file_offset, hoff_t buffer_offset) override; +}; + +} diff --git a/rocfile/src/rocfile.cpp b/rocfile/src/rocfile.cpp index 10f2b60d..0064b304 100644 --- a/rocfile/src/rocfile.cpp +++ b/rocfile/src/rocfile.cpp @@ -21,6 +21,7 @@ #include #include #include +#include using namespace rocFile; @@ -276,6 +277,10 @@ catch (const Sys::RuntimeError &e) { errno = e.error; return -1; } +catch (const std::system_error &e) { + errno = e.code().value(); + return -1; +} catch (...) { return -rocFileInternalError; } diff --git a/rocfile/src/state.cpp b/rocfile/src/state.cpp index f7806179..93e99cca 100644 --- a/rocfile/src/state.cpp +++ b/rocfile/src/state.cpp @@ -4,9 +4,11 @@ */ #include "backend/fallback.h" +#include "backend/fastpath.h" #include "batch/batch.h" #include "buffer.h" #include "file.h" +#include "hip.h" #include "state.h" #include "stream.h" @@ -26,7 +28,7 @@ struct Backend; namespace rocFile { -DriverState::DriverState() : ref_count{0}, backends{std::shared_ptr(new Fallback{})} +DriverState::DriverState() : ref_count{0} { this->file_map = std::make_unique(); this->batch_map = std::make_unique(); @@ -275,6 +277,15 @@ DriverState::ensureInitialized() std::vector> DriverState::getBackends() const { + static bool once = [&]() { + if (getHipAmdFileReadPtr() && getHipAmdFileWritePtr()) { + backends.emplace_back(new Fastpath{}); + } + backends.emplace_back(new Fallback{}); + return true; + }(); + (void)once; + return backends; } } diff --git a/rocfile/src/state.h b/rocfile/src/state.h index 68e42141..287d3a60 100644 --- a/rocfile/src/state.h +++ b/rocfile/src/state.h @@ -245,7 +245,7 @@ class DriverState { std::unique_ptr stream_map; // Backends available to service IO requests - const std::vector> backends; + mutable std::vector> backends; /// Mutex to protect the state mutable std::shared_mutex state_mutex; diff --git a/rocfile/test/CMakeLists.txt b/rocfile/test/CMakeLists.txt index a3bf1f36..99ac06f4 100644 --- a/rocfile/test/CMakeLists.txt +++ b/rocfile/test/CMakeLists.txt @@ -21,6 +21,7 @@ set(TEST_SOURCE_FILES driver.cpp handle.cpp hip.cpp + fastpath.cpp io.cpp misc.cpp mountinfo.cpp diff --git a/rocfile/test/fastpath.cpp b/rocfile/test/fastpath.cpp new file mode 100644 index 00000000..e74f7b6e --- /dev/null +++ b/rocfile/test/fastpath.cpp @@ -0,0 +1,354 @@ +/* Copyright (c) Advanced Micro Devices, Inc. All rights reserved. + * + * SPDX-License-Identifier: MIT + */ + +#include "backend/fastpath.h" +#include "hip.h" +#include "hipfile-warnings.h" +#include "io.h" +#include "mbuffer.h" +#include "mfile.h" +#include "mhip.h" +#include "rocfile-test.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace rocFile; +using namespace testing; +using namespace std; + +HIPFILE_WARN_NO_GLOBAL_CTOR_OFF + +static int SCORE_ACCEPT{100}; +static const int SCORE_REJECT{-1}; + +namespace rocFile { +inline bool +operator==(const hipAmdFileHandle_t &lhs, const hipAmdFileHandle_t &rhs) +{ + return lhs.fd == rhs.fd; +} +} + +// Provide default values for variables used in fastpath tests +struct FastpathTestBase { + const size_t DEFAULT_IO_SIZE{1024 * 1024}; + void *const DEFAULT_BUFFER_ADDR{reinterpret_cast(0xCAFE000)}; + const off_t DEFAULT_BUFFER_OFFSET{4096}; + const hipMemoryType DEFAULT_BUFFER_TYPE{hipMemoryTypeDevice}; + const int DEFAULT_FILE_DESCRIPTOR{7}; + const int DEFAULT_FILE_FLAGS{O_DIRECT}; + const off_t DEFAULT_FILE_OFFSET{8192}; + + // Buffer and file mocks used to setup expectations + shared_ptr> mfile{make_shared>()}; + shared_ptr> mbuffer{make_shared>()}; +}; + +struct FastpathTest : public FastpathTestBase, public Test {}; + +TEST_F(FastpathTest, DefaultExpecations) +{ + EXPECT_CALL(*mfile, getStatusFlags).WillOnce(Return(DEFAULT_FILE_FLAGS)); + EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); + + ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET), + SCORE_ACCEPT); +} + +TEST_F(FastpathTest, BufferedFile) +{ + EXPECT_CALL(*mfile, getStatusFlags).WillOnce(Return(DEFAULT_FILE_FLAGS & ~O_DIRECT)); + EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); + + 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) +{ + EXPECT_CALL(*mfile, getStatusFlags).WillOnce(Return(DEFAULT_FILE_FLAGS)); + EXPECT_CALL(*mbuffer, getType).WillOnce(Return(GetParam())); + + ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET), + SCORE_ACCEPT); +} + +INSTANTIATE_TEST_SUITE_P(FastpathTest, FastpathSupportedHipMemoryParam, ValuesIn(SupportedHipMemoryTypes)); + +struct FastpathUnsupportedHipMemoryParam : public FastpathTestBase, public TestWithParam {}; + +TEST_P(FastpathUnsupportedHipMemoryParam, Score) +{ + EXPECT_CALL(*mfile, getStatusFlags).WillOnce(Return(DEFAULT_FILE_FLAGS)); + EXPECT_CALL(*mbuffer, getType).WillOnce(Return(GetParam())); + + ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET), + SCORE_REJECT); +} + +INSTANTIATE_TEST_SUITE_P(FastpathTest, FastpathUnsupportedHipMemoryParam, + ValuesIn(UnsupportedHipMemoryTypes)); + +struct FastpathAlignedIoSizesParam : public FastpathTestBase, public TestWithParam {}; + +TEST_P(FastpathAlignedIoSizesParam, Score) +{ + EXPECT_CALL(*mfile, getStatusFlags).WillOnce(Return(DEFAULT_FILE_FLAGS)); + EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); + + ASSERT_EQ(Fastpath().score(mfile, mbuffer, GetParam(), DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET), + SCORE_ACCEPT); +} + +INSTANTIATE_TEST_SUITE_P(FastpathTest, FastpathAlignedIoSizesParam, + Values(0, 4096, 1024 * 1024, 1024 * 1024 * 1024)); + +struct FastpathUnalignedIoSizesParam : public FastpathTestBase, public TestWithParam {}; + +TEST_P(FastpathUnalignedIoSizesParam, Score) +{ + EXPECT_CALL(*mfile, getStatusFlags).WillOnce(Return(DEFAULT_FILE_FLAGS)); + EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); + + ASSERT_EQ(Fastpath().score(mfile, mbuffer, GetParam(), DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET), + SCORE_REJECT); +} + +INSTANTIATE_TEST_SUITE_P(FastpathTest, FastpathUnalignedIoSizesParam, + Values(1, 4096 - 1, 1024 * 1024 + 1, 1024 * 1024 * 1024 - 1)); + +struct FastpathAlignedFileOffsetsParam : public FastpathTestBase, public TestWithParam {}; + +TEST_P(FastpathAlignedFileOffsetsParam, Score) +{ + EXPECT_CALL(*mfile, getStatusFlags).WillOnce(Return(DEFAULT_FILE_FLAGS)); + EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); + + ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, GetParam(), DEFAULT_BUFFER_OFFSET), + SCORE_ACCEPT); +} + +static array aligned_offsets{0, 4096, 1024 * 1024, 1024 * 1024 * 1024}; +INSTANTIATE_TEST_SUITE_P(FastpathTest, FastpathAlignedFileOffsetsParam, ValuesIn(aligned_offsets)); + +/// @brief Tests negative and unaligned file offsets +struct FastpathInvalidFileOffsetsParam : public FastpathTestBase, public TestWithParam {}; + +TEST_P(FastpathInvalidFileOffsetsParam, Score) +{ + EXPECT_CALL(*mfile, getStatusFlags).WillOnce(Return(DEFAULT_FILE_FLAGS)); + EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); + + ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, GetParam(), DEFAULT_BUFFER_OFFSET), + SCORE_REJECT); +} + +static array invalid_offsets{-1024 * 1024 * 1024, -1024 * 1024, -4096, 1, 4096 - 1, + 1024 * 1024 + 1, 1024 * 1024 * 1024 - 1}; +INSTANTIATE_TEST_SUITE_P(FastpathTest, FastpathInvalidFileOffsetsParam, ValuesIn(invalid_offsets)); + +struct FastpathAlignedBufferOffsetsParam : public FastpathTestBase, public TestWithParam {}; + +TEST_P(FastpathAlignedBufferOffsetsParam, Score) +{ + EXPECT_CALL(*mfile, getStatusFlags).WillOnce(Return(DEFAULT_FILE_FLAGS)); + EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); + + ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_BUFFER_OFFSET, GetParam()), + SCORE_ACCEPT); +} + +INSTANTIATE_TEST_SUITE_P(FastpathTest, FastpathAlignedBufferOffsetsParam, ValuesIn(aligned_offsets)); + +/// @brief Tests negative and unaligned buffer offsets +struct FastpathInvalidBufferOffsetsParam : public FastpathTestBase, public TestWithParam {}; + +TEST_P(FastpathInvalidBufferOffsetsParam, Score) +{ + EXPECT_CALL(*mfile, getStatusFlags).WillOnce(Return(DEFAULT_FILE_FLAGS)); + EXPECT_CALL(*mbuffer, getType).WillOnce(Return(DEFAULT_BUFFER_TYPE)); + + ASSERT_EQ(Fastpath().score(mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, GetParam()), + SCORE_REJECT); +} + +INSTANTIATE_TEST_SUITE_P(FastpathTest, FastpathInvalidBufferOffsetsParam, ValuesIn(invalid_offsets)); + +struct FastpathIoParam : public FastpathTestBase, public TestWithParam {}; + +TEST_P(FastpathIoParam, IoConfiguresHandle) +{ + StrictMock mhip; + + hipAmdFileHandle_t handle{}; + handle.fd = DEFAULT_FILE_DESCRIPTOR; + + EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); + EXPECT_CALL(*mfile, getFd).WillOnce(Return(DEFAULT_FILE_DESCRIPTOR)); + switch (GetParam()) { + case IoType::Read: + EXPECT_CALL(mhip, hipAmdFileRead(Eq(handle), _, _, _)); + break; + case IoType::Write: + EXPECT_CALL(mhip, hipAmdFileWrite(Eq(handle), _, _, _)); + break; + default: + FAIL() << "Invalid IoType"; + } + + Fastpath().io(GetParam(), mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET); +} + +TEST_P(FastpathIoParam, IoCalculatesCorrectDevicePointer) +{ + StrictMock mhip; + + off_t buffer_offset{0x1000}; + void *buffer_addr{reinterpret_cast(0x20000)}; + void *expected_device_ptr{reinterpret_cast(0x21000)}; + + EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(buffer_addr)); + EXPECT_CALL(*mfile, getFd).WillOnce(Return(DEFAULT_FILE_DESCRIPTOR)); + switch (GetParam()) { + case IoType::Read: + EXPECT_CALL(mhip, hipAmdFileRead(_, expected_device_ptr, _, _)); + break; + case IoType::Write: + EXPECT_CALL(mhip, hipAmdFileWrite(_, expected_device_ptr, _, _)); + break; + default: + FAIL() << "Invalid IoType"; + } + + Fastpath().io(GetParam(), mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, buffer_offset); +} + +TEST_P(FastpathIoParam, IoPassesThroughSizeAndFileOffset) +{ + StrictMock mhip; + + EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); + EXPECT_CALL(*mfile, getFd).WillOnce(Return(DEFAULT_FILE_DESCRIPTOR)); + switch (GetParam()) { + case IoType::Read: + EXPECT_CALL(mhip, hipAmdFileRead(_, _, Eq(DEFAULT_IO_SIZE), Eq(DEFAULT_FILE_OFFSET))); + break; + case IoType::Write: + EXPECT_CALL(mhip, hipAmdFileWrite(_, _, Eq(DEFAULT_IO_SIZE), Eq(DEFAULT_FILE_OFFSET))); + break; + default: + FAIL() << "Invalid IoType"; + } + + Fastpath().io(GetParam(), mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET); +} + +TEST_P(FastpathIoParam, IoReturnsBytesTransferred) +{ + StrictMock mhip; + + EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); + EXPECT_CALL(*mfile, getFd).WillOnce(Return(DEFAULT_FILE_DESCRIPTOR)); + switch (GetParam()) { + case IoType::Read: + EXPECT_CALL(mhip, hipAmdFileRead(_, _, _, _)).WillOnce(Return(DEFAULT_IO_SIZE)); + break; + case IoType::Write: + EXPECT_CALL(mhip, hipAmdFileWrite(_, _, _, _)).WillOnce(Return(DEFAULT_IO_SIZE)); + break; + default: + FAIL() << "Invalid IoType"; + } + + ASSERT_EQ(Fastpath().io(GetParam(), mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, + DEFAULT_BUFFER_OFFSET), + DEFAULT_IO_SIZE); +} + +TEST_P(FastpathIoParam, IoReturnsBytesTransferredShort) +{ + StrictMock mhip; + + size_t nbytes{4096}; + + ASSERT_LT(nbytes, DEFAULT_IO_SIZE); + + EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); + EXPECT_CALL(*mfile, getFd).WillOnce(Return(DEFAULT_FILE_DESCRIPTOR)); + switch (GetParam()) { + case IoType::Read: + EXPECT_CALL(mhip, hipAmdFileRead(_, _, _, _)).WillOnce(Return(nbytes)); + break; + case IoType::Write: + EXPECT_CALL(mhip, hipAmdFileWrite(_, _, _, _)).WillOnce(Return(nbytes)); + break; + default: + FAIL() << "Invalid IoType"; + } + + ASSERT_EQ(Fastpath().io(GetParam(), mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, + DEFAULT_BUFFER_OFFSET), + nbytes); +} + +// Ensure hip errors thrown by Hip::hipAmdFileRead/Hip::hipAmdFileWrite are not masked +TEST_P(FastpathIoParam, IoDoesNotMaskHipRuntimeError) +{ + StrictMock mhip; + + EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); + EXPECT_CALL(*mfile, getFd).WillOnce(Return(DEFAULT_FILE_DESCRIPTOR)); + switch (GetParam()) { + case IoType::Read: + EXPECT_CALL(mhip, hipAmdFileRead).WillOnce(Throw(Hip::RuntimeError(hipErrorUnknown))); + break; + case IoType::Write: + EXPECT_CALL(mhip, hipAmdFileWrite).WillOnce(Throw(Hip::RuntimeError(hipErrorUnknown))); + break; + default: + FAIL() << "Invalid IoType"; + } + + EXPECT_THROW(Fastpath().io(GetParam(), mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, + DEFAULT_BUFFER_OFFSET), + Hip::RuntimeError); +} + +// Ensure hip errors thrown by Hip::hipAmdFileRead/Hip::hipAmdFileWrite are not masked +TEST_P(FastpathIoParam, IoDoesNotMaskSystemError) +{ + StrictMock mhip; + + EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); + EXPECT_CALL(*mfile, getFd).WillOnce(Return(DEFAULT_FILE_DESCRIPTOR)); + switch (GetParam()) { + case IoType::Read: + EXPECT_CALL(mhip, hipAmdFileRead).WillOnce(Throw(system_error(ENODEV, generic_category()))); + break; + case IoType::Write: + EXPECT_CALL(mhip, hipAmdFileWrite).WillOnce(Throw(system_error(EINVAL, generic_category()))); + break; + default: + FAIL() << "Invalid IoType"; + } + + EXPECT_THROW(Fastpath().io(GetParam(), mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, + DEFAULT_BUFFER_OFFSET), + std::system_error); +} + +INSTANTIATE_TEST_SUITE_P(FastpathTest, FastpathIoParam, Values(IoType::Read, IoType::Write)); + +HIPFILE_WARN_NO_GLOBAL_CTOR_ON diff --git a/rocfile/test/io.cpp b/rocfile/test/io.cpp index 9d3cc82a..e89aba89 100644 --- a/rocfile/test/io.cpp +++ b/rocfile/test/io.cpp @@ -121,6 +121,11 @@ struct RocFileIO : public RocFileOpened { StrictMock mhip; StrictMock msys; + // Initialize DriverState::backends + EXPECT_CALL(mhip, hipRuntimeGetVersion); + EXPECT_CALL(mhip, hipGetProcAddress(StrEq("hipAmdFileRead"), _, _, _)); + Context::get()->getBackends(); + expect_buffer_registration(mhip, hipMemoryTypeDevice); Context::get()->registerBuffer(buffer_data.data(), buffer_data.size(), 0); buffer = Context::get()->getBuffer(buffer_data.data());