پورت کتابخانه استاندارد Go به C، این بار بدون libc
خلاصهٔ کاملتر
نویسنده قبلاً سولاد رو ساخته: یه زیرمجموعه از Go که مستقیم به C ترجمه میشه. نوشتن کد C با کمک Go خوب بود، ولی بدون کتابخانه استاندارد محدود میموند؛ برای همین قدم بعدی پورت کردن استاندارد لایبرری Go به C بود. تو این مسیر، نویسنده هرجا میتونسته پکیجها رو freestanding کرده، یعنی بدون وابستگی به هیچ پیادهسازی libc یا OS خاص. الان از ۳۷ پکیج، ۳۱ تاشون freestanding کار میکنن.
زبان C دو جور محیط داره: hosted که کتابخانه استاندارد کامل رو در اختیارت میذاره، و freestanding که تقریباً هیچی نداری. کامپایلر با ماکروی STDC_HOSTED بهت میگه کدوم محیطی هستی. وقتی فلگ -ffreestanding رو میزنی و با -nostdlib لینک میکنی، دیگه نه printf داری، نه malloc، حتی نه memcpy؛ نه منبع تصادفی، نه عملیات فایلسیستم، نه ساعت. این حالت دقیقاً برای میکروکنترلر، sandboxهای WebAssembly و کرنل که سیستمعامل کامل ندارن بهکار میاد.
استاندارد C تضمین میکنه چندتا هدر حتی بدون libc هم در دسترس باشن (مثل float.h، stdint.h، stdbool.h) چون فقط تایپ و ماکرو تعریف میکنن، نه تابع؛ ولی هدرهایی مثل string.h یا stdio.h که واقعاً تابع میخوان کلاً حذف میشن. نویسنده یه هدر مشترک به اسم builtin.h ساخته که بین حالت hosted و freestanding شاخه میزنه، و هر پکیج با یه ماکروی so_build_hosted بین دو پیادهسازی سوییچ میکنه.
GCC و Clang یهسری تابع استاندارد رو بدون نیاز به libc با «کامپایلر بیلتین» پیاده میکنن؛ مثلاً __builtin_trap برای assert و panic، یا __builtin_alloca برای تخصیص حافظه رو استک که نویسنده یه رپر محدود روش گذاشته تا هر تخصیص بیشتر از ۶۴ کیلوبایت رو رد کنه. نکتهی جالب اینه که حتی وقتی خودت memcpy صدا نمیزنی، کامپایلر موقع کپی کردن یه struct بزرگ یا صفر کردن یه آرایه، ضمنی به memcpy یا memset نیاز پیدا میکنه؛ پس freestanding باز هم باید اینها رو یهجا فراهم کنه:
void* memcpy(void* dst, const void* src, size_t n) {
unsigned char* d = dst;
const unsigned char* s = src;
while (n--) *d++ = *s++;
return dst;
}وباسمبلی خودش memcpy، memmove و memset رو مستقیم به دستورهای memory.copy و memory.fill نگاشت میکنه، ولی memcmp همیشه باید یه تابع واقعی باشه. برای atomicها هم ماکروهای __atomic_xxx کامپایلر کار رو راحت کرده: نویسنده با فقط دوتا ماکروی so_atomic_load و so_atomic_store تونسته کل تایپهای sync/atomic گو رو پورت کنه، هرچند روی CPUهای ۳۲بیتی، اتمیک ۶۴بیتی بهجای یه دستور تکی، به تابع libatomic تبدیل میشه.
بعضی توابع مثل memchr یا strlen اصلاً بیلتین کامپایلری ندارن و همیشه باید دستی نوشته بشن. خوشبختانه کتابخانه استاندارد Go پر از الگوریتمهای مستقل (مثل تبدیل رشتهبهعدد تو strconv یا عملیات بیتی تو math/bits) هست که پورتشون به C تقریباً مکانیکی و سادهست.
نکات کلیدی:
- سولاد الان ۳۷ پکیج استاندارد Go رو به C پورت کرده و ۳۱ تاشون freestanding (بدون libc) کار میکنن.
- freestanding با -ffreestanding و -nostdlib فعال میشه و printf، malloc و حتی memcpy رو حذف میکنه.
- کامپایلر میتونه بدون صدا زدن صریح، به memcpy یا memset نیاز پیدا کنه؛ پس این توابع باید یهجا پیاده بشن.
- ماکروهای __atomic_xxx پورت کردن sync/atomic گو رو به دو ماکروی سادهی load/store ختم کرده.
- توابعی مثل memchr و strlen بیلتین ندارن و همیشه باید دستی نوشته بشن.