Skip to content
Open
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
6 changes: 4 additions & 2 deletions backends/apple/coreai/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ endif()

enable_language(OBJCXX)

set(_coreai_runtime_sources runtime/coreai_assets.mm)
set(_coreai_runtime_sources runtime/coreai_assets.mm runtime/coreai_storage.mm)

function(coreai_configure_objc_target target)
target_include_directories(
Expand All @@ -29,9 +29,11 @@ if(EXECUTORCH_BUILD_TESTS AND CMAKE_SYSTEM_NAME STREQUAL "Darwin")
add_executable(
coreai_host_test
runtime/test/coreai_host_test.mm runtime/test/coreai_manifest_test.mm
${_coreai_runtime_sources}
runtime/test/coreai_storage_test.mm
runtime/test/coreai_filesystem_fixture.mm ${_coreai_runtime_sources}
)
coreai_configure_objc_target(coreai_host_test)
target_compile_definitions(coreai_host_test PRIVATE COREAI_ASSETS_TESTING=1)
find_library(COREAI_FOUNDATION_FRAMEWORK Foundation REQUIRED)
target_link_libraries(
coreai_host_test PRIVATE executorch GTest::gtest
Expand Down
31 changes: 29 additions & 2 deletions backends/apple/coreai/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,39 @@ architectures=["h17p"])` requests that compiler architecture; omitting the list
lets the compiler emit its supported architectures for the target platform.
These are Core AI architecture names, not CPU names such as `arm64`.

### Asset storage

The assets root must be an absolute path. The application chooses it, should
reserve it for this backend, and is trusted not to rename, replace or modify its
contents while the backend uses it; the backend does not defend against other
processes of the same user rebinding paths. Written files and changed directories
are synced with `fsync`. Atomic publication uses `F_FULLFSYNC` on the temporary
file before rename and on the directory afterward. A failure before rename
preserves the old contents; a failure of the final flush reports an error even
though the new complete contents are already visible. Use a local APFS volume;
unsupported full-device flushes are reported as errors, not downgraded to `fsync`.

Preparing the assets root sets `NSURLIsExcludedFromBackupKey` on it, which covers
everything beneath it. Ancestors are not modified. This is backup exclusion, not
a control for iCloud Drive synchronization.

Newly created roots request
`NSFileProtectionCompleteUntilFirstUserAuthentication`. On iOS, this allows cache
access while the device is locked after its first unlock following a reboot. New
subdirectories and files inherit their parent's protection. Existing cache
contents are not migrated. Directory preparation skips creation when the root
already exists; child directories are opened first and created only when missing.
Directory creation uses default permissions filtered by the process umask, without
an explicit owner-only restriction. Existing permissions are not changed.
Backup exclusion and required durability flushes still apply on reused paths.

## Host Tests

On macOS, `EXECUTORCH_BUILD_COREAI=ON` with `EXECUTORCH_BUILD_TESTS=ON`
registers the standalone `coreai_host_test` executable with CTest. It tests
manifest parsing and AOT asset selection using Foundation, without Swift or
CoreAI SDK calls. The host test target is omitted for Apple cross-builds.
manifest parsing, AOT asset selection, and filesystem storage using Foundation,
without Swift or CoreAI SDK calls. The host test target is omitted for Apple
cross-builds.

CI builds and runs these tests on macOS 27. To use the same configuration from
the repository root:
Expand Down
53 changes: 53 additions & 0 deletions backends/apple/coreai/runtime/coreai_file.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/

#pragma once

#include <unistd.h>
#include <cerrno>

namespace executorch::backends::coreai {

class FileDescriptor {
public:
explicit FileDescriptor(int fd) : fd_(fd) {}
~FileDescriptor() {
reset(-1);
}
FileDescriptor(const FileDescriptor&) = delete;
FileDescriptor& operator=(const FileDescriptor&) = delete;
int get() const {
return fd_;
}
int release() {
const int fd = fd_;
fd_ = -1;
return fd;
}
void reset(int fd) {
if (fd_ >= 0) {
// Retrying close can close a descriptor reused by another thread.
::close(fd_);
}
fd_ = fd;
}

private:
int fd_;
};

template <typename F>
auto retry_eintr(F call) {
decltype(call()) result;
do {
result = call();
} while (result == -1 && errno == EINTR);
return result;
}

} // namespace executorch::backends::coreai
75 changes: 75 additions & 0 deletions backends/apple/coreai/runtime/coreai_storage.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/

#pragma once

#import <Foundation/Foundation.h>
#include <executorch/runtime/core/result.h>
#include <cstddef>

namespace executorch::backends::coreai {

// Resolves the default location without creating or modifying storage.
runtime::Result<NSString*> default_coreai_assets_root();
// Requires an absolute directory path. With create, makes the directory and
// excludes it from backup; otherwise a missing directory is InvalidArgument.
runtime::Result<NSString*> prepare_storage_root(NSString* path, bool create);
runtime::Error ensure_excluded_from_backup(NSURL* directory);

runtime::Error sync_storage_directory(int fd);
struct StorageDirectory {
int fd;
bool created;
};
// Opens one child component without following symlinks. The caller owns fd;
// missing with create=false returns {-1, false}. Does not flush directories.
runtime::Result<StorageDirectory>
open_storage_directory(int parent_fd, NSString* name, bool create);
// Returns an independently owned descriptor for a stable coordination file.
int open_storage_shared_file(int parent_fd, const char* name);
// Creates a new file and missing parents, then syncs the file and its parent.
// On failure, attempts to remove the new file; created parents may remain.
runtime::Error write_storage_file(
int root_fd,
NSString* relative_path,
const void* bytes,
size_t size);
// Atomically replaces name with data and fully flushes the file and renamed
// entry. On error, readers see either the old or the new complete bytes.
runtime::Error publish_storage_data(int root_fd, NSString* name, NSData* data);
// Lists sorted names without changing fd's enumeration offset.
runtime::Result<NSArray<NSString*>*> storage_children(
int fd,
bool skip_invalid_names = false);

enum class StorageOperation {
Write,
SyncFile,
SyncDirectory,
FullSync,
Rename,
BeforeCreateDirectory,
BeforeSDK,
AfterSDK,
BeforeEvict,
AfterEvict,
Remove,
};
#if defined(COREAI_ASSETS_TESTING) && COREAI_ASSETS_TESTING
int storage_fault(StorageOperation operation);
namespace testing {
using StorageFaultCallback = int (*)(StorageOperation);
extern thread_local StorageFaultCallback storage_fault_callback;
} // namespace testing
#else
constexpr int storage_fault(StorageOperation) {
return 0;
}
#endif

} // namespace executorch::backends::coreai
Loading
Loading