// ane_bridge.m — Objective-C implementation of ANE bridge for Python ctypes // Wraps _ANEInMemoryModel private APIs into C-callable functions #import #import #import #import #import #include "ane_bridge.h" // --- Private class references --- static Class g_ANEDesc = nil; static Class g_ANEInMem = nil; static Class g_ANEReq = nil; static Class g_ANEIO = nil; static bool g_initialized = false; static int g_compile_count = 0; // --- Kernel handle struct --- struct ANEKernelHandle { id model; // _ANEInMemoryModel IOSurfaceRef *ioInputs; IOSurfaceRef *ioOutputs; id request; // _ANERequest NSString *tmpDir; int nInputs, nOutputs; size_t *inputBytes; size_t *outputBytes; }; // --- Public API --- int ane_bridge_init(void) { if (g_initialized) return 0; void *handle = dlopen( "/System/Library/PrivateFrameworks/AppleNeuralEngine.framework/AppleNeuralEngine", RTLD_NOW); if (!handle) { fprintf(stderr, "ane_bridge: Failed to load AppleNeuralEngine.framework\n"); return -1; } 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_bridge: Failed to resolve ANE private classes\n"); return -1; } g_initialized = true; g_compile_count = 0; return 0; } static IOSurfaceRef 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 }); } ANEKernelHandle *ane_bridge_compile_multi_weights( const char *mil_text, size_t mil_len, const char **weight_names, const uint8_t **weight_datas, const size_t *weight_lens, int n_weights, int n_inputs, const size_t *input_sizes, int n_outputs, const size_t *output_sizes) { @autoreleasepool { if (!g_initialized) { fprintf(stderr, "ane_bridge: Not initialized\n"); return NULL; } NSData *milData = [NSData dataWithBytes:mil_text length:mil_len]; NSError *e = nil; // Build weight dictionary NSMutableDictionary *wdict = [NSMutableDictionary dictionary]; for (int i = 0; i < n_weights; i++) { NSString *name = [NSString stringWithUTF8String:weight_names[i]]; NSData *data = [NSData dataWithBytes:weight_datas[i] length:weight_lens[i]]; wdict[name] = @{@"offset": @0, @"data": data}; } id desc = ((id(*)(Class,SEL,id,id,id))objc_msgSend)( g_ANEDesc, @selector(modelWithMILText:weights:optionsPlist:), milData, wdict, nil); if (!desc) { fprintf(stderr, "ane_bridge: modelWithMILText failed\n"); return NULL; } id mdl = ((id(*)(Class,SEL,id))objc_msgSend)( g_ANEInMem, @selector(inMemoryModelWithDescriptor:), desc); if (!mdl) { fprintf(stderr, "ane_bridge: inMemoryModelWithDescriptor failed\n"); return NULL; } // Pre-populate temp dir id hx = ((id(*)(id,SEL))objc_msgSend)(mdl, @selector(hexStringIdentifier)); NSString *td = [NSTemporaryDirectory() stringByAppendingPathComponent:hx]; NSFileManager *fm = [NSFileManager defaultManager]; [fm createDirectoryAtPath:[td stringByAppendingPathComponent:@"weights"] withIntermediateDirectories:YES attributes:nil error:nil]; [milData writeToFile:[td stringByAppendingPathComponent:@"model.mil"] atomically:YES]; for (int i = 0; i < n_weights; i++) { NSString *name = [NSString stringWithUTF8String:weight_names[i]]; // Extract filename from path like "@model_path/weights/wq.bin" -> "weights/wq.bin" NSString *relPath = name; if ([name hasPrefix:@"@model_path/"]) { relPath = [name substringFromIndex:12]; } NSString *fullPath = [td stringByAppendingPathComponent:relPath]; NSString *dir = [fullPath stringByDeletingLastPathComponent]; [fm createDirectoryAtPath:dir withIntermediateDirectories:YES attributes:nil error:nil]; NSData *data = [NSData dataWithBytes:weight_datas[i] length:weight_lens[i]]; [data writeToFile:fullPath atomically:YES]; } // Compile if (!((BOOL(*)(id,SEL,unsigned int,id,NSError**))objc_msgSend)( mdl, @selector(compileWithQoS:options:error:), 21, @{}, &e)) { fprintf(stderr, "ane_bridge: ANE compile failed: %s\n", e ? [[e description] UTF8String] : "unknown"); [fm removeItemAtPath:td error:nil]; return NULL; } // Load (with one retry after a brief pause for ANE slot reclamation) BOOL loaded = ((BOOL(*)(id,SEL,unsigned int,id,NSError**))objc_msgSend)( mdl, @selector(loadWithQoS:options:error:), 21, @{}, &e); if (!loaded) { fprintf(stderr, "ane_bridge: ANE load failed (retrying in 100ms): %s\n", e ? [[e description] UTF8String] : "unknown"); usleep(100000); // 100ms e = nil; loaded = ((BOOL(*)(id,SEL,unsigned int,id,NSError**))objc_msgSend)( mdl, @selector(loadWithQoS:options:error:), 21, @{}, &e); } if (!loaded) { fprintf(stderr, "ane_bridge: ANE load failed after retry: %s\n", e ? [[e description] UTF8String] : "unknown"); [fm removeItemAtPath:td error:nil]; return NULL; } g_compile_count++; // Create kernel handle ANEKernelHandle *k = (ANEKernelHandle *)calloc(1, sizeof(ANEKernelHandle)); k->model = mdl; k->tmpDir = td; k->nInputs = n_inputs; k->nOutputs = n_outputs; k->inputBytes = (size_t *)malloc(n_inputs * sizeof(size_t)); k->outputBytes = (size_t *)malloc(n_outputs * sizeof(size_t)); memcpy(k->inputBytes, input_sizes, n_inputs * sizeof(size_t)); memcpy(k->outputBytes, output_sizes, n_outputs * sizeof(size_t)); // Create IOSurfaces k->ioInputs = (IOSurfaceRef *)malloc(n_inputs * sizeof(IOSurfaceRef)); k->ioOutputs = (IOSurfaceRef *)malloc(n_outputs * sizeof(IOSurfaceRef)); for (int i = 0; i < n_inputs; i++) k->ioInputs[i] = create_surface(input_sizes[i]); for (int i = 0; i < n_outputs; i++) k->ioOutputs[i] = create_surface(output_sizes[i]); // Build request NSMutableArray *wIns = [NSMutableArray arrayWithCapacity:n_inputs]; NSMutableArray *iIdx = [NSMutableArray arrayWithCapacity:n_inputs]; for (int i = 0; i < n_inputs; i++) { [wIns addObject:((id(*)(Class,SEL,IOSurfaceRef))objc_msgSend)( g_ANEIO, @selector(objectWithIOSurface:), k->ioInputs[i])]; [iIdx addObject:@(i)]; } NSMutableArray *wOuts = [NSMutableArray arrayWithCapacity:n_outputs]; NSMutableArray *oIdx = [NSMutableArray arrayWithCapacity:n_outputs]; for (int i = 0; i < n_outputs; 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; } } ANEKernelHandle *ane_bridge_compile(const char *mil_text, size_t mil_len, const uint8_t *weight_data, size_t weight_len, int n_inputs, const size_t *input_sizes, int n_outputs, const size_t *output_sizes) { if (weight_data && weight_len > 0) { const char *name = "@model_path/weights/weight.bin"; return ane_bridge_compile_multi_weights( mil_text, mil_len, &name, &weight_data, &weight_len, 1, n_inputs, input_sizes, n_outputs, output_sizes); } else { return ane_bridge_compile_multi_weights( mil_text, mil_len, NULL, NULL, NULL, 0, n_inputs, input_sizes, n_outputs, output_sizes); } } bool ane_bridge_eval(ANEKernelHandle *kernel) { @autoreleasepool { if (!kernel || !kernel->model) { fprintf(stderr, "ane_bridge: eval called with null kernel/model\n"); return false; } if (!kernel->request) { fprintf(stderr, "ane_bridge: eval called with null request\n"); return false; } NSError *e = nil; BOOL ok = ((BOOL(*)(id,SEL,unsigned int,id,id,NSError**))objc_msgSend)( kernel->model, @selector(evaluateWithQoS:options:request:error:), 21, @{}, kernel->request, &e); if (!ok) { fprintf(stderr, "ane_bridge: eval failed: %s\n", e ? [[e description] UTF8String] : "unknown error (no NSError)"); } return ok; } } void ane_bridge_write_input(ANEKernelHandle *kernel, int idx, const void *data, size_t bytes) { if (!kernel || idx < 0 || idx >= kernel->nInputs) return; IOSurfaceLock(kernel->ioInputs[idx], 0, NULL); memcpy(IOSurfaceGetBaseAddress(kernel->ioInputs[idx]), data, bytes); IOSurfaceUnlock(kernel->ioInputs[idx], 0, NULL); } void ane_bridge_read_output(ANEKernelHandle *kernel, int idx, void *data, size_t bytes) { if (!kernel || idx < 0 || idx >= kernel->nOutputs) return; IOSurfaceLock(kernel->ioOutputs[idx], kIOSurfaceLockReadOnly, NULL); memcpy(data, IOSurfaceGetBaseAddress(kernel->ioOutputs[idx]), bytes); IOSurfaceUnlock(kernel->ioOutputs[idx], kIOSurfaceLockReadOnly, NULL); } void ane_bridge_free(ANEKernelHandle *kernel) { @autoreleasepool { if (!kernel) return; NSError *e = nil; if (kernel->model) { ((BOOL(*)(id,SEL,unsigned int,NSError**))objc_msgSend)( kernel->model, @selector(unloadWithQoS:error:), 21, &e); } for (int i = 0; i < kernel->nInputs; i++) if (kernel->ioInputs[i]) CFRelease(kernel->ioInputs[i]); for (int i = 0; i < kernel->nOutputs; i++) if (kernel->ioOutputs[i]) CFRelease(kernel->ioOutputs[i]); if (kernel->tmpDir) { [[NSFileManager defaultManager] removeItemAtPath:kernel->tmpDir error:nil]; } free(kernel->ioInputs); free(kernel->ioOutputs); free(kernel->inputBytes); free(kernel->outputBytes); // Explicitly nil Objective-C objects to trigger ARC release before freeing struct kernel->model = nil; kernel->request = nil; kernel->tmpDir = nil; free(kernel); } } int ane_bridge_get_compile_count(void) { return g_compile_count; } void ane_bridge_reset_compile_count(void) { g_compile_count = 0; } uint8_t *ane_bridge_build_weight_blob(const float *src, int rows, int cols, size_t *out_len) { int wsize = rows * cols * 2; // fp16 int total = 128 + wsize; uint8_t *buf = (uint8_t *)calloc(total, 1); // ANE blob header buf[0] = 0x01; buf[4] = 0x02; buf[64] = 0xEF; buf[65] = 0xBE; buf[66] = 0xAD; buf[67] = 0xDE; buf[68] = 0x01; *(uint32_t*)(buf + 72) = wsize; *(uint32_t*)(buf + 80) = 128; // Convert float32 -> float16 _Float16 *fp16 = (_Float16 *)(buf + 128); for (int i = 0; i < rows * cols; i++) { fp16[i] = (_Float16)src[i]; } *out_len = total; return buf; } uint8_t *ane_bridge_build_weight_blob_transposed(const float *src, int rows, int cols, size_t *out_len) { int wsize = rows * cols * 2; int total = 128 + wsize; uint8_t *buf = (uint8_t *)calloc(total, 1); buf[0] = 0x01; buf[4] = 0x02; buf[64] = 0xEF; buf[65] = 0xBE; buf[66] = 0xAD; buf[67] = 0xDE; buf[68] = 0x01; *(uint32_t*)(buf + 72) = wsize; *(uint32_t*)(buf + 80) = 128; _Float16 *fp16 = (_Float16 *)(buf + 128); for (int i = 0; i < rows; i++) for (int j = 0; j < cols; j++) fp16[j * rows + i] = (_Float16)src[i * cols + j]; *out_len = total; return buf; } void ane_bridge_free_blob(void *ptr) { free(ptr); }