Skip to content

Commit 78a1494

Browse files
committed
up
1 parent e4bd6db commit 78a1494

4 files changed

Lines changed: 23 additions & 90 deletions

File tree

extension/apple/ExecuTorch/Exported/ExecuTorchBackendOption.mm

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,18 @@ - (BOOL)isEqual:(id)object {
102102
return _boolValue == other.boolValue;
103103
case ExecuTorchBackendOptionTypeInteger:
104104
return _intValue == other.intValue;
105-
case ExecuTorchBackendOptionTypeString:
105+
case ExecuTorchBackendOptionTypeString: {
106106
// Both are non-null when type is String (init enforces it), but be
107107
// defensive in case of subclass/manual misuse.
108-
if (_stringValue == other.stringValue) {
108+
NSString *otherString = other.stringValue;
109+
if (_stringValue == otherString) {
109110
return YES;
110111
}
111-
return [_stringValue isEqualToString:other.stringValue];
112+
if (_stringValue == nil || otherString == nil) {
113+
return NO;
114+
}
115+
return [_stringValue isEqualToString:otherString];
116+
}
112117
}
113118
return NO;
114119
}

extension/apple/ExecuTorch/Exported/ExecuTorchModule.mm

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -259,9 +259,8 @@ @implementation ExecuTorchModule {
259259
//
260260
// INVARIANT: this ivar is only ever overwritten with another non-nil
261261
// BackendOptionsMap, and never reset to nil while `_module` is alive.
262-
// Resetting it to nil would release the C++ map underneath `_module`'s
263-
// borrowed pointer and re-introduce the dangling-pointer bug class this
264-
// refactor was created to eliminate.
262+
// Resetting to nil would release the C++ map underneath `_module`'s
263+
// borrowed pointer, causing a dangling-pointer crash.
265264
//
266265
// THREAD SAFETY: like the rest of `ExecuTorchModule`, write access here
267266
// is not thread-safe. The ARC retain/release on assignment is non-atomic
@@ -376,11 +375,13 @@ - (BOOL)loadMethod:(NSString *)methodName
376375
options:(ExecuTorchBackendOptionsMap *)options
377376
error:(NSError **)error {
378377
NSParameterAssert(options);
379-
// Note: unlike -loadWithOptions:..., this path passes the C++ map pointer
380-
// synchronously into Module::load_method, which uses it within the same
381-
// call stack and does not store it. We therefore do NOT need to retain
382-
// `options` past this method's return — `options` is held alive by ARC
383-
// via the local parameter for the duration of the call.
378+
// Defensively retain the options for the Module's lifetime, matching
379+
// -loadWithOptions:. Today Module::load_method consumes the pointer
380+
// synchronously and does not cache it, so retention is not strictly
381+
// required for this call. We retain anyway so that a future change to
382+
// the C++ API that caches the pointer cannot silently introduce a
383+
// dangling-pointer bug in this wrapper.
384+
_loadedBackendOptions = options;
384385
const auto errorCode = _module->load_method(methodName.UTF8String,
385386
/*planned_memory=*/nullptr,
386387
/*event_tracer=*/nullptr,

extension/apple/ExecuTorch/__tests__/ModuleTest.swift

Lines changed: 5 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class ModuleTest: XCTestCase {
2828
if let path = resourceBundle.path(forResource: name, ofType: type) {
2929
return path
3030
}
31-
let message = "\(name).\(type) not bundled — run extension/apple/ExecuTorch/__tests__/resources/generate_coreml_test_models.py to generate it."
31+
let message = "\(name).\(type) not bundled."
3232
if ProcessInfo.processInfo.environment["CI"] != nil {
3333
// Throw a plain Error (NOT XCTSkip) so the test is reported as failed
3434
// rather than skipped. The thrown error's localizedDescription is the
@@ -263,80 +263,10 @@ class ModuleTest: XCTestCase {
263263
XCTAssertFalse(desc.contains("0x"), "description should not include a pointer: \(desc)")
264264
}
265265

266-
func testLoadWithBackendOptions() throws {
267-
guard let modelPath = resourceBundle.path(forResource: "add", ofType: "pte") else {
268-
XCTFail("Couldn't find the model file")
269-
return
270-
}
271-
let module = Module(filePath: modelPath)
272-
let options = try BackendOptionsMap(options: [
273-
"SomeBackend": [
274-
BackendOption("num_threads", 4),
275-
BackendOption("use_cache", true),
276-
]
277-
])
278-
XCTAssertNoThrow(try module.load(options))
279-
XCTAssertTrue(module.isLoaded())
280-
}
281-
282-
func testLoadWithEmptyBackendOptions() throws {
283-
guard let modelPath = resourceBundle.path(forResource: "add", ofType: "pte") else {
284-
XCTFail("Couldn't find the model file")
285-
return
286-
}
287-
let module = Module(filePath: modelPath)
288-
let options = try BackendOptionsMap(options: [:])
289-
XCTAssertNoThrow(try module.load(options))
290-
XCTAssertTrue(module.isLoaded())
291-
}
292-
293-
func testLoadMethodWithBackendOptions() throws {
294-
guard let modelPath = resourceBundle.path(forResource: "add", ofType: "pte") else {
295-
XCTFail("Couldn't find the model file")
296-
return
297-
}
298-
let module = Module(filePath: modelPath)
299-
let options = try BackendOptionsMap(options: [
300-
"SomeBackend": [
301-
BackendOption("compute_unit", "cpu_and_gpu"),
302-
]
303-
])
304-
XCTAssertNoThrow(try module.load("forward", options: options))
305-
XCTAssertTrue(module.isLoaded("forward"))
306-
}
307-
308-
func testLoadWithBackendOptionsThenExecute() throws {
309-
guard let modelPath = resourceBundle.path(forResource: "add", ofType: "pte") else {
310-
XCTFail("Couldn't find the model file")
311-
return
312-
}
313-
let module = Module(filePath: modelPath)
314-
let options = try BackendOptionsMap(options: [
315-
"SomeBackend": [
316-
BackendOption("num_threads", 4),
317-
]
318-
])
319-
XCTAssertNoThrow(try module.load(options))
320-
321-
let inputs: [Tensor<Float>] = [Tensor([1]), Tensor([1])]
322-
var outputs: [Value]?
323-
XCTAssertNoThrow(outputs = try module.forward(inputs))
324-
XCTAssertEqual(outputs?.first?.tensor(), Tensor([Float(2)]))
325-
}
326-
327-
// Regression test: when load(_:BackendOptionsMap) is followed by a lazy
328-
// load_method (triggered by forward without an explicit load("forward")),
329-
// the C++ LoadBackendOptionsMap held inside the BackendOptionsMap must
330-
// outlive the wrapper call. The Module retains the BackendOptionsMap via
331-
// ARC for exactly that reason. The per-delegate loop in Method::init
332-
// would otherwise dereference a dangling pointer in strcmp and crash
333-
// with EXC_BAD_ACCESS.
334-
//
335-
// The plain add.pte fixture does NOT trigger this because it has zero
336-
// delegates, so the per-delegate loop never executes. We use a
337-
// CoreML-delegated add model (add_coreml.pte, generated at CI time by
338-
// resources/generate_coreml_test_models.py) which has at least one
339-
// delegate.
266+
// Regression test for the lazy load_method path: after load(options),
267+
// forward() triggers load_method which must still see valid backend
268+
// options. Requires a delegated model — the plain add.pte has no
269+
// delegates and so does not exercise the code path.
340270
func testLoadWithBackendOptionsThenExecuteOnCoreMLDelegatedModel() throws {
341271
let modelPath = try requireFixture("add_coreml", ofType: "pte")
342272
let module = Module(filePath: modelPath)

extension/apple/ExecuTorch/__tests__/ObjC/ModuleTestObjC.m

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,7 @@ - (NSBundle *)resourceBundle {
3232
- (nullable NSString *)requireFixture:(NSString *)name ofType:(NSString *)type {
3333
NSString *path = [[self resourceBundle] pathForResource:name ofType:type];
3434
if (path) return path;
35-
NSString *message = [NSString stringWithFormat:
36-
@"%@.%@ not bundled — run "
37-
"extension/apple/ExecuTorch/__tests__/resources/generate_coreml_test_models.py "
38-
"to generate it.", name, type];
35+
NSString *message = [NSString stringWithFormat:@"%@.%@ not bundled.", name, type];
3936
if (NSProcessInfo.processInfo.environment[@"CI"] != nil) {
4037
// CI: hard fail. XCTFail records the failure; we then return nil so the
4138
// caller exits early. Single failure artifact.

0 commit comments

Comments
 (0)