Skip to content

Commit

Permalink
Adding iree_io_file_handle_create/iree_io_file_handle_open. (#19510)
Browse files Browse the repository at this point in the history
These utilities are optional but an easy way to get a file handle that
is compatible with the rest of IREE. This largely replaces the
need for the existing file_io utilities but cleanup/unification is left
for future changes.

Command line tools now default to --parameter_mode=file though users can
force mmap or preload. There's some flags that are available on the open
that may help or hurt performance and the defaults are provisional.
  • Loading branch information
benvanik authored Dec 18, 2024
1 parent 078c3ec commit ce65948
Show file tree
Hide file tree
Showing 6 changed files with 418 additions and 40 deletions.
31 changes: 24 additions & 7 deletions runtime/src/iree/base/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,38 +128,55 @@ typedef IREE_DEVICE_SIZE_T iree_device_size_t;
//===----------------------------------------------------------------------===//
// Synchronization and threading
//===----------------------------------------------------------------------===//

#if !defined(IREE_SYNCHRONIZATION_DISABLE_UNSAFE)
// On ultra-tiny systems where there may only be a single core - or a single
// core that is guaranteed to ever call an IREE API - all synchronization
// primitives used throughout IREE can be turned into no-ops. Note that behavior
// is undefined if there is use of any `iree_*` API call or memory that is
// owned by IREE from multiple threads concurrently or across threads without
// proper barriers in place. Unless your target system is in a similar class to
// an Arduino this is definitely not what you want.

#if !defined(IREE_SYNCHRONIZATION_DISABLE_UNSAFE)
#define IREE_SYNCHRONIZATION_DISABLE_UNSAFE 0
#endif // !IREE_SYNCHRONIZATION_DISABLE_UNSAFE

//===----------------------------------------------------------------------===//
// File I/O
//===----------------------------------------------------------------------===//

#if !defined(IREE_FILE_IO_ENABLE)
// On platforms without file systems or in applications where no file I/O
// utilities are used, all file I/O operations can be stripped out. Functions
// relying on file I/O will still be defined, but they will return errors.

#if !defined(IREE_FILE_IO_ENABLE)
#define IREE_FILE_IO_ENABLE 1
#endif // !IREE_FILE_IO_ENABLE

#if !defined(IREE_MAX_PATH)
// Maximum path C string length in characters excluding the NUL terminator.
// We stack allocate the path and want to keep it small enough to reasonably
// fit on any particular thread's stack (which may be as small as 64KB and we
// don't know who may be in the call stack above us).
//
// PATH_MAX is linux-only but _sometimes_ available on other platforms we use
// this code path for. Only when it's available it may indicate the PATH_MAX
// with _or_ without the NUL terminator. If we guess too large here the platform
// will fail as it scans the path and if we guess too small then users may want
// to re-evaluate their usage of the filesystem.
//
// MAX_PATH is 260 but most systems nowadays have long paths enabled and we
// don't want to limit ourselves to that.
#define IREE_MAX_PATH 2047
#endif // !IREE_MAX_PATH

//===----------------------------------------------------------------------===//
// Statistics/reporting
//===----------------------------------------------------------------------===//

#if !defined(IREE_STATISTICS_ENABLE)
// Conditionally enables programmatic access to aggregate statistics. When
// enabled statistics requires additional per-operation logic and per-resource
// state that can bloat otherwise minimal structures. Shared resources may also
// require synchronization where there otherwise would not be any.

#if !defined(IREE_STATISTICS_ENABLE)
#define IREE_STATISTICS_ENABLE 1
#endif // !IREE_STATISTICS_ENABLE

Expand All @@ -173,6 +190,7 @@ typedef IREE_DEVICE_SIZE_T iree_device_size_t;
// Specify a custom header with `-DIREE_TRACING_PROVIDER_H="my_provider.h"`.
// Specify a dependency with `-DIREE_TRACING_PROVIDER=my_provider_target`.

#if !defined(IREE_TRACING_MODE)
// Set IREE_TRACING_FEATURES based on IREE_TRACING_MODE if the user hasn't
// overridden it with more specific settings.
//
Expand All @@ -181,7 +199,6 @@ typedef IREE_DEVICE_SIZE_T iree_device_size_t;
// IREE_TRACING_MODE = 2: same as 1 with added allocation tracking
// IREE_TRACING_MODE = 3: same as 2 with callstacks for allocations
// IREE_TRACING_MODE = 4: same as 3 with callstacks for all instrumentation
#if !defined(IREE_TRACING_MODE)
#define IREE_TRACING_MODE 0
#endif // !IREE_TRACING_MODE

Expand Down
2 changes: 2 additions & 0 deletions runtime/src/iree/base/internal/file_io.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ void iree_file_contents_free(iree_file_contents_t* contents);

typedef enum iree_file_read_flag_bits_t {
IREE_FILE_READ_FLAG_PRELOAD = (1u << 0),
// TODO(benvanik): drop this (and possibly all file utilities) in favor of
// iree_io_file_handle_t + iree_io_file_map_view.
IREE_FILE_READ_FLAG_MMAP = (1u << 1),
IREE_FILE_READ_FLAG_DEFAULT = IREE_FILE_READ_FLAG_PRELOAD,
} iree_file_read_flags_t;
Expand Down
283 changes: 283 additions & 0 deletions runtime/src/iree/io/file_handle.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@
#if IREE_FILE_IO_ENABLE
#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
Expand Down Expand Up @@ -160,6 +162,287 @@ iree_io_file_handle_flush(iree_io_file_handle_t* handle) {
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, bool open_existing,
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 = open_existing ? OPEN_EXISTING : CREATE_ALWAYS;

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;
}

// 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 (!open_existing) {
// 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();
}

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, bool open_existing,
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 (!open_existing) {
// 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 (!open_existing) {
// 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();
}

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, bool open_existing,
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, open_existing, 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, 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, path, /*open_existing=*/false, 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, /*open_existing=*/true, 0ull, host_allocator, out_handle);
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");
}

#endif // IREE_FILE_IO_ENABLE

//===----------------------------------------------------------------------===//
// iree_io_file_mapping_t support
//===----------------------------------------------------------------------===//
Expand Down
Loading

0 comments on commit ce65948

Please sign in to comment.