From 407dca8cf002a7dcce001ece1e4aa6d1bafb0b0a Mon Sep 17 00:00:00 2001 From: Daniel Andersson Date: Thu, 4 Apr 2019 21:09:07 -0700 Subject: [PATCH] Support experimental re-mmap wrappers Summary: Add experimental support for reordering the pages of a file that is mmap:ed by JSBigFileString. The wrapper is auto-detected (by checking file size and magic header) and transparently reorders the pages. Reviewed By: ridiculousfish Differential Revision: D14721397 fbshipit-source-id: 34e095350a9eeb9b07105bed6f3379f2fe472ae6 --- ReactCommon/cxxreact/BUCK | 3 + ReactCommon/cxxreact/JSBigString.cpp | 67 ++++++++++++++++++++++ ReactCommon/cxxreact/JSBigString.h | 2 +- ReactCommon/cxxreact/tests/jsbigstring.cpp | 43 ++++++++++++++ 4 files changed, 114 insertions(+), 1 deletion(-) diff --git a/ReactCommon/cxxreact/BUCK b/ReactCommon/cxxreact/BUCK index 7a1c7aa654b..0f6f1076357 100644 --- a/ReactCommon/cxxreact/BUCK +++ b/ReactCommon/cxxreact/BUCK @@ -44,6 +44,9 @@ rn_xplat_cxx_library( ], fbobjc_compiler_flags = get_apple_compiler_flags(), force_static = True, + preprocessor_flags = [ + "-DWITH_FBREMAP=1", + ], visibility = [ "PUBLIC", ], diff --git a/ReactCommon/cxxreact/JSBigString.cpp b/ReactCommon/cxxreact/JSBigString.cpp index fb6540d143c..dc997d1cd5b 100644 --- a/ReactCommon/cxxreact/JSBigString.cpp +++ b/ReactCommon/cxxreact/JSBigString.cpp @@ -48,6 +48,65 @@ JSBigFileString::~JSBigFileString() { close(m_fd); } +#ifdef WITH_FBREMAP +// Read and advance the pointer. +static uint16_t read16(char *&data) { + uint16_t result; + ::memcpy(&result, data, sizeof(result)); + data += sizeof(result); + return result; +} + +// If the given file has a remapping table header, remap its pages accordingly +// and return the byte offset from the beginning to the unwrapped payload. +static off_t maybeRemap(char *data, size_t size, int fd) { + // A remapped file's size must be a multiple of its page size, so quickly + // filter out files with incorrect size, without touching any pages. + static const size_t kMinPageSize = 4096; + if (size < kMinPageSize || size % kMinPageSize != 0) { + return 0; + } + const auto begin = data; + static const uint8_t kRemapMagic[] = { + 0xc6, 0x1f, 0xbc, 0x03, 0xc1, 0x03, 0x19, 0x1f, 0xa1, 0xd0, 0xeb, 0x73 + }; + if (::memcmp(data, kRemapMagic, sizeof(kRemapMagic)) != 0) { + return 0; + } + data += sizeof(kRemapMagic); + const size_t filePS = static_cast(1) << read16(data); + if (size & (filePS - 1)) { + return 0; + } + { + // System page size must be at least as granular as the remapping. + // TODO: Consider fallback that reads entire file into memory. + const size_t systemPS = getpagesize(); + CHECK(filePS >= systemPS) + << "filePS: " << filePS + << "systemPS: " << systemPS; + } + const off_t headerPages = read16(data); + uint16_t numMappings = read16(data); + size_t curFilePage = headerPages; + while (numMappings--) { + auto memPage = read16(data) + headerPages; + auto numPages = read16(data); + if (mmap(begin + memPage * filePS, numPages * filePS, + PROT_READ, MAP_FILE | MAP_PRIVATE | MAP_FIXED, + fd, curFilePage * filePS) == MAP_FAILED) { + CHECK(false) + << " memPage: " << memPage + << " numPages: " << numPages + << " curFilePage: " << curFilePage + << " size: " << size + << " error: " << std::strerror(errno); + } + curFilePage += numPages; + } + return headerPages * filePS; +} +#endif // WITH_FBREMAP const char *JSBigFileString::c_str() const { if (!m_data) { @@ -58,11 +117,19 @@ const char *JSBigFileString::c_str() const { << " size: " << m_size << " offset: " << m_mapOff << " error: " << std::strerror(errno); +#ifdef WITH_FBREMAP + // Remapping is only attempted when the entire file was requested. + if (m_mapOff == 0 && m_pageOff == 0) { + m_pageOff = maybeRemap(const_cast(m_data), m_size, m_fd); + } +#endif // WITH_FBREMAP } return m_data + m_pageOff; } size_t JSBigFileString::size() const { + // Ensure mapping has been initialized. + c_str(); return m_size - m_pageOff; } diff --git a/ReactCommon/cxxreact/JSBigString.h b/ReactCommon/cxxreact/JSBigString.h index 8e0f2128a1f..c4bf86fd719 100644 --- a/ReactCommon/cxxreact/JSBigString.h +++ b/ReactCommon/cxxreact/JSBigString.h @@ -133,7 +133,7 @@ public: private: int m_fd; // The file descriptor being mmaped size_t m_size; // The size of the mmaped region - off_t m_pageOff; // The offset in the mmaped region to the data. + mutable off_t m_pageOff; // The offset in the mmaped region to the data. off_t m_mapOff; // The offset in the file to the mmaped region. mutable const char *m_data; // Pointer to the mmaped region. }; diff --git a/ReactCommon/cxxreact/tests/jsbigstring.cpp b/ReactCommon/cxxreact/tests/jsbigstring.cpp index a8f58f16646..dc1bd3bd371 100644 --- a/ReactCommon/cxxreact/tests/jsbigstring.cpp +++ b/ReactCommon/cxxreact/tests/jsbigstring.cpp @@ -60,3 +60,46 @@ TEST(JSBigFileString, MapPartTest) { ASSERT_EQ(needle[i], bigStr.c_str()[i]); } } + +TEST(JSBigFileString, RemapTest) { + static const uint8_t kRemapMagic[] = { + 0xc6, 0x1f, 0xbc, 0x03, 0xc1, 0x03, 0x19, 0x1f, 0xa1, 0xd0, 0xeb, 0x73 + }; + std::string data(std::begin(kRemapMagic), std::end(kRemapMagic)); + auto app = [&data](uint16_t v) { + data.append(reinterpret_cast(&v), sizeof(v)); + }; + size_t pageSizeLog2 = 16; + app(pageSizeLog2); + size_t pageSize = 1 << pageSizeLog2; + app(1); // header pages + app(2); // num mappings + // file page 0 -> memory page 1 + app(1); // memory page + app(1); // num pages + // file page 1 -> memory page 0 + app(0); // memory page + app(1); // num pages + while (data.size() < pageSize) { + app(0); + } + while (data.size() < pageSize * 2) { + app(0x1111); + } + while (data.size() < pageSize * 3) { + app(0x2222); + } + + int fd = tempFileFromString(data); + JSBigFileString bigStr {fd, data.size()}; + + ASSERT_EQ(pageSize * 2, bigStr.size()); + auto remapped = bigStr.c_str(); + size_t i = 0; + for (; i < pageSize; ++i) { + ASSERT_EQ(0x22, remapped[i]); + } + for (; i < pageSize * 2; ++i) { + ASSERT_EQ(0x11, remapped[i]); + } +}