Skip to content

Commit e4bd6db

Browse files
committed
up
1 parent 575eab7 commit e4bd6db

13 files changed

Lines changed: 797 additions & 155 deletions

Package.swift

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,26 @@ if FileManager.default.fileExists(atPath: "\(testResourcesDir)/add_coreml.pte")
138138
testResources.append(.copy("resources/add_coreml.pte"))
139139
}
140140

141+
// SwiftPM resources must live under the target's path, so the ObjC test
142+
// target uses symlinks to the canonical resources directory. The symlinks
143+
// themselves are gitignored and (re)created by scripts/build_apple_frameworks.sh
144+
// (CI) and swift_play/run.sh (local).
145+
let objcTestsDir = "extension/apple/ExecuTorch/__tests__/ObjC"
146+
var objcTestResources: [Resource] = []
147+
if FileManager.default.fileExists(atPath: "\(objcTestsDir)/add.pte") {
148+
objcTestResources.append(.copy("add.pte"))
149+
}
150+
if FileManager.default.fileExists(atPath: "\(objcTestsDir)/add_coreml.pte") {
151+
objcTestResources.append(.copy("add_coreml.pte"))
152+
}
153+
154+
let testLinkerSettings: [LinkerSetting] = [
155+
.unsafeFlags([
156+
"-Xlinker", "-force_load",
157+
"-Xlinker", "cmake-out/kernels_optimized.xcframework/macos-arm64/libkernels_optimized_macos.a",
158+
])
159+
]
160+
141161
let package = Package(
142162
name: "executorch",
143163
platforms: [
@@ -153,13 +173,20 @@ let package = Package(
153173
.target(name: "kernels_optimized\(dependencies_suffix)"),
154174
],
155175
path: "extension/apple/ExecuTorch/__tests__",
176+
exclude: ["ObjC", "resources/generate_coreml_test_models.py", "resources/.gitignore"],
156177
resources: testResources,
157-
linkerSettings: [
158-
.unsafeFlags([
159-
"-Xlinker", "-force_load",
160-
"-Xlinker", "cmake-out/kernels_optimized.xcframework/macos-arm64/libkernels_optimized_macos.a",
161-
])
162-
]
178+
linkerSettings: testLinkerSettings
179+
),
180+
.testTarget(
181+
name: "objc_tests",
182+
dependencies: [
183+
.target(name: "executorch\(debug_suffix)"),
184+
.target(name: "kernels_optimized\(dependencies_suffix)"),
185+
],
186+
path: "extension/apple/ExecuTorch/__tests__/ObjC",
187+
exclude: [".gitignore"],
188+
resources: objcTestResources,
189+
linkerSettings: testLinkerSettings
163190
)
164191
]
165192
)

extension/apple/ExecuTorch/Exported/ExecuTorch+Module.swift

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,30 +50,34 @@ public extension MethodMetadata {
5050
public extension Module {
5151
/// Loads the module's program with per-delegate backend options.
5252
///
53+
/// The receiver retains `options` for as long as the underlying program
54+
/// references it (lifetime tracked via ARC).
55+
///
5356
/// - Parameters:
54-
/// - backendOptions: A dictionary mapping backend identifiers (e.g. "CoreMLBackend")
55-
/// to arrays of `BackendOption` objects configuring that backend.
57+
/// - options: A `BackendOptionsMap` built once from a dict of
58+
/// per-backend `BackendOption`s, e.g.
59+
/// `try BackendOptionsMap(["CoreMLBackend": [BackendOption("compute_unit", "cpu_and_gpu")]])`.
5660
/// - verification: The verification level to apply when loading the program.
5761
/// - Throws: An error if loading fails.
5862
func load(
59-
backendOptions: [String: [BackendOption]],
63+
_ options: BackendOptionsMap,
6064
verification: ModuleVerification = .minimal
6165
) throws {
62-
try __load(backendOptions: backendOptions, verification: verification)
66+
try __load(withOptions: options, verification: verification)
6367
}
6468

6569
/// Loads a specific method from the program with per-delegate backend options.
6670
///
6771
/// - Parameters:
6872
/// - method: The name of the method to load.
69-
/// - backendOptions: A dictionary mapping backend identifiers (e.g. "CoreMLBackend")
70-
/// to arrays of `BackendOption` objects configuring that backend.
73+
/// - options: A `BackendOptionsMap` built once from a dict of
74+
/// per-backend `BackendOption`s.
7175
/// - Throws: An error if loading fails.
7276
func load(
7377
_ method: String,
74-
backendOptions: [String: [BackendOption]]
78+
options: BackendOptionsMap
7579
) throws {
76-
try __loadMethod(method, backendOptions: backendOptions)
80+
try __loadMethod(method, options: options)
7781
}
7882
}
7983

extension/apple/ExecuTorch/Exported/ExecuTorch.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
#import "ExecuTorchBackendOption.h"
10+
#import "ExecuTorchBackendOptionsMap.h"
1011
#import "ExecuTorchError.h"
1112
#import "ExecuTorchLog.h"
1213
#import "ExecuTorchModule.h"

extension/apple/ExecuTorch/Exported/ExecuTorchBackendOption.mm

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,69 @@ + (instancetype)optionWithKey:(NSString *)key
6464
return [[self alloc] initWithKey:key stringValue:value];
6565
}
6666

