Rust: Masalah pelacakan untuk `asm` (rakitan sebaris)

Dibuat pada 9 Nov 2015  ·  111Komentar  ·  Sumber: rust-lang/rust

Masalah ini melacak stabilisasi perakitan inline. Fitur saat ini belum melalui proses RFC, dan mungkin perlu melakukannya sebelum stabilisasi.

A-inline-assembly B-unstable C-tracking-issue T-lang requires-nightly

Komentar yang paling membantu

Saya ingin menunjukkan bahwa sintaks asm inline LLVM berbeda dari yang digunakan oleh dentang/gcc. Perbedaan meliputi:

  • LLVM menggunakan $0 alih-alih %0 .
  • LLVM tidak mendukung operand asm bernama %[name] .
  • LLVM mendukung jenis batasan register yang berbeda: misalnya "{eax}" alih-alih "a" pada x86.
  • LLVM mendukung batasan register eksplisit ( "{r11}" ). Di C Anda harus menggunakan variabel register asm untuk mengikat nilai ke register ( register asm("r11") int x ).
  • Batasan LLVM "m" dan "=m" pada dasarnya rusak. Dentang menerjemahkan ini ke dalam batasan memori tidak langsung "*m" dan "=*m" dan meneruskan alamat variabel ke LLVM alih-alih variabel itu sendiri.
  • dll...

Dentang akan mengonversi asm sebaris dari format gcc ke dalam format LLVM sebelum meneruskannya ke LLVM. Itu juga melakukan beberapa validasi batasan: misalnya memastikan bahwa operan "i" adalah konstanta waktu kompilasi,


Mengingat hal ini, saya pikir kita harus menerapkan terjemahan dan validasi yang sama dengan yang dilakukan dentang dan mendukung sintaks asm inline gcc yang tepat alih-alih yang aneh LLVM.

Semua 111 komentar

Apakah akan ada kesulitan dalam memastikan kompatibilitas mundur dari perakitan inline dalam kode stabil?

@main-- memiliki komentar yang bagus di https://github.com/rust-lang/rfcs/pull/1471#issuecomment -173982852 yang saya reproduksi di sini untuk anak cucu:

Dengan semua yang terbuka bug dan ketidakstabilan sekitarnya asm ()! (Ada banyak ), saya benar-benar tidak berpikir itu siap untuk stabilisasi - meskipun aku akan senang untuk memiliki inline asm stabil di Rust.

Kita juga harus mendiskusikan apakah asm!() hari ini benar-benar merupakan solusi terbaik atau jika sesuatu seperti RFC #129 atau bahkan D akan lebih baik. Satu poin penting untuk dipertimbangkan di sini adalah bahwa asm() tidak mendukung kumpulan batasan yang sama seperti gcc. Oleh karena itu, kita dapat:

  • Tetap berpegang pada perilaku LLVM dan tulis dokumen untuk itu (karena saya tidak dapat menemukannya). Bagus karena menghindari kerumitan di rustc. Buruk karena akan membingungkan programmer yang berasal dari C/C++ dan karena beberapa kendala mungkin sulit untuk ditiru dalam kode Rust.
  • Meniru gcc dan hanya menautkan ke dokumen mereka : Bagus karena banyak pemrogram sudah mengetahui hal ini dan ada banyak contoh yang dapat disalin-tempel dengan sedikit modifikasi. Buruk karena ini adalah ekstensi nontrivial untuk kompiler.
  • Lakukan sesuatu yang lain (seperti yang dilakukan D): Banyak pekerjaan yang mungkin berhasil atau tidak. Jika dilakukan dengan benar, ini bisa jauh lebih unggul daripada gaya gcc dalam hal ergonomi sementara mungkin berintegrasi lebih baik dengan bahasa dan kompiler daripada hanya gumpalan buram (banyak lambaian tangan di sini karena saya tidak cukup akrab dengan internal kompiler untuk menilai ini) .

Akhirnya, hal lain yang perlu dipertimbangkan adalah #1201 yang dalam desainnya saat ini (saya pikir) sangat bergantung pada inline asm - atau inline asm dilakukan dengan benar, dalam hal ini.

Saya pribadi berpikir akan lebih baik untuk melakukan apa yang dilakukan Microsoft di MSVC x64: mendefinisikan serangkaian fungsi intrinsik (hampir) yang komprehensif, untuk setiap instruksi asm, dan melakukan "inline asm" secara eksklusif melalui intrinsik tersebut. Jika tidak, sangat sulit untuk mengoptimalkan kode di sekitar inline asm, yang ironis karena banyak penggunaan inline asm dimaksudkan untuk optimasi kinerja.

Salah satu keuntungan dari pendekatan berbasis intrinsik adalah bahwa itu tidak perlu menjadi hal yang semua-atau-tidak sama sekali. Anda dapat menentukan intrinsik yang paling dibutuhkan terlebih dahulu, dan membangun set secara bertahap. Misalnya, untuk kripto, memiliki _addcarry_u64 , _addcarry_u32 . Perhatikan bahwa pekerjaan untuk melakukan instrinsik tampaknya telah dilakukan dengan cukup menyeluruh: https://github.com/huonw/llvmint.

Lebih lanjut, intrinsik akan menjadi ide yang baik untuk ditambahkan bahkan jika akhirnya diputuskan untuk mendukung asm sebaris, karena mereka jauh lebih nyaman digunakan (berdasarkan pengalaman saya menggunakannya dalam C dan C++), jadi mulailah dengan intrinsik dan melihat seberapa jauh kita mendapatkan tampaknya seperti hal yang tidak berisiko-salah-salah.

Intrinsiknya bagus, tetapi asm! dapat digunakan untuk lebih dari sekadar menyisipkan instruksi.
Misalnya, lihat cara saya membuat catatan ELF di peti probe .
https://github.com/cuviper/rust-libprobe/blob/master/src/platform/systemtap.rs

Saya berharap peretasan semacam itu akan jarang terjadi, tetapi saya pikir itu masih merupakan hal yang berguna untuk didukung.

@briansmith

Asm sebaris juga berguna untuk kode yang ingin melakukan alokasi register/stack sendiri (mis. fungsi telanjang).

@briansmith ya itu adalah beberapa alasan bagus untuk menggunakan intrinsik jika memungkinkan. Tapi itu bagus untuk memiliki perakitan inline sebagai palka excape pamungkas.

@briansmith Perhatikan bahwa asm!() adalah _kind of_ superset intrinsik karena Anda dapat membangun yang terakhir menggunakan yang pertama. (Argumen umum terhadap alasan ini adalah bahwa kompiler secara teoritis dapat mengoptimalkan _through_ intrinsik, misalnya mengangkatnya keluar dari loop, menjalankan CSE pada mereka, dll. Namun, itu adalah tandingan yang cukup kuat bahwa siapa pun yang menulis asm untuk tujuan _optimization_ akan melakukan pekerjaan yang lebih baik itu daripada kompilernya.) Lihat juga https://github.com/rust-lang/rust/issues/29722#issuecomment -207628164 dan https://github.com/rust-lang/rust/issues/29722# issuecomment -207823543 untuk kasus di mana inline asm berfungsi tetapi intrinsik tidak.

Di sisi lain, intrinsik sangat bergantung pada "kompiler yang cukup cerdas" untuk mencapai _setidaknya_ kinerja yang akan diperoleh dengan implementasi asm yang digulung secara manual. Pengetahuan saya tentang ini sudah usang tetapi kecuali jika ada kemajuan yang signifikan, implementasi berbasis intrinsik masih jauh lebih rendah dalam banyak - jika tidak sebagian besar - kasus. Tentu saja mereka jauh lebih nyaman untuk digunakan tetapi saya akan mengatakan bahwa programmer benar-benar tidak terlalu peduli tentang _that_ ketika mereka bersedia turun ke dunia instruksi CPU tertentu.

Sekarang pertimbangan menarik lainnya adalah bahwa intrinsik dapat digabungkan dengan kode fallback pada arsitektur yang tidak didukung. Ini memberi Anda yang terbaik dari kedua dunia: Kode Anda masih portabel - itu hanya dapat menggunakan beberapa operasi akselerasi perangkat keras di mana perangkat keras mendukungnya. Tentu saja ini hanya benar-benar terbayar baik untuk instruksi yang sangat umum atau jika aplikasi memiliki satu arsitektur target yang jelas. Sekarang alasan mengapa saya menyebutkan ini adalah bahwa sementara orang dapat berargumen bahwa ini bahkan mungkin _tidak diinginkan_ dengan intrinsik _compiler-provided_ (karena Anda mungkin akan peduli apakah Anda benar-benar mendapatkan versi yang dipercepat ditambah kompleksitas kompiler tidak pernah baik) saya akan mengatakan bahwa itu adalah cerita yang berbeda jika intrinsik disediakan oleh _library_ (dan hanya diimplementasikan menggunakan inline asm). Sebenarnya, ini adalah gambaran besar yang saya lebih suka meskipun saya dapat melihat diri saya menggunakan intrinsik lebih dari inline asm.

