Zig, नया @bitCast semantics और LLVM backend में सुधार
(ziglang.org)- Zig की master branch में LLVM backend के non-ABI integer handling में सुधार और नया
@bitCastsemantics merge किया गया है, जिससे optimization समस्याओं और language behavior mismatch—दोनों को साथ में सुलझाया गया है u4,i13,u40जैसे arbitrary bit-width integers अब SSA values में bit-int के रूप में संभाले जाएंगे, लेकिन memory में store करते समय ABI-size integers तक expand किए जाएंगे- पुराना
@bitCastmemory bytes की reinterpretation के ज़्यादा करीब था, लेकिन नई परिभाषा type के logical bit array के आधार पर interpret करती है, जिससे endian dependency कम होती है - यह बदलाव LLVM·C backend और
comptimeexecution तक विस्तारित किया गया है, और standard library·compiler·compiler_rtमें जुड़े उपयोगों की भी साथ में समीक्षा की गई है - छूट रहे LLVM optimizations फिर से सक्रिय होने के साथ Zig compiler itself में लगभग 5% performance improvement देखा गया है, और 0.17.0 में कुछ runtime performance gains की उम्मीद की जा सकती है
LLVM backend में arbitrary bit-width integer handling का बदलाव
- Zig पहले
u4,i13,u40जैसे arbitrary bit-width integer types को सीधे LLVM IR के bit-int typesi4,i13,i40में lower करता था - इस तरीके से LLVM की memory representation semantics optimizer पर अनावश्यक constraints लगाती थी, और क्योंकि Clang ऐसा LLVM IR बनाता नहीं है, LLVM के अंदर यह code path भी पर्याप्त रूप से test नहीं हुआ था
- पिछले कुछ वर्षों में वास्तव में optimization miss और miscompilation के मामले देखे गए
- नए तरीके में SSA value manipulation के लिए bit-int types बरकरार रहते हैं, लेकिन memory में store करते समय
i8,i16,i32जैसे ABI-size types तक zero-extend या sign-extend किया जाता है - यह lowering C के
_BitInt(N)को Clang जिस तरह lower करता है, उसके अनुरूप है, इसलिए LLVM में इसे बेहतर supported path माना जा रहा है
पुराने @bitCast की सीमाएँ
- पुराना
@bitCastअवधारणात्मक रूप से लगभग इस तरह काम करता था- operand value का pointer लिया जाता था
- उस pointer को destination type pointer में cast किया जाता था
- फिर उस pointer से value load की जाती थी
- यानी पुरानी परिभाषा type की logical structure से अधिक memory की byte reinterpretation के करीब थी
- समय के साथ वास्तविक behavior इस परिभाषा से अलग हो गया, और अधिकांश targets पर
@sizeOf(u24)का@sizeOf([3]u8)से बड़ा होने के बावजूद[3]u8कोu24में@bitCastकरना allowed था - LLVM backend एक ऐसे
@bitCastsemantics को implement कर रहा था जो पर्याप्त रूप से specified नहीं था, और integer types की memory storage विधि बदलते ही compiler test suite में Illegal Behavior और crashes सामने आए - LLVM backend में पुराने behavior की नकल करने वाला logic जोड़ने के बजाय, पूरे सिस्टम में नए
@bitCastdefinition को implement करने की दिशा चुनी गई
नया @bitCast semantics
- नया semantics 2024 में प्रस्तुत और स्वीकृत language proposal #19755 पर आधारित है
- यह semantics पहले से self-hosted x86_64 backend में implement था, और इस बदलाव के साथ LLVM·C backend और
comptimeexecution तक विस्तारित किया गया है - नया
@bitCastmemory bytes नहीं, बल्कि type को logically represent करने वाले bit order के आधार पर काम करता हैu5में least-significant bit से most-significant bit तक 5 logical bits होते हैं[2]u5में पहले element के 5 bits के बाद दूसरे element के 5 bits जुड़कर कुल 10 logical bits बनते हैं
u8को समान आकार वालेi8में बदलने जैसे साधारण integer-to-integer conversion में bits जस के तस रहते हैं, और सबसे ऊपरी bit को sign bit के रूप में interpret किया जाता है- integer types और
packed structयाpacked unionके बीच@bitCastsemantics भी बरकरार रहता है
array·vector में बदलता behavior
- नया semantics पुराने से वहाँ अलग होता है जहाँ aggregate types जैसे array और vector शामिल होते हैं
- उदाहरण के लिए
[2]u8कोu16में@bitCastकरने पर पुराने semantics में result target endian के अनुसार बदलता था- big-endian target पर पहला array element ऊपरी 8 bits बनता था
- little-endian target पर पहला array element निचले 8 bits बनता था
- नया semantics केवल logical bit representation को देखता है, इसलिए यह endian-independent है, और सभी targets पर पहला array element निचले 8 bits बनता है
- सामान्य रूप से यह little-endian targets पर पुराने behavior के ज़्यादा करीब है
[2]u3को@Vector(3, u2)में बदलने जैसे अनियमित conversion भी संभव हैं- array के logical bits को जोड़ने के बाद उन्हें 2-bit units में पढ़कर vector elements बनाए जाते हैं
- integer को
@Vector(n, u1)में@bitCastकरके individual bit vectors में तोड़ने के लिए भी इसका उपयोग किया जा सकता है
साथ में शामिल proposals और migration
- इस काम के दौरान
@bitCastसे जुड़े कुछ छोटे accepted proposals भी implement किए गए - नया semantics पुराने semantics से अर्थपूर्ण रूप से अलग है, इसलिए standard library, compiler,
compiler_rtजैसी supporting libraries में@bitCastके उपयोग की समीक्षा की गई - संबंधित PR है codeberg.org/ziglang/zig/pulls/35711, और master में merge होते ही कई issues भी साथ में बंद हुए
- बदला हुआ semantics और recommended migration procedure को Zig 0.17.0 release notes में संकलित किया जाएगा
0.17.0 में अपेक्षित performance प्रभाव
- मूल लक्ष्य, यानी LLVM backend में non-ABI integer lowering का बदलाव, छूट रहे optimizations को फिर से सक्रिय करने में सफल रहा
- संबंधित परिणाम demonstrably successful में देखे जा सकते हैं
- Zig compiler itself आंतरिक रूप से arbitrary bit-width integers का बहुत अधिक उपयोग नहीं करता, फिर भी बेहतर optimization के कारण इसमें लगभग 5% performance improvement दिखा
- 0.17.0 में कुछ code में मामूली runtime performance improvement हो सकती है
1 टिप्पणियां
Lobste.rs की राय
लेख में कहा गया logical bit representation भले ही endian-independent बताया गया हो, लेकिन वास्तविक विवरण साफ़ तौर पर little-endian तरीके जैसा लगता है, जो big-endian bit order या byte order को सपोर्ट नहीं करता
25 जून 2026 की नई development log के अनुसार, नए
@bitCastsemantics और LLVM backend improvements हाल की pull request में merge हो गए हैंदिलचस्प है, लेकिन क्या ऐसा नहीं हो सकता कि कम ही test किए जाने वाले big-endian targets पर नीचे जैसा लिखा code अचानक टूट जाए?
non-Zig pseudocode में लिखें तो:
शायद यह वास्तव में बहुत बड़ी समस्या नहीं होगी; Zig repository के हज़ारों
@bitCastमें से इस बदलाव से प्रभावित होने वाले उदाहरण 100 से काफी कम रहे होंगेसच कहूँ तो मुझे नहीं लगता कि arrays/vectors और scalars के बीच conversion में
@bitCastकैसे काम करता है, यह ज़्यादातर Zig users को ठीक-ठीक पता था। पहले जो code सिर्फ लेखक के system पर test हुआ था और केवल little-endian पर चलता था, अब ऐसे कई cases होंगे जो हर जगह काम करेंगेपुराने C programmer के रूप में मुझे याद है कि C के bit fields लोकप्रिय नहीं थे, क्योंकि उनका behavior अलग-अलग architectures पर portable नहीं होता था
नए Zig
@bitCastsemantics अलग architectures पर भी वही परिणाम देने वाले portable abstract semantics देते हैं, इसलिए मुझे लगता है कि यही सही दिशा थीमैं हाल में अपनी language के bit fields और bit casts design कर रहा हूँ, इसलिए मेरा code कैसे behave करना चाहिए यह साफ़ करने के लिए Zig के design और implementation docs को और ध्यान से देखूँगा
packed structऔरpacked unionहैं, और दोनों को नए@bitCastdefinition के साथ अच्छी तरह मेल खाने के लिए define किया गया हैpacked structfields के bits को “backing integer” में भरता है। उदाहरण के लिए अगर fieldsbool,u6,i9हों और backing integeru16हो, तोu16का सबसे कम महत्व वाला bitboolहोगा, अगले 6 bitsu6होंगे, और बाकी 9 bitsi9होंगे। यानी Zig का packed struct कई shifts और masks के ऊपर एक syntax sugar जैसा हैpacked unionमें भी backing integer होता है, लेकिन सभी fields को backing integer के बिल्कुल बराबर bit count का उपयोग करना होता है। इसलिए एक field में store करके दूसरी field से read करना नए semantics वाले@bitCastजैसा ही है। बसpacked union/packed structfields में array या vector types नहीं हो सकतेव्यक्तिगत रूप से मुझे लगता है कि ये tools “bit-related structures” को व्यक्त करने के लिए बहुत उपयुक्त हैं। कई values को
packed structसे bit-pack करके C bit fields की तरह इस्तेमाल किया जा सकता है, और क्योंकि यह bit operations के ऊपर syntax sugar है, इसलिए bit flags भी साफ़ ढंग से व्यक्त किए जा सकते हैं, जिन्हें C में type-safe न होने वाले macro hacks से संभाला जाता थाउदाहरण के लिए RWX access flags को C में
ACCESS_READ,ACCESS_WRITE,ACCESS_EXECmacros औरuint8_tAPI से लिया जा सकता है, लेकिन Zig मेंAccess = packed struct(u8)बनाकरread,write,exec,reservedfields define किए जा सकते हैं और API मेंAccessलिया जा सकता हैpacked structऔरpacked unionसे काफ़ी अजीब bit layouts भी व्यक्त किए जा सकते हैं। Mach-O object format की symbol table entry में ऐतिहासिक कारणों से लगता है एक अजीबn_typefield है, जिसेpacked union(u8)के अंदरbits: packed struct(u8)औरstab: enum(u8)के रूप में model किया जा सकता हैइस
n_typevalue को handle करते समय manual shifting या masking की ज़रूरत नहीं होती।n_type.bits.is_stab != 0जाँचें, और अगर true हो तोn_type.stabपरswitchकरें, नहीं तोn_type.bitsके दूसरे fields देखें। उल्टा.{ .stab = .gsym }या.{ .bits = .{ .ext = false, .type = .undf, .pext = false, .is_stab = 0 } }जैसी values भी बनाई जा सकती हैंयह मूल लेख के विषय से थोड़ा अलग language feature है इसलिए बात कुछ लंबी हो गई, लेकिन अगर आप नई language design के लिए reference ढूँढ़ रहे हैं, तो Zig के
packed structऔरpacked unionको खुद आज़माना अच्छा रहेगा। ये सरल हैं, लेकिन काफ़ी अच्छे tools हैं