Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding iree_io_file_handle_create/iree_io_file_handle_open. #19510

Merged
merged 2 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)) {
benvanik marked this conversation as resolved.
Show resolved Hide resolved
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);
benvanik marked this conversation as resolved.
Show resolved Hide resolved

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
Loading