(Saya menganggap intrinsik dari RFC #1199 agak ortogonal dengan diskusi ini karena sebagian besar ada untuk membuat SIMD berfungsi.)

@briansmith

Jika tidak, sangat sulit untuk mengoptimalkan kode di sekitar inline asm, yang ironis karena banyak penggunaan inline asm dimaksudkan untuk optimasi kinerja.

Saya tidak yakin apa yang Anda maksud di sini. Memang benar bahwa kompiler tidak dapat memecah asm menjadi operasi individualnya untuk melakukan pengurangan kekuatan atau pengoptimalan lubang intip di atasnya. Namun pada model GCC, setidaknya compiler dapat mengalokasikan register yang digunakannya, menyalinnya ketika mereplikasi jalur kode, menghapusnya jika tidak pernah digunakan, dan seterusnya. Jika asm tidak volatile, GCC memiliki informasi yang cukup untuk memperlakukannya seperti operasi buram lainnya seperti, katakanlah, fsin . Seluruh motivasi untuk desain yang aneh adalah untuk membuat asm sebaris sesuatu yang dapat dikacaukan oleh pengoptimal.

Tapi saya belum banyak menggunakannya, terutama baru-baru ini. Dan saya tidak punya pengalaman dengan fitur LLVM. Jadi saya bertanya-tanya apa yang berubah, atau apa yang saya salah paham selama ini.

Kami membahas masalah ini pada minggu kerja baru-baru ini karena survei @japaric tentang ekosistem no_std memiliki makro asm! sebagai salah satu fitur yang lebih umum digunakan. Sayangnya kami tidak melihat cara mudah untuk menstabilkan fitur ini, tetapi saya ingin mencatat catatan yang kami miliki untuk memastikan kami tidak melupakan semua ini.

  • Pertama, saat ini kami tidak memiliki spesifikasi sintaks yang bagus yang diterima di makro asm! . Saat ini biasanya berakhir dengan "lihat LLVM" yang mengatakan "lihat dentang" yang mengatakan "lihat gcc" yang tidak memiliki dokumen yang bagus. Pada akhirnya ini biasanya berakhir pada "baca contoh orang lain dan sesuaikan" atau "baca kode sumber LLVM". Untuk stabilisasi, minimal kita harus memiliki spesifikasi sintaks dan dokumentasi.

  • Saat ini, sejauh yang kami tahu, tidak ada jaminan stabilitas dari LLVM. Makro asm! adalah pengikatan langsung dengan apa yang dilakukan LLVM saat ini. Apakah ini berarti kita masih dapat dengan bebas meningkatkan LLVM kapan pun kita mau? Apakah LLVM menjamin tidak akan pernah merusak sintaks ini? Cara untuk mengatasi masalah ini adalah dengan memiliki lapisan kita sendiri yang mengkompilasi ke sintaks LLVM. Dengan begitu kita dapat mengubah LLVM kapan pun kita mau dan jika implementasi perakitan inline di LLVM berubah, kita bisa memperbarui terjemahan kita ke sintaks LLVM. Jika asm! ingin menjadi stabil, pada dasarnya kita membutuhkan beberapa mekanisme untuk menjamin stabilitas di Rust.

  • Saat ini ada beberapa bug yang terkait dengan perakitan inline. Tag A-inline-assembly adalah titik awal yang baik, dan saat ini dipenuhi dengan ICE, segfaults di LLVM, dll. Secara keseluruhan, fitur ini, seperti yang diterapkan hari ini, tampaknya tidak memenuhi jaminan kualitas yang diharapkan orang lain dari stabil fitur di Rust.

  • Menstabilkan perakitan inline dapat membuat implementasi backend alternatif menjadi sangat sulit. Misalnya backend seperti miri atau cranelift mungkin membutuhkan waktu yang sangat lama untuk mencapai paritas fitur dengan backend LLVM, tergantung pada implementasinya. Ini mungkin berarti bahwa ada bagian yang lebih kecil dari apa yang dapat dilakukan di sini, tetapi ini adalah sesuatu yang penting untuk diingat ketika mempertimbangkan untuk menstabilkan perakitan inline.


Terlepas dari masalah yang tercantum di atas, kami ingin memastikan setidaknya ada beberapa kemampuan untuk memajukan masalah ini! Untuk itu kami melakukan brainstorming beberapa strategi tentang bagaimana kami dapat mendorong perakitan inline menuju stabilisasi. Cara utama ke depan adalah menyelidiki apa yang dilakukan dentang. Agaknya dentang dan C memiliki sintaks perakitan sebaris yang stabil secara efektif dan mungkin saja kita dapat mencerminkan apa pun yang dilakukan dentang (terutama wrt LLVM). Akan sangat bagus untuk memahami secara lebih mendalam bagaimana dentang mengimplementasikan perakitan sebaris. Apakah dentang memiliki lapisan terjemahan sendiri? Apakah itu memvalidasi parameter input apa pun? (dll)

Kemungkinan lain untuk bergerak maju adalah untuk melihat apakah ada assembler yang bisa kita lepas dari rak dari tempat lain yang sudah stabil. Beberapa ide di sini adalah nasm atau assembler plan9. Menggunakan assembler LLVM memiliki masalah yang sama tentang jaminan stabilitas seperti instruksi perakitan inline di IR. (itu kemungkinan, tetapi kami membutuhkan jaminan stabilitas sebelum menggunakannya)

Saya ingin menunjukkan bahwa sintaks asm inline LLVM berbeda dari yang digunakan oleh dentang/gcc. Perbedaan meliputi:

  • LLVM menggunakan $0 alih-alih %0 .
  • LLVM tidak mendukung operand asm bernama %[name] .
  • LLVM mendukung jenis batasan register yang berbeda: misalnya "{eax}" alih-alih "a" pada x86.
  • LLVM mendukung batasan register eksplisit ( "{r11}" ). Di C Anda harus menggunakan variabel register asm untuk mengikat nilai ke register ( register asm("r11") int x ).
  • Batasan LLVM "m" dan "=m" pada dasarnya rusak. Dentang menerjemahkan ini ke dalam batasan memori tidak langsung "*m" dan "=*m" dan meneruskan alamat variabel ke LLVM alih-alih variabel itu sendiri.
  • dll...

Dentang akan mengonversi asm sebaris dari format gcc ke dalam format LLVM sebelum meneruskannya ke LLVM. Itu juga melakukan beberapa validasi batasan: misalnya memastikan bahwa operan "i" adalah konstanta waktu kompilasi,


Mengingat hal ini, saya pikir kita harus menerapkan terjemahan dan validasi yang sama dengan yang dilakukan dentang dan mendukung sintaks asm inline gcc yang tepat alih-alih yang aneh LLVM.

Ada video yang sangat bagus tentang ringkasan dengan D, MSVC, gcc, LLVM, dan Rust dengan slide online

Sebagai seseorang yang ingin dapat menggunakan ASM inline di Rust yang stabil, dan dengan lebih banyak pengalaman daripada saya ingin mencoba mengakses beberapa API MC LLVM dari Rust, beberapa pemikiran:

  • Inline ASM pada dasarnya adalah copy-paste potongan kode ke dalam file .s keluaran untuk dirakit, setelah beberapa substitusi string. Ia juga memiliki lampiran register input dan output serta register musnah. Kerangka dasar ini sepertinya tidak akan pernah benar-benar berubah di LLVM (walaupun beberapa detailnya mungkin sedikit berbeda), dan saya menduga bahwa ini adalah representasi yang cukup independen dari kerangka kerja.

  • Membuat terjemahan dari spesifikasi yang menghadap ke Rust ke format IR yang menghadap ke LLVM tidaklah sulit. Dan mungkin disarankan--sintaks rust {} untuk pemformatan tidak mengganggu bahasa rakitan, tidak seperti notasi $ dan GCC LLVM % .

  • LLVM melakukan pekerjaan yang sangat buruk dalam praktiknya untuk benar-benar mengidentifikasi register mana yang diblokir, terutama dalam instruksi yang tidak dibuat oleh LLVM. Ini berarti sangat penting bagi pengguna untuk secara manual menentukan register mana yang diblokir.

  • Mencoba mengurai perakitan sendiri kemungkinan akan menjadi mimpi buruk. LLVM-C API tidak mengekspos logika MCAsmParser, dan kelas-kelas ini cukup mengganggu untuk bekerja dengan bindgen (saya sudah melakukannya).

  • Untuk portabilitas ke backend lain, selama Anda menjaga rakitan sebaris sebagian besar pada tingkat "salin-tempel string ini dengan sedikit alokasi register dan substitusi string", itu seharusnya tidak terlalu menghambat backend. Menjatuhkan konstanta bilangan bulat dan batasan memori dan menyimpan batasan bank yang baru saja didaftarkan seharusnya tidak menimbulkan masalah.

Saya telah sedikit bermain untuk melihat apa yang bisa dilakukan dengan makro prosedural. Saya telah menulis satu yang mengubah Majelis sebaris gaya GCC menjadi gaya karat https://github.com/parched/gcc-asm-rs . Saya juga mulai mengerjakan salah satu yang menggunakan DSL di mana pengguna tidak perlu memahami batasannya dan semuanya ditangani secara otomatis.

Jadi saya sampai pada kesimpulan bahwa menurut saya karat seharusnya menstabilkan blok bangunan yang telanjang, kemudian komunitas dapat beralih dari pohon dengan makro untuk menghasilkan solusi terbaik. Pada dasarnya, cukup stabilkan gaya llvm yang kita miliki sekarang dengan hanya batasan "r" dan "i" dan mungkin "m", dan tanpa clobbers. Kendala dan clobbers lain dapat distabilkan nanti dengan jenis rfc mini mereka sendiri.

Secara pribadi saya mulai merasa seolah-olah menstabilkan fitur ini adalah jenis tugas besar yang tidak akan pernah selesai kecuali seseorang menyewa kontraktor ahli penuh waktu untuk mendorong ini selama satu tahun penuh. Saya ingin percaya bahwa saran @parched untuk menstabilkan asm! sedikit demi sedikit akan membuat ini mudah diatur. Saya berharap seseorang mengambilnya dan membawanya. Tetapi jika tidak, maka kita harus berhenti mencoba meraih solusi memuaskan yang tidak akan pernah datang dan meraih solusi tidak memuaskan yang akan: menstabilkan asm! apa adanya, kutil, ICE, bug, dan semua , dengan peringatan berani yang terang dalam dokumen yang mengiklankan jank dan nonportabilitas, dan dengan maksud untuk mencela suatu hari nanti jika implementasi yang memuaskan harus turun secara ajaib, dikirim oleh Tuhan, pada host surgawinya. TAK, kita harus melakukan persis seperti yang kita lakukan untuk macro_rules! (dan tentu saja, seperti untuk macro_rules! , kita dapat mengalami periode singkat dari bantuan pita yang panik dan pemeriksaan masa depan yang bocor). Saya sedih dengan konsekuensi untuk backend alternatif, tetapi memalukan bagi bahasa sistem untuk menurunkan perakitan sebaris ke limbo seperti itu, dan kami tidak dapat membiarkan kemungkinan hipotetis dari beberapa backend terus menghalangi keberadaan satu backend yang benar-benar dapat digunakan. Aku mohon, buktikan aku salah!

memalukan bagi bahasa sistem untuk menurunkan perakitan sebaris ke limbo seperti itu

Sebagai titik data, saya kebetulan sedang mengerjakan peti sekarang yang bergantung pada gcc untuk tujuan tunggal memancarkan beberapa asm dengan Karat yang stabil: https://github.com/main--/unwind- rs/blob/266e0f26b6423f4a2b8a8c72442b319b5c33b658/src/unwind_helper.c


Meskipun tentu memiliki kelebihan, saya agak waspada dengan pendekatan "stabilkan blok bangunan dan serahkan sisanya ke proc-makro". Ini pada dasarnya mengalihdayakan desain, RFC, dan proses implementasi kepada siapa pun yang ingin melakukan pekerjaan itu, yang berpotensi tidak seorang pun. Tentu saja memiliki jaminan stabilitas/kualitas yang lebih lemah adalah intinya (imbalannya adalah memiliki sesuatu yang tidak sempurna sudah jauh lebih baik daripada tidak memiliki sama sekali), saya mengerti itu.

Setidaknya blok bangunan harus dirancang dengan baik - dan menurut saya, "expr" : foo : bar : baz jelas tidak. Saya tidak ingat pernah mendapatkan pesanan dengan benar pada percobaan pertama, saya selalu harus mencarinya. "Kategori sihir dipisahkan oleh titik dua di mana Anda menentukan string konstan dengan karakter ajaib yang akhirnya melakukan hal-hal ajaib ke nama variabel yang juga Anda tumbuk di sana entah bagaimana" itu buruk.

Satu ide,…

Saat ini, sudah ada proyek, bernama dynasm, yang dapat membantu Anda menghasilkan kode rakitan dengan plugin yang digunakan untuk pra-proses rakitan dengan satu jenis kode x64.

Proyek ini tidak menjawab masalah perakitan inline, tetapi tentu saja dapat membantu, jika rustc menyediakan cara untuk memetakan variabel ke register, dan menerima untuk memasukkan set byte dalam kode, proyek tersebut juga dapat digunakan untuk mengisi- up set byte ini.

Dengan cara ini, satu-satunya bagian standarisasi yang diperlukan dari sudut pandang rustc, adalah kemampuan untuk menyuntikkan urutan byte apa pun dalam kode yang dihasilkan, dan untuk menegakkan alokasi register tertentu. Ini menghapus semua pilihan untuk rasa bahasa tertentu.

Bahkan tanpa dinamisme, ini juga dapat digunakan sebagai cara untuk membuat makro untuk instruksi cpuid / rtdsc, yang hanya akan diterjemahkan ke dalam urutan byte mentah.

Saya kira pertanyaan berikutnya mungkin jika kita ingin menambahkan properti/batasan tambahan ke urutan byte.

[EDIT: Saya tidak berpikir apa pun yang saya katakan dalam komentar ini benar.]

Jika kita ingin terus menggunakan assembler terintegrasi LLVM (saya berasumsi ini lebih cepat daripada menghasilkan assembler eksternal), maka stabilisasi berarti menstabilkan secara tepat ekspresi perakitan inline LLVM dan dukungan assembler terintegrasi—dan mengkompensasi perubahan tersebut, jika terjadi.

Jika kita ingin menelurkan assembler eksternal, maka kita dapat menggunakan sintaks apa pun yang kita inginkan, tetapi kemudian kita mengabaikan keuntungan dari assembler terintegrasi, dan terkena perubahan dalam assembler eksternal apa pun yang kita panggil.

Saya pikir akan aneh untuk menstabilkan pada format LLVM ketika bahkan Clang tidak melakukan itu. Agaknya memang menggunakan dukungan LLVM secara internal, tetapi menyajikan antarmuka lebih seperti GCC.

Saya 100% baik-baik saja dengan mengatakan "Rust mendukung persis apa yang didukung Clang" dan menyebutnya sehari, terutama karena sikap AFAIK Clang adalah "Dentang mendukung persis apa yang didukung GCC". Jika kita pernah memiliki spesifikasi Rust yang sebenarnya, kita dapat melunakkan bahasanya menjadi "rakitan sebaris ditentukan oleh implementasi". Prioritas dan standarisasi de-facto adalah alat yang ampuh. Jika kita dapat menggunakan kembali kode Clang sendiri untuk menerjemahkan sintaks GCC ke LLVM, itu lebih baik. Masalah backend alternatif tidak hilang, tetapi secara teoritis frontend Rust ke GCC tidak akan terlalu mengganggu. Lebih sedikit untuk kami desain, lebih sedikit untuk kami bersepeda tanpa henti, lebih sedikit untuk kami ajarkan, lebih sedikit untuk kami pertahankan.

Jika kita menstabilkan sesuatu yang didefinisikan dalam hal apa yang didukung oleh dentang, maka kita harus menyebutnya clang_asm! . Nama asm! harus dicadangkan untuk sesuatu yang telah dirancang melalui proses RFC penuh, seperti fitur utama Rust lainnya. #gudang sepeda

Ada beberapa hal yang ingin saya lihat di perakitan inline Rust:

  • Pola template-dengan-substitusi jelek. Saya selalu melompat-lompat antara teks Majelis dan daftar kendala. Singkat mendorong orang untuk menggunakan parameter posisi, yang membuat keterbacaan lebih buruk. Nama simbolis sering kali berarti Anda memiliki nama yang sama yang diulang tiga kali: dalam templat, penamaan operan, dan dalam ekspresi yang terikat pada operan. Slide yang disebutkan dalam komentar Alex menunjukkan bahwa D dan MSVC memungkinkan Anda mereferensikan variabel dalam kode, yang tampaknya jauh lebih baik.

  • Kendala keduanya sulit dipahami, dan (kebanyakan) berlebihan dengan kode Majelis. Jika Rust memiliki assembler terintegrasi dengan model instruksi yang cukup rinci, Rust dapat menyimpulkan kendala pada operan, menghilangkan sumber kesalahan dan kebingungan. Jika programmer membutuhkan pengkodean instruksi yang spesifik, maka mereka perlu menyediakan batasan eksplisit, tetapi ini biasanya tidak diperlukan.

Norman Ramsey dan Mary Fernández menulis beberapa makalah tentang Toolkit Kode Mesin New Jersey saat itu memiliki ide bagus untuk menjelaskan pasangan bahasa rakitan/mesin dengan cara yang ringkas. Mereka menangani (era Pentium Pro) pengkodean instruksi iA-32; itu sama sekali tidak terbatas pada ISA RISC yang rapi.

Saya ingin mengulangi lagi kesimpulan dari minggu kerja terbaru :

  • Hari ini, sejauh yang kami tahu, pada dasarnya tidak ada dokumentasi untuk fitur ini. Ini termasuk internal LLVM dan semuanya.
  • Sejauh yang kami tahu, kami tidak memiliki jaminan stabilitas dari LLVM. Untuk semua yang kita tahu, implementasi perakitan inline di LLVM dapat berubah kapan saja.
  • Saat ini, ini adalah fitur yang sangat bermasalah di rustc. Ini penuh dengan (pada waktu kompilasi) segfault, ICE, dan kesalahan LLVM yang aneh.
  • Tanpa spesifikasi, hampir tidak mungkin membayangkan backend alternatif untuk ini.

Bagi saya ini adalah definisi "jika kita menstabilkan ini sekarang, kami akan menjamin untuk menyesalinya di masa depan", dan tidak hanya "menyesalinya" tetapi tampaknya sangat mungkin untuk "menyebabkan masalah serius untuk menerapkan sistem baru".

Paling tidak, saya sangat yakin bahwa peluru (2) tidak dapat dikompromikan (alias definisi stabil di "saluran stabil"). Peluru lainnya akan sangat menyedihkan untuk dilupakan karena mengikis kualitas yang diharapkan dari kompiler Rust yang saat ini cukup tinggi.

@jcranmer menulis:

LLVM melakukan pekerjaan yang sangat buruk dalam praktiknya untuk benar-benar mengidentifikasi register mana yang diblokir, terutama dalam instruksi yang tidak dibuat oleh LLVM. Ini berarti sangat penting bagi pengguna untuk secara manual menentukan register mana yang diblokir.

Saya akan berpikir bahwa, dalam praktiknya, akan sangat sulit untuk menyimpulkan daftar clobber. Hanya karena sebuah fragmen bahasa mesin menggunakan sebuah register tidak berarti itu menghancurkannya; mungkin itu menyimpannya dan mengembalikannya. Pendekatan konservatif dapat mencegah pembuat kode menggunakan register yang baik untuk digunakan.

@alexcrichton menulis:

Sejauh yang kami tahu, kami tidak memiliki jaminan stabilitas dari LLVM. Untuk semua yang kita tahu, implementasi perakitan inline di LLVM dapat berubah kapan saja.

Dokumen LLVM menjamin "Rilis yang lebih baru dapat mengabaikan fitur dari rilis yang lebih lama, tetapi mereka tidak dapat salah mengkompilasinya." (sehubungan dengan kompatibilitas IR). Itu agak membatasi seberapa banyak mereka dapat mengubah perakitan sebaris, dan, seperti yang saya katakan di atas, sebenarnya tidak ada pengganti yang layak di tingkat LLVM yang secara radikal akan mengubah semantik dari situasi saat ini (tidak seperti, katakanlah, masalah yang sedang berlangsung seputar racun dan undef). Mengatakan bahwa ketidakstabilan prospektifnya menghalangi menggunakannya sebagai basis untuk blok Rust asm! karena itu agak tidak jujur. Sekarang bukan berarti ada masalah lain dengannya (dokumentasi yang buruk, meskipun itu telah membaik; kendala kendala; diagnostik yang buruk; dan bugginess dalam skenario yang kurang umum adalah yang muncul dalam pikiran).

Kekhawatiran terbesar saya dalam membaca utas adalah bahwa kita membuat yang sempurna menjadi musuh yang baik. Secara khusus, saya khawatir bahwa mencari beberapa perantara DSL ajaib akan memakan waktu beberapa tahun untuk mencoba bertengkar menjadi bentuk yang dapat digunakan untuk inline-asm ketika orang menemukan bahwa mengintegrasikan parser asm dan mencoba membuatnya bekerja dengan LLVM menyebabkan lebih banyak masalah dalam kasus tepi.

Apakah LLVM benar-benar menjamin bahwa mereka tidak akan pernah salah mengkompilasi fitur yang perilakunya tidak pernah mereka tentukan? Bagaimana mereka memutuskan apakah perubahan itu salah kompilasi atau bukan? Saya bisa melihatnya untuk bagian lain dari IR, tetapi ini sepertinya banyak yang diharapkan.

Saya pikir akan aneh untuk menstabilkan pada format LLVM ketika bahkan Clang tidak melakukan itu.

Dentang tidak melakukan itu karena bertujuan untuk dapat mengkompilasi kode yang ditulis untuk GCC. rustc tidak memiliki tujuan itu. Format GCC tidak terlalu ergonomis, jadi pada akhirnya saya pikir kami tidak menginginkannya, tetapi apakah itu lebih baik untuk digunakan saat ini, saya tidak yakin. Ada banyak kode (malam) di luar sana yang menggunakan format Rust saat ini yang akan rusak jika kita mengubah ke gaya GCC jadi mungkin hanya layak untuk diubah jika kita bisa menghasilkan sesuatu yang lebih baik.

Setidaknya blok bangunan harus dirancang dengan baik - dan menurut saya, "expr" : foo : bar : baz jelas tidak.

Sepakat. Setidaknya saya lebih suka format LLVM mentah di mana kendala dan clobbers semua dalam satu daftar. Saat ini ada redundansi yang harus menentukan awalan "=" dan memasukkannya ke dalam daftar keluaran. Saya juga berpikir bagaimana LLVM memperlakukannya lebih seperti panggilan fungsi di mana output adalah hasil dari ekspresi, AFAIK implementasi asm! ini adalah satu-satunya bagian dari rust yang memiliki parameter "keluar".

LLVM melakukan pekerjaan yang sangat buruk dalam praktiknya untuk benar-benar mengidentifikasi register mana yang dihancurkan

AFAIK LLVM tidak mencoba melakukan ini karena alasan utama perakitan sebaris adalah untuk memasukkan beberapa kode yang tidak dipahami LLVM. Itu hanya mendaftar alokasi dan substitusi template tanpa melihat perakitan yang sebenarnya. (Jelas itu mem-parsing Majelis yang sebenarnya pada tahap tertentu untuk menghasilkan kode mesin, tapi saya pikir itu terjadi nanti)

Jika kami ingin menelurkan assembler eksternal

Saya tidak yakin akan ada alternatif untuk menggunakan assembler inline terintegrasi karena beberapa cara Anda harus mendapatkan LLVM untuk mengalokasikan register untuk itu. Untuk perakitan global, assembler eksternal akan bisa diterapkan.

Mengenai melanggar perubahan dalam assembler inline LLVM, kami berada di kapal yang sama dengan Dentang. Artinya, jika mereka membuat beberapa perubahan, kita hanya perlu mengatasinya ketika itu terjadi.

Jika kita menstabilkan sesuatu yang didefinisikan dalam hal apa yang didukung oleh dentang, maka kita harus menyebutnya clang_asm!. Asma! nama harus dicadangkan untuk sesuatu yang telah dirancang melalui proses RFC penuh, seperti fitur Rust utama lainnya. #gudang sepeda

Saya semua untuk itu. +1

Ada banyak kode (malam) di luar sana yang menggunakan format Rust saat ini yang akan rusak jika kita mengubah ke gaya GCC jadi mungkin hanya layak untuk diubah jika kita bisa menghasilkan sesuatu yang lebih baik.

@parched Mengikuti saran asm! dengan senang hati akan tetap dapat menggunakannya.

Hari ini, sejauh yang kami tahu, pada dasarnya tidak ada dokumentasi untuk fitur ini. Ini termasuk internal LLVM dan semuanya.

Jika sintaks Majelis GCC benar-benar tidak ditentukan atau didokumentasikan setelah 30 tahun, maka tampaknya aman untuk mengasumsikan bahwa memproduksi subbahasa Majelis yang terdokumentasi adalah tugas yang sangat sulit sehingga berada di luar kemampuan Rust untuk menyelesaikannya mengingat sumber daya kami yang terbatas, atau itu orang yang ingin menggunakan perakitan tidak peduli.

Sejauh yang kami tahu, kami tidak memiliki jaminan stabilitas dari LLVM. Untuk semua yang kita tahu, implementasi perakitan inline di LLVM dapat berubah kapan saja.

Tampaknya implementasi GCC/Dentang perakitan inline tidak akan pernah berubah, karena itu akan merusak semua kode C yang ditulis sejak tahun 90-an.

Tanpa spesifikasi, hampir tidak mungkin membayangkan backend alternatif untuk ini.

Dengan risiko tidak berperasaan, prospek backend alternatif diperdebatkan jika Rust sebagai bahasa tidak bertahan karena ketidakmampuannya yang memalukan untuk masuk ke perakitan. Nightly tidak cukup, kecuali jika seseorang ingin secara diam-diam mendukung gagasan bahwa Nightly adalah Rust, yang lebih merusak jaminan stabilitas Rust daripada prospek perubahan LLVM.

Peluru lainnya akan sangat menyedihkan untuk dilupakan karena mengikis kualitas yang diharapkan dari kompiler Rust yang saat ini cukup tinggi.

Saya tidak berbohong ketika saya mengatakan bahwa setiap hari saya berterima kasih atas sikap pengembang Rust dan standar kualitas yang sangat tinggi yang mereka pegang (bahkan terkadang saya berharap Anda semua akan melambat sehingga Anda dapat mempertahankan kualitas itu tanpa membakar dirimu seperti yang dilakukan Brian). Namun, berbicara sebagai seseorang yang ada di sini ketika luqmana menambahkan makro Karat membutuhkan perakitan inline yang stabil pada tahun 2018. Kami membutuhkan seni sebelumnya untuk melakukannya. Situasi macro_rules! mengakui bahwa terkadang lebih buruk lebih baik. Sekali lagi, saya memohon seseorang untuk membuktikan bahwa saya salah.

FWIW dan datang terlambat ke pesta Saya suka apa yang diusulkan oleh dari pembicaraan cologne . Buat yang belum nonton, ini intinya:

// Add 5 to variable:
let mut var = 0;
unsafe {
    asm!("add $5, {}", inout(reg) var);
}

// Get L1 cache size
let ebx: i32;
let ecx: i32;
unsafe {
    asm!(r"
        mov $$4, %eax;
        xor %ecx, %ecx;
        cpuid;
        mov %ebx, {};",
        out(reg) ebx, out(ecx) ecx, clobber(eax, ebx, edx)
    );
}
println!("L1 Cache: {}", ((ebx >> 22) + 1)
    * (((ebx >> 12) & 0x3ff) + 1)
    * ((ebx & 0xfff) + 1) * (ecx + 1));

Bagaimana dengan strategi berikut: ganti nama asm menjadi llvm_asm (ditambah mungkin beberapa perubahan kecil) dan nyatakan bahwa perilakunya adalah detail implementasi LLVM, sehingga jaminan stabilitas Rust tidak sepenuhnya meluas ke sana? Masalah backend yang berbeda harus lebih atau kurang diselesaikan dengan fungsionalitas seperti target_feature untuk kompilasi bersyarat tergantung pada backend yang digunakan. Ya, pendekatan seperti itu akan sedikit mengaburkan stabilitas Rust, tetapi menjaga perakitan dalam keadaan limbo seperti ini merusak Rust dengan caranya sendiri.

Saya telah memposting pra-RFC dengan proposal sintaks alternatif ke forum internal: https://internals.rust-lang.org/t/pre-rfc-inline-assembly/6443 . Umpan balik selamat datang.

Bagi saya sepertinya yang terbaik jelas merupakan musuh yang baik-baik saja di sini. Saya sepenuhnya mendukung menempelkan makro gcc_asm! atau clang_asm! atau llvm_asm! (atau subset yang tepat darinya) ke dalam stabil dengan sintaks dan semantik yang kompatibel untuk saat ini, sementara solusi yang lebih baik sedang dikerjakan . Saya tidak melihat mendukung hal seperti itu selamanya sebagai beban pemeliharaan yang besar: sistem yang lebih canggih yang diusulkan di atas terlihat seperti mereka akan dengan mudah mendukung hanya dengan mengubah makro gaya lama menjadi sintaksis sakarin untuk yang baru.

Saya memiliki program biner http://[email protected]/BartMassey/popcount yang memerlukan perakitan sebaris untuk instruksi x86_64 popcntl . Majelis sebaris ini adalah satu-satunya hal yang menjaga kode ini di malam hari. Kode ini berasal dari program C berusia 12 tahun.

Saat ini, majelis saya dikondisikan

    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]

