Pointer Provenance در Rust و C++؛ چرا pointer فقط یک عدد نیست؟

چرا مهم است

اگر با raw pointer کار می‌کنی، این مثال‌ها نشان می‌دهند چرا دانستن آدرس عددی برای توضیح رفتار compiler کافی نیست. همچنین تفاوت میان بیان قصد با API‌های Rust و بهینه‌سازی واقعاً مشاهده‌شده را می‌بینی.

قبل از خواندن، این‌ها را بدان۳ مفهوم
Pointer Provenance

رابطهٔ منشأ یک pointer با allocation مربوط به آن که در تعیین دسترسی مجاز به حافظه و استدلال compiler نقش دارد.

بیشتر درباره این مفهوم

Intermediate Representation

شکل میانی برنامه در فرایند compilation که میان زبان مبدأ و machine code قرار می‌گیرد و برای بررسی و تبدیل برنامه استفاده می‌شود.

بیشتر درباره این مفهوم

allocation

ناحیه‌ای از حافظه که برای نگهداری داده در اختیار برنامه قرار می‌گیرد؛ منشأ pointer می‌تواند به این ناحیه مرتبط باشد.

بیشتر درباره این مفهوم

یک pointer فقط عددی برای نمایش آدرس حافظه نیست. این مطلب با مقایسهٔ نمونه‌هایی از C++ و Rust،؜ مفهوم Pointer Provenance را توضیح می‌دهد: رابطه‌ای که مشخص می‌کند pointer از کدام allocation آمده و مجاز است به کدام حافظه دسترسی داشته باشد. نویسنده تأکید می‌کند که در مدل مورد بحث، این رابطه یک مفهوم انتزاعی برای compiler است، نه داده‌ای که الزاماً کنار آدرس در حافظه ذخیره شود.

مثال نخست در C++ تابعی است که متغیر محلی x را با مقدار ۱ می‌سازد، pointer ورودی p را به تابع ناشناختهٔ opaque می‌دهد و سپس x را برمی‌گرداند. Clang با گزینهٔ ‎-O2 نتیجه را به بازگرداندن مستقیم ۱ تبدیل می‌کند. توضیح مطلب این است که p از allocation مربوط به x مشتق نشده؛ بنابراین compiler فراخوانی opaque(p) را تغییردهندهٔ x در نظر نمی‌گیرد. اما وقتی خود ‎&x هم به opaque داده شود، مقدار x پس از فراخوانی از حافظه خوانده می‌شود.

نویسنده برای بررسی این رفتار سراغ LLVM IR می‌رود؛ یک Intermediate Representation میان زبان‌هایی مانند Rust و C++ و machine code. در نمونهٔ نمایش‌داده‌شده، allocation محلی و مسیر استفاده از pointer‌ها قابل بررسی است. هدف این بررسی، دنبال‌کردن منشأ pointer‌ها و امکان دسترسی آن‌ها به یک ناحیهٔ حافظه است.

در نمونهٔ بعدی C++، تبدیل ‎&x به std::uintptr_t و ارسال آن به تابع دیگر، آدرس را exposed می‌کند. طبق توضیح مطلب، LLVM باید احتمال بازسازی pointer از این عدد را در نظر بگیرد و دیگر همان بهینه‌سازی قبلی را انجام نمی‌دهد.

Rust برای بیان صریح‌تر این قصد، API‌هایی دارد. متد addr مقدار عددی آدرس را بدون exposed کردن provenance می‌گیرد. بااین‌حال، در آزمایش نویسنده، خروجی همچنان x را پس از opaque(p) دوباره از حافظه می‌خواند؛ پس این نمونه شاهد بهینه‌سازی فعلی نیست. مزیت مطرح‌شده، بیان روشن‌تر قصد برای compiler و Miri است.

بخش پایانیِ موجود به with_addr می‌پردازد: تغییر آدرس یک pointer همراه با حفظ provenance آن. نمونه، یک bit پایین آدرس را برای flag تنظیم و سپس پاک می‌کند. مقدار میانی همچنان pointer می‌ماند، نه usize؛ چون عدد به‌تنهایی provenance را حمل نمی‌کند.

نکته کلیدی

برای خواندن آدرس بدون exposed کردن provenance،؜ نقش addr را به خاطر بسپار؛ برای تغییر آدرس همراه با حفظ provenance،؜ with_addr را بشناس. از این مثال نتیجه نگیر که addr همین حالا بهینه‌سازی مورد انتظار را تضمین می‌کند.

خواندن مقاله اصلی (در زبانه‌ی جدید باز می‌شود)
برچسب‌ها (۴)