C ++: Metaprogramming dengan API kompiler daripada dengan fitur C ++

10

Ini dimulai sebagai pertanyaan SO tetapi saya menyadari bahwa itu sangat tidak konvensional dan berdasarkan pada deskripsi aktual di situs web, mungkin lebih cocok untuk programmer. Karena pertanyaan itu memiliki banyak bobot konseptual.

Saya telah belajar dentang LibTooling dan itu adalah alat yang sangat kuat yang mampu mengekspos seluruh "seluk beluk" kode dengan cara yang ramah, yaitu, dengan cara semantik , dan bukan dengan menebak-nebak. Jika dentang dapat mengkompilasi kode Anda, maka dentang pasti tentang semantik setiap karakter tunggal di dalam kode itu.

Sekarang izinkan saya untuk mundur sejenak.

Ada banyak masalah praktis yang muncul ketika seseorang terlibat dalam metaprogramming template C ++ (dan terutama ketika menjelajah di luar templat ke dalam wilayah makro yang pintar meskipun menakutkan). Sejujurnya, bagi banyak programmer, termasuk saya sendiri, banyak penggunaan template biasa juga agak menakutkan.

Saya kira contoh yang baik akan menjadi kompilasi-waktu string . Ini adalah pertanyaan yang sudah berumur lebih dari satu tahun sekarang, tetapi jelas bahwa C ++ pada saat ini tidak membuat ini mudah bagi manusia biasa. Walaupun melihat opsi-opsi ini tidak cukup untuk menimbulkan mual bagi saya, namun tetap membuat saya tidak percaya diri untuk dapat menghasilkan kode mesin ajaib, efisien secara maksimal untuk memenuhi aplikasi mewah apa pun yang saya miliki untuk perangkat lunak saya.

Maksudku, mari kita hadapi itu, teman-teman, string cukup sederhana dan mendasar. Beberapa dari kita hanya menginginkan cara yang nyaman untuk memancarkan kode mesin yang memiliki string tertentu "dipanggang" secara signifikan lebih banyak daripada yang kita dapatkan ketika mengkodekannya dengan cara langsung. Dalam kode C ++ kami.

Masukkan dentang dan LibTooling, yang mengekspos pohon sintaksis abstrak (AST) dari kode sumber dan memungkinkan aplikasi kustom C ++ sederhana untuk memanipulasi kode sumber mentah dengan benar dan andal (menggunakan Rewriter) di samping model berorientasi objek semantik yang kaya dari segala sesuatu di AST. Ini menangani banyak hal. Ia tahu tentang ekspansi makro, dan memungkinkan Anda mengikuti rantai itu. Ya, saya berbicara tentang transformasi atau terjemahan kode sumber ke sumber.

Tesis mendasar saya di sini adalah bahwa dentang sekarang memungkinkan kita untuk membuat executable yang mereka sendiri dapat berfungsi sebagai tahapan preprocessor kustom yang ideal untuk perangkat lunak C ++ kami, dan kami dapat mengimplementasikan tahapan pemrograman metaprogram ini dengan C ++. Kami hanya dibatasi oleh fakta bahwa tahap ini harus mengambil input yang merupakan kode C ++ yang valid dan menghasilkan sebagai kode C ++ yang lebih valid. Ditambah kendala apa pun lainnya yang diterapkan sistem build Anda.

Input harus setidaknya sangat dekat dengan kode C ++ yang valid karena, bagaimanapun, dentang adalah front-end kompiler dan kami hanya melihat-lihat dan menjadi kreatif dengan API-nya. Saya tidak tahu apakah ada ketentuan untuk dapat mendefinisikan sintaks baru untuk digunakan, tetapi jelas kita harus mengembangkan cara untuk menguraikannya dengan benar dan menambahkannya ke proyek dentang untuk melakukan ini. Mengharapkan lagi adalah memiliki sesuatu dalam proyek dentang yang di luar jangkauan.

Bukan masalah. Saya akan membayangkan bahwa beberapa fungsi makro no-op dapat menangani tugas ini.

Cara lain untuk melihat apa yang saya jelaskan adalah menerapkan konstruksi metaprogramming menggunakan runtime C ++ dengan memanipulasi AST dari kode sumber kami (terima kasih untuk dentang dan API-nya) alih-alih menerapkannya menggunakan alat yang lebih terbatas yang tersedia dalam bahasa itu sendiri. Ini juga memiliki manfaat kinerja kompilasi yang jelas (header-template header kompilasi lambat secara proporsional dengan seberapa sering Anda menggunakannya. Banyak hal yang dikompilasi kemudian dengan hati-hati dicocokkan dan dibuang oleh linker).