dan kemudian dapatkan info cpuid untuk melihat apakah popcnt ada. Akan menyenangkan untuk memiliki sesuatu di Rust yang mirip dengan pustaka cpu_features Google baru-baru ini https://opensource.googleblog.com/2018/02/cpu-features-library.html di Rust, tetapi c'est la vie.

Karena ini adalah program demo, saya ingin menyimpan perakitan inline. Untuk program nyata, count_ones() intrinsik sudah cukup — kecuali untuk menggunakannya popcntl memerlukan pengiriman "-C target-cpu=native" ke Cargo, mungkin melalui RUSTFLAGS (lihat edisi #1137 dan beberapa masalah terkait) karena mendistribusikan .cargo/config dengan sumber saya sepertinya bukan ide yang bagus, yang berarti bahwa saat ini saya memiliki Makefile yang memanggil Cargo.

Singkatnya, alangkah baiknya jika seseorang dapat menggunakan instruksi popcount mewah Intel dan lainnya dalam aplikasi nyata, tetapi tampaknya lebih sulit daripada yang seharusnya. Intrinsik tidak sepenuhnya jawabannya. asm! ini adalah jawaban yang ok jika tersedia di stabil. Akan sangat bagus untuk memiliki sintaks dan semantik yang lebih baik untuk perakitan sebaris, tetapi saya tidak terlalu membutuhkannya. Akan sangat bagus untuk dapat menentukan target-cpu=native secara langsung di Cargo.toml , tetapi itu tidak akan benar-benar menyelesaikan masalah saya.

Maaf, bertele-tele. Hanya berpikir saya akan berbagi mengapa saya peduli tentang ini.

@BartMassey Saya tidak mengerti, mengapa Anda sangat perlu mengkompilasi ke popcnt? Satu-satunya alasan yang bisa saya lihat adalah kinerja dan IMO Anda harus menggunakan count_ones() dalam kasus itu. Apa yang Anda cari bukanlah asm sebaris tetapi target_feature (rust-lang/rfcs#2045) sehingga Anda dapat memberi tahu kompiler bahwa ia diizinkan untuk memancarkan popcnt.

@BartMassey Anda bahkan tidak perlu menggunakan rakitan sebaris untuk ini, cukup gunakan coresimd cfg_feature_enabled!("popcnt") untuk menanyakan apakah cpu yang dijalankan biner Anda mendukung instruksi popcnt (akan selesaikan ini pada waktu kompilasi jika memungkinkan untuk melakukannya).

coresimd juga menyediakan intrinsik popcnt yang dijamin untuk menggunakan instruksi popcnt .

@gnzlbg

coresimd juga menyediakan intrinsik popcnt yang dijamin untuk menggunakan instruksi popcnt.

Ini agak di luar topik, tetapi pernyataan ini tidak sepenuhnya benar. _popcnt64 menggunakan leading_zeros bawah tenda, jadi jika fitur popcnt tidak akan diaktifkan oleh pengguna peti dan pembuat peti akan lupa menggunakan #![cfg(target_feature = "popcnt")] intrinsik ini akan didapat dikompilasi menjadi perakitan yang tidak efektif dan tidak ada perlindungan terhadapnya.

jadi jika fitur popcnt tidak akan diaktifkan oleh pengguna peti

Ini salah karena intrinsik menggunakan atribut #[target_feature(enable = "popcnt")] untuk mengaktifkan fitur popcnt untuk intrinsik tanpa syarat, terlepas dari apa yang diaktifkan atau dinonaktifkan oleh pengguna peti. Juga, atribut assert_instr(popcnt) memastikan bahwa intrinsik dibongkar menjadi popcnt pada semua platform x86 yang didukung Rust.

Jika seseorang menggunakan Rust pada platform x86 yang saat ini tidak didukung Rust, maka terserah siapa pun yang mem-porting core untuk memastikan bahwa intrinsik ini menghasilkan popcnt pada target itu.


EDIT: @newpavlov

jadi jika fitur popcnt tidak akan diaktifkan oleh pengguna peti dan pembuat peti akan lupa menggunakan #![cfg(target_feature = "popcnt")] intrinsik ini akan dikompilasi menjadi perakitan yang tidak efektif dan tidak ada perlindungan terhadapnya.

Setidaknya dalam contoh yang Anda sebutkan dalam masalah, melakukan ini memperkenalkan perilaku tidak terdefinisi ke dalam program, dan dalam hal ini, kompiler diizinkan untuk melakukan apa saja. Codegen buruk yang berfungsi adalah salah satu dari banyak hasil yang bisa didapat.

Pertama-tama, mohon maaf karena menggagalkan diskusi. Hanya ingin menegaskan kembali poin utama saya, yaitu "Saya sepenuhnya mendukung menempelkan gcc_asm! atau clang_asm! atau llvm_asm! makro (atau subset yang tepat darinya) menjadi stabil dengan sintaks dan semantik yang kompatibel untuk saat ini, sementara solusi yang lebih baik sedang dikerjakan. "

Inti dari perakitan inline adalah bahwa ini adalah tolok ukur / demo popcount. Saya ingin instruksi popcntl dijamin benar jika memungkinkan baik sebagai garis dasar dan untuk mengilustrasikan cara menggunakan perakitan sebaris. Saya juga ingin menjamin bahwa count_ones() menggunakan instruksi popcount bila memungkinkan sehingga Rustc tidak terlihat buruk dibandingkan dengan GCC dan Dentang.

Terima kasih telah menunjukkan target_feature=popcnt . Saya akan memikirkan cara menggunakannya di sini. Saya pikir saya ingin membuat count_ones() terlepas dari CPU apa yang dikompilasi pengguna dan terlepas dari apakah ia memiliki instruksi popcount. Saya hanya ingin memastikan bahwa jika CPU target memiliki popcount count_ones() menggunakannya.

Peti stdsimd / coresimd terlihat bagus, dan mungkin harus diaktifkan untuk tolok ukur ini. Terima kasih! Untuk aplikasi ini, saya lebih suka menggunakan sedikit di luar fitur bahasa standar (saya sudah merasa bersalah tentang lazy_static ). Namun, fasilitas ini terlihat terlalu bagus untuk diabaikan, dan sepertinya mereka sedang dalam perjalanan untuk menjadi "resmi".

Ada ide yang dilontarkan oleh

Penyambungan byte kode arbitrer ke tempat-tempat arbitrer dalam suatu fungsi tampak seperti masalah yang jauh lebih mudah untuk dipecahkan (walaupun kemampuan untuk menentukan input, output, dan batasannya serta clobbers masih diperlukan).

cc @eddyb

@nagisa ini sedikit lebih dari sekadar sedikit kode mesin, Anda juga harus berhati-hati dengan register input, output, dan clobber. Jika potongan ASM mengatakan bahwa ia menginginkan variabel tertentu di %rax dan itu akan menghancurkan %esi, Anda perlu memastikan bahwa kode di sekitarnya berfungsi dengan baik. Juga jika pengembang mengizinkan kompiler mengalokasikan register, Anda mungkin ingin mengoptimalkan alokasi untuk menghindari menumpahkan dan memindahkan nilai.

@simias , memang Anda harus menentukan bagaimana variabel dikaitkan dengan register tertentu, dan register mana yang dimusnahkan, tetapi semua ini lebih kecil daripada menstandarisasi bahasa rakitan apa pun, atau bahasa rakitan LLVM apa pun.

Standarisasi pada urutan byte, mungkin merupakan cara termudah ke depan dengan memindahkan rasa perakitan ke driver/proc-makro.

Salah satu masalah memiliki byte kata demi kata alih-alih perakitan sebaris yang tepat, adalah bahwa kompiler tidak memiliki opsi untuk melakukan penggantian nama alfa register, yang saya tidak berharap orang-orang yang menulis perakitan sebaris juga mengharapkannya.

Tetapi bagaimana cara kerjanya dengan alokasi register jika saya ingin membiarkan kompiler menanganinya? Misalnya, menggunakan sintaks (mengerikan) GCC:

asm ("leal (%1, %1, 4), %0"
     : "=r" (five_times_x)
     : "r" (x));

Dalam hal seperti ini saya membiarkan kompiler mengalokasikan register, berharap itu akan memberi saya apa pun yang paling nyaman dan efisien. Misalnya pada x86 64 jika five_time_x adalah nilai kembalian maka kompilator mungkin mengalokasikan eax dan jika x adalah parameter fungsi, mungkin sudah tersedia di beberapa register. Tentu saja kompiler hanya tahu persis bagaimana ia akan mengalokasikan register cukup terlambat dalam urutan kompilasi (terutama jika itu tidak sepele seperti fungsi params dan mengembalikan nilai).

Apakah solusi yang Anda usulkan bekerja dengan sesuatu seperti itu?

@nbp Saya harus mengatakan saya agak bingung dengan proposal ini.
Pertama-tama, menstandarisasi bahasa rakitan bukanlah sesuatu yang ingin kami capai dengan rakitan sebaris. Setidaknya bagi saya, premisnya selalu bahwa bahasa assembly yang digunakan oleh assembler sistem akan diterima.
Masalahnya adalah perakitan tidak diurai/dirakit, kita dapat meneruskannya ke LLVM dengan mudah.
Masalahnya adalah dengan mengisi perakitan templated (atau memberi LLVM informasi yang diperlukan untuk melakukannya), dan menentukan input, output, dan clobbers.
Masalah selanjutnya sebenarnya tidak diselesaikan oleh proposal Anda. Namun itu diringankan, karena Anda tidak akan/tidak dapat mendukung kelas register (yang ditanyakan oleh
Pada titik di mana kendala disederhanakan hingga sejauh itu, sebenarnya sama mudahnya untuk mendukung perakitan inline "nyata". Argumen pertama adalah string yang berisi rakitan (non-template), argumen lainnya adalah batasannya. Ini agak mudah dipetakan ke ekspresi assembler inline LLVM.
Memasukkan byte mentah di sisi lain tidak sejauh yang saya tahu (atau dapat dikatakan dari Manual Referensi IR LLVM) yang didukung oleh LLVM. Jadi pada dasarnya kami akan memperluas LLVM IR, dan mengimplementasikan kembali fitur (perakitan sistem perakitan) yang sudah ada di LLVM menggunakan peti terpisah.

@nbp

memang Anda harus menentukan bagaimana variabel dikaitkan dengan register tertentu, dan register mana yang dimusnahkan, tetapi semua ini lebih kecil daripada menstandarisasi bahasa rakitan apa pun, atau bahasa rakitan LLVM apa pun.

Jadi bagaimana hal itu dilakukan? Saya memiliki urutan byte dengan register yang di-hardcode dengan pada dasarnya berarti bahwa register input/out, clobbers, dll. Semuanya di-hardcode di dalam urutan byte ini.

Sekarang saya menyuntikkan byte ini di suatu tempat di biner karat saya. Bagaimana cara memberi tahu rustc register mana yang merupakan input/output, register mana yang rusak, dll.? Bagaimana ini masalah yang lebih kecil untuk dipecahkan daripada menstabilkan perakitan inline? Tampak bagi saya bahwa inilah tepatnya yang dilakukan perakitan sebaris, hanya mungkin sedikit lebih sulit karena sekarang seseorang perlu menentukan input/output clobbers dua kali, dalam perakitan tertulis, dan dengan cara apa pun kami meneruskan informasi ini ke rustc. Juga, rustc tidak akan memiliki waktu yang mudah untuk memvalidasi ini, karena untuk itu ia harus dapat mengurai urutan byte ke dalam Majelis, dan kemudian memeriksanya. Apa yang saya lewatkan?

@simias

asm ("leal (%1, %1, 4), %0"
     : "=r" (five_times_x)
     : "r" (x));

Ini tidak akan mungkin, karena byte mentah tidak mengizinkan penggantian nama register alfa, dan register harus ditegakkan oleh urutan kode di depan.

@Florob

Setidaknya bagi saya, premisnya selalu bahwa bahasa assembly yang digunakan oleh assembler sistem akan diterima.

Pemahaman saya, adalah bahwa mengandalkan assembler sistem bukanlah sesuatu yang ingin kita andalkan, tetapi lebih merupakan cacat yang diterima sebagai bagian dari asm! makro. Juga mengandalkan asm! menjadi sintaks LLVM akan menyakitkan untuk pengembangan backend tambahan.

@gnzlbg

Jadi bagaimana hal itu dilakukan? Saya memiliki urutan byte dengan register yang di-hardcode dengan pada dasarnya berarti bahwa register input/out, clobbers, dll. Semuanya di-hardcode di dalam urutan byte ini.

Idenya adalah untuk memiliki daftar input, output, dan register musnah, di mana input akan menjadi tupel dari nama register yang terkait dengan referensi atau salinan (bisa berubah), register yang rusak akan menjadi daftar nama register, dan outputnya akan menjadi daftar output register akan membentuk tuple register bernama yang merupakan tipe terkait.

fn swap(a: u32, b: u32) -> (u32, u32) {
  unsafe{
    asm_raw!{
       bytes: [0x91],
       inputs: [(std::asm::eax, a), (std::asm::ecx, b)],
       clobbered: [],
       outputs: (std::asm::eax, std::asm::ecx),
    }
  }
}

Urutan kode ini mungkin merupakan output dari beberapa makro prosedural kompiler, yang mungkin terlihat seperti:

fn swap(a: u32, b: u32) -> (u32, u32) {
  unsafe{
    asm_x64!{
       ; <-- (eax, a), (ecx, b)
       xchg eax, ecx
       ; --> (eax, ecx)
    }
  }
}

Urutan-urutan ini, tidak akan dapat secara langsung menyematkan simbol atau alamat apa pun dan harus dihitung dan diberikan sebagai register. Saya yakin kita dapat mengetahui bagaimana cara menambahkan kemampuan untuk memasukkan beberapa alamat simbol dalam urutan byte nanti.

Keuntungan dari pendekatan ini adalah bahwa hanya daftar register dan batasan yang harus distandarisasi, dan ini adalah sesuatu yang akan dengan mudah didukung oleh backend mana pun di masa mendatang.

@nbp

Pemahaman saya, adalah bahwa mengandalkan assembler sistem bukanlah sesuatu yang ingin kita andalkan, tetapi lebih merupakan cacat yang diterima sebagai bagian dari asm! makro. Juga mengandalkan asm! menjadi sintaks LLVM akan menyakitkan untuk pengembangan backend tambahan.

Saya tidak berpikir itu penilaian yang akurat? Dengan pengecualian kecil dari dua sintaks berbeda untuk rakitan x86, sintaks rakitan sebagian besar standar dan portabel. Satu-satunya masalah dengan assembler sistem mungkin adalah kurangnya instruksi yang lebih baru, tetapi itu adalah situasi khusus yang tidak layak untuk dioptimalkan.

Masalah sebenarnya adalah lem ke dalam alokasi register. Tetapi, sejauh menyangkut string Majelis yang sebenarnya, ini hanya berarti seseorang harus melakukan beberapa hal substitusi string dan mungkin beberapa penguraian - dan substitusi semacam ini harus tersedia secara sepele untuk setiap backend yang diduga.

Saya setuju bahwa sintaks LLVM (atau gcc) untuk hal ini adalah omong kosong, tetapi pindah ke byte yang telah dikompilasi berarti bahwa setiap peti asm sekarang perlu menginstal assembler penuh dan mungkin pengalokasi register lengkap (atau membuat programmer mengalokasikan register tangan), atau mencoba untuk menggunakan perakit sistem. Pada saat itu, sepertinya itu tidak benar-benar menambah banyak nilai.

@jcranmer

tetapi pindah ke byte yang telah dikompilasi berarti bahwa setiap peti asm sekarang perlu menginstal assembler penuh dan mungkin pengalokasi register penuh (atau membuat programmer mengalokasikan register), atau mencoba menggunakan assembler sistem

https://github.com/CensoredUsername/dynasm-rs

Peti ini menggunakan makro yang ditangani oleh plugin untuk merakit kode rakitan dan menghasilkan vektor kode rakitan mentah untuk digabungkan saat runtime.

@nbp mungkin kasus penggunaan saya aneh tetapi kurangnya penggantian nama register dan membiarkan kompiler mengalokasikan register untuk saya akan menjadi pemecah kesepakatan karena itu berarti saya harus sangat beruntung dengan pilihan register saya dan kebetulan " tekan kanan" atau kompiler harus mengeluarkan kode yang tidak optimal untuk mengacak register agar sesuai dengan konvensi arbitrer saya.

Jika gumpalan Majelis tidak terintegrasi dengan baik dengan rakitan yang dipancarkan oleh kompiler di sekitarnya, saya mungkin juga hanya memasukkan rintisan ASM dalam metode gaya-C eksternal dalam file rakitan .s yang berdiri sendiri karena panggilan fungsi memiliki jenis register yang sama -kendala alokasi. Ini sudah berfungsi hari ini, meskipun saya kira memilikinya dibangun ke dalam rustc mungkin menyederhanakan sistem pembangunan dibandingkan dengan memiliki file rakitan mandiri. Saya kira apa yang saya katakan adalah bahwa IMO proposal Anda tidak membawa kita jauh dibandingkan dengan situasi saat ini.

Dan bagaimana jika kode ASM memanggil simbol eksternal yang akan diselesaikan oleh linker? Anda perlu menyebarkan info itu karena Anda tidak mungkin menyelesaikannya sampai akhir dalam proses kompilasi. Anda harus memberikan referensi di sana di samping array byte Anda dan membiarkan tautan menyelesaikannya nanti.

@jcranmer

Dengan pengecualian kecil dari dua sintaks berbeda untuk rakitan x86, sintaks rakitan sebagian besar standar dan portabel.

Saya tidak yakin saya mengerti apa yang Anda maksud dengan itu, jelas sintaks ASM tidak portabel di seluruh arsitektur. Dan bahkan dalam arsitektur yang sama sering ada variasi dan opsi yang mengubah cara bahasa dirakit.

Saya dapat memberikan MIPS sebagai contoh, ada dua flag konfigurasi penting yang mengubah perilaku assembler: at dan reorder . at mengatakan apakah assembler diperbolehkan untuk menggunakan register AT (assembler sementara) secara implisit ketika merakit instruksi semu tertentu. Kode yang secara eksplisit menggunakan AT untuk menyimpan data harus dirangkai dengan at atau akan rusak.

reorder mendefinisikan jika pembuat kode secara manual menangani slot penundaan cabang atau jika mereka mempercayai assembler untuk menanganinya. Merakit kode dengan pengaturan reorder hampir pasti akan menghasilkan kode mesin palsu. Saat Anda menulis rakitan MIPS, Anda harus mengetahui mode saat ini setiap saat jika berisi instruksi percabangan. Misalnya tidak mungkin mengetahui arti dari daftar MIPS ini jika Anda tidak tahu apakah reorder diaktifkan:

    addui   $a0, 4
    jal     some_func
    addui   $a1, $s0, 3

Perakitan ARM 32bit memiliki variasi Jempol/ARM, penting untuk mengetahui set instruksi mana yang Anda targetkan (dan Anda dapat mengubah panggilan fungsi dengan cepat). Mencampur kedua set perlu dilakukan dengan sangat hati-hati. Kode ARM juga biasanya memuat nilai langsung yang besar menggunakan beban relatif PC implisit, jika Anda pra-rakitan kode Anda, Anda harus berhati-hati tentang bagaimana Anda meneruskan nilai-nilai ini karena mereka harus tetap dekat tetapi bukan instruksi aktual dengan lokasi yang terdefinisi dengan baik. Saya sedang berbicara tentang pseudo-instruksi seperti:

   ldr   r2, =0x89abcdef

MIPS di sisi lain cenderung membagi nilai langsung menjadi dua nilai 16bit dan menggunakan kombo lui/ori atau lui/andi. Biasanya tersembunyi di balik instruksi semu li / la tetapi jika Anda menulis kode dengan noreorder dan tidak ingin menyia-nyiakan slot penundaan terkadang Anda harus menangani dengan tangan yang menghasilkan kode yang tampak lucu:

.set noreorder

   /* Display a message using printf */
   lui $a0, %hi(hello)
   jal printf
   ori $a0, %lo(hello)

.data

hello:
.string "Hello, world!\n"

Konstruksi %hi dan %lo adalah cara untuk memberitahu Majelis untuk menghasilkan referensi ke 16bit tinggi dan rendah dari simbol hello masing-masing.

Beberapa kode memerlukan batasan penyelarasan yang sangat aneh (umum ketika Anda berurusan dengan kode pembatalan cache misalnya, Anda harus yakin bahwa Anda tidak membawa gergaji ke cabang tempat Anda duduk). Dan ada masalah penanganan simbol eksternal yang tidak dapat diselesaikan pada saat ini dalam proses kompilasi seperti yang saya sebutkan sebelumnya.

Saya yakin saya bisa menemukan kekhasan untuk banyak arsitektur lain yang kurang saya kenal. Untuk alasan ini saya tidak yakin saya sangat optimis untuk pendekatan makro/DSL. Saya mengerti bahwa memiliki string literal buram acak di tengah kode tidak terlalu elegan, tetapi saya tidak benar-benar melihat apa yang mengintegrasikan sintaks ASM penuh ke dalam karat dengan satu atau lain cara akan memberi kita kecuali sakit kepala tambahan saat menambahkan dukungan untuk sebuah arsitektur baru.

Menulis assembler adalah sesuatu yang sekilas mungkin tampak sepele tetapi bisa menjadi sangat rumit jika Anda ingin mendukung semua lonceng, peluit, dan kebiasaan semua arsitektur di luar sana.

Di sisi lain, memiliki cara yang baik untuk menentukan binding dan clobbers akan sangat berharga (dibandingkan dengan... sintaks sempurna gcc).

Halo kawan-kawan,

Maaf mengganggu Anda, saya hanya ingin menjatuhkan dua sen saya, karena saya hanya pengguna, dan memang sangat pemalu/pendiam, oh, dan pendatang baru, saya baru saja mendarat di Rust, tapi saya sudah jatuh cinta dengan itu.

Tapi pertemuan ini benar-benar gila, maksud saya, ini adalah percakapan selama tiga tahun, dengan banyak ide dan keluhan, tapi tidak ada yang tampak seperti konsensus minimum. Tiga tahun dan bukan RFC, sepertinya seperti akhir kematian. Saya sedang mengembangkan perpustakaan matematika yang sederhana (yang mudah-mudahan akan terwujud dalam dua atau tiga peti), dan bagi saya (dan saya menduga bahwa untuk orang lain yang tertarik menulis perakitan di rust), yang paling penting adalah benar-benar mampu lakukan! dengan jaminan minimum bahwa semuanya tidak akan berubah keesokan harinya (itulah yang membuat saya merasa saluran yang tidak stabil, dan khususnya percakapan ini).

Saya mengerti bahwa semua orang di sini menginginkan solusi terbaik, dan mungkin suatu hari seseorang keluar dengan yang itu, tetapi untuk hari ini saya percaya bahwa makro saat ini baik-baik saja (yah, mungkin sedikit membatasi dalam beberapa hal, tapi semoga tidak ada yang tidak bisa ditangani secara bertahap). Menulis perakitan adalah hal yang paling penting dalam bahasa sistem, fitur yang sangat sangat diperlukan, dan meskipun saya baik-baik saja mengandalkan cpp_build sampai ini diperbaiki, saya sangat takut jika dibutuhkan lebih banyak waktu itu akan menjadi ketergantungan selamanya. Saya tidak tahu mengapa, sebut saja itu ide yang tidak rasional, tetapi saya menemukan bahwa harus memanggil cpp untuk memanggil Majelis agak menyedihkan, saya ingin solusi karat murni.

FWIW Rust tidak terlalu istimewa di sini, MSVC juga tidak memiliki inline asm untuk x86_64. Mereka memang memiliki implementasi yang sangat aneh di mana Anda dapat menggunakan variabel sebagai operan tetapi itu hanya berfungsi untuk x86.

@josevalaad Bisakah Anda berbicara lebih banyak tentang apa yang Anda gunakan untuk perakitan sebaris?

Kami biasanya hanya melihatnya digunakan dalam situasi seperti OS, yang biasanya macet di malam hari karena alasan lain juga, dan bahkan saat itu mereka hampir tidak menggunakannya asm! , jadi menstabilkan asm! belum menjadi prioritas yang cukup tinggi untuk merancang & mengembangkan sesuatu yang dapat bertahan dengan baik di luar LLVM dan menyenangkan semua orang.

Selain itu, sebagian besar hal dapat dilakukan dengan menggunakan platform intrinsik yang terbuka. x86 dan x86_64 telah distabilkan dan platform lain sedang dalam proses. Ini harapan kebanyakan orang bahwa ini akan mencapai 95-99% dari tujuan. Anda dapat melihat peti saya sendiri jetscii sebagai contoh penggunaan beberapa intrinsik.

Kami baru saja menggabungkan PR jemalloc yang menggunakan perakitan sebaris untuk mengatasi bug pembuatan kode di LLVM - https://github.com/jemalloc/jemalloc/pull/1303 . Seseorang menggunakan perakitan sebaris dalam masalah ini ( https://github.com/rust-lang/rust/issues/53232#issue-349262078 ) untuk mengatasi bug pembuatan kode di Rust (LLVM) yang terjadi di peti jetscii. Keduanya terjadi dalam dua minggu terakhir, dan dalam kedua kasus pengguna mencoba dengan intrinsik tetapi kompiler gagal.

Ketika pembuatan kode untuk kompiler C tidak dapat diterima, kasus terburuk pengguna dapat menggunakan perakitan sebaris dan terus bekerja di C.

Ketika ini terjadi di Rust yang stabil, saat ini kami harus memberi tahu orang-orang untuk menggunakan bahasa pemrograman yang berbeda atau menunggu dalam waktu yang tidak ditentukan (seringkali dalam urutan tahun). Itu tidak baik.

@eddyb Yah, saya sedang menulis perpustakaan aljabar matriks kecil. Di dalam perpustakaan itu, saya menerapkan BLAS, mungkin beberapa rutinitas LAPACK (belum ada) di Rust, karena saya ingin perpustakaan itu menjadi implementasi karat murni. Belum ada yang serius, tapi bagaimanapun, saya ingin pengguna dapat memilih kecepatan dan kesenangan tertentu, khususnya dengan operasi GEMM, yang dulunya penting (yang paling sering digunakan, dan jika Anda mengikuti pendekatan orang BLIS itu semua yang Anda butuhkan), setidaknya di x86/x86_64. Dan itulah cerita lengkapnya. Jelas saya dapat menggunakan saluran malam juga, saya hanya ingin mendorong sedikit ke arah pragmatis stabilisasi fitur.

@shepmaster Ada _banyak_ kasus penggunaan yang intrinsiknya tidak cukup. Dari bagian atas kepala saya tentang hal-hal baru-baru ini di mana saya berpikir "mengapa oh mengapa Rust tidak memiliki asm yang stabil?", Tidak ada intrinsik XACQUIRE/XRELEASE.

Asm inline yang stabil sangat penting dan tidak, intrinsik tidak cukup.

Poin awal saya adalah mencoba membantu seseorang memiliki kemampuan untuk menulis kode lebih cepat. Mereka tidak menyebutkan mengetahui bahwa intrinsik bahkan tersedia, dan hanya itu yang ingin saya bagikan. Sisanya adalah informasi latar belakang.

Saya bahkan tidak menganjurkan sudut pandang tertentu, jadi tolong jangan mencoba berdebat dengan saya — saya tidak memiliki kepentingan dalam perlombaan ini. Saya hanya mengulangi apa sudut pandang saat ini proyek yang memerlukan perakitan inline yang sangat tidak mungkin memiliki intrinsik dalam waktu dekat, jadi saya juga tertarik pada sejumlah perakitan inline yang stabil, tetapi perakitan malam tidak terlalu mengganggu saya, juga tidak meminta assembler.

Ya, ada kasing yang memerlukan perakitan untuk saat ini dan ada kasing yang selamanya akan membutuhkannya, kata saya pada awalnya (tambahkan penekanan untuk kejelasan):

Harapan kebanyakan orang bahwa [intrinsik] akan mencapai 95-99% dari tujuan .

Menurut pendapat saya, jika Anda ingin melihat perakitan yang stabil, seseorang (atau sekelompok orang) perlu mendapatkan konsensus umum dari tim Rust tentang arah untuk memulai dan kemudian berusaha keras untuk mewujudkannya. .

Belum ada yang serius, tapi bagaimanapun, saya ingin pengguna dapat memilih kecepatan dan kesenangan tertentu, khususnya dengan operasi GEMM, yang dulunya penting (yang paling sering digunakan, dan jika Anda mengikuti pendekatan orang BLIS itu semua yang Anda butuhkan), setidaknya di x86/x86_64.

Saya masih tidak mengerti instruksi apa yang Anda perlukan untuk mengakses yang tidak dapat Anda akses tanpa perakitan sebaris. Atau hanya urutan instruksi aritmatika tertentu?
Jika demikian, apakah Anda telah membandingkan sumber Rust yang setara dengan rakitan sebaris?

instruksi apa yang Anda butuhkan untuk mengakses yang tidak dapat Anda akses tanpa perakitan sebaris

Nah, ketika Anda berbicara tentang perakitan dalam matematika, Anda pada dasarnya berbicara tentang menggunakan register SIMD dan instruksi seperti _mm256_mul_pd, _mm256_permute2f128_pd, dll. dan operasi vektorisasi di mana ia melanjutkan. Masalahnya adalah Anda dapat mengambil pendekatan yang berbeda untuk vektorisasi, dan biasanya ini adalah sedikit percobaan dan kesalahan sampai Anda mendapatkan kinerja yang dioptimalkan untuk prosesor yang Anda targetkan dan penggunaan yang Anda pikirkan. Jadi biasanya di tingkat perpustakaan Anda harus terlebih dahulu menanyakan prosesor yang menyuntikkan kode asm untuk mengetahui set instruksi dan register yang didukung, dan kemudian kompilasi kondisional versi tertentu dari kernel matematika asm Anda.

Jika demikian, apakah Anda telah membandingkan sumber Rust yang setara dengan rakitan sebaris?

Saat ini saya tidak memiliki tes khusus, dan saya sedang berlibur, jadi saya lebih suka untuk tidak banyak melibatkan diri dengannya, tapi ya, jika Anda memberi saya beberapa minggu, saya dapat memposting perbandingan kinerja. Bagaimanapun, dulu tidak mungkin bagi kompiler untuk menghasilkan kode secepat yang Anda bisa dengan perakitan yang disetel secara manual. Itu tidak mungkin di C setidaknya, bahkan jika Anda menggunakan teknik kinerja klasik seperti membuka gulungan manual jika diperlukan, dll., jadi saya membayangkan itu seharusnya tidak mungkin di Rust.

Taylor Cramer menyarankan saya memposting di sini. Maafkan saya karena saya belum membaca semua komentar untuk mempercepat diskusi saat ini; ini hanya suara dukungan dan pernyataan situasi kita.

Untuk proyek bare-metal di Google, kami ingin melihat beberapa gerakan dalam menstabilkan assembler tingkat inline dan modul. Alternatifnya adalah menggunakan FFI untuk memanggil fungsi yang ditulis dalam perakitan murni dan dirakit secara terpisah dan dihubungkan bersama menjadi biner.

Kami dapat mendefinisikan fungsi di assembler dan memanggilnya melalui FFI, menghubungkannya dalam langkah terpisah, tetapi saya tahu tidak ada proyek bare-metal serius yang melakukan itu secara eksklusif, karena memiliki kelemahan dalam hal kompleksitas dan kinerja. Redoks menggunakan 'asm!'. Para tersangka umum dari Linux, BSD, macOS, Windows, dll, semuanya banyak menggunakan assembler inline. Zirkon dan seL4 melakukannya. Bahkan Rencana 9 menyerah pada ini beberapa tahun yang lalu di garpu Harvey.

Untuk hal-hal yang kritis terhadap kinerja, overhead panggilan fungsi mungkin mendominasi tergantung pada kompleksitas fungsi yang dipanggil. Dalam hal kompleksitas, mendefinisikan fungsi assembler terpisah hanya untuk menjalankan satu instruksi, membaca atau menulis register, atau memanipulasi status mesin yang biasanya tersembunyi dari programmer ruang pengguna berarti boilerplate yang lebih membosankan untuk melakukan kesalahan. Bagaimanapun, kita harus lebih kreatif dalam menggunakan Cargo (atau melengkapi dengan sistem build eksternal atau skrip shell atau sesuatu) untuk melakukan ini. Mungkin build.rs dapat membantu di sini, tetapi memasukkannya ke dalam tautan tampaknya lebih menantang.

Saya juga sangat suka jika ada cara untuk memasukkan nilai konstanta simbolis ke dalam template assembler.

kami ingin melihat beberapa gerakan dalam menstabilkan inline dan assembler tingkat modul.

Pra-RFC terakhir (https://internals.rust-lang.org/t/pre-rfc-inline-assembly/6443) mencapai konsensus 6 bulan yang lalu (setidaknya pada sebagian besar masalah mendasar), jadi langkah selanjutnya adalah untuk mengirimkan RFC yang dibangun di atas itu. Jika Anda ingin ini terjadi lebih cepat, saya sarankan untuk menghubungi @Florob tentang hal itu.

Untuk apa nilainya, saya memerlukan akses langsung ke register FSGS untuk mendapatkan pointer ke struct TEB di Windows, saya juga membutuhkan intrinsik _bittest64 -like untuk menerapkan bt ke lokasi memori yang berubah-ubah, tidak satu pun yang dapat saya temukan cara melakukannya tanpa perakitan inline atau panggilan eksternal.

Poin ketiga yang disebutkan di sini menyangkut saya, karena LLVM memang lebih suka Just Crash jika ada yang salah dengan tidak memberikan pesan kesalahan apa pun.

@MSxDOS

Saya juga memerlukan intrinsik seperti _bittest64 untuk menerapkan bt ke lokasi memori arbitrer, yang keduanya tidak dapat saya temukan cara untuk melakukannya tanpa perakitan inline atau panggilan eksternal.

Seharusnya tidak sulit untuk menambahkan yang itu ke stdsimd , dentang mengimplementasikan ini menggunakan perakitan sebaris (https://github.com/llvm-mirror/clang/blob/c1c07cca8cae5f924cedaac7b202b0f3c167111d/test/CodeGen/bittest-intrin .c#L45) tetapi kita dapat menggunakannya di perpustakaan std dan mengekspos intrinsik ke Rust yang aman.

Merasa terdorong untuk membuka masalah di repo stdsimd tentang intrinsik yang hilang.

@josevalaad

Nah, ketika Anda berbicara tentang perakitan dalam matematika, Anda pada dasarnya berbicara tentang menggunakan register SIMD dan instruksi seperti _mm256_mul_pd, _mm256_permute2f128_pd, dll. dan operasi vektorisasi di mana ia melanjutkan.

Ah, saya menduga itu mungkin terjadi. Nah, jika Anda ingin mencobanya, Anda dapat menerjemahkan Majelis ke dalam panggilan intrinsik std::arch dan melihat apakah Anda mendapatkan kinerja yang sama darinya.

Jika tidak , silakan ajukan masalah. LLVM bukan sihir, tetapi setidaknya intrinsik harus sebagus asm.

@dancrossnyc Jika Anda tidak keberatan saya bertanya, apakah ada kasus penggunaan/fitur platform khususnya yang memerlukan perakitan sebaris, dalam situasi Anda?

@MSxDOS Mungkin kita harus mengekspos intrinsik untuk membaca register "segmen"?


Mungkin kita harus melakukan beberapa pengumpulan data dan mendapatkan perincian tentang apa yang sebenarnya diinginkan orang dari asm! , dan melihat berapa banyak dari itu yang dapat didukung dengan cara lain.

Mungkin kita harus melakukan pengumpulan data dan mendapatkan perincian tentang apa yang benar-benar diinginkan orang!

Saya ingin asm! untuk:

  • bekerja di sekitar intrinsik yang tidak disediakan oleh kompiler
  • mengatasi bug kompiler / pembuatan kode sub-optimal
  • melakukan operasi yang tidak dapat dilakukan melalui urutan panggilan intrinsik tunggal, misalnya, membaca EFLAGS-modify-write EFLAGS di mana LLVM diizinkan untuk memodifikasi eflags antara membaca dan menulis, dan di mana LLVM juga mengasumsikan bahwa pengguna tidak akan mengubah ini di belakangnya (yaitu, satu-satunya cara untuk bekerja dengan EFLAGS dengan aman adalah dengan menulis operasi baca-modifikasi-tulis sebagai satu blok atom asm! ).

dan lihat berapa banyak dari mereka yang dapat didukung dengan cara lain.

Saya tidak melihat cara lain untuk mendukung salah satu dari kasus penggunaan yang tidak melibatkan beberapa bentuk perakitan sebaris tetapi pikiran saya terbuka.

Disalin dari posting saya di utas pra-RFC, berikut adalah beberapa perakitan sebaris (ARM64) yang saya gunakan dalam proyek saya saat ini:

// Common code for interruptible syscalls
macro_rules! asm_interruptible_syscall {
    () => {
        r#"
            # If a signal interrupts us between 0 and 1, the signal handler
            # will rewind the PC back to 0 so that the interrupt flag check is
            # atomic.
            0:
                ldrb ${0:w}, $2
                cbnz ${0:w}, 2f
            1:
               svc #0
            2:

            # Record the range of instructions which should be atomic.
            .section interrupt_restart_list, "aw"
            .quad 0b
            .quad 1b
            .previous
        "#
    };
}

// There are other versions of this function with different numbers of
// arguments, however they all share the same asm code above.
#[inline]
pub unsafe fn interruptible_syscall3(
    interrupt_flag: &AtomicBool,
    nr: usize,
    arg0: usize,
    arg1: usize,
    arg2: usize,
) -> Interruptible<usize> {
    let result;
    let interrupted: u64;
    asm!(
        asm_interruptible_syscall!()
        : "=&r" (interrupted)
          "={x0}" (result)
        : "*m" (interrupt_flag)
          "{x8}" (nr as u64)
          "{x0}" (arg0 as u64)
          "{x1}" (arg1 as u64)
          "{x2}" (arg2 as u64)
        : "x8", "memory"
        : "volatile"
    );
    if interrupted == 0 {
        Ok(result)
    } else {
        Err(Interrupted)
    }
}

@Amanieu perhatikan bahwa @japaric sedang bekerja menuju intrinsik untuk ARM . Perlu diperiksa untuk melihat apakah proposal itu memenuhi kebutuhan Anda.

@shepmaster

@Amanieu perhatikan bahwa @japaric sedang bekerja menuju intrinsik untuk ARM. Perlu diperiksa untuk melihat apakah proposal itu memenuhi kebutuhan Anda.

Perlu dicatat bahwa:

  • pekerjaan ini tidak menggantikan perakitan sebaris, itu hanya melengkapinya. Pendekatan ini mengimplementasikan API vendor di std::arch , API ini sudah tidak mencukupi untuk beberapa orang.

  • pendekatan ini hanya dapat digunakan ketika urutan panggilan intrinsik seperti foo(); bar(); baz(); menghasilkan kode yang tidak dapat dibedakan dari urutan instruksi itu - ini belum tentu demikian, dan ketika tidak, kode yang terlihat benar menghasilkan yang terbaik salah hasil, dan paling buruk memiliki perilaku yang tidak terdefinisi (kami memiliki bug karena ini di x86 dan x86_64 di std sudah, misalnya, https://github.com/rust- lang-nursery/stdsimd/blob/master/coresimd/x86/cpuid.rs#L108 - arsitektur lain juga mengalami masalah ini).

  • beberapa intrinsik memiliki argumen mode langsung, yang tidak dapat Anda lewati melalui panggilan fungsi, sehingga foo(3) tidak akan berfungsi. Setiap solusi untuk masalah ini saat ini merupakan solusi yang aneh, dan dalam beberapa kasus, saat ini tidak ada solusi yang mungkin dilakukan di Rust, jadi kami tidak menyediakan beberapa dari intrinsik ini.

Jadi jika API vendor dapat diimplementasikan di Rust, tersedia di std::arch , dan dapat digabungkan untuk memecahkan masalah, saya setuju bahwa mereka lebih baik daripada perakitan sebaris. Tetapi kadang-kadang API tidak tersedia, bahkan mungkin tidak dapat diterapkan, dan / atau tidak dapat digabungkan dengan benar. Meskipun kami dapat memperbaiki "masalah implementasi" di masa mendatang, jika apa yang ingin Anda lakukan tidak diekspos oleh API vendor, atau API tidak dapat digabungkan, pendekatan ini tidak akan membantu Anda.

Apa yang bisa sangat mengejutkan tentang implementasi intrinsik LLVM (khususnya SIMD) adalah bahwa mereka tidak sesuai dengan pemetaan eksplisit Intel terhadap instruksi sama sekali - mereka tunduk pada berbagai optimasi kompiler. Misalnya saya ingat suatu saat saya mencoba mengurangi tekanan memori dengan menghitung beberapa konstanta dari konstanta lain alih-alih memuatnya dari memori. Tetapi LLVM hanya melanjutkan untuk melipat-lipatkan semuanya kembali ke beban memori yang tepat yang saya coba hindari. Dalam kasus yang berbeda saya ingin menyelidiki mengganti shuffle 16-bit dengan shuffle 8-bit untuk mengurangi tekanan port5. Namun dalam kebijaksanaannya yang tak ada habisnya, pengoptimal LLVM yang selalu membantu memperhatikan bahwa pengocokan 8-bit saya sebenarnya adalah pengocokan 16-bit dan menggantinya.

Kedua pengoptimalan tentu saja menghasilkan throughput yang lebih baik (terutama dalam menghadapi hyperthreading) tetapi bukan pengurangan latensi yang saya harapkan untuk dicapai. Saya akhirnya jatuh ke nasm untuk percobaan itu tetapi harus menulis ulang kode dari intrinsik ke asm biasa hanyalah gesekan yang tidak perlu. Tentu saja saya ingin pengoptimal menangani hal-hal seperti pemilihan instruksi atau pelipatan konstan saat menggunakan beberapa API vektor tingkat tinggi. Tetapi ketika saya secara eksplisit memutuskan instruksi mana yang akan digunakan, saya benar-benar tidak ingin kompiler dipusingkan dengan itu. Satu-satunya alternatif adalah inline asm.

Jadi jika API vendor dapat diimplementasikan di Rust, tersedia di std::arch , dan dapat digabungkan untuk memecahkan masalah, saya setuju bahwa mereka lebih baik daripada perakitan sebaris

Itu saja yang saya katakan pada awalnya

mencapai 95-99% dari tujuan

dan lagi

Ya, ada kasing yang memerlukan perakitan untuk saat ini dan ada kasing yang selamanya akan membutuhkannya, kata saya pada awalnya (tambahkan penekanan untuk kejelasan):

Harapan kebanyakan orang bahwa [intrinsik] akan mencapai 95-99% dari tujuan.

Ini adalah hal yang sama yang dikatakan @eddyb secara paralel. Saya tidak jelas mengapa banyak orang bertindak seolah-olah saya benar-benar mengabaikan kegunaan perakitan sebaris ketika mencoba menunjukkan realitas situasi saat ini .

saya sudah

  1. Menunjuk satu poster yang tidak menyebutkan mengetahui bahwa intrinsik ada menuju intrinsik yang stabil saat ini .
  2. Menunjuk poster lain di intrinsik yang diusulkan sehingga mereka dapat memberikan umpan balik awal untuk proposal tersebut.

Izinkan saya menyatakan ini dengan sangat jelas: ya, perakitan sebaris terkadang diperlukan dan good . Saya tidak memperdebatkan itu. Saya hanya mencoba membantu orang memecahkan masalah dunia nyata dengan alat yang tersedia sekarang.

Apa yang saya coba katakan adalah bahwa kita harus memiliki pendekatan yang lebih terorganisir untuk ini, survei yang tepat, dan mengumpulkan lebih banyak data daripada beberapa dari kita di utas ini, dan kemudian menggunakannya untuk menunjukkan kebutuhan paling umum dari perakitan sebaris (karena jelas bahwa intrinsik tidak dapat sepenuhnya menggantikannya).

Saya menduga bahwa setiap arsitektur memiliki subset yang rumit untuk dimodelkan, yang digunakan dari inline asm! , dan mungkin kita harus fokus pada subset tersebut, dan kemudian mencoba untuk menggeneralisasi.

cc @rust-lang/lang

@eddyb _require_ adalah kata yang kuat, dan saya terpaksa mengatakan bahwa tidak, kami tidak sepenuhnya diharuskan menggunakan assembler sebaris. Seperti yang saya sebutkan sebelumnya, kami _could_ mendefinisikan prosedur dalam bahasa rakitan murni, merakitnya secara terpisah, dan menautkannya ke program Rust kami melalui FFI.

Namun, seperti yang saya katakan sebelumnya, saya tahu tidak ada proyek tingkat OS yang serius yang melakukan itu. Itu berarti banyak pelat ketel (baca: lebih banyak peluang untuk membuat kesalahan), proses pembuatan yang lebih kompleks (saat ini kami cukup beruntung bahwa kami dapat lolos dengan pemanggilan cargo dan tautan dan kernel yang hampir siap dijalankan muncul dari ujung yang lain; kita harus memanggil assembler dan menautkannya dalam langkah terpisah), dan penurunan drastis dalam kemampuan untuk menyelaraskan hal-hal, dll; hampir pasti akan ada hit kinerja.

Hal-hal seperti intrinsik kompiler membantu dalam banyak kasus, tetapi untuk hal-hal seperti set instruksi pengawasan dari ISA target, terutama fitur perangkat keras yang lebih esoteris (fitur hypervisor dan enclave, misalnya), sering kali tidak ada intrinsik dan kami berada di lingkungan no_std. Instrinsik apa yang sering ada tidak cukup; misalnya, konvensi pemanggilan interupsi x86 tampaknya keren tetapi tidak memberi Anda akses yang dapat diubah ke register tujuan umum dalam bingkai jebakan: misalkan saya mengambil pengecualian instruksi yang tidak ditentukan dengan maksud untuk melakukan emulasi, dan anggaplah instruksi yang diemulasi mengembalikan nilai di %rax atau sesuatu; konvensi panggilan tidak memberi saya cara yang baik untuk meneruskannya kembali ke situs panggilan, jadi kami harus memutar sendiri. Itu berarti menulis kode penanganan pengecualian saya sendiri di assembler.

Jadi sejujurnya tidak, kami tidak _memerlukan_ assembler sebaris, tetapi cukup berguna sehingga hampir menjadi non-starter untuk tidak memilikinya.

@dancrossnyc Saya secara khusus ingin tahu tentang menghindari perakitan terpisah, yaitu, jenis perakitan apa yang Anda butuhkan di proyek Anda, tidak peduli bagaimana Anda menautkannya.

Dalam kasus Anda, ini tampaknya merupakan subset ISA dengan hak istimewa supervisor/hypervisor/enclave, apakah itu benar?

sering tidak ada intrinsik

Apakah ini karena kebutuhan, yaitu apakah instruksi memiliki persyaratan yang terlalu sulit atau bahkan tidak mungkin untuk ditegakkan ketika dikompilasi sebagai panggilan intrinsik melalui, misalnya LLVM?
Atau apakah ini hanya karena mereka dianggap terlalu khusus untuk berguna bagi sebagian besar pengembang?

dan kita berada di lingkungan no_std

Sebagai catatan, intrinsik vendor ada di std::arch dan core::arch (yang pertama adalah ekspor ulang).

konvensi pemanggilan interupsi x86 tampaknya keren tetapi tidak memberi Anda akses yang dapat diubah ke register tujuan umum dalam bingkai jebakan

cc @rkruppe Bisakah ini diimplementasikan di LLVM?

@eddyb benar; kita membutuhkan subset supervisor dari ISA. Saya khawatir saya tidak bisa mengatakan lebih banyak saat ini tentang kasus penggunaan khusus kami.

Apakah ini karena kebutuhan, yaitu apakah instruksi memiliki persyaratan yang terlalu sulit atau bahkan tidak mungkin untuk ditegakkan ketika dikompilasi sebagai panggilan intrinsik melalui, misalnya LLVM?
Atau apakah ini hanya karena mereka dianggap terlalu khusus untuk berguna bagi sebagian besar pengembang?

Sampai batas tertentu keduanya benar, tetapi secara seimbang saya akan mengatakan yang terakhir lebih relevan di sini. Beberapa hal bersifat mikroarsitektur dan bergantung pada konfigurasi paket prosesor tertentu. Apakah masuk akal bagi kompiler untuk (misalnya) mengekspos sesuatu sebagai intrinsik yang merupakan bagian dari subset instruksi istimewa _and_ yang dikondisikan pada versi prosesor tertentu? sejujurnya saya tidak tahu.

Sebagai catatan, intrinsik vendor ada di std::arch dan core::arch (yang pertama adalah ekspor ulang).

Itu sebenarnya sangat bagus untuk diketahui. Terima kasih!

Apakah masuk akal bagi kompiler untuk (misalnya) mengekspos sesuatu sebagai intrinsik yang merupakan bagian dari subset instruksi istimewa dan dikondisikan pada versi prosesor tertentu? sejujurnya saya tidak tahu.

Kami sudah melakukannya. Misalnya, instruksi xsave x86 diimplementasikan dan diekspos di core::arch , tidak tersedia di semua prosesor, dan sebagian besar memerlukan mode istimewa.

@gnzlbg xsave tidak diistimewakan; Apakah maksud Anda xsaves ?

Saya melihat-lihat https://rust-lang-nursery.github.io/stdsimd/x86_64/stdsimd/arch/x86_64/index.html dan satu-satunya instruksi istimewa yang saya lihat di sapuan cepat saya (saya tidak melakukan pencarian lengkap) adalah xsaves , xsaves64 , xrstors , dan xrstors64 . Saya menduga itu adalah intrinsik karena mereka termasuk dalam keluarga XSAVE* dan tidak menghasilkan pengecualian dalam mode nyata, dan beberapa orang ingin menggunakan dentang/llvm untuk mengkompilasi kode mode nyata.

@dancrossnyc ya beberapa di antaranya adalah yang saya maksud (kami menerapkan xsave , xsaves , xsaveopt , ... dalam modul xsave : https: //github.com/rust-lang-nursery/stdsimd/blob/master/coresimd/x86/xsave.rs).

Ini tersedia di core , sehingga Anda dapat menggunakannya untuk menulis kernel OS untuk x86. Di ruang pengguna mereka adalah AFAICT yang tidak berguna (mereka akan selalu memunculkan pengecualian), tetapi kami tidak memiliki cara untuk membedakannya di core . Kami hanya dapat mengekspos mereka di core dan tidak di std , tetapi karena mereka sudah stabil, kapal itu telah berlayar. Siapa tahu, mungkin beberapa OS menjalankan semuanya di ring 0 suatu hari nanti, dan Anda dapat menggunakannya di sana...

@gnzlbg Saya tidak tahu mengapa xsaveopt atau xsave akan memunculkan pengecualian di ruang pengguna: xsaves adalah satu-satunya keluarga yang didefinisikan untuk menghasilkan pengecualian (#GP jika CPL>0), dan kemudian hanya dalam mode terproteksi (SDM vol.1 bagan 13; vol.2C bagan 5 XSAVES). xsave dan xsaveopt berguna untuk mengimplementasikan, misalnya, utas ruang pengguna pre-emptive, jadi kehadirannya sebagai intrinsik sebenarnya masuk akal. Saya menduga intrinsik untuk xsaves adalah karena seseorang baru saja menambahkan semuanya dari keluarga xsave tanpa menyadari masalah hak istimewa (yaitu, dengan asumsi itu dapat dibatalkan dari ruang pengguna), atau seseorang ingin menyebutnya dari modus nyata. Yang terakhir mungkin tampak tidak masuk akal, tetapi saya tahu orang-orang misalnya membangun firmware mode nyata dengan Dentang dan LLVM.

Jangan salah paham; kehadiran intrinsik LLVM di core sangat bagus; jika saya tidak perlu menulis urutan instruksi konyol itu untuk mendapatkan hasil rdtscp ke dalam format yang berguna lagi, saya akan senang. Tetapi set intrinsik saat ini bukan pengganti assembler sebaris saat Anda menulis kernel atau semacam pengawasan bare-metal lainnya.

@dancrossnyc ketika saya menyebutkan xsave Saya mengacu pada beberapa intrinsik yang tersedia di belakang bit CPUID XSAVE, XSAVEOPT, XSAVEC, dll. Beberapa intrinsik ini memerlukan mode istimewa.

Apakah masuk akal bagi kompiler untuk (misalnya) mengekspos sesuatu sebagai intrinsik yang merupakan bagian dari subset instruksi istimewa dan dikondisikan pada versi prosesor tertentu?

Kami sudah melakukannya dan mereka tersedia di Rust yang stabil.

Saya menduga intrinsik untuk xsaves adalah karena seseorang baru saja menambahkan semuanya dari keluarga xsave tanpa menyadari masalah hak istimewa

Saya menambahkan intrinsik ini. Kami menyadari masalah hak istimewa dan memutuskan untuk menambahkannya karena itu baik-baik saja untuk sebuah program yang bergantung pada core untuk menjadi kernel OS yang ingin menggunakan ini, dan mereka tidak berbahaya di ruang pengguna (seperti, jika Anda coba gunakan, proses Anda berakhir).

Tetapi set intrinsik saat ini bukan pengganti assembler sebaris saat Anda menulis kernel atau semacam pengawasan bare-metal lainnya.

Setuju, itu sebabnya masalah ini masih terbuka;)

@gnzlbg maaf, saya tidak bermaksud menggagalkan ini dengan melubangi xsave et al.

Namun, sedekat yang saya tahu, satu-satunya intrinsik yang memerlukan eksekusi istimewa adalah yang terkait dengan xsaves dan bahkan itu tidak selalu istimewa (sekali lagi, mode nyata tidak peduli). Sangat menyenangkan bahwa itu tersedia di Rust yang stabil (serius). Yang lain mungkin berguna di ruang pengguna dan saya pikir itu bagus karena mereka ada di sana. Namun, xsaves dan xrstors adalah bagian yang sangat, sangat kecil dari set instruksi istimewa dan menambahkan intrinsik untuk dua instruksi secara kualitatif berbeda dari melakukannya secara umum dan saya pikir pertanyaannya tetap apakah itu sesuai _secara umum_. Perhatikan instruksi VMWRITE dari ekstensi VMX, misalnya; Saya membayangkan intrinsik akan melakukan sesuatu seperti mengeksekusi instruksi dan kemudian "mengembalikan" rflags . Itu semacam hal khusus yang aneh untuk dimiliki sebagai intrinsik.

Saya pikir sebaliknya kita setuju di sini.

FWIW per std::arch RFC saat ini kami hanya dapat menambahkan intrinsik ke std::arch yang diekspos vendor di API mereka. Untuk kasus xsave , Intel mengeksposnya di C API , jadi itu sebabnya tidak apa-apa di sana. Jika Anda memerlukan intrinsik vendor yang saat ini tidak terekspos, buka masalah, apakah itu memerlukan mode istimewa atau tidak, tidak relevan.

Jika vendor tidak mengekspos intrinsik untuk itu, maka std::arch mungkin bukan tempat untuk itu, tetapi ada banyak alternatif untuk itu (rakitan sebaris, asm global, panggilan C, ...).

Maaf, saya mengerti Anda mengatakan Anda menulis intrinsik untuk xsave berarti intrinsik Intel; komentar saya sebelumnya masih berlaku mengapa saya pikir xsaves adalah intrinsik saat itu (baik kecelakaan oleh penulis kompiler di Intel atau karena seseorang menginginkannya untuk mode nyata; Saya merasa yang pertama akan diperhatikan dengan sangat cepat tetapi firmware melakukan hal-hal aneh, jadi yang terakhir tidak akan mengejutkan saya sama sekali).

Bagaimanapun, ya, saya pikir kami pada dasarnya setuju: intrinsik bukanlah tempat untuk segalanya, dan itulah mengapa kami ingin melihat asm!() dipindahkan ke stable. Saya sangat senang mendengar bahwa kemajuan sedang dibuat di area ini, seperti yang Anda katakan kemarin, dan jika kami dapat dengan lembut mendorong @Florob untuk menggelembungkan ini lebih dekat ke bagian atas tumpukan, kami akan dengan senang hati melakukannya!

Beberapa detail tambahan dan kasus penggunaan untuk asm! :

Saat Anda menulis sistem operasi, firmware, jenis perpustakaan tertentu, atau jenis kode sistem tertentu lainnya, Anda memerlukan akses penuh ke perakitan tingkat platform. Bahkan jika kami memiliki intrinsik yang mengekspos setiap instruksi tunggal di setiap arsitektur yang didukung Rust (yang hampir tidak kami miliki), itu masih tidak akan cukup untuk beberapa aksi yang dilakukan orang secara teratur dengan perakitan inline.

Berikut adalah sebagian kecil dari hal-hal yang dapat Anda lakukan dengan perakitan sebaris yang tidak dapat Anda lakukan dengan mudah dengan cara lain. Setiap satu dari ini adalah contoh dunia nyata yang pernah saya lihat (atau dalam beberapa kasus tertulis), bukan hipotetis.

  • Kumpulkan semua implementasi pola instruksi tertentu di bagian ELF yang terpisah, dan kemudian dalam memuat kode, tambal bagian itu saat runtime berdasarkan karakteristik sistem yang Anda jalankan.
  • Tulis instruksi lompat yang targetnya ditambal saat runtime.
  • Keluarkan urutan instruksi yang tepat (sehingga Anda tidak dapat mengandalkan intrinsik untuk instruksi individual), sehingga Anda dapat menerapkan pola yang menangani potensi interupsi dengan hati-hati di tengah.
  • Keluarkan instruksi, diikuti oleh lompatan ke ujung blok asm, diikuti oleh kode pemulihan kesalahan untuk penangan kesalahan perangkat keras untuk melompat jika instruksi menghasilkan kesalahan.
  • Keluarkan urutan byte yang sesuai dengan instruksi yang belum diketahui oleh assembler.
  • Tulis sepotong kode yang dengan hati-hati beralih ke tumpukan yang berbeda dan kemudian memanggil fungsi lain.
  • Panggil rutinitas perakitan atau panggilan sistem yang memerlukan argumen dalam register tertentu.

+1e6

@eddyb

Ok, saya akan mencoba pendekatan intrinsik dan melihat di mana dibutuhkan. Anda mungkin benar dan itulah pendekatan terbaik untuk kasus saya. Terima kasih!

@joshtriplett berhasil! Ini adalah kasus penggunaan yang tepat yang ada dalam pikiran saya.

loop {
   :thumbs_up:
}

Saya akan menambahkan beberapa kasus penggunaan lainnya:

  • menulis kode dalam mode arsitektur yang aneh, seperti panggilan BIOS/EFI dan mode nyata 16-bit.
  • menulis kode dengan mode pengalamatan aneh/tidak biasa (yang sering muncul dalam mode nyata 16-bit, bootloader, dll.)

@mark-im Tentu saja! Dan menggeneralisasi titik yang memiliki sub-kasus di kedua daftar kami: menerjemahkan di antara konvensi pemanggilan.

Saya menutup #53118 untuk mendukung masalah ini dan menyalin PR di sini sebagai catatan. Perhatikan bahwa ini dari Agustus, tetapi pandangan singkat tampaknya menunjukkan bahwa situasinya tidak berubah:


Bagian perakitan inline perlu dirombak; dalam keadaan sekarang ini menyiratkan bahwa perilaku dan sintaks terkait dengan rustc dan bahasa rust secara umum. Hampir seluruh dokumentasi khusus untuk perakitan x86/x86_64 dengan rantai alat llvm. Untuk lebih jelasnya, saya tidak mengacu pada kode perakitan itu sendiri, yang jelas merupakan platform khusus, melainkan arsitektur umum dan penggunaan perakitan inline sama sekali.

Saya tidak menemukan sumber otoritatif untuk perilaku perakitan inline ketika datang ke target ARM, tetapi per eksperimen saya dan referensi dokumentasi perakitan inline ARM GCC , poin-poin berikut tampaknya benar-benar tidak aktif:

  • Sintaks ASM, karena ARM/MIPS (dan sebagian besar CISC lainnya?) menggunakan sintaksis-intel dengan register tujuan terlebih dahulu. Saya memahami dokumentasi yang berarti/menyiratkan bahwa inline asm mengambil sintaks at&t yang ditranspilasikan ke sintaks khusus platform/kompiler yang sebenarnya, dan bahwa saya hanya harus mengganti nama register x86 dengan register ARM saja.
  • Terkait, opsi intel tidak valid, karena menyebabkan kesalahan "direktif tidak dikenal" saat mengkompilasi .
  • Mengadaptasi dari dokumentasi perakitan inline ARM GCC (untuk membangun melawan thumbv7em-none-eabi dengan arm-none-eabi-* toolchain, tampaknya bahkan beberapa asumsi dasar tentang format perakitan inline adalah khusus platform. Secara khusus, itu tampaknya untuk ARM register keluaran (argumen makro kedua) dihitung sebagai referensi register, yaitu $0 merujuk ke register keluaran pertama dan bukan register masukan pertama, seperti halnya dengan instruksi x86 llvm.
  • Pada saat yang sama, fitur khusus kompiler lainnya _not_ hadir; Saya tidak dapat menggunakan referensi bernama untuk register, hanya indeks (misalnya asm("mov %[result],%[value],ror #1":[result] "=r" (y):[value] "r" (x)); tidak valid).
  • (Bahkan untuk target x86/x86_64, penggunaan $0 dan $2 dalam contoh rakitan sebaris sangat membingungkan, karena tidak menjelaskan mengapa angka-angka itu dipilih.)

Saya pikir yang paling membuat saya kaget adalah pernyataan penutup:

Implementasi asm saat ini! macro adalah pengikatan langsung ke ekspresi assembler inline LLVM, jadi pastikan untuk memeriksa dokumentasi mereka juga untuk informasi lebih lanjut tentang clobbers, kendala, dll.

Yang tampaknya tidak benar secara universal.

Saya memahami dokumentasi yang berarti/menyiratkan bahwa inline asm mengambil sintaks at&t yang ditranspilasikan ke sintaks khusus platform/kompiler yang sebenarnya, dan bahwa saya hanya harus mengganti nama register x86 dengan register ARM saja.

Gagasan tentang sintaks intel vs at&t hanya ada di x86 (meskipun mungkin ada kasus lain yang tidak saya ketahui). Ini unik karena mereka adalah dua bahasa berbeda yang berbagi set mnemonik yang sama untuk mewakili set kode biner yang sama. Ekosistem GNU telah menetapkan sintaks at&t sebagai default yang mendominasi untuk dunia x86, itulah sebabnya inilah yang menjadi default asm inline. Anda keliru karena ini sangat mengikat langsung ke ekspresi assembler sebaris LLVM yang pada gilirannya sebagian besar hanya membuang plaintext (setelah memproses substitusi) ke dalam program perakitan tekstual. Tidak satu pun dari ini yang unik (atau bahkan relevan) untuk atau tentang asm!() karena sepenuhnya khusus platform dan sama sekali tidak berarti di luar dunia x86.

Terkait, opsi intel tidak valid, karena menyebabkan kesalahan "perintah tidak dikenal" saat kompilasi.

Ini adalah konsekuensi langsung dari penyisipan plaintext "bodoh"/sederhana yang saya jelaskan di atas. Seperti yang ditunjukkan oleh pesan kesalahan, direktif .intel_syntax tidak didukung. Ini adalah solusi lama dan terkenal untuk menggunakan intel-style inline-asm dengan GCC (yang memancarkan att style): seseorang cukup menulis .intel_syntax di awal blok inline asm, kemudian menulis beberapa intel- style asm dan akhirnya diakhiri dengan .att_syntax untuk mengatur assembler kembali ke mode att sehingga dengan benar memproses kode yang dihasilkan kompiler (berikut) sekali lagi. Ini adalah peretasan yang kotor dan saya ingat setidaknya implementasi LLVM memiliki beberapa kebiasaan aneh untuk waktu yang lama sehingga sepertinya Anda melihat kesalahan ini karena akhirnya dihapus. Sayangnya, satu-satunya tindakan yang benar di sini adalah menghapus opsi "intel" dari rustc.

tampaknya bahkan beberapa asumsi dasar tentang format perakitan inline adalah khusus platform

Pengamatan Anda sepenuhnya benar, setiap platform membuat format binernya sendiri dan bahasa rakitannya sendiri. Mereka sepenuhnya independen dan (kebanyakan) tidak diproses oleh kompiler - yang merupakan inti dari pemrograman dalam assembler mentah!

Saya tidak dapat menggunakan referensi bernama ke register, hanya indeks

Sayangnya ada ketidakcocokan yang cukup besar antara implementasi inline asm dari LLVM yang diekspos oleh rustc dan implementasi GCC (yang ditiru oleh dentang). Tanpa keputusan tentang bagaimana untuk bergerak maju dengan asm!() ada sedikit motivasi untuk meningkatkan ini - selain itu, saya telah menguraikan opsi utama sejak lama, semuanya memiliki kekurangan yang jelas. Karena ini tampaknya bukan prioritas, Anda mungkin akan terjebak dengan asm!() hari ini setidaknya selama beberapa tahun. Ada solusi yang layak:

  • mengandalkan pengoptimal untuk menghasilkan kode yang optimal (dengan sedikit dorongan Anda biasanya bisa mendapatkan apa yang Anda inginkan tanpa pernah menulis sendiri perakitan mentah)
  • gunakan intrinsik, solusi lain yang cukup elegan yang lebih baik daripada inline asm di hampir segala hal (kecuali jika Anda memerlukan kontrol yang tepat atas pemilihan dan penjadwalan instruksi)
  • aktifkan peti cc dari build.rs untuk menautkan objek C dengan asm sebaris

    • pada dasarnya hanya memanggil assembler apa pun yang Anda suka dari build.rs , menggunakan kompiler C mungkin tampak berlebihan tetapi menghemat kerumitan mengintegrasikan dengan sistem build.rs

Solusi ini berlaku untuk semua kecuali satu set kecil kasus tepi yang sangat spesifik. Jika Anda menekan salah satu dari itu (untungnya saya belum melakukannya), Anda kurang beruntung.

Saya setuju bahwa dokumentasinya cukup loyo tetapi cukup baik untuk siapa pun yang akrab dengan asm sebaris. Jika tidak, Anda mungkin tidak boleh menggunakannya . Jangan salah paham - Anda pasti merasa bebas untuk bereksperimen dan belajar tetapi karena asm!() tidak stabil dan diabaikan dan karena ada solusi yang sangat bagus, saya sangat menyarankan untuk tidak menggunakannya dalam proyek serius apa pun jika memungkinkan .

aktifkan peti cc dari build.rs untuk menautkan objek C dengan inline asm

Anda juga dapat memanggil peti cc dari build.rs untuk membuat file rakitan biasa, yang memberikan jumlah kontrol maksimum. Saya sangat menyarankan melakukan hal ini jika dua "solusi" di atas ini tidak berfungsi untuk kasus penggunaan Anda.

@main-- menulis:

Solusi ini berlaku untuk semua kecuali satu set kecil kasus tepi yang sangat spesifik. Jika Anda menekan salah satu dari itu (untungnya saya belum melakukannya), Anda kurang beruntung.

Maksudku, tidak sepenuhnya kurang beruntung. Anda hanya perlu menggunakan inline asm Rust. Saya memiliki kasus tepi yang tidak ada solusi yang tercantum di sini . Seperti yang Anda katakan, jika Anda terbiasa dengan proses dari kompiler lain, sebagian besar baik-baik saja.

(Saya memiliki kasus penggunaan lain: Saya ingin mengajarkan arsitektur komputer pemrograman sistem dan hal-hal yang menggunakan Rust alih-alih C suatu hari nanti. Tidak memiliki perakitan inline akan membuat ini jauh lebih canggung.)

Saya berharap kami akan menjadikan perakitan sebaris sebagai prioritas di Rust dan menstabilkannya lebih cepat daripada nanti. Mungkin ini harus menjadi tujuan Rust 2019. Saya baik-baik saja dengan salah satu solusi yang Anda cantumkan di komentar bagus Anda sebelumnya : Saya bisa hidup dengan masalah salah satu dari mereka. Mampu membuat kode Majelis sebaris bagi saya merupakan prasyarat untuk menulis Rust alih-alih C di mana-mana: Saya benar-benar membutuhkannya agar stabil.

Saya berharap kami akan menjadikan perakitan sebaris sebagai prioritas di Rust dan menstabilkannya lebih cepat daripada nanti. Mungkin ini harus menjadi tujuan Rust 2019.

Silakan tulis posting blog Rust 2019 dan ungkapkan kekhawatiran ini. Saya pikir jika cukup banyak dari kita melakukan itu, kita dapat mempengaruhi peta jalan.

Untuk memperjelas komentar saya di atas - masalahnya adalah dokumentasi tidak menjelaskan seberapa "dalam" konten makro asm!(..) diurai/diinteraksikan. Saya akrab dengan rakitan x86 dan MIPS/ARM tetapi menganggap bahwa llvm memiliki format bahasa rakitannya sendiri. Saya telah menggunakan perakitan sebaris untuk x86 sebelumnya, tetapi tidak jelas sejauh mana bajingan asm ke brige C dan ASM berjalan. Anggapan saya (sekarang tidak valid) berdasarkan kata-kata di bagian rust inline assembly adalah bahwa LLVM memiliki format ASM sendiri yang dibuat untuk meniru perakitan x86 baik dalam mode at&t atau intel, dan tentu saja terlihat seperti contoh x86 yang ditampilkan.

(Yang membantu saya adalah mempelajari keluaran makro yang diperluas, yang memperjelas apa yang sedang terjadi)

Saya pikir perlu ada lebih sedikit abstraksi pada halaman itu. Perjelas apa yang diuraikan oleh LLVM dan apa yang ditafsirkan sebagai ASM secara langsung. Bagian apa yang khusus untuk karat, bagian apa yang khusus untuk perangkat keras yang Anda gunakan, dan bagian mana yang melekat pada lem yang menyatukannya.

aktifkan peti cc dari build.rs untuk menautkan objek C dengan asm sebaris

Kemajuan terbaru pada LTO lintas bahasa membuat saya bertanya-tanya apakah beberapa kelemahan dari jalan ini dapat dikurangi, secara efektif menyejajarkan "gumpalan perakitan eksternal" ini. ( mungkin tidak )

aktifkan peti cc dari build.rs untuk menautkan objek C dengan asm sebaris

Kemajuan terbaru pada LTO lintas bahasa membuat saya bertanya-tanya apakah beberapa kelemahan dari jalan ini dapat dikurangi, secara efektif menyejajarkan "gumpalan perakitan eksternal" ini.

Bahkan jika ini berhasil, saya tidak ingin menulis rakitan sebaris saya dalam C. Saya ingin menulisnya dalam Rust. :-)

Saya tidak ingin menulis Majelis sebaris saya di C.

Anda dapat mengkompilasi dan menautkan file .s dan .S secara langsung (lihat misalnya peti ini), yang dalam buku saya cukup jauh dari C. :)

jika beberapa kerugian dari jalan ini dapat dikurangi

Saya percaya ini saat ini tidak layak karena LTO lintas bahasa bergantung pada LLVM IR dan Majelis tidak akan menghasilkan ini.

Saya percaya ini saat ini tidak layak karena LTO lintas bahasa bergantung pada LLVM IR dan Majelis tidak akan menghasilkan ini.

Anda dapat memasukkan perakitan ke dalam perakitan tingkat modul di modul LLVM IR.

Adakah yang tahu apa proposal/status terbaru? Karena tema tahun ini adalah "kedewasaan dan menyelesaikan apa yang kita mulai", sepertinya ini adalah kesempatan besar untuk akhirnya menyelesaikan asm .

Rencana yang tidak jelas untuk sintaks baru (yang akan distabilkan) dibahas Februari lalu: https://paper.dropbox.com/doc/FFI-5NmXV30TGiSsr9dIxpqpq

Menurut catatan itu, @joshtriplett dan @Amanieu mendaftar untuk menulis RFC.

Apa status sintaks baru?

Itu perlu RFC dan diimplementasikan setiap malam

ping @joshtriplett @Amanieu Beri tahu saya jika saya dapat membantu memindahkan barang-barang di sini! Saya akan segera menghubungi Anda.

@cramertj AFAICT siapa pun dapat memajukan ini, ini tidak diblokir dan menunggu seseorang untuk masuk dan mengerjakannya. Ada pra-RFC yang membuat sketsa desain keseluruhan, dan langkah selanjutnya adalah mengimplementasikannya dan melihat apakah itu benar-benar berfungsi, baik sebagai makro proc, di garpu, atau sebagai fitur tidak stabil yang berbeda.

Seseorang mungkin dapat mencoba mengubah pra-RFC itu menjadi RFC yang tepat dan mengirimkannya, tetapi saya ragu bahwa tanpa implementasi RFC seperti itu dapat meyakinkan.


EDIT: untuk lebih jelasnya, dengan meyakinkan saya maksudkan secara khusus bagian dari pra-RFC seperti ini:

tambahan pemetaan untuk kelas register ditambahkan sebagaimana mestinya (lih. llvm-constraint 6)

di mana ada lusinan kelas register khusus lengkung di lang-ref. RFC tidak bisa begitu saja melambai semua ini, dan memastikan bahwa semuanya berfungsi seperti yang seharusnya, atau bermakna, atau cukup "stabil" di LLVM untuk diekspos dari sini, dll. Akan mendapat manfaat dari implementasi yang bisa dilakukan. coba saja ini.

Apakah perakitan inline RISC-V didukung di sini dengan #![feature(asm)] ?

Sejauh pengetahuan saya, semua perakitan pada platform yang didukung didukung; itu cukup banyak akses mentah ke dukungan asm kompiler llvm.

Ya, RISC-V didukung. Kelas batasan input/output/clobber khusus arsitektur didokumentasikan dalam LLVM langref .

Namun, ada peringatan - jika Anda perlu membatasi register individu dalam batasan input/output/clobber, Anda harus menggunakan nama register arsitektur (x0-x31, f0-f31), bukan nama ABI. Dalam fragmen Majelis itu sendiri, Anda dapat menggunakan salah satu jenis nama register.

Sebagai seseorang yang baru mengenal konsep ini, bisakah saya katakan... seluruh diskusi ini tampak _konyol_. Bagaimana bahasa (perakitan) yang seharusnya menjadi pemetaan 1 banding 1 dengan kode mesinnya menyebabkan banyak sakit kepala?

Saya cukup bingung:

  • Jika Anda menulis asm, bukankah itu harus ditulis ulang (oleh manusia dengan #[cfg(...)] ) untuk setiap arsitektur _dan backend_ yang Anda coba dukung?
  • Ini berarti bahwa pertanyaan "sintaks" dapat diperdebatkan ... cukup gunakan sintaks untuk arsitektur itu dan backend yang kebetulan digunakan oleh kompiler.
  • Rust hanya membutuhkan std unsafe function untuk dapat memasukkan byte ke register yang benar dan Push/pop ke stack untuk arsitektur apa pun yang sedang dikompilasi -- sekali lagi, ini mungkin harus ditulis ulang untuk setiap arsitektur dan bahkan mungkin setiap backend.

Saya mengerti bahwa kompatibilitas mundur adalah masalah, tetapi dengan banyaknya bug dan fakta bahwa ini tidak pernah distabilkan, mungkin akan lebih baik untuk meneruskannya ke backend. Rust seharusnya tidak berusaha untuk memperbaiki kesalahan sintaks aneh LLVM atau gcc atau siapa pun. Rust dalam bisnis memancarkan kode mesin untuk arsitektur dan kompiler yang ditargetkan ... dan asm pada dasarnya adalah kode itu!

Alasan tidak ada kemajuan di sini adalah karena tidak ada yang menginvestasikan waktu untuk memperbaiki masalah ini. Itu bukan alasan yang baik untuk menstabilkan fitur.

Saat membaca utas ini, saya punya ide dan harus mempostingnya. Maaf jika saya menjawab posting lama, tetapi saya pikir itu sepadan:

@main-- berkata:

Kedua pengoptimalan tentu saja menghasilkan throughput yang lebih baik (terutama dalam menghadapi hyperthreading) tetapi bukan pengurangan latensi yang saya harapkan untuk dicapai. Saya akhirnya jatuh ke nasm untuk percobaan itu tetapi harus menulis ulang kode dari intrinsik ke asm biasa hanyalah gesekan yang tidak perlu. Tentu saja saya ingin pengoptimal menangani hal-hal seperti pemilihan instruksi atau pelipatan konstan saat menggunakan beberapa API vektor tingkat tinggi. Tetapi ketika saya secara eksplisit memutuskan instruksi mana yang akan digunakan, saya benar-benar tidak ingin kompiler dipusingkan dengan itu. Satu-satunya alternatif adalah inline asm.

Mungkin alih-alih asm sebaris, yang benar-benar kita butuhkan di sini adalah atribut fungsi untuk LLVM yang memberi tahu pengoptimal: "optimalkan ini untuk throughput", "optimalkan ini untuk latensi", "optimalkan ini untuk ukuran biner". Saya tahu solusi ini adalah hulu, tetapi itu tidak hanya akan menyelesaikan masalah khusus Anda secara otomatis (dengan menyediakan latensi yang lebih rendah tetapi sebaliknya implementasi algoritma yang isomorfik), itu juga akan memungkinkan pemrogram Rust untuk memiliki kontrol yang lebih halus atas karakteristik kinerja itu penting bagi mereka.

@felix91gr Itu tidak menyelesaikan kasus penggunaan yang mengharuskan memancarkan urutan instruksi yang tepat, misalnya penangan interupsi.

@mark-im tentu saja tidak. Itu sebabnya saya memberikan kutipan literal! 🙂

Maksud saya adalah bahwa meskipun Anda mungkin menyelesaikan "kompiler mengoptimalkan dengan cara yang berlawanan dengan apa yang saya butuhkan" (yang klasik dalam kasus mereka: latensi vs throughput) dengan menggunakan fitur asm sebaris, mungkin (dan pasti) kasus penggunaan itu akan dilayani lebih baik oleh kontrol pengoptimalan yang lebih halus :)

Mengingat perubahan yang akan datang pada perakitan inline, sebagian besar diskusi dalam masalah ini tidak lagi relevan. Karena itu, saya akan menutup masalah ini demi dua masalah pelacakan terpisah untuk setiap jenis perakitan inline yang kami miliki:

  • Masalah Pelacakan untuk perakitan inline gaya LLVM ( llvm_asm ) #70173
  • Masalah Pelacakan untuk perakitan inline ( asm! ) #72016
Apakah halaman ini membantu?
0 / 5 - 0 peringkat