1 पॉइंट द्वारा GN⁺ 17 시간 전 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • सिर्फ ISO C मानक का पालन करने वाला कोड बहुत कम मिलता है, और वास्तविक C कोडबेस फीचर जोड़ने तथा compiler·library-विशिष्ट कमियों को बायपास करने के लिए non-standard extensions पर निर्भर रहते हैं
  • उपयोगी C compiler को <stdio.h> जैसे system headers से ही निपटना पड़ता है, लेकिन glibc __attribute__((packed)), #include_next जैसे GNU extensions और धारणाओं के जरिए बाधा खड़ी करता है
  • SDL का byteswapping logic ISA macros मौजूद होने पर inline assembly चुन सकता है, इसलिए GCC·clang नहीं होने वाले compilers से भी GCC-शैली extensions की अपेक्षा की जा सकती है
  • OpenBSD और Gnulib में extern inline का हैंडलिंग C99 और GCC semantics के अंतर, platform-विशिष्ट branching, और _FORTIFY_SOURCE शर्तों की वजह से inline semantics compatibility को जटिल बनाता है
  • छोटे C compilers को upstream patch, downstream patch, समर्पित guard हासिल करने, या GCC compatibility की नकल करने में से चुनना पड़ता है, और feature test macros का विस्तार अधिक बेहतर दिशा लगता है

glibc headers से बनने वाली पहली बाधा

  • उपयोगी C compiler बनने के लिए system C library headers को preprocess और parse कर पाना ज़रूरी है, और अगर <stdio.h> को ही process नहीं किया जा सके तो hello world तक पास करना मुश्किल हो जाता है
  • GNU/Linux environment में यह बाधा सीधे glibc तक जाती है
  • glibc, sys/cdefs.h में — जिसे लगभग सभी libc headers अप्रत्यक्ष रूप से include करते हैं — compiler द्वारा predefined macros की जाँच करके तय करता है कि कौन-से extensions समर्थित हैं
  • unsupported extensions को संबंधित definitions हटाकर संभाला जाता है, लेकिन यह compatibility logic भी व्यवहार में टूट सकता है
  • struct epoll_event और __attribute__((packed))

    • Linux के sys/epoll.h में मौजूद struct epoll_event, GNU __attribute__((packed)) का उपयोग करने वाला packed struct है
    • यह attribute 64-bit पर struct layout बदल देता है, इसलिए इसे ignore करने पर ABI टूट जाता है
    • सिर्फ compiler में __attribute__((packed)) implement होना काफी नहीं है
    • sys/cdefs.h में ऐसा code है जो GCC, clang, या tcc न होने पर __attribute__(xyz) को empty macro के रूप में define कर देता है
    • नतीजतन, दूसरे compilers packed attribute को support करते हुए भी glibc headers में उससे वंचित हो सकते हैं
    • यह तर्क भी दिया जा सकता है कि epoll header Linux-विशिष्ट है, इसलिए C standard portability के मानदंड को वैसा का वैसा लागू करना आसान नहीं है
  • limits.h और #include_next

    • stddef.h, stdint.h, limits.h, float.h जैसे कुछ C headers freestanding implementations के लिए भी आवश्यक होते हैं, इसलिए compiler को इन्हें प्रदान करना पड़ता है
    • POSIX, standard C constants के अलावा POSIX-विशिष्ट constants को भी limits.h में define करने की मांग करता है, इसलिए compiler के limits.h के ऊपर platform-विशिष्ट limits.h की जरूरत पड़ती है
    • glibc का <limits.h>, GNU C न होने पर ANSI limits.h values सीधे define करता है, और GCC environment में #include_next <limits.h> के जरिए compiler header लाता है
    • यह संरचना मानती है कि GCC-विशिष्ट builtin limits.h कुछ खास macros define करेगा, और #include_next extension पर भी निर्भर करती है
    • clang को भी इस संरचना को बायपास करना पड़ता है