Namun, hal ini datang dengan biaya untuk memperkenalkan satu atau dua langkah tambahan dalam proses pembuatan dan juga dalam persyaratan untuk menulis beberapa (tentu saja) perangkat lunak yang agak lebih bertele-tele (tetapi setidaknya itu adalah runtime langsung C ++) sebagai bagian dari alat kami .

Itu bukan gambaran keseluruhan. Saya cukup yakin bahwa ada ruang fungsionalitas yang jauh lebih besar yang bisa didapat dari menghasilkan kode yang sangat sulit atau tidak mungkin dengan fitur-fitur bahasa inti. Dalam C ++ Anda dapat menulis templat atau makro atau kombinasi gila keduanya, tetapi dalam alat dentang Anda dapat memodifikasi kelas dan fungsi dengan cara APA PUN yang dapat Anda capai dengan C ++, saat runtime , sambil memiliki akses penuh ke konten semantik, selain template dan makro dan yang lainnya.

Jadi, saya bertanya-tanya mengapa semua orang belum melakukan ini. Apakah fungsi ini dari dentang begitu baru dan tidak ada yang akrab dengan hirarki kelas besar AST dentang? Itu tidak mungkin.

Mungkin saya hanya meremehkan kesulitan ini sedikit, tetapi melakukan "kompilasi waktu manipulasi string" dengan alat dentang hampir secara kriminal sederhana. Itu verbose, tapi sangat mudah. Yang diperlukan hanyalah sekelompok fungsi makro tanpa std::stringoperasi yang memetakan ke operasi nyata yang sebenarnya . Plugin dentang mengimplementasikan ini dengan mengambil semua panggilan makro no-op yang relevan, dan melakukan operasi dengan string. Alat ini kemudian dimasukkan sebagai bagian dari proses pembangunan. Selama build, panggilan fungsi makro no-op ini secara otomatis dievaluasi ke dalam hasilnya, dan kemudian dimasukkan kembali sebagai string waktu kompilasi lama yang biasa dalam program. Program kemudian dapat dikompilasi seperti biasa. Bahkan program yang dihasilkan ini juga jauh lebih portabel sebagai hasilnya, tidak memerlukan kompiler baru yang mendukung C ++ 11.

Steven Lu
sumber
Ini pertanyaan yang sangat panjang. Bisakah Anda memadatkannya ke poin paling relevan?
amon
Saya memposting banyak pertanyaan panjang. Tetapi terutama dengan yang ini, saya pikir semua bagian dari pertanyaan itu penting. Mungkin melewati 6 paragraf pertama? Ha ha.
Steven Lu
3
Kedengarannya sangat mirip dengan macro sintaksis yang dipelopori di Lisp dan baru-baru ini diambil oleh Haxe, Nemerle, Scala dan bahasa-bahasa serupa. Ada beberapa bacaan tentang mengapa Lisp macro dianggap berbahaya. Meskipun saya belum mendengar argumen yang meyakinkan, Anda mungkin menemukan alasan mengapa orang enggan menambahkannya ke setiap bahasa (selain fakta bahwa itu tidak selalu langsung ke depan).
back2dos
Ya itu C ++ meta-ifying. Yang bisa berarti lebih baik, kode lebih cepat. Adapun bahasa - bahasa itu. Di mana saya harus mulai? Apa itu permainan video multi-juta dolar yang diterapkan dalam salah satu bahasa itu? Apa yang diterapkan browser web modern dalam bahasa-bahasa tersebut? Kernel OS? Baiklah, sepertinya Haxe memiliki daya tarik yang besar, tetapi Anda mendapatkan idenya.
Steven Lu
1
@ nwp, Ya, saya tidak bisa tidak menunjukkan bahwa Anda sepertinya telah melewatkan seluruh poin dari postingan ini. String compile-time hanyalah contoh konkret yang paling dibuat-buat dan minimal dari kemampuan yang tersedia bagi kita sekarang.
Steven Lu

Jawaban:

7

Ya, Virginia, ada Sinterklas.

Gagasan menggunakan program untuk memodifikasi program sudah ada sejak lama. Ide asli datang dari John von Neumann dalam bentuk komputer program yang tersimpan. Tetapi kode mesin yang memodifikasi kode mesin dengan cara sewenang-wenang cukup merepotkan.

Orang pada umumnya ingin memodifikasi kode sumber . Ini sebagian besar diwujudkan dalam bentuk sistem transformasi program (PTS) .

