blob: 7130eb30b955fda868dffab0467afb0807b8e3c7 [file]
// Copyright 2023 The IREE Authors
//
// Licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "iree/io/file_handle.h"
#include "iree/base/internal/atomics.h"
#include "iree/io/file_contents.h"
#if IREE_FILE_IO_ENABLE
#include "iree/io/stdio_util.h"
#if defined(IREE_PLATFORM_WINDOWS)
#include <fcntl.h> // _open_osfhandle constants
#include <io.h> // _commit
#include <werapi.h> // WerRegisterExcludedMemoryBlock
#else
#include <fcntl.h> // open
#include <sys/mman.h> // mmap
#include <sys/stat.h> // fstat
#include <unistd.h> // fsync
#endif // IREE_PLATFORM_WINDOWS
#endif // IREE_FILE_IO_ENABLE
//===----------------------------------------------------------------------===//
// iree_io_file_handle_t
//===----------------------------------------------------------------------===//
typedef struct iree_io_file_handle_t {
// Reference count controlling handle lifetime.
iree_atomic_ref_count_t ref_count;
// Host allocator used for this handle.
iree_allocator_t host_allocator;
// Original platform open mode bits known to this handle.
iree_io_file_mode_t mode;
// Access operations allowed through this handle.
iree_io_file_access_t access;
// Wrapped platform primitive.
iree_io_file_handle_primitive_t primitive;
// Callback used to release the wrapped primitive.
iree_io_file_handle_release_callback_t release_callback;
} iree_io_file_handle_t;
IREE_API_EXPORT iree_status_t iree_io_file_handle_wrap(
iree_io_file_access_t allowed_access, iree_io_file_mode_t mode,
iree_io_file_handle_primitive_t handle_primitive,
iree_io_file_handle_release_callback_t release_callback,
iree_allocator_t host_allocator, iree_io_file_handle_t** out_handle) {
IREE_ASSERT_ARGUMENT(out_handle);
*out_handle = NULL;
IREE_TRACE_ZONE_BEGIN(z0);
iree_io_file_handle_t* handle = NULL;
IREE_RETURN_AND_END_ZONE_IF_ERROR(
z0,
iree_allocator_malloc(host_allocator, sizeof(*handle), (void**)&handle));
iree_atomic_ref_count_init(&handle->ref_count);
handle->host_allocator = host_allocator;
handle->mode = mode;
handle->access = allowed_access;
handle->primitive = handle_primitive;
handle->release_callback = release_callback;
*out_handle = handle;
IREE_TRACE_ZONE_END(z0);
return iree_ok_status();
}
IREE_API_EXPORT iree_status_t iree_io_file_handle_wrap_host_allocation(
iree_io_file_access_t allowed_access, iree_byte_span_t host_allocation,
iree_io_file_handle_release_callback_t release_callback,
iree_allocator_t host_allocator, iree_io_file_handle_t** out_handle) {
iree_io_file_handle_primitive_t handle_primitive = {
.type = IREE_IO_FILE_HANDLE_TYPE_HOST_ALLOCATION,
.value =
{
.host_allocation = host_allocation,
},
};
return iree_io_file_handle_wrap(allowed_access, IREE_IO_FILE_MODE_NONE,
handle_primitive, release_callback,
host_allocator, out_handle);
}
static void iree_io_file_handle_destroy(iree_io_file_handle_t* handle) {
IREE_ASSERT_ARGUMENT(handle);
IREE_TRACE_ZONE_BEGIN(z0);
iree_allocator_t host_allocator = handle->host_allocator;
if (handle->release_callback.fn) {
handle->release_callback.fn(handle->release_callback.user_data,
handle->primitive);
}
iree_allocator_free(host_allocator, handle);
IREE_TRACE_ZONE_END(z0);
}
IREE_API_EXPORT void iree_io_file_handle_retain(iree_io_file_handle_t* handle) {
if (IREE_LIKELY(handle)) {
iree_atomic_ref_count_inc(&handle->ref_count);
}
}
IREE_API_EXPORT void iree_io_file_handle_release(
iree_io_file_handle_t* handle) {
if (IREE_LIKELY(handle) &&
iree_atomic_ref_count_dec(&handle->ref_count) == 1) {
iree_io_file_handle_destroy(handle);
}
}
IREE_API_EXPORT iree_io_file_access_t
iree_io_file_handle_access(const iree_io_file_handle_t* handle) {
IREE_ASSERT_ARGUMENT(handle);
return handle->access;
}
IREE_API_EXPORT iree_io_file_handle_primitive_t
iree_io_file_handle_primitive(const iree_io_file_handle_t* handle) {
IREE_ASSERT_ARGUMENT(handle);
return handle->primitive;
}
IREE_API_EXPORT bool iree_io_file_handle_uses_async_io(
const iree_io_file_handle_t* handle) {
IREE_ASSERT_ARGUMENT(handle);
return iree_all_bits_set(handle->mode, IREE_IO_FILE_MODE_ASYNC);
}
static iree_status_t iree_io_platform_fd_flush(int fd) {
#if IREE_FILE_IO_ENABLE
#if defined(IREE_PLATFORM_WINDOWS)
int ret = _commit(fd);
#else
int ret = fsync(fd);
#endif // IREE_PLATFORM_WINDOWS
return ret != -1 ? iree_ok_status()
: iree_make_status(iree_status_code_from_errno(errno),
"unable to sync file writes");
#else
return iree_make_status(IREE_STATUS_UNAVAILABLE,
"file support has been compiled out of this binary; "
"set IREE_FILE_IO_ENABLE=1 to include it");
#endif // IREE_FILE_IO_ENABLE
}
IREE_API_EXPORT iree_status_t
iree_io_file_handle_flush(iree_io_file_handle_t* handle) {
IREE_ASSERT_ARGUMENT(handle);
IREE_TRACE_ZONE_BEGIN(z0);
iree_status_t status = iree_ok_status();
switch (handle->primitive.type) {
case IREE_IO_FILE_HANDLE_TYPE_HOST_ALLOCATION: {
// No-op (though we could flush when known mapped).
break;
}
case IREE_IO_FILE_HANDLE_TYPE_FD: {
status = iree_io_platform_fd_flush(handle->primitive.value.fd);
break;
}
default: {
status = iree_make_status(IREE_STATUS_UNIMPLEMENTED,
"flush not supported on handle type %d",
(int)handle->primitive.type);
break;
}
}
IREE_TRACE_ZONE_END(z0);
return status;
}
//===----------------------------------------------------------------------===//
// iree_io_file_handle_t utilities
//===----------------------------------------------------------------------===//
#if IREE_FILE_IO_ENABLE
#if defined(IREE_PLATFORM_WINDOWS)
static iree_status_t iree_io_file_handle_platform_open(
iree_io_file_mode_t mode, iree_string_view_t path, uint64_t initial_size,
iree_io_file_handle_primitive_t* out_handle_primitive) {
IREE_ASSERT_ARGUMENT(out_handle_primitive);
memset(out_handle_primitive, 0, sizeof(*out_handle_primitive));
// Convert path from a string view to a NUL-terminated C string.
if (path.size >= IREE_MAX_PATH) {
return iree_make_status(IREE_STATUS_OUT_OF_RANGE,
"path length %" PRIhsz
" exceeds maximum character length of %d",
path.size, IREE_MAX_PATH);
}
char* path_str = iree_alloca(path.size + 1);
iree_string_view_to_cstring(path, path_str, path.size + 1);
DWORD desired_access = 0;
if (iree_all_bits_set(mode, IREE_IO_FILE_MODE_READ)) {
desired_access |= GENERIC_READ;
}
if (iree_all_bits_set(mode, IREE_IO_FILE_MODE_WRITE)) {
desired_access |= GENERIC_WRITE;
}
DWORD share_mode = 0;
if (iree_all_bits_set(mode, IREE_IO_FILE_MODE_SHARE_READ)) {
share_mode |= FILE_SHARE_READ;
}
if (iree_all_bits_set(mode, IREE_IO_FILE_MODE_SHARE_WRITE)) {
share_mode |= FILE_SHARE_WRITE;
}
DWORD creation_disposition =
iree_all_bits_set(mode, IREE_IO_FILE_MODE_OVERWRITE) ? CREATE_ALWAYS
: OPEN_EXISTING;
DWORD flags = FILE_ATTRIBUTE_NORMAL;
if (iree_all_bits_set(mode, IREE_IO_FILE_MODE_RANDOM_ACCESS)) {
flags |= FILE_FLAG_RANDOM_ACCESS;
} else if (iree_all_bits_set(mode, IREE_IO_FILE_MODE_SEQUENTIAL_SCAN)) {
flags |= FILE_FLAG_SEQUENTIAL_SCAN;
}
if (iree_all_bits_set(mode, IREE_IO_FILE_MODE_TEMPORARY)) {
flags |= FILE_FLAG_DELETE_ON_CLOSE;
}
if (iree_all_bits_set(mode, IREE_IO_FILE_MODE_ASYNC)) {
flags |= FILE_FLAG_OVERLAPPED;
}
// Create or open the file.
HANDLE handle = CreateFileA(path_str, desired_access, share_mode, NULL,
creation_disposition, flags, NULL);
if (handle == INVALID_HANDLE_VALUE) {
return iree_make_status(iree_status_code_from_win32_error(GetLastError()),
"failed to open file '%.*s'", (int)path.size,
path.data);
}
// If we were provided an initialize size and are creating the file then
// adjust the file length.
if (iree_all_bits_set(mode, IREE_IO_FILE_MODE_OVERWRITE)) {
// Zeroish-extend the file up to the total file size specified by the
// caller. This may be larger than the virtual address space can handle but
// so long as the length requested for mapping is under the size_t limit
// this will succeed.
LARGE_INTEGER file_size = {0};
file_size.QuadPart = initial_size;
if (!SetFilePointerEx(handle, file_size, NULL, FILE_BEGIN) ||
!SetEndOfFile(handle)) {
CloseHandle(handle);
return iree_make_status(iree_status_code_from_win32_error(GetLastError()),
"failed to extend file '%.*s' to %" PRIu64
" bytes (out of disk space or permission denied)",
(int)path.size, path.data, initial_size);
}
}
// Transfer ownership of the handle to a CRT file descriptor.
// After this succeeds we cannot call CloseHandle as the CRT owns it.
int open_flags = 0;
if (!iree_all_bits_set(mode, IREE_IO_FILE_MODE_WRITE)) {
open_flags |= _O_RDONLY;
}
int fd = _open_osfhandle((intptr_t)handle, open_flags);
if (fd == -1) {
CloseHandle(handle); // must close since we didn't transfer
return iree_make_status(
IREE_STATUS_INTERNAL,
"unable to transfer Win32 HANDLE to a CRT file descriptor");
}
out_handle_primitive->type = IREE_IO_FILE_HANDLE_TYPE_FD;
out_handle_primitive->value.fd = fd;
return iree_ok_status();
}
IREE_API_EXPORT iree_status_t iree_io_file_handle_platform_open_fd(
int fd, iree_allocator_t host_allocator,
iree_io_file_handle_primitive_t* out_handle_primitive) {
IREE_ASSERT_ARGUMENT(out_handle_primitive);
memset(out_handle_primitive, 0, sizeof(*out_handle_primitive));
int new_fd = _dup(fd);
if (new_fd == -1) {
return iree_make_status(IREE_STATUS_INVALID_ARGUMENT,
"invalid file_descriptor");
}
out_handle_primitive->type = IREE_IO_FILE_HANDLE_TYPE_FD;
out_handle_primitive->value.fd = new_fd;
return iree_ok_status();
}
static void iree_io_file_handle_platform_close(
void* user_data, iree_io_file_handle_primitive_t handle_primitive) {
// NOTE: we opened the file using Win32 APIs but it's safe to _close since we
// transferred ownership to the CRT with _open_osfhandle. If we used
// IREE_IO_FILE_HANDLE_TYPE_WIN32_HANDLE we'd want to switch on that instead.
IREE_ASSERT_EQ(handle_primitive.type, IREE_IO_FILE_HANDLE_TYPE_FD);
_close(handle_primitive.value.fd);
}
#else
static iree_status_t iree_io_file_handle_platform_open(
iree_io_file_mode_t mode, iree_string_view_t path, uint64_t initial_size,
iree_io_file_handle_primitive_t* out_handle_primitive) {
IREE_ASSERT_ARGUMENT(out_handle_primitive);
memset(out_handle_primitive, 0, sizeof(*out_handle_primitive));
// Convert path from a string view to a NUL-terminated C string.
if (path.size >= IREE_MAX_PATH) {
return iree_make_status(IREE_STATUS_OUT_OF_RANGE,
"path length %" PRIhsz
" exceeds maximum character length of %d",
path.size, IREE_MAX_PATH);
}
char* path_str = iree_alloca(path.size + 1);
iree_string_view_to_cstring(path, path_str, path.size + 1);
int flags = 0;
// TODO(benvanik): add a flag for forking behavior.
flags |= O_CLOEXEC;
if (iree_all_bits_set(mode, IREE_IO_FILE_MODE_OVERWRITE)) {
// If the file exists open anyway and truncate as if it had been recreated.
// This matches Win32 CREATE_ALWAYS behavior.
flags |= O_CREAT | O_TRUNC;
}
if (iree_all_bits_set(mode,
IREE_IO_FILE_MODE_READ | IREE_IO_FILE_MODE_WRITE)) {
// NOTE: O_RDWR != O_RDONLY | O_WRONLY!
flags |= O_RDWR;
} else if (iree_all_bits_set(mode, IREE_IO_FILE_MODE_READ)) {
flags |= O_RDONLY;
} else if (iree_all_bits_set(mode, IREE_IO_FILE_MODE_WRITE)) {
flags |= O_WRONLY;
}
#if defined(O_DIRECT)
if (iree_all_bits_set(mode, IREE_IO_FILE_MODE_DIRECT)) {
flags |= O_DIRECT;
}
#endif // O_DIRECT
#if defined(O_TMPFILE)
if (iree_all_bits_set(mode, IREE_IO_FILE_MODE_TEMPORARY)) {
flags |= O_TMPFILE;
}
#endif // O_TMPFILE
// I don't know, unix file permissions are dumb. User and group seems fine?
const mode_t open_mode = (S_IRUSR | S_IWUSR) | (S_IRGRP | S_IWGRP);
int fd = open(path_str, flags, open_mode);
if (fd == -1) {
return iree_make_status(iree_status_code_from_errno(errno),
"failed to open file '%.*s'", (int)path.size,
path.data);
}
// If we were provided an initialize size and are creating the file then
// adjust the file length.
if (iree_all_bits_set(mode, IREE_IO_FILE_MODE_OVERWRITE)) {
// Zero-extend the file up to the total file size specified by the
// caller. Note that `ftruncate` extends too.
if (ftruncate(fd, (off_t)initial_size) == -1) {
return iree_make_status(iree_status_code_from_errno(errno),
"failed to extend file '%.*s' to %" PRIu64
" bytes (out of disk space or permission denied)",
(int)path.size, path.data, initial_size);
}
}
out_handle_primitive->type = IREE_IO_FILE_HANDLE_TYPE_FD;
out_handle_primitive->value.fd = fd;
return iree_ok_status();
}
IREE_API_EXPORT iree_status_t iree_io_file_handle_platform_open_fd(
int fd, iree_allocator_t host_allocator,
iree_io_file_handle_primitive_t* out_handle_primitive) {
IREE_ASSERT_ARGUMENT(out_handle_primitive);
memset(out_handle_primitive, 0, sizeof(*out_handle_primitive));
int new_fd = dup(fd);
if (new_fd == -1) {
return iree_make_status(IREE_STATUS_INVALID_ARGUMENT,
"invalid file_descriptor");
}
out_handle_primitive->type = IREE_IO_FILE_HANDLE_TYPE_FD;
out_handle_primitive->value.fd = new_fd;
return iree_ok_status();
}
static void iree_io_file_handle_platform_close(
void* user_data, iree_io_file_handle_primitive_t handle_primitive) {
IREE_ASSERT_EQ(handle_primitive.type, IREE_IO_FILE_HANDLE_TYPE_FD);
close(handle_primitive.value.fd);
}
#endif // IREE_PLATFORM_WINDOWS
static iree_status_t iree_io_file_handle_create_or_open(
iree_io_file_mode_t mode, iree_string_view_t path, uint64_t initial_size,
iree_allocator_t host_allocator, iree_io_file_handle_t** out_handle) {
if (iree_all_bits_set(mode, IREE_IO_FILE_MODE_RANDOM_ACCESS |
IREE_IO_FILE_MODE_SEQUENTIAL_SCAN)) {
return iree_make_status(IREE_STATUS_INVALID_ARGUMENT,
"at most one access pattern hint may be specified");
}
iree_io_file_handle_primitive_t handle_primitive = {0};
IREE_RETURN_IF_ERROR(iree_io_file_handle_platform_open(
mode, path, initial_size, &handle_primitive));
iree_io_file_access_t allowed_access = 0;
if (iree_all_bits_set(mode, IREE_IO_FILE_MODE_READ)) {
allowed_access |= IREE_IO_FILE_ACCESS_READ;
}
if (iree_all_bits_set(mode, IREE_IO_FILE_MODE_WRITE)) {
allowed_access |= IREE_IO_FILE_ACCESS_WRITE;
}
iree_io_file_handle_release_callback_t release_callback = {
.fn = iree_io_file_handle_platform_close,
.user_data = NULL,
};
iree_io_file_handle_t* handle = NULL;
iree_status_t status =
iree_io_file_handle_wrap(allowed_access, mode, handle_primitive,
release_callback, host_allocator, &handle);
if (iree_status_is_ok(status)) {
*out_handle = handle;
} else {
release_callback.fn(release_callback.user_data, handle_primitive);
}
return status;
}
IREE_API_EXPORT iree_status_t iree_io_file_handle_create(
iree_io_file_mode_t mode, iree_string_view_t path, uint64_t initial_size,
iree_allocator_t host_allocator, iree_io_file_handle_t** out_handle) {
IREE_ASSERT_ARGUMENT(out_handle);
*out_handle = NULL;
IREE_TRACE_ZONE_BEGIN(z0);
IREE_TRACE_ZONE_APPEND_TEXT(z0, path.data, path.size);
iree_status_t status = iree_io_file_handle_create_or_open(
mode | IREE_IO_FILE_MODE_OVERWRITE, path, initial_size, host_allocator,
out_handle);
IREE_TRACE_ZONE_END(z0);
return status;
}
IREE_API_EXPORT iree_status_t iree_io_file_handle_open(
iree_io_file_mode_t mode, iree_string_view_t path,
iree_allocator_t host_allocator, iree_io_file_handle_t** out_handle) {
IREE_ASSERT_ARGUMENT(out_handle);
*out_handle = NULL;
IREE_TRACE_ZONE_BEGIN(z0);
IREE_TRACE_ZONE_APPEND_TEXT(z0, path.data, path.size);
iree_status_t status = iree_io_file_handle_create_or_open(
mode, path, 0ull, host_allocator, out_handle);
IREE_TRACE_ZONE_END(z0);
return status;
}
IREE_API_EXPORT iree_status_t iree_io_file_handle_open_fd(
iree_io_file_mode_t mode, int fd, iree_allocator_t host_allocator,
iree_io_file_handle_t** out_handle) {
IREE_ASSERT_ARGUMENT(out_handle);
*out_handle = NULL;
IREE_TRACE_ZONE_BEGIN(z0);
IREE_TRACE_ZONE_APPEND_VALUE_I64(z0, fd);
iree_io_file_access_t allowed_access = 0;
if (iree_all_bits_set(mode, IREE_IO_FILE_MODE_READ)) {
allowed_access |= IREE_IO_FILE_ACCESS_READ;
}
if (iree_all_bits_set(mode, IREE_IO_FILE_MODE_WRITE)) {
allowed_access |= IREE_IO_FILE_ACCESS_WRITE;
}
iree_io_file_handle_primitive_t handle_primitive = {0};
IREE_RETURN_AND_END_ZONE_IF_ERROR(
z0, iree_io_file_handle_platform_open_fd(fd, host_allocator,
&handle_primitive));
iree_io_file_handle_release_callback_t release_callback = {
.fn = iree_io_file_handle_platform_close,
.user_data = NULL,
};
iree_io_file_handle_t* handle = NULL;
iree_status_t status =
iree_io_file_handle_wrap(allowed_access, mode, handle_primitive,
release_callback, host_allocator, &handle);
if (iree_status_is_ok(status)) {
*out_handle = handle;
} else {
release_callback.fn(release_callback.user_data, handle_primitive);
}
IREE_TRACE_ZONE_END(z0);
return status;
}
// Frees the file contents using the original buffer allocator.
static void iree_io_file_contents_release_callback(
void* user_data, iree_io_file_handle_primitive_t handle_primitive) {
iree_io_file_contents_t* contents = (iree_io_file_contents_t*)user_data;
iree_io_file_contents_free(contents);
}
IREE_API_EXPORT iree_status_t iree_io_file_handle_preload(
iree_io_file_mode_t mode, iree_string_view_t path,
iree_allocator_t host_allocator, iree_io_file_handle_t** out_handle) {
IREE_ASSERT_ARGUMENT(out_handle);
*out_handle = NULL;
IREE_TRACE_ZONE_BEGIN(z0);
// Read the entire file contents into memory.
iree_io_file_contents_t* contents = NULL;
IREE_RETURN_AND_END_ZONE_IF_ERROR(
z0, iree_io_file_contents_read(path, host_allocator, &contents));
// Wrap the contents in a file handle.
// When the file handle is destroyed the release callback will free the
// loaded file contents.
iree_io_file_handle_t* handle = NULL;
const iree_io_file_handle_release_callback_t release_callback = {
.fn = iree_io_file_contents_release_callback,
.user_data = contents,
};
iree_status_t status = iree_io_file_handle_wrap_host_allocation(
mode, contents->buffer, release_callback, host_allocator, &handle);
if (iree_status_is_ok(status)) {
*out_handle = handle;
} else {
iree_io_file_contents_free(contents);
}
IREE_TRACE_ZONE_END(z0);
return status;
}
#else
IREE_API_EXPORT iree_status_t iree_io_file_handle_create(
iree_io_file_mode_t mode, iree_string_view_t path, uint64_t initial_size,
iree_allocator_t host_allocator, iree_io_file_handle_t** out_handle) {
IREE_ASSERT_ARGUMENT(out_handle);
*out_handle = NULL;
return iree_make_status(IREE_STATUS_UNAVAILABLE,
"file support has been compiled out of this binary; "
"set IREE_FILE_IO_ENABLE=1 to include it");
}
IREE_API_EXPORT iree_status_t iree_io_file_handle_open(
iree_io_file_mode_t mode, iree_string_view_t path,
iree_allocator_t host_allocator, iree_io_file_handle_t** out_handle) {
IREE_ASSERT_ARGUMENT(out_handle);
*out_handle = NULL;
return iree_make_status(IREE_STATUS_UNAVAILABLE,
"file support has been compiled out of this binary; "
"set IREE_FILE_IO_ENABLE=1 to include it");
}
IREE_API_EXPORT iree_status_t iree_io_file_handle_open_fd(
iree_io_file_mode_t mode, int fd, iree_allocator_t host_allocator,
iree_io_file_handle_t** out_handle) {
IREE_ASSERT_ARGUMENT(out_handle);
*out_handle = NULL;
return iree_make_status(IREE_STATUS_UNAVAILABLE,
"file support has been compiled out of this binary; "
"set IREE_FILE_IO_ENABLE=1 to include it");
}
IREE_API_EXPORT iree_status_t iree_io_file_handle_preload(
iree_io_file_mode_t mode, iree_string_view_t path,
iree_allocator_t host_allocator, iree_io_file_handle_t** out_handle) {
IREE_ASSERT_ARGUMENT(out_handle);
*out_handle = NULL;
return iree_make_status(IREE_STATUS_UNAVAILABLE,
"file support has been compiled out of this binary; "
"set IREE_FILE_IO_ENABLE=1 to include it");
}
#endif // IREE_FILE_IO_ENABLE
//===----------------------------------------------------------------------===//
// iree_io_file_mapping_t support
//===----------------------------------------------------------------------===//
static iree_status_t iree_io_calculate_file_view_range(
uint64_t file_size, uint64_t offset, iree_host_size_t length,
iree_host_size_t* out_adjusted_length) {
*out_adjusted_length = 0;
// Check if the start of the range runs off the end of the buffer.
if (IREE_UNLIKELY(offset > file_size)) {
return iree_make_status(IREE_STATUS_OUT_OF_RANGE,
"attempted to access an address off the end of the "
"file range (offset=%" PRIu64 ", length=%" PRIhsz
", file size=%" PRIu64 ")",
offset, length, file_size);
}
// Calculate the real length adjusted for our region within the allocation.
const iree_host_size_t adjusted_length =
length == IREE_HOST_SIZE_MAX ? file_size - offset : length;
if (adjusted_length == 0) {
// Fine (but silly) to have a zero length.
return iree_ok_status();
}
// Check if the end runs over the allocation.
const uint64_t end = offset + adjusted_length - 1;
if (IREE_UNLIKELY(end >= file_size)) {
return iree_make_status(IREE_STATUS_OUT_OF_RANGE,
"attempted to access an address outside of the "
"file range (offset=%" PRIu64
", adjusted_length=%" PRIhsz ", end=%" PRIu64
", file size=%" PRIu64 ")",
offset, adjusted_length, end, file_size);
}
*out_adjusted_length = adjusted_length;
return iree_ok_status();
}
static iree_status_t iree_io_file_mapping_from_host_allocation(
iree_byte_span_t buffer, uint64_t offset, iree_host_size_t length,
iree_byte_span_t* out_range) {
*out_range = iree_byte_span_empty();
iree_host_size_t adjusted_length = 0;
IREE_RETURN_IF_ERROR(iree_io_calculate_file_view_range(
(uint64_t)buffer.data_length, offset, length, &adjusted_length));
*out_range = iree_make_byte_span(buffer.data + offset, adjusted_length);
return iree_ok_status();
}
#if IREE_FILE_IO_ENABLE && \
(defined(IREE_PLATFORM_ANDROID) || defined(IREE_PLATFORM_IOS) || \
defined(IREE_PLATFORM_LINUX) || defined(IREE_PLATFORM_MACOS))
static iree_status_t iree_io_file_handle_to_fd(
iree_io_file_handle_primitive_t primitive, int* out_fd) {
*out_fd = -1;
switch (primitive.type) {
case IREE_IO_FILE_HANDLE_TYPE_FD:
*out_fd = primitive.value.fd;
return iree_ok_status();
default:
return iree_make_status(
IREE_STATUS_UNIMPLEMENTED,
"no file descriptor available for file handles of type %d",
(int)primitive.type);
}
}
static iree_status_t iree_io_platform_map_file_view(
iree_io_file_handle_primitive_t primitive, iree_io_file_access_t access,
uint64_t offset, iree_host_size_t length,
iree_io_file_mapping_flags_t flags, void** out_impl,
iree_byte_span_t* out_contents) {
*out_impl = NULL;
*out_contents = iree_byte_span_empty();
// Attempt to get a file descriptor from the provided IREE file handle.
int fd = -1;
IREE_RETURN_IF_ERROR(iree_io_file_handle_to_fd(primitive, &fd),
"mapping file handle to file descriptor");
// Query file size. We don't support extending/truncating files today and make
// the user do that - we just allow the length to be IREE_HOST_SIZE_MAX to
// indicate the remaining file should be mapped.
struct stat file_stat = {0};
if (fstat(fd, &file_stat) == -1) {
return iree_make_status(iree_status_code_from_errno(errno),
"unable to query file size");
}
const uint64_t file_size = file_stat.st_size;
// Validate and adjust view size if needed.
iree_host_size_t adjusted_length = 0;
IREE_RETURN_IF_ERROR(iree_io_calculate_file_view_range(
file_size, offset, length, &adjusted_length));
int prot = 0;
if (iree_all_bits_set(access, IREE_IO_FILE_ACCESS_READ)) {
prot |= PROT_READ;
}
if (iree_all_bits_set(access, IREE_IO_FILE_ACCESS_WRITE)) {
prot |= PROT_WRITE;
}
int map_flags = 0;
if (iree_all_bits_set(flags, IREE_IO_FILE_MAPPING_FLAG_PRIVATE)) {
map_flags |= MAP_PRIVATE;
} else {
map_flags |= MAP_SHARED;
}
#if defined(MAP_HUGETLB)
if (iree_all_bits_set(flags, IREE_IO_FILE_MAPPING_FLAG_LARGE_PAGES)) {
map_flags |= MAP_HUGETLB;
}
#endif // MAP_HUGETLB
// Map the memory.
void* ptr = mmap(NULL, adjusted_length, prot, map_flags, fd, offset);
if (ptr == MAP_FAILED) {
return iree_make_status(iree_status_code_from_errno(errno),
"failed to map file handle range %" PRIu64
"-%" PRIu64 " (%" PRIhsz
" bytes) from file of %" PRIu64 " total bytes",
offset, offset + length, length, file_size);
}
// Pass hints to the memory manager - informational only.
int advice = 0;
if (iree_all_bits_set(flags, IREE_IO_FILE_MAPPING_FLAG_SEQUENTIAL_ACCESS)) {
advice |= MADV_SEQUENTIAL;
}
#if defined(MADV_DONTDUMP)
if (iree_all_bits_set(flags, IREE_IO_FILE_MAPPING_FLAG_EXCLUDE_FROM_DUMPS)) {
advice |= MADV_DONTDUMP;
}
#endif // MADV_DONTDUMP
if (advice) {
madvise(ptr, adjusted_length, advice);
}
*out_impl = ptr;
*out_contents = iree_make_byte_span(ptr, adjusted_length);
return iree_ok_status();
}
static void iree_io_platform_unmap_file_view(iree_io_file_mapping_flags_t flags,
void* impl,
iree_byte_span_t contents) {
if (impl) {
munmap(impl, (size_t)contents.data_length);
}
}
#elif IREE_FILE_IO_ENABLE && defined(IREE_PLATFORM_WINDOWS)
static iree_status_t iree_io_file_handle_to_win32_handle(
iree_io_file_handle_primitive_t primitive, HANDLE* out_handle) {
*out_handle = INVALID_HANDLE_VALUE;
switch (primitive.type) {
case IREE_IO_FILE_HANDLE_TYPE_FD:
*out_handle = (HANDLE)_get_osfhandle(primitive.value.fd);
if (*out_handle == INVALID_HANDLE_VALUE) {
return iree_make_status(
IREE_STATUS_INVALID_ARGUMENT,
"file descriptor is not backed by a valid Win32 HANDLE");
}
return iree_ok_status();
default:
return iree_make_status(
IREE_STATUS_UNIMPLEMENTED,
"no Win32 HANDLE available for file handles of type %d",
(int)primitive.type);
}
}
static iree_status_t iree_io_platform_map_file_view(
iree_io_file_handle_primitive_t primitive, iree_io_file_access_t access,
uint64_t offset, iree_host_size_t length,
iree_io_file_mapping_flags_t flags, void** out_impl,
iree_byte_span_t* out_contents) {
*out_impl = NULL;
*out_contents = iree_byte_span_empty();
// Attempt to get a Win32 HANDLE from the provided IREE file handle.
HANDLE handle = INVALID_HANDLE_VALUE;
IREE_RETURN_IF_ERROR(iree_io_file_handle_to_win32_handle(primitive, &handle),
"mapping file handle to win32 handle");
// Query file size. We don't support extending/truncating files today and make
// the user do that - we just allow the length to be IREE_HOST_SIZE_MAX to
// indicate the remaining file should be mapped.
FILE_STANDARD_INFO file_info = {0};
if (!GetFileInformationByHandleEx(handle, FileStandardInfo, &file_info,
(DWORD)sizeof(file_info))) {
return iree_make_status(iree_status_code_from_win32_error(GetLastError()),
"failed to query file handle information");
}
const uint64_t file_size = file_info.EndOfFile.QuadPart;
// Validate and adjust view size if needed.
iree_host_size_t adjusted_length = 0;
IREE_RETURN_IF_ERROR(iree_io_calculate_file_view_range(
file_size, offset, length, &adjusted_length));
// Create a file mapping object which will retain the file handle for the
// lifetime of the mapping.
DWORD protect = 0;
if (iree_all_bits_set(access, IREE_IO_FILE_ACCESS_WRITE)) {
protect |= PAGE_READWRITE;
} else if (iree_all_bits_set(access, IREE_IO_FILE_ACCESS_READ)) {
protect |= PAGE_READONLY;
}
if (iree_all_bits_set(flags, IREE_IO_FILE_MAPPING_FLAG_LARGE_PAGES)) {
protect |= SEC_LARGE_PAGES;
}
HANDLE mapping =
CreateFileMappingA(handle, NULL, protect, /*dwMaximumSizeHigh=*/0,
/*dwMaximumSizeLow=*/0, /*lpName=*/NULL);
if (!mapping) {
return iree_make_status(iree_status_code_from_win32_error(GetLastError()),
"failed to create file mapping for file handle");
}
// Map the requested range into the virtual address space of the process.
DWORD desired_access = 0;
if (iree_all_bits_set(access, IREE_IO_FILE_ACCESS_READ)) {
desired_access |= FILE_MAP_READ;
} else if (iree_all_bits_set(access, IREE_IO_FILE_ACCESS_WRITE)) {
desired_access |= FILE_MAP_WRITE;
}
LARGE_INTEGER offset_li = {0};
offset_li.QuadPart = offset;
void* ptr = MapViewOfFileEx(mapping, desired_access, offset_li.HighPart,
offset_li.LowPart, (SIZE_T)adjusted_length,
/*lpBaseAddress=*/NULL);
if (!ptr) {
CloseHandle(mapping);
return iree_make_status(
iree_status_code_from_win32_error(GetLastError()),
"failed to map file handle range %" PRIu64 "-%" PRIu64 " (%" PRIhsz
" bytes) from file of %" PRIu64 " total bytes",
offset, offset + adjusted_length, adjusted_length, file_size);
}
#if defined(WER_MAX_REGISTERED_ENTRIES) && \
WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM)
// If the user specified that we should exclude the contents from dumps then
// we need to tell Windows Error Reporting. Unfortunately the API is broken
// and only accepts a DWORD (it was added in Windows 10 **and uses a DWORD for
// size** :facepalm:). This is informational so we just try and maybe fail.
// Note that there's also a very small limit on the number of exclusions
// (WER_MAX_REGISTERED_ENTRIES = 512) so we can't just loop and try to exclude
// 4GB blocks in all cases. We try anyway, though. Maybe this isn't even
// useful - the docs are iffy. Oh well.
if (iree_all_bits_set(flags, IREE_IO_FILE_MAPPING_FLAG_EXCLUDE_FROM_DUMPS)) {
iree_host_size_t bytes_excluded = 0;
iree_host_size_t bytes_remaining = adjusted_length;
while (bytes_remaining > 0) {
const DWORD bytes_to_exclude = iree_min(bytes_remaining, UINT32_MAX);
WerRegisterExcludedMemoryBlock((uint8_t*)ptr + bytes_excluded,
bytes_to_exclude);
bytes_excluded += bytes_to_exclude;
bytes_remaining -= bytes_to_exclude;
}
}
#endif // WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP |
// WINAPI_PARTITION_SYSTEM)
*out_impl = mapping; // transferred to caller
*out_contents = iree_make_byte_span(ptr, adjusted_length);
return iree_ok_status();
}
static void iree_io_platform_unmap_file_view(iree_io_file_mapping_flags_t flags,
void* impl,
iree_byte_span_t contents) {
if (contents.data) {
UnmapViewOfFile(contents.data);
}
#if defined(WER_MAX_REGISTERED_ENTRIES) && \
WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM)
if (contents.data &&
iree_all_bits_set(flags, IREE_IO_FILE_MAPPING_FLAG_EXCLUDE_FROM_DUMPS)) {
WerUnregisterExcludedMemoryBlock(contents.data);
iree_host_size_t bytes_unexcluded = 0;
iree_host_size_t bytes_remaining = contents.data_length;
while (bytes_remaining > 0) {
const DWORD bytes_to_unexclude = iree_min(bytes_remaining, UINT32_MAX);
WerUnregisterExcludedMemoryBlock(contents.data + bytes_unexcluded);
bytes_unexcluded += bytes_to_unexclude;
bytes_remaining -= bytes_to_unexclude;
}
}
#endif // WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP |
// WINAPI_PARTITION_SYSTEM)
if (impl) {
CloseHandle((HANDLE)impl);
}
}
#else
static iree_status_t iree_io_platform_map_file_view(
iree_io_file_handle_primitive_t primitive, iree_io_file_access_t access,
uint64_t offset, iree_host_size_t length,
iree_io_file_mapping_flags_t flags, void** out_impl,
iree_byte_span_t* out_contents) {
*out_impl = NULL;
*out_contents = iree_byte_span_empty();
return iree_make_status(IREE_STATUS_UNIMPLEMENTED,
"no support for mapping file views on this platform");
}
static void iree_io_platform_unmap_file_view(iree_io_file_mapping_flags_t flags,
void* impl,
iree_byte_span_t contents) {}
#endif // IREE_FILE_IO_ENABLE && IREE_PLATFORM_*
//===----------------------------------------------------------------------===//
// iree_io_file_mapping_t
//===----------------------------------------------------------------------===//
struct iree_io_file_mapping_t {
iree_atomic_ref_count_t ref_count;
iree_allocator_t host_allocator;
// File handle that owns the underlying file. Retained.
iree_io_file_handle_t* handle;
// Flags used when creating the mapping.
iree_io_file_mapping_flags_t flags;
// Platform-defined implementation handle.
// - mmap: base pointer returned from mmap
// - Win32: HANDLE returned by CreateFileMappingA
void* impl;
// Mapped contents in host memory. Access matches that requested on mapping.
iree_byte_span_t contents;
};
IREE_API_EXPORT iree_status_t iree_io_file_map_view(
iree_io_file_handle_t* handle, iree_io_file_access_t access,
uint64_t offset, iree_host_size_t length,
iree_io_file_mapping_flags_t flags, iree_allocator_t host_allocator,
iree_io_file_mapping_t** out_mapping) {
IREE_ASSERT_ARGUMENT(handle);
IREE_ASSERT_ARGUMENT(out_mapping);
*out_mapping = NULL;
IREE_TRACE_ZONE_BEGIN(z0);
IREE_TRACE_ZONE_APPEND_VALUE_I64(z0, offset);
IREE_TRACE_ZONE_APPEND_VALUE_I64(z0, length);
IREE_TRACE_ZONE_APPEND_VALUE_I64(z0, flags);
iree_io_file_mapping_t* mapping = NULL;
IREE_RETURN_AND_END_ZONE_IF_ERROR(
z0, iree_allocator_malloc(host_allocator, sizeof(*mapping),
(void**)&mapping));
iree_atomic_ref_count_init(&mapping->ref_count);
mapping->host_allocator = host_allocator;
mapping->handle = handle;
iree_io_file_handle_retain(mapping->handle);
mapping->flags = flags;
mapping->contents = iree_byte_span_empty();
iree_status_t status = iree_ok_status();
// Special case for for host allocations: we can directly use them (with
// translation). Otherwise we let the platform-specific logic take care of
// things (if it exists).
iree_io_file_handle_primitive_t primitive =
iree_io_file_handle_primitive(handle);
if (primitive.type == IREE_IO_FILE_HANDLE_TYPE_HOST_ALLOCATION) {
iree_byte_span_t file_buffer = primitive.value.host_allocation;
status = iree_io_file_mapping_from_host_allocation(
file_buffer, offset, length, &mapping->contents);
} else {
// Use platform APIs to map the file.
status =
iree_io_platform_map_file_view(primitive, access, offset, length, flags,
&mapping->impl, &mapping->contents);
}
if (iree_status_is_ok(status)) {
*out_mapping = mapping;
} else {
iree_io_file_mapping_release(mapping);
}
IREE_TRACE_ZONE_END(z0);
return status;
}
static void iree_io_file_mapping_destroy(iree_io_file_mapping_t* mapping) {
IREE_ASSERT_ARGUMENT(mapping);
IREE_TRACE_ZONE_BEGIN(z0);
iree_allocator_t host_allocator = mapping->host_allocator;
if (mapping->impl) {
iree_io_platform_unmap_file_view(mapping->flags, mapping->impl,
mapping->contents);
}
iree_io_file_handle_release(mapping->handle);
iree_allocator_free(host_allocator, mapping);
IREE_TRACE_ZONE_END(z0);
}
IREE_API_EXPORT void iree_io_file_mapping_retain(
iree_io_file_mapping_t* mapping) {
if (IREE_LIKELY(mapping)) {
iree_atomic_ref_count_inc(&mapping->ref_count);
}
}
IREE_API_EXPORT void iree_io_file_mapping_release(
iree_io_file_mapping_t* mapping) {
if (IREE_LIKELY(mapping) &&
iree_atomic_ref_count_dec(&mapping->ref_count) == 1) {
iree_io_file_mapping_destroy(mapping);
}
}
IREE_API_EXPORT iree_host_size_t
iree_io_file_mapping_length(const iree_io_file_mapping_t* mapping) {
IREE_ASSERT_ARGUMENT(mapping);
return mapping->contents.data_length;
}
IREE_API_EXPORT iree_const_byte_span_t
iree_io_file_mapping_contents_ro(const iree_io_file_mapping_t* mapping) {
return iree_make_const_byte_span(mapping->contents.data,
mapping->contents.data_length);
}
IREE_API_EXPORT iree_byte_span_t
iree_io_file_mapping_contents_rw(iree_io_file_mapping_t* mapping) {
IREE_ASSERT_ARGUMENT(mapping);
return mapping->contents;
}