67+
#pragma mark - NSObject
68+
69+
- (NSString *)description {
70+
switch (_type) {
71+
case ExecuTorchBackendOptionTypeBoolean:
72+
return [NSString stringWithFormat:@"<%@ %@=%@ (bool)>",
73+
NSStringFromClass([self class]), _key, _boolValue ? @"true" : @"false"];
74+
case ExecuTorchBackendOptionTypeInteger:
75+
return [NSString stringWithFormat:@"<%@ %@=%ld (int)>",
76+
NSStringFromClass([self class]), _key, (long)_intValue];
77+
case ExecuTorchBackendOptionTypeString:
78+
return [NSString stringWithFormat:@"<%@ %@=%@ (string)>",
79+
NSStringFromClass([self class]), _key,
80+
_stringValue ? [NSString stringWithFormat:@"\"%@\"", _stringValue] : @"(null)"];
81+
}
82+
return [super description];
83+
}
84+
85+
- (NSString *)debugDescription {
86+
return [self description];
87+
}
88+
89+
- (BOOL)isEqual:(id)object {
90+
if (self == object) {
91+
return YES;
92+
}
93+
if (![object isKindOfClass:[ExecuTorchBackendOption class]]) {
94+
return NO;
95+
}
96+
ExecuTorchBackendOption *other = (ExecuTorchBackendOption *)object;
97+
if (_type != other.type || ![_key isEqualToString:other.key]) {
98+
return NO;
99+
}
100+
switch (_type) {
101+
case ExecuTorchBackendOptionTypeBoolean:
102+
return _boolValue == other.boolValue;
103+
case ExecuTorchBackendOptionTypeInteger:
104+
return _intValue == other.intValue;
105+
case ExecuTorchBackendOptionTypeString:
106+
// Both are non-null when type is String (init enforces it), but be
107+
// defensive in case of subclass/manual misuse.
108+
if (_stringValue == other.stringValue) {
109+
return YES;
110+
}
111+
return [_stringValue isEqualToString:other.stringValue];
112+
}
113+
return NO;
114+
}
115+
116+
- (NSUInteger)hash {
117+
NSUInteger h = _key.hash ^ (NSUInteger)_type;
118+
switch (_type) {
119+
case ExecuTorchBackendOptionTypeBoolean:
120+
h ^= (NSUInteger)(_boolValue ? 1 : 0);
121+
break;
122+
case ExecuTorchBackendOptionTypeInteger:
123+
h ^= (NSUInteger)_intValue;
124+
break;
125+
case ExecuTorchBackendOptionTypeString:
126+
h ^= _stringValue.hash;
127+
break;
128+
}
129+
return h;
130+
}
131+
67132
@end
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
#import <Foundation/Foundation.h>
10+
11+
#import "ExecuTorchBackendOption.h"
12+
13+
NS_ASSUME_NONNULL_BEGIN
14+
15+
/**
16+
* An immutable, opaque container for per-delegate load-time configuration,
17+
* built from a dictionary mapping backend identifiers to arrays of
18+
* `ExecuTorchBackendOption` objects.
19+
*
20+
* # Lifetime
21+
*
22+
* Once a `BackendOptionsMap` is passed to a `Module` load call, the `Module`
23+
* **retains** it for as long as the underlying program references it. The
24+
* caller does not need to manage this lifetime manually — ARC handles it.
25+
*
26+
* # Reuse
27+
*
28+
* The same `BackendOptionsMap` instance can be reused across multiple `Module`s
29+
* and across multiple load calls. Build it once, pass it many times.
30+
*
31+
* # Validation
32+
*
33+
* Validation (option-key length, string-value length, integer 32-bit range)
34+
* happens at construction time. If the input dictionary contains an invalid
35+
* entry, the initializer returns `nil` and populates the out-error.
36+
*
37+
* @note The current C++ runtime stores integer option values as 32-bit `int`.
38+
* Passing an integer outside `[INT32_MIN, INT32_MAX]` will cause the
39+
* initializer to fail with `Error::InvalidArgument`.
40+
*/
41+
NS_SWIFT_NAME(BackendOptionsMap)
42+
__attribute__((objc_subclassing_restricted))
43+
@interface ExecuTorchBackendOptionsMap : NSObject
44+
45+
/**
46+
* Creates a backend options map from a dictionary of per-backend options.
47+
*
48+
* @param options A dictionary mapping backend identifiers (e.g. "CoreMLBackend")
49+
* to arrays of `ExecuTorchBackendOption` objects configuring that backend.
50+
* @param error On failure, populated with an `NSError` describing the validation
51+
* problem (e.g. invalid integer range).
52+
* @return A new instance, or `nil` if validation fails.
53+
*/
54+
- (nullable instancetype)initWithOptions:(NSDictionary<NSString *, NSArray<ExecuTorchBackendOption *> *> *)options
55+
error:(NSError **)error
56+
NS_DESIGNATED_INITIALIZER NS_SWIFT_NAME(init(options:));
57+
58+
/**
59+
* Convenience class factory mirroring `-initWithOptions:error:`.
60+
*/
61+
+ (nullable instancetype)mapWithOptions:(NSDictionary<NSString *, NSArray<ExecuTorchBackendOption *> *> *)options
62+
error:(NSError **)error
63+
NS_RETURNS_RETAINED;
64+
65+
/**
66+
* The options the receiver was constructed with, exposed as a deep-immutable
67+
* snapshot dictionary captured at construction time. Useful for debugging and
68+
* round-tripping.
69+
*/
70+
@property (nonatomic, readonly) NSDictionary<NSString *, NSArray<ExecuTorchBackendOption *> *> *options;
71+
72+
+ (instancetype)new NS_UNAVAILABLE;
73+
- (instancetype)init NS_UNAVAILABLE;
74+
75+
@end
76+
77+
NS_ASSUME_NONNULL_END

0 commit comments

Comments
 (0)