Ketika mendesain bahasa pemrograman sendiri, kapan masuk akal untuk menulis konverter yang mengambil kode sumber dan mengubahnya menjadi kode C atau C ++ sehingga saya bisa menggunakan kompiler yang ada seperti gcc untuk berakhir dengan kode mesin? Apakah ada proyek yang menggunakan pendekatan ini?
34
Jawaban:
Meneruskan kode C adalah kebiasaan yang sudah sangat mapan. C asli dengan kelas (dan implementasi C ++ awal, kemudian disebut Cfront ) berhasil melakukannya. Beberapa implementasi Lisp atau Skema melakukan hal itu, misalnya Skema Ayam , Skema48 , Bigloo . Beberapa orang diterjemahkan Prolog ke C . Dan begitu pula beberapa versi Mozart (dan ada upaya untuk mengkompilasi bytecode Ocaml ke C ). Sistem kecerdasan buatan J.Pitrat, CAIA, juga bootstrap dan menghasilkan semua kode C-nya. Vala juga diterjemahkan ke C, untuk kode terkait GTK. Buku Queinnec, Lisp In Small Pieces memiliki beberapa bab tentang terjemahan ke C.
Salah satu masalah saat menerjemahkan ke C adalah panggilan berulang-ulang . Standar C tidak menjamin bahwa kompiler C menerjemahkannya dengan benar (menjadi "lompatan dengan argumen", yaitu tanpa memakan tumpukan panggilan), bahkan jika dalam beberapa kasus, versi terbaru GCC (atau Dentang / LLVM) melakukan optimasi itu .
Masalah lainnya adalah pengumpulan sampah . Beberapa implementasi hanya menggunakan pengumpul sampah konservatif Boehm (yang ramah-C ...). Jika Anda ingin mengumpulkan kode sampah (seperti beberapa implementasi Lisp lakukan, misalnya SBCL) yang mungkin menjadi mimpi buruk (Anda ingin
dlclose
di Posix).Namun masalah lain adalah berurusan dengan kelanjutan kelas satu dan panggilan / cc . Tapi trik pintar mungkin dilakukan (lihat di dalam Skema Ayam). Mengakses tumpukan panggilan bisa memerlukan banyak trik (tapi lihat GNU backtrace , dll ....). Kegigihan ortogonal dari kelanjutan (yaitu tumpukan atau benang) akan sulit di C.
Penanganan pengecualian sering merupakan masalah untuk memancarkan panggilan pintar ke longjmp dll ...
Anda mungkin ingin menghasilkan (dalam kode C Anda yang dipancarkan)
#line
arahan yang sesuai . Ini membosankan dan membutuhkan banyak pekerjaan (Anda akan ingin itu misalnya menghasilkangdb
kode yang lebih mudah -debuggable).Saya MELT lispy bahasa domain yang spesifik (untuk menyesuaikan atau memperpanjang GCC ) diterjemahkan ke C (sebenarnya untuk miskin C ++ sekarang). Ia memiliki pengumpul sampah penyalinan generasinya sendiri. (Anda mungkin tertarik oleh Qish atau Ravenbrook MPS ). Sebenarnya, GC generasi lebih mudah dalam kode C yang dihasilkan mesin daripada kode C yang ditulis tangan (karena Anda akan menyesuaikan generator kode C Anda untuk penghalang tulis dan mesin GC Anda).
Saya tidak tahu implementasi bahasa apa pun yang diterjemahkan ke kode C ++ asli , yaitu menggunakan beberapa teknik "pengumpulan sampah waktu" untuk memancarkan kode C ++ menggunakan banyak template STL dan menghormati idiom RAII . (tolong beri tahu jika Anda tahu satu).
Apa yang lucu hari ini adalah bahwa (pada desktop Linux saat ini) kompiler C mungkin cukup cepat untuk mengimplementasikan loop read-eval-print- level interaktif yang diterjemahkan ke C: Anda akan memancarkan kode C (beberapa ratus baris) pada setiap pengguna interaksi, Anda akan
fork
kompilasi menjadi objek bersama, yang kemudian Anda lakukandlopen
. (MELT melakukan semuanya sudah siap, dan biasanya cukup cepat). Semua ini mungkin memerlukan beberapa persepuluh detik dan dapat diterima oleh pengguna akhir.Jika memungkinkan, saya akan merekomendasikan untuk menerjemahkan ke C, bukan ke C ++, khususnya karena kompilasi C ++ lambat.
Jika Anda menerapkan bahasa Anda, Anda mungkin juga mempertimbangkan (alih-alih memancarkan kode C) beberapa pustaka JIT seperti libjit , GNU lightning , asmjit , atau bahkan LLVM atau GCCJIT . Jika Anda ingin menerjemahkan ke C, Anda terkadang menggunakan tinycc : ia mengkompilasi dengan sangat cepat kode C yang dihasilkan (bahkan dalam memori) untuk memperlambat kode mesin. Tetapi secara umum Anda ingin mengambil keuntungan dari optimasi yang dilakukan oleh kompiler C nyata seperti GCC
Jika Anda menerjemahkan ke bahasa C Anda, pastikan untuk membangun AST seluruh kode C yang dihasilkan dalam memori terlebih dahulu (ini juga membuat lebih mudah untuk menghasilkan semua deklarasi terlebih dahulu, lalu semua definisi dan kode fungsi). Anda dapat melakukan beberapa optimasi / normalisasi dengan cara ini. Anda juga dapat tertarik pada beberapa ekstensi GCC (mis. Goto yang dihitung). Anda mungkin ingin menghindari menghasilkan fungsi C yang sangat besar - misalnya dari seratus ribu garis C yang dihasilkan - (Anda sebaiknya membaginya menjadi bagian-bagian yang lebih kecil) karena mengoptimalkan kompiler C sangat tidak senang dengan fungsi C yang sangat besar (dalam praktiknya, dan secara eksperimental,
gcc -O
waktu kompilasi fungsi besar sebanding dengan kuadrat ukuran kode fungsi). Jadi batasi ukuran fungsi C yang Anda buat masing-masing beberapa ribu baris.Perhatikan bahwa kedua dentang (melalui LLVM ) dan GCC (melalui libgccjit ) C & C ++ compiler menawarkan beberapa cara untuk memancarkan beberapa representasi internal yang cocok untuk compiler ini, namun hal ini kekuatan (atau tidak) lebih sulit daripada memancarkan C (atau C ++) kode, dan khusus untuk setiap kompiler.
Jika mendesain bahasa yang akan diterjemahkan ke C, Anda mungkin ingin memiliki beberapa trik (atau konstruksi) untuk menghasilkan campuran C dengan bahasa Anda. Kertas DSL2011 saya MELT : Bahasa Tertentu Domain Diterjemahkan yang tertanam dalam GCC Compiler akan memberi Anda petunjuk yang bermanfaat.
sumber
Masuk akal ketika waktu untuk menghasilkan kode mesin penuh melebihi ketidaknyamanan memiliki langkah perantara mengkompilasi "IL" Anda ke dalam kode mesin menggunakan kompiler C.
Biasanya bahasa khusus domain ditulis dengan cara ini, sistem tingkat yang sangat tinggi digunakan untuk mendefinisikan atau menggambarkan proses yang kemudian dikompilasi ke dalam executable atau dll. Waktu yang dibutuhkan untuk menghasilkan kerja / perakitan yang baik jauh lebih besar daripada menghasilkan C, dan C cukup menutup kode perakitan untuk kinerja, sehingga masuk akal untuk menghasilkan C dan menggunakan kembali keterampilan penulis kompiler C. Perhatikan bahwa ini tidak hanya mengkompilasi, tetapi juga mengoptimalkan - orang-orang yang menulis gcc atau llvm telah menghabiskan banyak waktu membuat kode mesin yang dioptimalkan, itu akan bodoh untuk mencoba menemukan kembali semua kerja keras mereka.
Mungkin lebih bisa diterima untuk menggunakan kembali backend compiler LLVM yang IIRC netral bahasa, jadi Anda menghasilkan instruksi LLVM daripada kode C.
sumber
Menulis kompiler untuk menghasilkan kode mesin mungkin tidak jauh lebih sulit daripada menulis yang menghasilkan C (dalam beberapa kasus mungkin lebih mudah), tetapi kompiler yang menghasilkan kode mesin hanya akan dapat menghasilkan program yang bisa dijalankan pada platform tertentu yang ini sudah tertulis; Sebaliknya, sebuah kompiler yang menghasilkan kode C mungkin dapat menghasilkan program untuk platform apa pun yang menggunakan dialek C yang mana kode yang dihasilkan dirancang untuk mendukung. Perhatikan bahwa dalam banyak kasus dimungkinkan untuk menulis kode C yang sepenuhnya portabel dan yang akan berperilaku seperti yang diinginkan tanpa menggunakan perilaku yang tidak dijamin oleh standar C, tetapi kode yang bergantung pada perilaku yang dijamin platform mungkin dapat berjalan lebih cepat pada platform yang membuat jaminan itu daripada kode yang tidak.
Sebagai contoh, anggap suatu bahasa mendukung fitur untuk menghasilkan a
UInt32
dari empat byte berturut-turut dari yang selarasUInt8[]
, ditafsirkan dalam mode big-endian. Pada beberapa kompiler, seseorang dapat menulis kode sebagai:dan minta kompiler menghasilkan operasi memuat kata yang diikuti dengan instruksi reverse-bytes-in-word. Beberapa kompiler, bagaimanapun, tidak akan mendukung pengubah __packed dan jika tidak ada akan menghasilkan kode yang tidak akan berfungsi.
Atau, seseorang dapat menulis kode sebagai:
kode seperti itu harus bekerja pada platform apa pun, bahkan di mana
CHAR_BITS
tidak 8 (dengan asumsi bahwa setiap oktet data sumber berakhir dalam elemen array yang berbeda), tetapi kode tersebut mungkin tidak berjalan hampir secepat seperti yang non-portabel versi pada platform yang mendukung yang pertama.Perhatikan bahwa portabilitas sering kali mengharuskan kode menjadi sangat liberal dengan typecast dan konstruksi serupa. Sebagai contoh, kode yang ingin mengalikan dua bilangan bulat 32-bit unsigned dan menghasilkan 32 bit yang lebih rendah dari hasil harus untuk portabilitas ditulis sebagai:
Tanpa itu
1u
, kompiler pada sistem di mana INT_BITS berkisar antara 33 hingga 64 dapat secara sah melakukan apa pun yang diinginkan jika produk x dan y lebih besar dari 2.147.483.647, dan beberapa kompiler cenderung mengambil keuntungan dari peluang tersebut.sumber
Anda memiliki beberapa jawaban yang sangat baik di atas tetapi mengingat bahwa, dalam komentar, Anda menjawab pertanyaan, "Mengapa Anda ingin membuat bahasa pemrograman sendiri di tempat pertama?" Dengan "Itu terutama untuk tujuan belajar," Saya akan menjawab dari sudut yang berbeda.
Masuk akal untuk menulis konverter yang mengambil kode sumber dan mengubahnya menjadi kode C atau C ++, sehingga Anda dapat menggunakan kompiler yang ada seperti gcc untuk berakhir dengan kode mesin, jika Anda lebih tertarik untuk belajar tentang leksikal, sintaksis dan analisis semantik daripada Anda belajar tentang pembuatan kode dan optimisasi!
Menulis generator kode mesin Anda sendiri adalah pekerjaan yang cukup signifikan yang dapat Anda hindari dengan mengkompilasi ke kode C, jika itu bukan yang Anda minati!
Namun, jika Anda tertarik dengan program perakitan dan terpesona oleh tantangan mengoptimalkan kode di level terendah, maka tentu saja, tulis pembuat kode sendiri untuk pengalaman belajar!
sumber
Itu tergantung pada Sistem Operasi apa yang Anda gunakan jika Anda menggunakan Windows ada Microsoft IL (Bahasa Menengah) yang mengubah kode Anda menjadi bahasa perantara sehingga tidak perlu waktu untuk dikompilasi ke dalam kode mesin. Atau Jika Anda menggunakan Linux ada kompiler terpisah untuk itu
Kembali ke pertanyaan Anda adalah ketika Anda ketika merancang bahasa Anda sendiri, Anda harus memiliki kompiler atau juru bahasa terpisah untuk itu karena mesin tidak tahu bahasa tingkat tinggi. Kode Anda harus dikompilasi ke dalam kode mesin untuk membuatnya berguna untuk mesin
sumber
Your code should be compiled into machine code to make it useful for machine
- Jika kompiler Anda menghasilkan kode c sebagai output, Anda bisa memasukkan kode c ke dalam kompiler ac untuk menghasilkan kode mesin, bukan?