#import #import #import #import #import enum { kMaxHits = 256, kThreadsPerGroup = 256, kParamsSize = 104 }; typedef struct { uint64_t index; uint64_t key; uint64_t plain; } Hit; static void fail(const char *msg) { fprintf(stderr, "%s\n", msg); exit(2); } static uint64_t parse_hex_u64(const char *hex) { const char *p = hex; if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) p += 2; return strtoull(p, NULL, 16); } static NSDictionary *parse_opts(int argc, const char **argv) { NSMutableDictionary *opts = [NSMutableDictionary dictionary]; for (int i = 2; i + 1 < argc; i += 2) { if (strncmp(argv[i], "--", 2) != 0) { i -= 1; continue; } opts[@(argv[i] + 2)] = @(argv[i + 1]); } return opts; } static void json_escape(const char *src, char *dst, size_t dstsz) { size_t o = 0; for (size_t i = 0; src[i] && o + 2 < dstsz; i++) { unsigned char c = (unsigned char)src[i]; if (c == '"' || c == '\\') { dst[o++] = '\\'; dst[o++] = (char)c; } else if (c < 32) { dst[o++] = '?'; } else { dst[o++] = (char)c; } } dst[o] = 0; } static void write_params(void *dst, uint64_t start, uint32_t key_len, uint32_t charset_len, uint32_t pad_byte, uint32_t fill_count, uint32_t batch_count, uint64_t target, const uint64_t *fills, int nfills) { uint8_t *p = dst; memcpy(p, &start, 8); p += 8; memcpy(p, &key_len, 4); p += 4; memcpy(p, &charset_len, 4); p += 4; memcpy(p, &pad_byte, 4); p += 4; memcpy(p, &fill_count, 4); p += 4; memcpy(p, &batch_count, 4); p += 4; uint32_t z = 0; memcpy(p, &z, 4); p += 4; memcpy(p, &target, 8); p += 8; for (int i = 0; i < 8; i++) { uint64_t f = i < nfills ? fills[i] : 0; memcpy(p, &f, 8); p += 8; } } static NSArray> *all_devices(void) { NSArray> *devs = MTLCopyAllDevices(); if (devs && devs.count > 0) { return devs; } id def = MTLCreateSystemDefaultDevice(); if (!def) { return @[]; } return @[def]; } static id pick_device(NSDictionary *opts) { NSArray> *devs = all_devices(); if (devs.count == 0) { fail("no Metal GPU"); } int idx = opts[@"device"] ? opts[@"device"].intValue : 0; if (idx < 0 || idx >= (int)devs.count) { idx = 0; } return devs[(NSUInteger)idx]; } static void run_devices(void) { NSArray> *devs = all_devices(); printf("["); for (NSUInteger i = 0; i < devs.count; i++) { char name[512]; json_escape(devs[i].name.UTF8String ?: "Metal GPU", name, sizeof(name)); uint64_t mem = 0; if ([devs[i] respondsToSelector:@selector(recommendedMaxWorkingSetSize)]) { mem = (uint64_t)devs[i].recommendedMaxWorkingSetSize; } printf("%s{\"id\":%lu,\"name\":\"%s\",\"type\":\"metal\",\"memory\":%llu}", i ? "," : "", (unsigned long)i, name, (unsigned long long)mem); } printf("]\n"); } static id compile_library(id device, NSString *path) { NSError *err = nil; NSString *src = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&err]; if (!src) { fprintf(stderr, "cannot read shader: %s\n", err.localizedDescription.UTF8String); exit(2); } MTLCompileOptions *copts = [[MTLCompileOptions alloc] init]; id lib = [device newLibraryWithSource:src options:copts error:&err]; if (!lib) { fprintf(stderr, "Metal compile failed:\n%s\n", err.localizedDescription.UTF8String); exit(2); } return lib; } static void run_selftest(id device, id lib) { NSError *err = nil; id fn = [lib newFunctionWithName:@"des_known_answer"]; id pipe = [device newComputePipelineStateWithFunction:fn error:&err]; if (!pipe) fail("failed to build DES known-answer pipeline"); id queue = [device newCommandQueue]; id out = [device newBufferWithLength:16 options:MTLResourceStorageModeShared]; id cmd = [queue commandBuffer]; id enc = [cmd computeCommandEncoder]; [enc setComputePipelineState:pipe]; [enc setBuffer:out offset:0 atIndex:0]; [enc dispatchThreads:MTLSizeMake(1, 1, 1) threadsPerThreadgroup:MTLSizeMake(1, 1, 1)]; [enc endEncoding]; [cmd commit]; [cmd waitUntilCompleted]; uint64_t *words = out.contents; uint64_t expected = 0x85E813540F0AB405ULL; uint64_t plain = 0x0123456789ABCDEFULL; if (words[0] != expected || words[1] != plain) { fprintf(stderr, "DES self-test failed ct=%016llx expected=%016llx roundtrip=%016llx\n", words[0], expected, words[1]); exit(2); } fprintf(stderr, "metal_selftest ok gpu=%s\n", device.name.UTF8String); } static void run_brute(id device, id lib, NSDictionary *opts) { NSError *err = nil; id fn = [lib newFunctionWithName:@"des_brute"]; id pipe = [device newComputePipelineStateWithFunction:fn error:&err]; if (!pipe) fail("failed to build DES brute pipeline"); id queue = [device newCommandQueue]; NSString *keyLenStr = opts[@"key-len"] ?: @"0"; NSString *charsetStr = opts[@"charset"] ?: @""; NSString *padStr = opts[@"pad"] ?: @"00"; NSString *targetStr = opts[@"target"] ?: @"0"; NSString *startStr = opts[@"start"] ?: @"0"; NSString *countStr = opts[@"count"] ?: @"0"; NSString *batchStr = opts[@"batch"] ?: @"16777216"; uint32_t key_len = (uint32_t)keyLenStr.intValue; const char *cs = charsetStr.UTF8String; uint32_t charset_len = (uint32_t)strlen(cs); uint32_t pad_byte = (uint32_t)(parse_hex_u64(padStr.UTF8String) & 0xFF); uint64_t target = parse_hex_u64(targetStr.UTF8String); uint64_t start = strtoull(startStr.UTF8String, NULL, 10); uint64_t count = strtoull(countStr.UTF8String, NULL, 10); uint64_t batch = strtoull(batchStr.UTF8String, NULL, 10); if (key_len == 0 || charset_len == 0 || count == 0) fail("brute requires --key-len --charset --count"); uint64_t fills[8] = {0}; int nfills = 0; NSString *fillsStr = opts[@"fills"] ?: @"0000000000000000"; NSArray *parts = [fillsStr componentsSeparatedByString:@","]; for (NSString *part in parts) { if (nfills >= 8) break; fills[nfills++] = parse_hex_u64(part.UTF8String); } id paramsBuf = [device newBufferWithLength:kParamsSize options:MTLResourceStorageModeShared]; id charsetBuf = [device newBufferWithBytes:cs length:charset_len options:MTLResourceStorageModeShared]; id hitsBuf = [device newBufferWithLength:kMaxHits * sizeof(Hit) options:MTLResourceStorageModeShared]; id countBuf = [device newBufferWithLength:4 options:MTLResourceStorageModeShared]; uint64_t done = 0; CFAbsoluteTime t0 = CFAbsoluteTimeGetCurrent(); while (done < count) { uint64_t n = batch < (count - done) ? batch : (count - done); *(uint32_t *)countBuf.contents = 0; write_params(paramsBuf.contents, start + done, key_len, charset_len, pad_byte, (uint32_t)nfills, (uint32_t)n, target, fills, nfills); id cmd = [queue commandBuffer]; id enc = [cmd computeCommandEncoder]; [enc setComputePipelineState:pipe]; [enc setBuffer:paramsBuf offset:0 atIndex:0]; [enc setBuffer:charsetBuf offset:0 atIndex:1]; [enc setBuffer:hitsBuf offset:0 atIndex:2]; [enc setBuffer:countBuf offset:0 atIndex:3]; NSUInteger groups = (NSUInteger)((n + kThreadsPerGroup - 1) / kThreadsPerGroup); [enc dispatchThreadgroups:MTLSizeMake(groups, 1, 1) threadsPerThreadgroup:MTLSizeMake(kThreadsPerGroup, 1, 1)]; [enc endEncoding]; [cmd commit]; [cmd waitUntilCompleted]; uint32_t hitn = *(uint32_t *)countBuf.contents; if (hitn > kMaxHits) hitn = kMaxHits; Hit *hits = hitsBuf.contents; for (uint32_t i = 0; i < hitn; i++) { printf("{\"index\":%llu,\"key_hex\":\"%016llx\",\"plain_hex\":\"%016llx\"}\n", hits[i].index, hits[i].key, hits[i].plain); fflush(stdout); } done += n; double elapsed = CFAbsoluteTimeGetCurrent() - t0; if (elapsed < 1e-6) elapsed = 1e-6; fprintf(stderr, "gpu %llu/%llu %.0f keys/s\n", done, count, done / elapsed); } } int main(int argc, const char **argv) { if (argc < 2) { fail("usage: med_gpu devices | selftest --shader PATH | brute --shader PATH [options]"); } @autoreleasepool { if (strcmp(argv[1], "devices") == 0) { run_devices(); return 0; } NSDictionary *opts = parse_opts(argc, argv); NSString *shader = opts[@"shader"]; if (!shader) fail("missing --shader"); id device = pick_device(opts); id lib = compile_library(device, shader); if (strcmp(argv[1], "selftest") == 0) { run_selftest(device, lib); } else if (strcmp(argv[1], "brute") == 0) { run_selftest(device, lib); run_brute(device, lib, opts); } else { fail("unknown command"); } } return 0; }