// ane_runtime.h — Reusable ANE in-memory compile/load/eval wrapper // Uses _ANEInMemoryModel via private AppleNeuralEngine.framework #pragma once #import #import #import #import #import typedef struct { id model; // _ANEInMemoryModel IOSurfaceRef *ioInputs; IOSurfaceRef *ioOutputs; id request; // _ANERequest NSString *tmpDir; int nInputs, nOutputs; size_t *inputBytes; size_t *outputBytes; } ANEKernel; static Class g_ANEDesc, g_ANEInMem, g_ANEReq, g_ANEIO; static bool g_ane_ok = false; // true only when all private classes loaded successfully static void ane_init(void) { // MED-06: dispatch_once is Apple's canonical thread-safe one-time init pattern. // It provides a full memory barrier and is lock-free after the first call. // Replaces manual g_ane_loaded bool guard which had a Check-Then-Act race. static dispatch_once_t ane_once; dispatch_once(&ane_once, ^{ void *handle = dlopen( "/System/Library/PrivateFrameworks/AppleNeuralEngine.framework/AppleNeuralEngine", RTLD_NOW); if (!handle) { fprintf(stderr, "ANE: dlopen failed: %s\n", dlerror()); return; } g_ANEDesc = NSClassFromString(@"_ANEInMemoryModelDescriptor"); g_ANEInMem = NSClassFromString(@"_ANEInMemoryModel"); g_ANEReq = NSClassFromString(@"_ANERequest"); g_ANEIO = NSClassFromString(@"_ANEIOSurfaceObject"); if (!g_ANEDesc || !g_ANEInMem || !g_ANEReq || !g_ANEIO) { fprintf(stderr, "ANE: Private classes not found (macOS version mismatch?)\n"); return; } g_ane_ok = true; // dispatch_once guarantees memory barrier before completion }); } static IOSurfaceRef ane_create_surface(size_t bytes) { return IOSurfaceCreate((__bridge CFDictionaryRef)@{ (id)kIOSurfaceWidth: @(bytes), (id)kIOSurfaceHeight: @1, (id)kIOSurfaceBytesPerElement: @1, (id)kIOSurfaceBytesPerRow: @(bytes), (id)kIOSurfaceAllocSize: @(bytes), (id)kIOSurfacePixelFormat: @0 }); } // Compile a MIL graph with weight blob into an ANE kernel. // milText: NSData of MIL text // weightData: NSData of raw weight blob (can be nil) // inputSizes/outputSizes: arrays of byte sizes for each I/O tensor static ANEKernel *ane_compile(NSData *milText, NSData *weightData, int nInputs, size_t *inputSizes, int nOutputs, size_t *outputSizes) { ane_init(); if (!g_ane_ok) { fprintf(stderr, "ANE: not available\n"); return NULL; } // CRIT-01/02 NSError *e = nil; NSDictionary *wdict = nil; if (weightData) { wdict = @{@"@model_path/weights/weight.bin": @{@"offset": @0, @"data": weightData}}; } id desc = ((id(*)(Class,SEL,id,id,id))objc_msgSend)( g_ANEDesc, @selector(modelWithMILText:weights:optionsPlist:), milText, wdict, nil); if (!desc) return NULL; id mdl = ((id(*)(Class,SEL,id))objc_msgSend)( g_ANEInMem, @selector(inMemoryModelWithDescriptor:), desc); if (!mdl) { fprintf(stderr, "ANE: inMemoryModel allocation failed\n"); return NULL; } // CRIT-02 // Pre-populate temp dir with MIL + weights id hx = ((id(*)(id,SEL))objc_msgSend)(mdl, @selector(hexStringIdentifier)); // MED-02: pid + atomic sequence counter make the directory unique per process and // per call, preventing TOCTOU conflicts when two instances compile the same model. static int ane_compile_seq = 0; int seq = __sync_fetch_and_add(&ane_compile_seq, 1); // atomic, consistent with g_compile_count NSString *td = [NSTemporaryDirectory() stringByAppendingPathComponent: [NSString stringWithFormat:@"ANE_%d_%d_%@", getpid(), seq, hx]]; NSFileManager *fm = [NSFileManager defaultManager]; [fm createDirectoryAtPath:[td stringByAppendingPathComponent:@"weights"] withIntermediateDirectories:YES attributes:nil error:nil]; [milText writeToFile:[td stringByAppendingPathComponent:@"model.mil"] atomically:YES]; if (weightData) [weightData writeToFile:[td stringByAppendingPathComponent:@"weights/weight.bin"] atomically:YES]; if (!((BOOL(*)(id,SEL,unsigned int,id,NSError**))objc_msgSend)( mdl, @selector(compileWithQoS:options:error:), 21, @{}, &e)) { fprintf(stderr, "ANE compile failed: %s\n", [[e description] UTF8String]); [fm removeItemAtPath:td error:nil]; return NULL; } if (!((BOOL(*)(id,SEL,unsigned int,id,NSError**))objc_msgSend)( mdl, @selector(loadWithQoS:options:error:), 21, @{}, &e)) { fprintf(stderr, "ANE load failed: %s\n", [[e description] UTF8String]); [fm removeItemAtPath:td error:nil]; return NULL; } ANEKernel *k = calloc(1, sizeof(ANEKernel)); k->model = mdl; k->tmpDir = td; k->nInputs = nInputs; k->nOutputs = nOutputs; k->inputBytes = malloc(nInputs * sizeof(size_t)); k->outputBytes = malloc(nOutputs * sizeof(size_t)); memcpy(k->inputBytes, inputSizes, nInputs * sizeof(size_t)); memcpy(k->outputBytes, outputSizes, nOutputs * sizeof(size_t)); // Create IOSurfaces k->ioInputs = malloc(nInputs * sizeof(IOSurfaceRef)); k->ioOutputs = malloc(nOutputs * sizeof(IOSurfaceRef)); for (int i = 0; i < nInputs; i++) k->ioInputs[i] = ane_create_surface(inputSizes[i]); for (int i = 0; i < nOutputs; i++) k->ioOutputs[i] = ane_create_surface(outputSizes[i]); // Build request NSMutableArray *wIns = [NSMutableArray arrayWithCapacity:nInputs]; NSMutableArray *iIdx = [NSMutableArray arrayWithCapacity:nInputs]; for (int i = 0; i < nInputs; i++) { [wIns addObject:((id(*)(Class,SEL,IOSurfaceRef))objc_msgSend)( g_ANEIO, @selector(objectWithIOSurface:), k->ioInputs[i])]; [iIdx addObject:@(i)]; } NSMutableArray *wOuts = [NSMutableArray arrayWithCapacity:nOutputs]; NSMutableArray *oIdx = [NSMutableArray arrayWithCapacity:nOutputs]; for (int i = 0; i < nOutputs; i++) { [wOuts addObject:((id(*)(Class,SEL,IOSurfaceRef))objc_msgSend)( g_ANEIO, @selector(objectWithIOSurface:), k->ioOutputs[i])]; [oIdx addObject:@(i)]; } k->request = ((id(*)(Class,SEL,id,id,id,id,id,id,id))objc_msgSend)( g_ANEReq, @selector(requestWithInputs:inputIndices:outputs:outputIndices:weightsBuffer:perfStats:procedureIndex:), wIns, iIdx, wOuts, oIdx, nil, nil, @0); return k; } static void ane_write_input(ANEKernel *k, int idx, const void *data, size_t bytes) { if (IOSurfaceLock(k->ioInputs[idx], 0, NULL) != kIOReturnSuccess) { // MED-01 fprintf(stderr, "IOSurfaceLock(write) failed — surface write skipped\n"); return; } memcpy(IOSurfaceGetBaseAddress(k->ioInputs[idx]), data, bytes); IOSurfaceUnlock(k->ioInputs[idx], 0, NULL); } static void ane_read_output(ANEKernel *k, int idx, void *data, size_t bytes) { if (IOSurfaceLock(k->ioOutputs[idx], kIOSurfaceLockReadOnly, NULL) != kIOReturnSuccess) { // MED-01 fprintf(stderr, "IOSurfaceLock(read) failed — output read skipped\n"); return; } memcpy(data, IOSurfaceGetBaseAddress(k->ioOutputs[idx]), bytes); IOSurfaceUnlock(k->ioOutputs[idx], kIOSurfaceLockReadOnly, NULL); } static bool ane_eval(ANEKernel *k) { NSError *e = nil; return ((BOOL(*)(id,SEL,unsigned int,id,id,NSError**))objc_msgSend)( k->model, @selector(evaluateWithQoS:options:request:error:), 21, @{}, k->request, &e); } static void ane_free(ANEKernel *k) { if (!k) return; NSError *e = nil; ((BOOL(*)(id,SEL,unsigned int,NSError**))objc_msgSend)( k->model, @selector(unloadWithQoS:error:), 21, &e); for (int i = 0; i < k->nInputs; i++) CFRelease(k->ioInputs[i]); for (int i = 0; i < k->nOutputs; i++) CFRelease(k->ioOutputs[i]); [[NSFileManager defaultManager] removeItemAtPath:k->tmpDir error:nil]; free(k->ioInputs); free(k->ioOutputs); free(k->inputBytes); free(k->outputBytes); free(k); }