PTS umumnya menawarkan, setidaknya untuk satu bahasa pemrograman, kemampuan untuk menguraikan AST, memanipulasi AST itu, dan meregenerasi teks sumber yang valid. Jika sebenarnya Anda menggali, untuk sebagian besar bahasa arus utama, seseorang telah membangun alat seperti itu (Dentang adalah contoh untuk C ++, kompiler Java menawarkan kemampuan ini sebagai API, Microsoft menawarkan Rosyln, Eclipse's JDT, ...) dengan prosedural API yang sebenarnya cukup bermanfaat. Untuk komunitas yang lebih luas, hampir setiap komunitas spesifik bahasa dapat menunjuk ke sesuatu seperti ini, diimplementasikan dengan berbagai tingkat kematangan (biasanya sederhana, banyak "hanya pengurai yang memproduksi AST"). Selamat metaprogramming.

[Ada komunitas berorientasi refleksi yang mencoba melakukan metaprogramming dari dalam bahasa pemrograman, tetapi hanya mencapai modifikasi perilaku "runtime", dan hanya sejauh penyusun bahasa membuat beberapa informasi tersedia dengan refleksi. Dengan pengecualian LISP, selalu ada detail tentang program yang tidak tersedia oleh refleksi ("Luke, Anda membutuhkan sumber") yang selalu membatasi apa yang dapat dilakukan refleksi.]

PTS yang lebih menarik melakukan ini untuk bahasa yang arbitrer (Anda memberikan alat deskripsi bahasa sebagai parameter konfigurasi, termasuk minimal BNF). PTS semacam itu juga memungkinkan Anda melakukan transformasi "sumber ke sumber", misalnya, menentukan pola secara langsung menggunakan sintaksis permukaan bahasa yang ditargetkan; menggunakan pola seperti itu, Anda dapat memberi kode fragmen yang menarik, dan / atau menemukan dan mengganti fragmen kode. Ini jauh lebih nyaman daripada API pemrograman, karena Anda tidak perlu tahu setiap detail mikroskopis tentang AST untuk melakukan sebagian besar pekerjaan Anda. Pikirkan ini sebagai meta-metaprogramming: -}

Kelemahan: kecuali PTS menawarkan berbagai macam analisis statis yang berguna (tabel simbol, kontrol dan analisis aliran data), sulit untuk menulis transformasi yang sangat menarik dengan cara ini, karena Anda perlu memeriksa jenis dan memverifikasi aliran informasi untuk sebagian besar tugas praktis. Sayangnya, kemampuan ini sebenarnya jarang terjadi di PTS umum. (Itu selalu tidak tersedia dengan yang pernah diusulkan "Jika saya hanya memiliki parser ..." Lihat bio saya untuk diskusi lebih lama tentang "Kehidupan Setelah Parsing").

Ada teorema yang mengatakan jika Anda dapat melakukan penulisan ulang string [demikian penulisan ulang pohon] Anda dapat melakukan transformasi sewenang-wenang; dan dengan demikian sejumlah PTS bersandar pada ini untuk mengklaim bahwa Anda dapat memrogram apa pun hanya dengan penulisan ulang pohon yang mereka tawarkan. Walaupun teorinya memuaskan dalam arti Anda sekarang yakin dapat melakukan apa saja, itu tidak memuaskan dengan cara yang sama bahwa kemampuan Mesin Turing untuk melakukan apa pun tidak menjadikan pemrograman Mesin Turing sebagai metode pilihan. (Hal yang sama berlaku untuk sistem hanya dengan API prosedural, jika mereka akan membiarkan Anda membuat perubahan sewenang-wenang ke AST [dan sebenarnya saya pikir ini tidak berlaku untuk Dentang]).

Apa yang Anda inginkan adalah yang terbaik dari kedua dunia, sebuah sistem yang menawarkan kepada Anda sifat umum dari jenis PTS parameter-bahasa (bahkan menangani beberapa bahasa), dengan analisis statis tambahan, kemampuan untuk mencampur transformasi sumber-ke-sumber dengan prosedural. Lebah. Saya hanya tahu dua yang melakukan ini:

  • Bahasa MetaProgramming Rascal (MPL)
  • Perangkat Reengineering Perangkat Lunak DMS kami

Kecuali Anda ingin menulis deskripsi bahasa dan analisis statis sendiri (untuk C ++ ini adalah pekerjaan yang luar biasa, itulah sebabnya Clang dibangun baik sebagai kompiler maupun sebagai fondasi metaprogram pemrograman prosedural umum), Anda akan menginginkan PTS dengan deskripsi bahasa matang sudah tersedia. Kalau tidak, Anda akan menghabiskan seluruh waktu Anda mengkonfigurasi PTS, dan tidak ada yang melakukan pekerjaan yang sebenarnya ingin Anda lakukan. [Jika Anda memilih bahasa acak, non-mainstream, langkah ini sangat sulit untuk dihindari].

