Skip to content

Commit 9cfd878

Browse files
committed
Implement load_into(); Add unit tests
1 parent 0536862 commit 9cfd878

File tree

3 files changed

+175
-5
lines changed

3 files changed

+175
-5
lines changed

extension/data_loader/mmap_data_loader.cpp

Lines changed: 114 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -150,11 +150,11 @@ void MunmapSegment(void* context, void* data, size_t size) {
150150
}
151151
} // namespace
152152

153-
Result<FreeableBuffer> MmapDataLoader::load(
154-
size_t offset,
155-
size_t size,
156-
ET_UNUSED const DataLoader::SegmentInfo& segment_info) const {
157-
ET_CHECK_OR_RETURN_ERROR(
153+
/**
154+
* Helper for input validation.
155+
*/
156+
Error MmapDataLoader::validate_input(size_t offset, size_t size) const {
157+
ET_CHECK_OR_RETURN_ERROR(
158158
// Probably had its value moved to another instance.
159159
fd_ >= 0,
160160
InvalidState,
@@ -173,6 +173,19 @@ Result<FreeableBuffer> MmapDataLoader::load(
173173
InvalidArgument,
174174
"Offset %zu too large for off_t",
175175
offset);
176+
return Error::Ok;
177+
}
178+
179+
Result<FreeableBuffer> MmapDataLoader::load(
180+
size_t offset,
181+
size_t size,
182+
ET_UNUSED const DataLoader::SegmentInfo& segment_info) const {
183+
184+
// Validate input.
185+
auto err = validate_input(offset, size);
186+
if (err != Error::Ok) {
187+
return err;
188+
}
176189

177190
// mmap() will fail if the size is zero.
178191
if (size == 0) {
@@ -268,5 +281,101 @@ Result<size_t> MmapDataLoader::size() const {
268281
return file_size_;
269282
}
270283

284+
Error MmapDataLoader::load_into(
285+
size_t offset,
286+
size_t size,
287+
ET_UNUSED const SegmentInfo& segment_info,
288+
void* buffer) const {
289+
290+
ET_CHECK_OR_RETURN_ERROR(buffer != nullptr, InvalidArgument, "Buffer is null");
291+
292+
// Validate input.
293+
auto err = validate_input(offset, size);
294+
if (err != Error::Ok) {
295+
return err;
296+
}
297+
298+
// Nothing to copy
299+
if (size == 0) {
300+
return Error::Ok;
301+
}
302+
303+
// Find the range of pages that covers the requested region.
304+
Range range =
305+
get_overlapping_pages(static_cast<uintptr_t>(offset), size, page_size_);
306+
307+
size_t map_size = range.size;
308+
if (range.start + map_size > file_size_) {
309+
// Clamp to the end of the file.
310+
//
311+
// The Windows implementation of mmap uses CreateFileMapping which returns
312+
// error STATUS_SECTION_TOO_BIG (0xc0000040) if we try to map past the end
313+
// of the last page of a file mapped in as read-only.
314+
map_size = file_size_ - range.start;
315+
}
316+
317+
// Map the pages read-only. MAP_PRIVATE vs. MAP_SHARED doesn't matter since
318+
// the data is read-only, but use PRIVATE just to further avoid accidentally
319+
// modifying the file.
320+
void* pages = ::mmap(
321+
nullptr,
322+
map_size,
323+
PROT_READ,
324+
MAP_PRIVATE,
325+
fd_,
326+
static_cast<off_t>(range.start));
327+
ET_CHECK_OR_RETURN_ERROR(
328+
pages != MAP_FAILED,
329+
AccessFailed,
330+
"Failed to map %s: mmap(..., size=%zd, ..., fd=%d, offset=0x%zx)",
331+
file_name_,
332+
range.size,
333+
fd_,
334+
range.start);
335+
336+
if (mlock_config_ == MlockConfig::UseMlock ||
337+
mlock_config_ == MlockConfig::UseMlockIgnoreErrors) {
338+
int err = ::mlock(pages, size);
339+
if (err < 0) {
340+
if (mlock_config_ == MlockConfig::UseMlockIgnoreErrors) {
341+
ET_LOG(
342+
Debug,
343+
"Ignoring mlock error for file %s (off=0x%zd): "
344+
"mlock(%p, %zu) failed: %s (%d)",
345+
file_name_,
346+
offset,
347+
pages,
348+
size,
349+
::strerror(errno),
350+
errno);
351+
} else {
352+
ET_LOG(
353+
Error,
354+
"File %s (off=0x%zd): mlock(%p, %zu) failed: %s (%d)",
355+
file_name_,
356+
offset,
357+
pages,
358+
size,
359+
::strerror(errno),
360+
errno);
361+
::munmap(pages, size);
362+
return Error::NotSupported;
363+
}
364+
}
365+
// No need to keep track of this. munmap() will unlock as a side effect.
366+
}
367+
368+
// Offset into mapped region.
369+
const size_t map_delta = offset - range.start;
370+
371+
// Copy data into caller's buffer.
372+
std::memcpy(buffer, static_cast<uint8_t*>(pages) + map_delta, size);
373+
374+
// Unmap mapped region.
375+
::munmap(pages, map_size);
376+
377+
return Error::Ok;
378+
}
379+
271380
} // namespace extension
272381
} // namespace executorch

extension/data_loader/mmap_data_loader.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,15 @@ class MmapDataLoader final : public executorch::runtime::DataLoader {
9595

9696
ET_NODISCARD executorch::runtime::Result<size_t> size() const override;
9797

98+
ET_NODISCARD executorch::runtime::Error validate_input(size_t offset, size_t size) const;
99+
100+
ET_NODISCARD
101+
executorch::runtime::Error load_into(
102+
size_t offset,
103+
size_t size,
104+
ET_UNUSED const SegmentInfo& segment_info,
105+
void* buffer) const override;
106+
98107
private:
99108
MmapDataLoader(
100109
int fd,

extension/data_loader/test/mmap_data_loader_test.cpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,3 +376,55 @@ TEST_F(MmapDataLoaderTest, DEPRECATEDFrom) {
376376
ASSERT_EQ(total_size.error(), Error::Ok);
377377
EXPECT_EQ(*total_size, contents_size);
378378
}
379+
380+
// Tests that load_into copies bytes correctly.
381+
TEST_F(MmapDataLoaderTest, LoadIntoCopiesCorrectly) {
382+
// Create a test string.
383+
const char* test_text = "FILE_CONTENTS";
384+
const size_t text_size = std::strlen(test_text);
385+
TempFile tf(test_text);
386+
387+
// Wrap it in a loader.
388+
Result<MmapDataLoader> mdl = MmapDataLoader::from(tf.path().c_str());
389+
ASSERT_EQ(mdl.error(), Error::Ok);
390+
391+
// Destination buffer.
392+
std::vector<uint8_t> dst(text_size);
393+
394+
// Call load_into()
395+
Error err = mdl->load_into(
396+
/*offset=*/0,
397+
/*size=*/text_size,
398+
DataLoader::SegmentInfo(DataLoader::SegmentInfo::Type::Program),
399+
dst.data());
400+
ASSERT_EQ(err, Error::Ok);
401+
402+
// Verify memory copied correctly.
403+
EXPECT_EQ(0, std::memcmp(dst.data(), test_text, text_size));
404+
}
405+
406+
// Tests that load_into copies offset slice correctly.
407+
TEST_F(MmapDataLoaderTest, LoadIntoCopiesOffsetCorrectly) {
408+
// Create a test string.
409+
const char* contents = "ABCDEFGH";
410+
TempFile tf(contents);
411+
412+
// Wrap it in a loader.
413+
Result<MmapDataLoader> mdl = MmapDataLoader::from(tf.path().c_str());
414+
ASSERT_EQ(mdl.error(), Error::Ok);
415+
416+
// Copying 3 bytes starting at offset 2 = "CDE"
417+
const size_t offset = 2;
418+
const size_t size = 3;
419+
uint8_t dst[size];
420+
421+
// Call load_into()
422+
Error err = mdl->load_into(
423+
offset, size,
424+
DataLoader::SegmentInfo(DataLoader::SegmentInfo::Type::Program),
425+
dst);
426+
ASSERT_EQ(err, Error::Ok);
427+
428+
// Verify memory copied correctly.
429+
EXPECT_EQ(0, std::memcmp(dst, contents + offset, size));
430+
}

0 commit comments

Comments
 (0)