Mengapa MIPS menggunakan R0 sebagai "nol" ketika Anda hanya bisa XOR dua register untuk menghasilkan 0?

10

Saya pikir saya sedang mencari jawaban untuk pertanyaan trivia. Saya mencoba memahami mengapa arsitektur MIPS menggunakan nilai "nol" eksplisit dalam register ketika Anda dapat mencapai hal yang sama dengan hanya XOR'ing register apa pun terhadap dirinya sendiri. Orang bisa mengatakan bahwa operasi sudah dilakukan untuk Anda; Namun, saya tidak bisa membayangkan situasi di mana Anda akan menggunakan banyak nilai "nol". Saya membaca makalah asli Hennessey, dan hanya menetapkan nol sebagai fakta tanpa pembenaran nyata.

Apakah alasan logis untuk memiliki tugas biner kode-nol nol ada?

pembaruan: Dalam 8k dari executable dari xc32-gcc untuk inti MIPS di PIC32MZ, saya punya satu contoh "nol".

add     t3,t1,zero

jawaban aktual: Saya memberikan hadiah kepada orang yang memiliki informasi tentang MIPS dan kode kondisi. Jawabannya sebenarnya terletak pada arsitektur MIPS untuk kondisi. Meskipun pada awalnya saya tidak ingin menetapkan waktu untuk ini, saya meninjau arsitektur untuk opensparc , MIPS-V , dan OpenPOWER (dokumen ini bersifat internal) dan di sini adalah ringkasan temuan. Daftar R0 diperlukan untuk perbandingan pada cabang karena arsitektur pipa.

  • bilangan bulat dibandingkan dengan nol dan cabang (bgez, bgtz, blez, bltz)
  • integer membandingkan dua register dan cabang (beq, bne)
  • integer membandingkan dua register dan trap (teq, tge, tlt, tne)
  • integer bandingkan register dan segera dan trap (teqi, tgei, tlti, tnei)

Itu hanya datang ke bagaimana hardware terlihat dalam implementasi. Dari manual MIPS-V, ada kutipan tanpa referensi di halaman 68:

Cabang bersyarat dirancang untuk mencakup operasi perbandingan aritmatika antara dua register (seperti yang juga dilakukan dalam PA-RISC dan Xtensa ISA), daripada menggunakan kode kondisi (x86, ARM, SPARC, PowerPC), atau hanya membandingkan satu register dengan nol ( Alpha, MIPS), atau dua register only for equality (MIPS). Desain ini dimotivasi oleh pengamatan bahwa gabungan perintah bandingkan dan cabang ke dalam pipa biasa, menghindari status kode kondisi tambahan atau penggunaan register sementara, dan mengurangi ukuran kode statis dan instruksi dinamis mengambil trac. Poin lain adalah bahwa perbandingan terhadap nol memerlukan penundaan sirkuit non-trivial (terutama setelah pindah ke logika statis dalam proses lanjutan) dan karenanya hampir semahal perbandingan besarnya aritmatika. Keuntungan lain dari instruksi membandingkan-dan-cabang menyatu adalah bahwa cabang diamati sebelumnya dalam aliran instruksi front-end, dan dengan demikian dapat diprediksi sebelumnya. Mungkin ada keuntungan untuk desain dengan kode kondisi dalam kasus di mana beberapa cabang dapat diambil berdasarkan kode kondisi yang sama, tetapi kami percaya kasus ini relatif jarang.

Dokumen MIPS-V tidak mengenai penulis bagian yang dikutip. Saya berterima kasih kepada semua orang atas waktu dan pertimbangan mereka.