SDL की feature detection और inline assembly समस्या

  • SDL_endian.h में byteswapping functions, जहाँ संभव हो, compiler builtins या inline assembly का उपयोग करती हैं, और अंतिम विकल्प के रूप में सामान्य bit operations implementation पर लौटती हैं
  • detection logic मोटे तौर पर इस क्रम में काम करता है
    • अगर GCC या clang है और __has_builtin(__builtin_bswapX) उपलब्ध है, तो builtin उपयोग किया जाता है
    • अगर MSVC 8.0 या उससे ऊपर है, तो MSVC intrinsic #pragma उपयोग होता है
    • अगर __x86_64__ जैसे ISA-विशिष्ट macros defined हैं, तो inline assembly उपयोग होती है
    • अन्यथा सामान्य bit operations implementation उपयोग होती है
  • GCC या clang नहीं होने वाला compiler यदि उचित कारणों से ISA-विशिष्ट predefined macro define करता है, तो यह क्रम समस्या पैदा करता है
  • ऐसा compiler भले ही bswap builtin और __has_builtin special operator प्रदान करे, logic के कारण वह GCC-शैली inline assembly की ओर जा सकता है
  • परिणामस्वरूप संरचना यह मानकर चलती है कि अज्ञात compiler भी GCC-style inline assembly support करेगा

OpenBSD libc और extern inline की उलझन

  • OpenBSD के कुछ headers optimization के दौरान compiler द्वारा वैकल्पिक रूप से उपयोग की जाने वाली inline function definitions शामिल करते हैं
  • ये functions __only_inline macro से define होती हैं, और अगर compiler वास्तव में inline नहीं करता तो उन्हें external symbol से replace होना चाहिए
  • यानी extern linkage वाली inline function की जरूरत होती है
  • C99 inline और GCC inline semantics का अंतर

    • inline, C99 में निर्दिष्ट है, लेकिन standard behavior, C99 से पहले के non-standard GCC behavior से टकराता है
    • header के अंदर inline definition को function body के साथ extern inline का उपयोग करना चाहिए, और इस स्थिति में वास्तविक exported function emit नहीं होती
    • translation unit में function definition export करने के लिए declaration पर सिर्फ inline लगाना चाहिए
    • inline का अर्थ C++ और C में भी अलग होता है
    • इस अंतर पर Youtao Guo के लेख में विस्तार से चर्चा है
  • OpenBSD का __only_inline

    • OpenBSD, GCC inline semantics पर निर्भर करता है
    • GCC versions के अंतर को ढकने के लिए sys/cdefs.h का __only_inline macro, नए GCC पर पुराने gnu89 inline semantics को स्पष्ट __attribute__ के जरिए निर्दिष्ट करता है
    • non-GNU compilers में __only_inline, static linkage के रूप में define होता है
    • परिणामस्वरूप functions परस्पर विरोधी linkage के साथ declare/define होकर टूट सकती हैं
  • _ANSI_LIBRARY workaround

    • OpenBSD, _ANSI_LIBRARY macro का सम्मान करता है
    • इस macro को define करने पर signal.h जैसे standard headers में टूटने वाली __only_inline definitions का उपयोग पूरी तरह छोड़ दिया जाता है
    • optimized version नहीं मिलता, लेकिन कम-से-कम build काम करता है
  • Gnulib का extern inline compatibility code

    • Gnulib का extern inline compatibility code, Guile और nano build करते समय भी सामने आता है
    • extern-inline.m4 इस C corner case की टूटी और विचित्र implementations को संभालने के लिए जटिल conditional branching शामिल करता है
    • इन शर्तों में Apple, DragonFly, FreeBSD, GCC, clang, PCC, HP cc, PGI, SunPro C, _FORTIFY_SOURCE, __GNUC_STDC_INLINE__, __GNUC_GNU_INLINE__ जैसे environment differences झलकते हैं

Android bionic की clang-आधारित धारणाएँ

  • bionic Android की libc है, और उसके headers, GCC की तुलना में clang को कहीं अधिक मजबूती से assume करते हैं
  • bionic headers, nullability checks के लिए _Nonnull, _Null_unspecified जैसे clang-विशिष्ट extensions का खूब उपयोग करते हैं
  • ऐसे macros को command-line flags से #define करके हटाना बहुत मुश्किल नहीं है
  • Android फोन को Termux के जरिए native aarch64 development environment की तरह इस्तेमाल करते समय bionic headers में यह समस्या सामने आती है
  • _Null_unspecified को __BIONIC_COMPLICATED_NULLNESS भी कहा जाता है, और संबंधित definition bionic के sys/cdefs.h में है

