From d6956689f341254349d10a09a16ebbc9666396de Mon Sep 17 00:00:00 2001 From: Mihai Parparita Date: Sun, 5 Jul 2026 15:59:59 -0700 Subject: [PATCH] utils: Use mmap for disk images in deterministic mode We were reading the entire disk image into a stringstream for deterministic mode, which is slow and memory-intensive. Instead on POSIX hosts we can use `mmap` to lazily map the disk image into memory, allowing it to be streamed in as the guest actually needs data from it. Helps with iteration speed when debugging via deterministic mode. --- utils/imgfile_sdl.cpp | 246 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 214 insertions(+), 32 deletions(-) diff --git a/utils/imgfile_sdl.cpp b/utils/imgfile_sdl.cpp index 97e190c422..a3904cb220 100644 --- a/utils/imgfile_sdl.cpp +++ b/utils/imgfile_sdl.cpp @@ -22,15 +22,40 @@ along with this program. If not, see . #include #include +#include +#include #include -#include #include +#include + +#if !defined(_WIN32) && \ + (defined(__APPLE__) || defined(__linux__) || defined(__unix__)) +#define DPPC_HAS_PRIVATE_MMAP 1 +#include +#include +#include +#include +#include +#endif extern bool is_deterministic; +class ImgFileBackend { +public: + virtual ~ImgFileBackend() = default; + + virtual bool open(const std::string &img_path) = 0; + virtual void close() = 0; + virtual uint64_t size() const = 0; + virtual uint64_t read(void *buf, uint64_t offset, uint64_t length) const = 0; + virtual uint64_t write(const void *buf, uint64_t offset, uint64_t length) = 0; +}; + +static std::unique_ptr make_imgfile_backend(const std::string &img_path); + class ImgFile::Impl { public: - std::unique_ptr stream; + std::unique_ptr backend; }; ImgFile::ImgFile(): impl(std::make_unique()) @@ -42,62 +67,219 @@ ImgFile::~ImgFile() = default; bool ImgFile::open(const std::string &img_path) { - if (is_deterministic) { - // Avoid writes to the underlying file by reading it all in memory and - // only operating on that. - auto mem_stream = std::make_unique(); - std::ifstream temp(img_path, std::ios::in | std::ios::binary); - if (!temp) return false; - *mem_stream << temp.rdbuf(); - impl->stream = std::move(mem_stream); - } else { - auto file_stream = std::make_unique(img_path, std::ios::in | std::ios::out | std::ios::binary); - if (!file_stream->is_open()) return false; - impl->stream = std::move(file_stream); - } - return impl->stream && impl->stream->good(); + impl->backend = make_imgfile_backend(img_path); + return !!impl->backend; } void ImgFile::close() { - if (!impl->stream) { + if (!impl->backend) { LOG_F(WARNING, "ImgFile::close before disk was opened, ignoring."); return; } - impl->stream.reset(); + impl->backend->close(); + impl->backend.reset(); } uint64_t ImgFile::size() const { - if (!impl->stream) { + if (!impl->backend) { LOG_F(WARNING, "ImgFile::size before disk was opened, ignoring."); return 0; } - impl->stream->seekg(0, impl->stream->end); - return impl->stream->tellg(); + return impl->backend->size(); } uint64_t ImgFile::read(void* buf, uint64_t offset, uint64_t length) const { - if (!impl->stream) { + if (!impl->backend) { LOG_F(WARNING, "ImgFile::read before disk was opened, ignoring."); return 0; } - impl->stream->seekg(offset, std::ios::beg); - impl->stream->read((char *)buf, length); - return impl->stream->gcount(); + return impl->backend->read(buf, offset, length); } uint64_t ImgFile::write(const void* buf, uint64_t offset, uint64_t length) { - if (!impl->stream) { + if (!impl->backend) { LOG_F(WARNING, "ImgFile::write before disk was opened, ignoring."); return 0; } - impl->stream->seekp(offset, std::ios::beg); - impl->stream->write((const char *)buf, length); - #if defined(WIN32) || defined(__APPLE__) || defined(__linux) - impl->stream->flush(); - #endif - return uint64_t(impl->stream->tellp()) - offset; + return impl->backend->write(buf, offset, length); +} + +class FileStreamBackend : public ImgFileBackend { +public: + bool open(const std::string &img_path) override { + stream = std::make_unique( + img_path, std::ios::in | std::ios::out | std::ios::binary); + if (!stream->is_open()) + return false; + + stream->seekg(0, std::ios::end); + file_size = stream->tellg(); + stream->clear(); + return stream->good(); + } + + void close() override { + stream.reset(); + } + + uint64_t size() const override { + return file_size; + } + + uint64_t read(void *buf, uint64_t offset, uint64_t length) const override { + stream->clear(); + stream->seekg(offset, std::ios::beg); + stream->read(static_cast(buf), length); + return stream->gcount(); + } + + uint64_t write(const void *buf, uint64_t offset, uint64_t length) override { + stream->clear(); + stream->seekp(offset, std::ios::beg); + stream->write(static_cast(buf), length); +#if defined(WIN32) || defined(__APPLE__) || defined(__linux) + stream->flush(); +#endif + if (!stream->good()) + return 0; + + file_size = std::max(file_size, offset + length); + return length; + } + +private: + mutable std::unique_ptr stream; + uint64_t file_size = 0; +}; + +class MemoryStreamBackend : public ImgFileBackend { +public: + bool open(const std::string &img_path) override { + std::ifstream file(img_path, std::ios::in | std::ios::binary); + if (!file) + return false; + + stream = std::make_unique(); + *stream << file.rdbuf(); + stream->seekg(0, std::ios::end); + file_size = stream->tellg(); + stream->clear(); + return stream->good(); + } + + void close() override { + stream.reset(); + } + + uint64_t size() const override { + return file_size; + } + + uint64_t read(void *buf, uint64_t offset, uint64_t length) const override { + stream->clear(); + stream->seekg(offset, std::ios::beg); + stream->read(static_cast(buf), length); + return stream->gcount(); + } + + uint64_t write(const void *buf, uint64_t offset, uint64_t length) override { + stream->clear(); + stream->seekp(offset, std::ios::beg); + stream->write(static_cast(buf), length); + return stream->good() ? length : 0; + } + +private: + mutable std::unique_ptr stream; + uint64_t file_size = 0; +}; + +#if DPPC_HAS_PRIVATE_MMAP +class PrivateMmapBackend : public ImgFileBackend { +public: + ~PrivateMmapBackend() override { + close(); + } + + bool open(const std::string &img_path) override { + fd = ::open(img_path.c_str(), O_RDONLY); + if (fd < 0) + return false; + + struct stat st {}; + if (::fstat(fd, &st) < 0 || st.st_size <= 0) { + close(); + return false; + } + + file_size = st.st_size; + + void *addr = ::mmap(nullptr, file_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); + if (addr == MAP_FAILED) { + LOG_F(WARNING, "Private mmap failed: %s", std::strerror(errno)); + mapping = nullptr; + close(); + return false; + } + + mapping = static_cast(addr); + return true; + } + + void close() override { + if (mapping) { + ::munmap(mapping, file_size); + mapping = nullptr; + } + if (fd >= 0) { + ::close(fd); + fd = -1; + } + } + + uint64_t size() const override { + return file_size; + } + + uint64_t read(void *buf, uint64_t offset, uint64_t length) const override { + std::memcpy(buf, mapping + offset, length); + return length; + } + + uint64_t write(const void *buf, uint64_t offset, uint64_t length) override { + std::memcpy(mapping + offset, buf, length); + return length; + } + +private: + int fd = -1; + uint8_t *mapping = nullptr; + uint64_t file_size = 0; +}; +#endif + +static std::unique_ptr make_imgfile_backend(const std::string &img_path) { + std::unique_ptr backend; + if (is_deterministic) { +#if DPPC_HAS_PRIVATE_MMAP + backend = std::make_unique(); + if (backend->open(img_path)) + return backend; + // Intentional fallthrough to MemoryStreamBackend if mmap fails. +#else + backend = std::make_unique(); +#endif + } else { + backend = std::make_unique(); + } + + if (!backend->open(img_path)) { + return nullptr; + } + + return backend; }