b degnan
sumber
6
Anda sering ingin menggunakan register bernilai 0 dalam beberapa operasi sebagai nilai sumber. Ini akan menjadi biaya overhead untuk nol register sebelum operasi tersebut, jadi manfaat kinerja jika Anda hanya dapat menggunakan nol yang disediakan daripada membuatnya sendiri setiap kali diperlukan. Contohnya termasuk penambahan bendera carry.
JimmyB
3
Pada arsitektur AVR, gcc berhati-hati untuk menginisialisasi r1 ke nol pada saat startup dan tidak pernah menyentuh nilai itu lagi, menggunakan r1 sebagai sumber di mana 0 langsung tidak dapat digunakan. Di sini, register nol khusus 'ditiru' dalam perangkat lunak oleh kompiler untuk alasan kinerja. (Sebagian besar AVR memiliki 32 register, jadi menyisihkan satu (dua, sebenarnya) selain tidak memerlukan biaya banyak sehubungan dengan kemungkinan kinerja dan manfaat ukuran kode.)
JimmyB
1
Saya tidak tahu tentang MIPS, tetapi mungkin lebih cepat untuk memindahkan r0 ke register lain dibandingkan dengan XORing yang mendaftar untuk membersihkannya.
JimmyB
Jadi Anda tidak setuju pada poin bahwa nol begitu sering sehingga bernilai posisi dalam file register? Maka mungkin Anda benar karena memang ini kontroversial dan ada banyak ISA memilih untuk tidak mendaftar nol register. Seperti fitur kontroversial lainnya pada saat itu seperti register windows, slot cabang, predikasi instruksi dari "masa lalu" ... jika Anda ingin mendesain ISA, Anda tidak harus menggunakannya jika Anda memutuskan untuk tidak melakukannya.
user3528438
2
Mungkin menarik untuk membaca salah satu makalah RISC Berkeley lama, RISC I: A Reduced Instruction Set VLSI Computer . Ini menunjukkan bagaimana menggunakan register nol kabel, R0, memungkinkan sejumlah instruksi VAX dan mode pengalamatan untuk diimplementasikan dalam instruksi RISC tunggal.
Mark Plotnick

Jawaban:

14

Daftar nol pada RISC CPU bermanfaat karena dua alasan:

Ini adalah konstanta yang berguna

Bergantung pada batasan ISA, Anda tidak dapat menggunakan literal dalam beberapa instruksi penyandian, tetapi Anda dapat yakin Anda dapat menggunakannya r0untuk mendapatkan 0.

Ini dapat digunakan untuk mensintesis instruksi lain

Ini mungkin poin paling penting. Sebagai perancang ISA, Anda dapat menukar register tujuan umum dengan register nol untuk dapat mensintesis instruksi berguna lainnya. Instruksi mensintesis itu baik karena dengan instruksi yang kurang aktual, Anda memerlukan lebih sedikit bit untuk meng-encode operasi dalam opcode, yang membebaskan ruang di ruang instruksi encoding. Anda dapat menggunakan ruang itu untuk memiliki mis. Alamat dan / atau literal alamat yang lebih besar.

Semantik register nol seperti /dev/zeropada sistem * nix: semua yang ditulis untuk itu dibuang, dan Anda selalu membaca kembali 0.

Mari kita lihat beberapa contoh bagaimana kita dapat membuat instruksi semu dengan bantuan daftar r0nol:

; ### Hypothetical CPU ###

; Assembler with syntax:
; op rd, rm, rn 
; => rd: destination, rm: 1st operand, rn: 2nd operand
; literal as #lit

; On an CPU architecture with a status register (which contains arithmetic status
; flags), `sub` can be used, with r0 as destination to discard result.
cmp rn, rm     ; => sub r0, rn, rm

; `add` instruction can be used as a `mov` instruction:
mov rd, rm     ; => add rd, rm, r0
mov rd, #lit   ; => add rd, r0, #lit

; Negate:
neg rd, rm     ; => sub rd, r0, rm

; On CPU without status flags,
nop            ; => add r0, r0, r0

; RISC-V's `jal` instruction -- Jump and Link: Jump to PC-relative instruction,
; save return address into rd; we can synthesize a `jmp` instruction out of it.
jmp dest       ; => jal r0, dest

