1 पॉइंट द्वारा GN⁺ 5 시간 전 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • Zig की master branch में LLVM backend के non-ABI integer handling में सुधार और नया @bitCast semantics merge किया गया है, जिससे optimization समस्याओं और language behavior mismatch—दोनों को साथ में सुलझाया गया है
  • u4, i13, u40 जैसे arbitrary bit-width integers अब SSA values में bit-int के रूप में संभाले जाएंगे, लेकिन memory में store करते समय ABI-size integers तक expand किए जाएंगे
  • पुराना @bitCast memory bytes की reinterpretation के ज़्यादा करीब था, लेकिन नई परिभाषा type के logical bit array के आधार पर interpret करती है, जिससे endian dependency कम होती है
  • यह बदलाव LLVM·C backend और comptime execution तक विस्तारित किया गया है, और 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 types i4, 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 एक ऐसे @bitCast semantics को implement कर रहा था जो पर्याप्त रूप से specified नहीं था, और integer types की memory storage विधि बदलते ही compiler test suite में Illegal Behavior और crashes सामने आए
  • LLVM backend में पुराने behavior की नकल करने वाला logic जोड़ने के बजाय, पूरे सिस्टम में नए @bitCast definition को implement करने की दिशा चुनी गई

नया @bitCast semantics

  • नया semantics 2024 में प्रस्तुत और स्वीकृत language proposal #19755 पर आधारित है
  • यह semantics पहले से self-hosted x86_64 backend में implement था, और इस बदलाव के साथ LLVM·C backend और comptime execution तक विस्तारित किया गया है
  • नया @bitCast memory 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 के बीच @bitCast semantics भी बरकरार रहता है

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 किए गए
    • pointer vector के साथ @bitCast पर प्रतिबंध: #18936
    • enum के लिए @bitCast की अनुमति: #35602 का एक हिस्सा
  • नया 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 टिप्पणियां

 
GN⁺ 5 시간 전
Lobste.rs की राय
  • लेख में कहा गया logical bit representation भले ही endian-independent बताया गया हो, लेकिन वास्तविक विवरण साफ़ तौर पर little-endian तरीके जैसा लगता है, जो big-endian bit order या byte order को सपोर्ट नहीं करता

    • यहाँ endian-independent का मतलब शायद यह है कि little-endian/big-endian architectures के बीच behavior अलग नहीं होगा
  • 25 जून 2026 की नई development log के अनुसार, नए @bitCast semantics और LLVM backend improvements हाल की pull request में merge हो गए हैं

  • दिलचस्प है, लेकिन क्या ऐसा नहीं हो सकता कि कम ही test किए जाने वाले big-endian targets पर नीचे जैसा लिखा code अचानक टूट जाए?
    non-Zig pseudocode में लिखें तो:

    if target_is_little_endian {  
        my_int = @bitCast(my_array);  
    } else {  
        my_int = @bitCast([my_array[1], my_array[0]]);  
    }  
    
    • मेरे मन में भी यही आया था, लेकिन आखिरकार जिन बदलावों से बचा नहीं जा सकता उन्हें टालने से समस्या बस और बड़ी होती है
      शायद यह वास्तव में बहुत बड़ी समस्या नहीं होगी; 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 @bitCast semantics अलग architectures पर भी वही परिणाम देने वाले portable abstract semantics देते हैं, इसलिए मुझे लगता है कि यही सही दिशा थी
    मैं हाल में अपनी language के bit fields और bit casts design कर रहा हूँ, इसलिए मेरा code कैसे behave करना चाहिए यह साफ़ करने के लिए Zig के design और implementation docs को और ध्यान से देखूँगा

    • C bit fields के लिए Zig का मुख्य विकल्प शायद packed struct और packed union हैं, और दोनों को नए @bitCast definition के साथ अच्छी तरह मेल खाने के लिए define किया गया है
      packed struct fields के bits को “backing integer” में भरता है। उदाहरण के लिए अगर fields bool, u6, i9 हों और backing integer u16 हो, तो u16 का सबसे कम महत्व वाला bit bool होगा, अगले 6 bits u6 होंगे, और बाकी 9 bits i9 होंगे। यानी 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 struct fields में 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_EXEC macros और uint8_t API से लिया जा सकता है, लेकिन Zig में Access = packed struct(u8) बनाकर read, write, exec, reserved fields define किए जा सकते हैं और API में Access लिया जा सकता है
      packed struct और packed union से काफ़ी अजीब bit layouts भी व्यक्त किए जा सकते हैं। Mach-O object format की symbol table entry में ऐतिहासिक कारणों से लगता है एक अजीब n_type field है, जिसे packed union(u8) के अंदर bits: packed struct(u8) और stab: enum(u8) के रूप में model किया जा सकता है
      इस n_type value को 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 हैं