MVS'te 19 Parmak İzi Tuttu, Sıfır Yine de Yanlıştı
Platformlar arası bir determinizm kontrolü, iç durumu bozuk bir build'de de geçebilir. FlyBatch'in 19 golden parmak izi MVS 3.8j'de eşleşirken kernel'in sıfır sabiti aslında 1e-318 civarında bir subnormal'di. Aşağıda parmak izinin neyi hash'lediği, hatayı neyin yakaladığı ve arkasındaki sekiz baytlık kopya var.
FlyBatch'in CRC-32 parmak izi neyi kapsıyor
27 Eylül 2026'ya kadar adı ONFLY olan FlyBatch, 501 nöronluk bir meyve sineği beslenme devresini tek bir C89 kaynağından çalıştırıyor: x86-64'te, QEMU altında Linux s390x'te ve Hercules altında MVS 3.8j'de. Kanıtlaması gereken şey, aynı isteğin üç platformda da aynı cevabı vermesi. Kanıt da bir parmak izi (fingerprint): cevabın CRC-32'si, proje spesifikasyonunda IR-COM-05 gereksinimi olarak tanımlı.
Hash'lenen baytlar şunlar: ağ payload'unun CRC'si, isteğin sayısal alanları, dönüş kodu, çıktı ve adım sayıları, ardından her okuma nöronu için kimlik, ilk spike gecikmesi ve spike sayısı. Her alan big-endian 32 bit tamsayı. Metin alanları bilerek dışarıda bırakılmış; böylece parmak izi EBCDIC ve ASCII makinelerde aynı çıkıyor.
Bu alanların hepsi tamsayı, yani birebir karşılaştırılabiliyor. Membran ve sinaps değerleri ise binary64 ve parmak izi bunların hiçbirini görmüyor.
19.909 satırın 200'ü, hepsi sessiz istekte
Hata, projeye chunk stream eklenince ortaya çıktı. MVS job'u çalışırken ilerlemesini host'a kart delici üzerinden basıyor; bunun içinde her okuma nöronunun durumu binary64 bit deseni olarak yer alıyor, ve bu akış x86'nınkiyle satır satır karşılaştırılabiliyor. CRLF, LF'ye çevrildikten sonra 19.909 satırın 200'ü farklı çıktı. Hepsi G-15'e, yani hiçbir şey olmaması gereken rate 0 isteğine ait durum satırlarıydı (VL-118).
x86 her chunk'ta iki okuma nöronu için de tam +0.0 yazdı ve Python oracle ile uyuştu. MVS ise 1e-318 mertebesinde subnormal değerler yazdı: ilk 34 chunk boyunca artıp kalan 166 chunk'ta bit düzeyinde sabit kaldılar (VL-119). Uyarı da sinaptik girdi de olmadığında spesifikasyondaki zaman adımı her adımda sıfır veriyor; doğru olan x86'ydı.
Tek bir yardımcı fonksiyon, kernel'e dört giriş
Motorun kendi float birimlerine bağlanan bir probe, ilk satırında FPP ZERO 00000000000AE782 bastı. +0.0 döndürmesi gereken onffzer() fonksiyonu bunu yapmıyordu, üstelik C kaynağı doğruydu (VL-122). Motor her membran değerini, her sinaps değerini ve ring buffer'ın her girdisini bu fonksiyonla başlatıyor, kullanılan her ring slot'unu bununla temizliyor, spike atan nöronun sinaps değerini de bununla sıfırlıyordu.
Subnormal kırpmasını da bu fonksiyon besliyordu. NR-08 gereksinimi, mutlak değeri G_EPS'in altına düşen her sinaps değerini tam +0.0 yapar; G_EPS, en küçük normal binary64 olan 2^-1022. Amaç hiçbir platformun subnormal aritmetik yapmaması, böylece subnormal'leri farklı işleyen makinelerin ayrışmaması. MVS'te ise kırpma her devreye girdiğinde subnormal'i geri yazıyordu.
Tek bir parmak izi bile değişmedi. 19 golden isteğin hepsi eşleşti; cevap kayıtları da, MVS'in EBCDIC olarak sakladığı tek bir metin alanı dışında, x86 kayıtlarıyla bayt bayt aynıydı (VL-125, sürüm notları). 1e-318 civarındaki bir değer karşılaştığı her normal büyüklükteki sayının yanında kayboluyor ve 7.0 mV'luk spike eşiğine hiç yaklaşmıyor. Spesifikasyon buna açıkça şans diyor, çünkü motorda buna karşı bir koruma yoktu.
Dört baytlık sabitten sekiz baytlık kopya
Minimal reproducer 27 Eylül 2026'da commit edildi: içinde FlyBatch'ten hiçbir şey olmayan 26 satırlık bir C89 programı. İki word'lük bir struct, iki alanı da 0UL yapıp döndüren zero0(void) ve argümanlarını döndüren make2(h, l). GCCMVS 3.2.3 ile -O1'de zero0, lo=000A5B8A bastı. -O0'da ve JCC 1.50.00 derleyicisinde iki fonksiyon da sıfır bastı.
Nedeni -O1 assembler listesinde görülüyor. Listede zero0'ın kod bloğu üç komut: LR 2,0, MVC 0(8,2),=F'0' ve LR 15,0. Ortadaki MVC, dört bayt uzunluğundaki bir fullword literal'den sekiz bayt kopyalıyor. Literal'den sonraki dört bayt fonksiyonun page table'ı ve tek girdisi DC A(PG0) adres sabiti; böylece "sıfırın" alt yarısına bir adres yazılıyor (VL-145).
Motorun yanlış sıfırı iki probe build'i arasında değişti, birinde 0AE782, diğerinde 0AE612; yani değer kod yerleşimine bağlıydı (VL-124). VL-145, bu değerin reproducer'daki adres benzeri word ile aynı türden olduğunu kaydediyor. Reproducer'da ise 000A5B8A'nın arkasındaki yükleme adresi, basılmadığı için çıkarım olarak kayıtlı.
Temsili açığa çıkarmadan düzeltmek
D-420 kararı onffzer()'ı float API'sinden kaldırdı. Üç çağrı noktası artık onffbit(0UL, 0UL) kullanıyor; bu fonksiyon değeri iki açık word'den kuruyor ve aynı hatalı build'de 0000000000000000 döndürdüğü ölçüldü.
D-420 üç seçeneği reddetti; biri motoru olduğu gibi bırakmaktı. Sahte bir argüman eklemek, tetikleyicinin boş argüman listesi olduğu yönündeki test edilmemiş bir teoriye dayanıyordu. Her çağrı noktasında z.hi = 0; z.lo = 0 yazmak da doğru ölçüldü, ama binary64 düzenini float katmanının tam da uzak tutmak için var olduğu kernel'in içine taşıyordu.
Düzeltmeden sonra MVS ve x86 akışları bayt bayt aynı. JCC ile yeniden build alındığında satırın zaten taşıdığı beş parmak izi aynen döndü, ve spesifikasyon bunun hiçbir şeyi aklamadığını açıkça yazıyor: hatanın hiç kıpırdatmadığı bir kontrol, bir derleyiciyi o hatadan temize çıkaramaz (VL-128). JCC sorusunu sonradan reproducer yanıtladı, o da yalnızca bu tek program için.
Sınırları projenin kendisi koyuyor: tek bir program, bir emülatör üzerinde, GCCMVS'in kod üreticisinin kaynağı okunmadan, başka struct boyutları ve sabitler denenmeden. FlyBatch IBM Z donanımında hiç çalışmadı; buradaki her MVS rakamı tek bir dizüstü bilgisayarda Hercules altında koşan MVS 3.8j'den geliyor (sürüm notları).
Parmak izine güvenmeden önce durumu karşılaştırın
Platformlar arası bir determinizm kontrolü kuruyorsanız, kontrolün göremediği iç durumu listeleyin ve onu kapsayan en az bir karşılaştırma ekleyin. Cevapları onaylayan bir matris için cevabı hash'lemek doğru. FlyBatch'te spike'ları hiç değiştirmeyen yanlış bir sıfırı görebilen ilk MVS kontrolü, akıştaki okuma nöronlarının membran satırlarıydı (VL-121); float self-test'i de artık MVS'te sıfırın bit desenini kontrol ediyor (D-421).
Aynı port daha önce her SoftFloat sembolünü sekiz karaktere sığdırmak zorunda kalmıştı; bunu Berkeley SoftFloat'ı MVS 3.8j'ye taşıma yazısı anlatıyor. Kopyalama hatasını kendiniz görmek için FlyBatch deposundan tools/mvsgcc.py içindeki structret probe'unu bir TK5 sistemine karşı çalıştırın.