छोटे C compilers के सामने आने वाले विकल्प

  • सिर्फ ISO C मानक का पालन करने वाला कोड वास्तविक दुनिया में दुर्लभ है, और कई C codebases non-standard behavior और language extensions पर निर्भर रहते हैं
  • यह निर्भरता सिर्फ अतिरिक्त features के लिए नहीं, बल्कि compiler और library के अनुसार अलग-अलग bugs और gaps को बायपास करने की प्रक्रिया में भी पैदा होती है
  • कई environments को support करने वाले codebases preprocessor checks और guards पर निर्भर रहते हैं, लेकिन यह तरीका आसानी से टूटता है और संभालना कठिन होता है
  • antcc जैसे C compiler बनाते समय ऐसे compatibility issues बार-बार सामने आते हैं
  • जब कई open source projects गैर-जरूरी कामों के लिए भी compiler-विशिष्ट non-standard extensions और behaviors पर निर्भर करते हैं, तो वैकल्पिक compilers पर बोझ बढ़ जाता है
  • साथ ही यह मांग करना भी कठिन है कि हर developer छोटे और कम-ज्ञात compilers समेत कई compilers पर C code test करे
  • C portability अपने आप में ही काफी कठिन है
  • compiler लेखक के नज़रिए से चार संभावित विकल्प हैं
    • incompatibility के लिए upstream में patch करने की कोशिश करना
    • इतना प्रसिद्ध हो जाना कि developers समर्पित #ifdef checks और basic tests जोड़ें
    • downstream में इसे संभालना, और patches या अलग patches वितरित करना
    • किसी खास GCC version होने का ढोंग करना और उन extensions को implement करना
  • upstream patches एक कठिन लड़ाई जैसी लगती हैं, और downstream patches सबसे आसान तरीका हैं
  • बहुत-से codebases को users और developers के लिए न्यूनतम भ्रम के साथ support करना हो, तो GCC compatibility की नकल व्यावहारिक लगती है, लेकिन इसकी implementation cost बड़ी है
  • clang, GCC 4.2.1 compatibility का दावा करने के लिए __GNUC__=4, __GNUC_MINOR__=2, __GNUC_PATCHLEVEL__=1 define करता है
  • आज clang लगभग अलग support target जैसा है, लेकिन Linux kernel को clang से compile करने लायक बनाने के लिए दोनों projects में patching की बड़ी मेहनत लगी थी

GCC macros और catch-up की समस्या

  • GCC होने का ढोंग करने वाली पद्धति में भी समस्या है
  • कई codebases सिर्फ #ifdef __GNUC__ जाँचते हैं और version check के बिना नए GCC extensions इस्तेमाल कर सकते हैं
  • इस स्थिति में वैकल्पिक compiler को लगातार catch-up करना पड़ता है
  • clang, 4.2.1 से नए GNU extensions support करने के बावजूद __GNUC__ macro value न बढ़ाने की एक वजह यही है
  • संबंधित पृष्ठभूमि LLVM की __GNUC__ minor version बढ़ाने पर चर्चा में मिलती है

बेहतर दिशा और वर्तमान स्थिति

  • आदर्श रूप से compiler-विशिष्ट guards और version checks के बजाय feature test macros का अधिक व्यापक उपयोग होना चाहिए
  • उपयोगी feature test macros में __has_builtin, __has_feature, __has_attribute शामिल हैं
  • __STDC_NO_VLA__ जैसे standard macros की शैली भी अधिक उपयोग की जा सकती है
  • मौजूदा *NIX दुनिया में, अच्छा हो या बुरा, GCC/clang quasi-duopoly डिफ़ॉल्ट स्थिति है
  • इसके बावजूद स्वतंत्र छोटे C compilers का development जारी है