; You can even load from an absolute (direct) address, for a usually small range
; of addresses by using a literal offset as an address.
ld rd, addr    ; => ld rd, [r0, #addr]

Kasus MIPS

Saya melihat lebih dekat pada set instruksi MIPS. Ada beberapa instruksi palsu yang menggunakan $zero; mereka terutama digunakan untuk cabang. Berikut adalah beberapa contoh dari apa yang saya temukan:

move $rt, $rs          => add $rt, $rs, $zero

not $rt, $rs           => nor $rt, $rs, $zero

b Label                => beq $zero, $zero, Label ; a small relative branch

bgt $rs, $rt, Label    => slt $at, $rt, $rs
                          bne $at, $zero, Label

blt $rs, $rt, Label    => slt $at, $rs, $rt
                          bne $at, $zero, Label

bge $rs, $rt, Label    => slt $at, $rs, $rt
                          beq $at, $zero, Label

ble $rs, $rt, Label    => slt $at, $rt, $rs
                          beq $at, $zero, Label

Adapun mengapa Anda hanya menemukan satu contoh $zeroregister dalam pembongkaran Anda, mungkin pembongkaran Anda yang cukup pintar untuk mengubah urutan instruksi yang dikenal menjadi instruksi semu yang setara.

Apakah daftar nol itu benar - benar bermanfaat?

Well, rupanya, ARM menemukan memiliki register nol cukup berguna sehingga pada (ARMv8-A core baru mereka, yang mengimplementasikan AArch64, sekarang ada register nol dalam mode 64-bit; tidak ada daftar nol sebelumnya. (Register agak istimewa, dalam beberapa konteks penyandian, ini adalah register nol, sedangkan register lain menunjuk stack pointer )

Jarhmander
sumber
Saya tidak berpikir MIPS menggunakan bendera, bukan? Daftar nol menambah kemampuan untuk membaca / menulis alamat tertentu tanpa syarat untuk konten register CPU apa pun dan membantu memfasilitasi operasi gaya "bergerak segera", tetapi gerakan lain dapat dilakukan dengan logis atau menggunakan sumber dengan sendirinya .
supercat
1
Memang, tidak ada register yang memegang bendera aritmatika, bukannya ada tiga petunjuk yang membantu meniru umum cabang bersyarat ( slt, slti, sltu).
Jarhmander
Melihat set instruksi MIPS, dan mengingat bahwa dari apa yang saya pahami setiap instruksi akan diambil pada saat instruksi sebelumnya dijalankan, saya bertanya-tanya apakah akan sulit untuk memiliki opcode yang tidak melakukan apa pun secara langsung tetapi sebaliknya mengatakan bahwa jika instruksi modus langsung dijalankan dan instruksi yang diambil berikutnya memiliki pola bit itu, 16 bit operan atas akan diambil dari instruksi yang sudah dibuat sebelumnya? Operasi langsung mode 32-bit akan ditangani dengan instruksi dua siklus dua kata daripada harus menghabiskan dua kata dan dua siklus ...
supercat
... memuat operan dan kemudian siklus ketiga untuk benar-benar menggunakannya.
supercat
7

Sebagian besar implementasi ARM / POWER / SPARC memiliki register RAZ tersembunyi

Anda mungkin berpikir bahwa ARM32, SPARC dll tidak memiliki 0 register tetapi kenyataannya mereka melakukannya! Pada tingkat mikro-arsitektur, sebagian besar insinyur desain CPU menambahkan register 0 yang mungkin tidak terlihat oleh perangkat lunak (register nol ARM tidak terlihat) dan menggunakan register nol itu untuk merampingkan decode instruksi.

Pertimbangkan desain ARM32 modern khas yang memiliki register perangkat lunak tidak terlihat, katakan R16 berkabel ke 0. Pertimbangkan beban ARM32, banyak kasus instruksi memuat ARM32 termasuk dalam salah satu bentuk ini (Abaikan pengindeksan pra-posting sementara waktu untuk membuat diskusi tetap sederhana ) ...

LDR ra, [rb] // NOTE:The ! is optional and represents address writeback.
LDR ra, [rb, rc](!)
LDR ra, [rb, #k](!)

Di dalam prosesor, ini diterjemahkan ke jenderal

ldr.uop ra, rb, rx, rc, #c // Internal decoded instruction format.

sebelum memasuki tahap masalah di mana register dibaca. Perhatikan bahwa rx mewakili register untuk menulis kembali alamat yang diperbarui. Berikut ini beberapa contoh dekode:

LDR R0, [R1]      ==> ldr.uop R0, R1, R16, R16, #0 // Writeback to NULL. 
LDR R0, [R1, R2]! ==> ldr.uop R0, R1, R1, R2,   #0 // Writeback to R1.
LDR R0, [R1, #2]  ==> ldr.uop R0, R1, R16, R16, #2 // Writeback to NULL.

Pada level sirkuit, ketiga beban sebenarnya adalah instruksi internal yang sama dan cara mudah untuk mendapatkan ortogonalitas semacam ini adalah dengan membuat ground register R16. Karena R16 selalu di-ground, instruksi ini secara alami memecahkan kode dengan benar tanpa logika tambahan. Memetakan kelas instruksi ke format internal tunggal sangat membantu dalam implementasi superscalar karena mengurangi kompleksitas logika.

Alasan lain adalah cara efisien untuk membuang menulis. Instruksi dapat dinonaktifkan dengan hanya mengatur register tujuan dan bendera ke R16. Tidak perlu membuat sinyal kontrol lain untuk menonaktifkan write-back dll.

Sebagian besar implementasi prosesor terlepas dari arsitektur berakhir dengan model register RAZ di awal dalam pipa. Pipa MIPS pada dasarnya dimulai pada titik yang akan di arsitektur lain menjadi beberapa tahap di.

MIPS membuat pilihan yang tepat

Dengan demikian, register read-as-zero hampir wajib dalam setiap implementasi prosesor modern dan MIPS membuatnya terlihat oleh perangkat lunak jelas merupakan poin plus mengingat bagaimana ia merampingkan logika decode internal. Desainer prosesor MIPS tidak perlu menambahkan register RAZ tambahan karena $ 0 sudah tersedia. Karena RAZ tersedia untuk assembler, banyak instruksi psuedo tersedia untuk MIPS dan orang dapat menganggap ini sebagai mendorong bagian dari logika decode ke assembler itu sendiri alih-alih membuat format khusus untuk setiap jenis instruksi untuk menyembunyikan register RAZ dari perangkat lunak seperti arsitektur lainnya. Register RAZ adalah ide yang bagus dan itulah sebabnya ARMv8 menyalinnya.

Jika ARM32 memiliki register $ 0, logika decode akan menjadi lebih sederhana dan arsitektur akan jauh lebih baik dalam hal kecepatan, luas, dan daya. Misalnya, dari tiga versi LDR yang disajikan di atas, hanya 2 format yang diperlukan. Demikian pula, tidak perlu cadangan logika dekode untuk instruksi MOV dan MVN. Juga, CMP / CMN / TST / TEQ akan menjadi berlebihan. Juga tidak perlu membedakan antara short (MUL) dan multiplication panjang (UMULL / SMULL) karena multiplikasi pendek dapat dianggap sebagai multiplikasi panjang dengan register tinggi ditetapkan ke $ 0 dll.

Karena MIPS pada awalnya dirancang oleh tim kecil, kesederhanaan desain adalah penting dan dengan demikian $ 0 secara eksplisit dipilih dalam semangat RISC. ARM32 mempertahankan banyak fitur CISC tradisional di tingkat arsitektur.

Revanth Kamaraj
sumber
1
Tidak semua CPU ARM32 bekerja seperti yang Anda gambarkan. Beberapa memiliki kinerja yang lebih rendah untuk instruksi pemuatan yang lebih kompleks dan / atau untuk menulis kembali ke register. Jadi mereka tidak bisa memecahkan kode dengan cara yang persis sama.
Peter Cordes
6

Disclamer: Saya tidak benar-benar tahu assembler MIPS, tetapi register 0-nilai tidak unik untuk arsitektur ini, dan saya kira itu digunakan dengan cara yang sama seperti pada arsitektur RISC lainnya yang saya tahu.

XORing sebuah register untuk mendapatkan 0 akan dikenakan biaya satu instruksi, sementara menggunakan register 0-nilai yang ditetapkan tidak akan dikenakan biaya.

Sebagai contoh, mov RX, RYinstruksi sering diterapkan sebagai add RX, RY, R0. Tanpa register bernilai 0, Anda harus xor RZ, RZsetiap kali ingin menggunakannya mov.

Contoh lain adalah cmpinstruksi dan variannya (seperti "bandingkan dan lompati", "bandingkan dan pindahkan", dll), di mana cmp RX, R0digunakan untuk menguji angka negatif.

Dmitry Grigoryev
sumber
1
Akankah ada masalah menerapkan MOV Rx,Rysebagai AND Rx,Ry,Ry?
supercat
3
@supercat Anda tidak akan dapat menyandikan mov RX, Immatau mov RX, mem[RY]jika set instruksi Anda hanya mendukung nilai langsung tunggal dan akses memori tunggal per instruksi.
Dmitry Grigoryev
Saya tidak terbiasa dengan mode pengalamatan apa yang dimiliki MIPS. Saya tahu ARM memiliki mode [Rx + Ry << skala] dan [Rx + disp], dan walaupun dapat menggunakan yang terakhir untuk beberapa alamat absolut dapat berguna dalam beberapa kasus, umumnya tidak penting. Mode [Rx] lurus dapat ditiru melalui [Rx + disp] menggunakan perpindahan nol. Apa yang digunakan MIPS?
supercat
movadalah contoh yang buruk; Anda bisa menerapkannya dengan 0 langsung, bukan nol register. mis ori dst, src, 0. Tapi ya, Anda akan memerlukan opcode untuk mendaftar segera jika Anda belum memilikinya addiu $dst, $zero, 1234, seperti luitetapi untuk 16 bit yang lebih rendah daripada 16 bagian atas. Dan Anda tidak dapat menggunakan noratau subuntuk membangun satu-operan bukan / neg .
Peter Cordes
@supercat: jika Anda masih bertanya-tanya: MIPS klasik hanya memiliki mode pengalamatan tunggal: register + disp16. MIPS modern menambahkan opcode lain untuk mode pengalamatan 2-register untuk beban / penyimpanan FP, mempercepat pengindeksan array. (Tapi tetap tidak untuk integer load / store, mungkin karena itu bisa memerlukan lebih banyak port baca dalam file register integer untuk 2 reg alamat + reg data untuk toko. Lihat Menggunakan register sebagai offset )
Peter Cordes
3

Mengikat beberapa petunjuk ke tanah di ujung bank register Anda adalah murah (lebih murah daripada menjadikannya sebagai daftar lengkap).

Melakukan xor yang sebenarnya membutuhkan sedikit daya dan waktu untuk mengganti gerbang dan untuk kemudian menyimpannya di register, mengapa membayar biaya itu ketika nilai 0 yang ada dapat dengan mudah tersedia.

CPU modern juga memiliki register bernilai 0 (tersembunyi) yang dapat mereka gunakan sebagai hasil xor eax eaxinstruksi melalui penggantian nama register.

aneh ratchet
sumber
6
Biaya sebenarnya R0adalah tidak membumikan beberapa kabel tetapi pada kenyataan bahwa Anda harus memesan kode untuk itu dalam setiap instruksi yang berkaitan dengan register.
Dmitry Grigoryev
Xor adalah herring merah. xor-zeroing hanya baik pada x86, di mana CPU mengenali idiom dan menghindari ketergantungan pada input. Seperti yang Anda tunjukkan, keluarga Sandybridge bahkan tidak menjalankan uop untuk itu, hanya menanganinya pada tahap register-rename. ( Apa cara terbaik untuk mengatur register ke nol dalam rakitan x86: xor, mov atau dan? ). Tetapi pada MIPS, XORing register akan memiliki ketergantungan salah; aturan urutan ketergantungan memori (setara HW dari C ++ std::memory_order_consume) memerlukan XOR untuk menyebarkan dependensi.
Peter Cordes
Jika Anda tidak memiliki nol register, Anda akan menyertakan opcode untuk memindahkan langsung ke register. Suka luitapi tidak bergeser ke kiri oleh 16. Jadi Anda masih bisa memasukkan nomor kecil dalam register dengan satu instruksi. Membiarkan hanya nol dengan ketergantungan salah akan menjadi gila. (MIPS normal menciptakan nilai-nilai non-nol dengan addiu $dst, $zero, 1234atau ori, sehingga argumen "biaya daya" Anda rusak. Jika Anda ingin menghindari menyalakan ALU, Anda akan menyertakan opcode untuk segera bergerak untuk mendaftar daripada memiliki peranti lunak ADD atau OR langsung dengan nol.)
Peter Cordes