Rascal mencoba melakukan ini dengan mengkooptasi "OPP" (Parsers Orang Lain) tetapi itu tidak membantu bagian analisis statis. Saya pikir mereka memiliki Java yang cukup baik, tetapi saya sangat yakin mereka tidak melakukan C atau C ++. Tapi, ini adalah alat penelitian akademik; sulit untuk menyalahkan mereka.

Saya tekankan, kami [komersial] DMS alat memang memiliki Java, C, C ++ ujung depan penuh yang tersedia. Untuk C ++, ia mencakup hampir semua yang ada di C ++ 14 untuk GCC dan bahkan variasi Microsoft (dan kami sedang memoles sekarang), ekspansi makro dan manajemen bersyarat, dan kontrol tingkat metode dan analisis aliran data. Dan ya, Anda dapat menentukan perubahan tata bahasa secara praktis; kami membangun sistem VectorC ++ khusus untuk klien yang secara radikal memperluas C ++ untuk menggunakan jumlah apa untuk operasi array data-paralel F90 / APL. DMS telah digunakan untuk melakukan tugas metaprogramming masif lainnya pada sistem C ++ besar (misalnya, pembentukan kembali arsitektur aplikasi). (Saya arsitek di belakang DMS).

Selamat meta-metaprogramming.

Ira Baxter
sumber
Keren, saya pikir Dentang dan DMS, sementara mereka memiliki beberapa kemampuan yang tumpang tindih, adalah bagian dari perangkat lunak yang tidak benar-benar dalam kategori yang sama. Maksud saya, satu mungkin sangat mahal dan saya mungkin tidak akan pernah bisa membenarkan sumber daya yang diperlukan untuk mendapatkan akses ke sana, dan yang lainnya adalah open source gratis yang tidak terbatas. Ini adalah perbedaan besar ... Bagian dari apa yang membuat saya bersemangat tentang kemampuan pemrograman metaprogram yang menarik ini adalah fakta bahwa diizinkan tidak hanya untuk menggunakannya secara bebas tetapi juga untuk mendistribusikan alat biner berbasis dentang secara bebas.
Steven Lu
Apa pun yang dijual secara komersial adalah "mahal sekali" dibandingkan dengan gratis. Biaya mentah bukan masalah; yang penting bagi sebagian orang, pengembalian untuk memperoleh produk komersial lebih tinggi daripada pengembalian untuk artefak gratis, jika tidak maka tidak akan ada perangkat lunak komersial. Ini jelas tergantung pada kebutuhan spesifik Anda. Dentang adalah titik menarik dalam ruang alat, dan pasti akan memiliki poin aplikasi yang berguna. Saya ingin berpikir (karena saya arsitek DMS) bahwa DMS memiliki kemampuan yang lebih luas. Dentang tidak mungkin mendukung bahasa selain C ++ dengan baik, sebagai contoh.
Ira Baxter
Pasti. Tidak ada pertanyaan bahwa DMS sangat kuat, hampir sampai ke titik ajaib (à la Arthur C. Clarke), dan meskipun dentang hebat, itu hanya merupakan antarmuka C ++ yang ditulis dengan baik, yang jumlahnya banyak. Banyak sekali langkah kecil ke depan, yaitu, masih tidak adil untuk membandingkannya dengan DMS. Sayangnya, bahkan dengan alat yang begitu kuat yang kita miliki, perangkat lunak yang berfungsi tidak menulis sendiri. Itu harus tetap ada melalui terjemahan yang cermat menggunakan alat, atau (cukup banyak selalu pilihan superior) ditulis segar.
Steven Lu
Anda tidak dapat membangun alat seperti Dentang atau DMS dari yang baru. Anda juga biasanya tidak mampu untuk membuang aplikasi yang Anda tulis dengan tim 10 lebih dari 5 tahun. Kita akan semakin membutuhkan alat-alat seperti itu, karena ukuran dan masa pakai perangkat lunak terus bertambah.
Ira Baxter
@ SevenLu: Ya, DMS terima kasih atas pujiannya, tapi tidak ada yang ajaib tentang hal itu. DMS memang memiliki manfaat hampir 2 dekade linier teknik dan platform arsitektur yang bersih (aw, shuck, YMMV) yang telah bertahan cukup baik. Demikian pula, Dentang memiliki banyak teknik yang baik di dalamnya. Saya setuju, mereka tidak dirancang untuk memecahkan masalah yang sama persis ... Ruang lingkup DMS secara eksplisit dimaksudkan untuk menjadi lebih besar ketika menyangkut manipulasi program simbolis, dan jauh lebih kecil ketika menjadi kompiler produksi.
Ira Baxter
4