1 टिप्पणियां

 
Lobste.rs की राय
  • (kefir compiler के लेखक) अनुभव के अनुसार <sys/cdefs.h> की __attribute__ समस्या सबसे सिरदर्द वाली चीज़ों में से एक है। इसने epoll, आम packed struct, constructor और symbol visibility को तोड़ दिया, इसलिए मुझे kefir के साथ यह monkeypatch header भी शामिल करना पड़ा
    यह आदर्श नहीं है, लेकिन शायद सबसे व्यावहारिक तरीका है, और वास्तव में इससे external test suite में ज़्यादातर custom patch हटाए जा सके
    विफलता का एक और प्रकार buggy alternative code है। कुछ प्रोजेक्ट compiler को पहचानकर उसी हिसाब से काम करने की कोशिश करते हैं, लेकिन alternative compiler पर testing कम होने की वजह से fallback code bug से भरा होता है या ठीक से maintain नहीं किया जाता। compiler लेखक के नज़रिए से यह सीधे “unsupported compiler” कहकर fail होने से कहीं ज़्यादा परेशान करने वाला है। उदाहरण के लिए, program और precompiled library के बीच integer typedef width mismatch जैसी अजीब miscompile को खुद debug करना पड़ता है

    • terminal में भी ऐसा ही होता है। अगर $TERM को xterm-256color पर सेट करके xterm होने का नाटक न करो, तो हर तरह की चीज़ें टूट जाती हैं
      इसे कैसे सुलझाया जाए, सच में समझ नहीं आता। आख़िरकार क्या हमारे प्रोजेक्ट को इतना व्यापक और मशहूर होना पड़ेगा? आसान है!
    • monkeypatch header वाला तरीका slimcc भी इस्तेमाल करता है, और यह काफ़ी ठीक-ठाक समझौता लगता है
      compiler-detection fallback को ठीक से maintain न करने से होने वाले अजीब miscompile मैं भी कुछ बार झेल चुका हूँ, और यह सच में बहुत झुंझलाने वाला है
  • मैं मुख्य रूप से linux-musl पर cproc विकसित करता हूँ, इसलिए यह नहीं पता था कि glibc दूसरे compiler पर __attribute__ को disable कर देता है, लेकिन यह वाकई काफ़ी बुरी स्थिति है। comment में लिखा है कि attribute usage को ignore कर दिया जाए तो भी ठीक है, लेकिन इसमें यह ध्यान नहीं रखा गया कि ज़्यादातर application code अप्रत्यक्ष रूप से sys/cdefs.h को include करता है और ऐसे attribute इस्तेमाल कर सकता है जिन्हें ignore नहीं किया जाना चाहिए
    packed के अलावा aligned और constructor भी आम तौर पर इस्तेमाल होते हैं
    यह कहीं issue tracker में report हुआ है या नहीं, जानने की उत्सुकता है। लगता है cdefs.h में attribute का ज़्यादातर इस्तेमाल पहले से __glibc_has_attribute से guard किया गया है, इसलिए यह भी जानना चाहता हूँ कि __attribute__ को एकदम disable कर देने से वास्तव में हासिल क्या होता है, और क्या इसे हटाया जा सकता है
    libc header जिन features का इस्तेमाल करते हैं, उनमें कुछ ऐसे भी हैं जिनके लिए compiler के पास support को ठीक से indicate करने का तरीका नहीं होता। वे __has_attribute या __has_builtin जैसी विधि से सामने नहीं आते; जो उदाहरण तुरंत याद आता है वह __asm__ label है। NetBSD इसे symbol name बदलने के लिए इस्तेमाल करता है, और अगर __GNUC__ या __PCC__ न हो तो #error देता है। लेकिन support न होने पर बस कोशिश करने और fail होने देने के अलावा क्या सुझाया जाए, यह समझ नहीं आता
    __builtin_va_list से जुड़ी समस्या भी झेली है। libc, __GNUC__ के बिना define va_list to void * कर देता है, या कभी-कभी तो conflicting definition भी रखता है। इसे भी __has_builtin से test नहीं किया जा सकता। __has_builtin(__builtin_va_arg) शायद काफ़ी अच्छा test हो सकता है, लेकिन macOS में इसे कैसे ठीक करवाया जाए, यह नहीं पता

    • /usr/include/sys और /usr/include/bits में __attribute__ usage को जल्दी से खोजने पर बहुत-से unguarded इस्तेमाल मिले। ज़्यादातर __format__, __aligned__, __noreturn__ थे, इसलिए इन्हें भी ठीक करना होगा
      कुल मिलाकर glibc शायद GCC के अलावा दूसरे compiler के साथ compatibility को प्राथमिकता नहीं देता, इसलिए ऐसे patch स्वीकार किए जाएँगे या नहीं, कहना मुश्किल है। इस साल की शुरुआत में system upgrade के बाद glibc ने Linux header में unguarded __SIZE_TYPE__ usage जोड़ दिया, जिससे मेरा compiler कुछ प्रोजेक्ट compile नहीं कर पा रहा था। मैंने report किया, लेकिन अभी तक ठीक नहीं हुआ, और आखिरकार GCC के अनुरूप होने के लिए मुझे __X_TYPE__ शैली के predefined macro जोड़ने पड़े
      __asm__ label वाली समस्या का कोई अच्छा हल सूझता नहीं। लेकिन अगर asm name change सच में काम करने के लिए 100% ज़रूरी है, तो compiler check करने से बेहतर शायद यह होगा कि बस कोशिश की जाए और fail होने दिया जाए
      __builtin_va_list काफ़ी गंभीर मामला है। मुझे लगा था कि __has_builtin(__builtin_va_list) काम करेगा, लेकिन apparently ऐसा नहीं है