Metaprogramming di C ++ dengan API kompiler (alih-alih menggunakan templat) memang menarik dan praktis mungkin. Karena metaprogramming belum (belum) distandarisasi, Anda akan diikat ke kompiler tertentu, yang tidak demikian halnya dengan templat.

Jadi, saya bertanya-tanya mengapa semua orang belum melakukan ini. Apakah fungsi ini dari dentang begitu baru dan tidak ada yang akrab dengan hirarki kelas besar AST dentang? Itu tidak mungkin.

Banyak orang melakukan ini, tetapi dalam bahasa lain. Pendapat saya adalah bahwa sebagian besar pengembang C ++ (atau Java, atau C) tidak melihat perlunya (mungkin memang seharusnya), atau tidak terbiasa dengan pendekatan metaprogramming; Saya juga berpikir bahwa mereka senang dengan fitur-fitur penghasil ulang kode / refactoring dari IDE mereka, dan bahwa apa pun yang lebih bagus mungkin terlihat terlalu rumit / sulit untuk dipertahankan / sulit di-debug. Tanpa alat yang tepat, itu mungkin benar. Anda juga harus mempertimbangkan inersia akun dan masalah non-teknis lainnya, seperti mempekerjakan dan / atau melatih orang.

Ngomong-ngomong, karena kita menyebutkan Common Lisp dan sistem makronya (lihat jawaban Basile), saya harus mengatakan bahwa baru kemarin, Clasp dirilis (saya tidak berafiliasi):

Clasp bermaksud untuk menjadi implementasi Common Lisp yang sesuai yang mengkompilasi ke LLVM IR. Selain itu, itu mengekspos perpustakaan Dentang (AST, Matcher) ke pengembang.

  • Pertama, itu berarti Anda bisa menulis di CL dan tidak menggunakan C ++ lagi, kecuali saat menggunakan pustaka-pustaka (dan jika Anda membutuhkan makro, gunakan makro CL).

  • Kedua, Anda dapat menulis alat di CL untuk kode C ++ yang ada (analisis, refactoring, ...).

coredump
sumber
3

Beberapa kompiler C ++ memiliki lebih atau kurang API yang terdokumentasi dan stabil, khususnya kompiler perangkat lunak yang paling gratis.

Dentang / LLVM sebagian besar adalah set besar perpustakaan, dan Anda bisa menggunakannya.

GCC terbaru menerima plugin . Khususnya, Anda dapat memperluasnya menggunakan MELT (yang merupakan meta-plugin, yang memberikan bahasa spesifik tingkat tinggi untuk memperpanjang GCC).

Perhatikan bahwa sintaksis C ++ tidak mudah diperluas dalam GCC (dan mungkin juga tidak dalam Dentang), tetapi Anda dapat menambahkan pragma, builtin, atribut, dan kompiler pass Anda sendiri untuk melakukan apa yang Anda inginkan (mungkin juga menyediakan beberapa makro preprosesor yang menjalankan hal-hal ini) untuk memberikan sintaks yang ramah pengguna).

Anda mungkin tertarik dengan bahasa dan kompiler multi-tahap, lihat mis. Menerapkan Bahasa Multi-tahap Menggunakan AST, Gensym, dan kertas Refleksi oleh C.Calcagno et al. dan bekerja di sekitar MetaOcaml . Anda tentu harus melihat ke dalam fasilitas makro Common Lisp . Dan Anda bisa tertarik oleh pustaka JIT seperti libjit , GNU lightning , bahkan LLVM , atau cukup -dari run-time! - buat beberapa kode C ++, ambil kompilasi dari itu menjadi pustaka dinamis objek bersama, lalu dlopen (3) yang dibagikan obyek. Blog J.Pitrat juga terkait dengan pendekatan reflektif semacam itu. Dan juga RefPerSys .

Basile Starynkevitch
sumber
Menarik. Sangat bagus melihat GCC terus berevolusi di sini. Ini bukan jawaban yang menjawab apa pun yang saya tanyakan, tetapi saya tetap menyukainya.
Steven Lu
Re: suntingan baru Anda ... Itu poin bagus tentang penulisan ulang kode itu sendiri. Ini sebenarnya mulai membawa kemampuan meta-program seperti itu ke C ++ juga, jauh lebih mudah diakses dari sebelumnya, yang juga cukup menarik.
Steven Lu